Alex R.
Alex R.

Reputation: 469

Javascript Selection API :: containsNode() does not return true when span element is selected

My question is about this function: https://developer.mozilla.org/en-US/docs/Web/API/Selection/containsNode and I have created this sandbox: https://codesandbox.io/s/amazing-bartik-smjt2?file=/src/index.js

code:

document.getElementById("app").innerHTML = `
<h1>Hello Vanilla!</h1>
<div>
  We use the same configuration.<s> <span id="_span"><img src="http://www.mandysam.com/img/random.jpg" id="_img" alt="🙂" aria-label="🙂" width="40"></span> as Parcel to bundle this </s> sandbox, you can find more
  info about Parcel.
  <h2 id="y" hidden=true>span selected</h2>
  <h2 id="z" hidden=true>img selected</h2>
</div>
`;

document.addEventListener("selectionchange", () => {
  const selection = window.getSelection();

  let span = document.querySelector("#_span");
  const foundSpan = selection.containsNode(span);

  let img = document.querySelector("#_img");
  const foundImg = selection.containsNode(img);

  let y = document.querySelector("#y");
  y.toggleAttribute("hidden", !foundSpan);
  let z = document.querySelector("#z");
  z.toggleAttribute("hidden", !foundImg);
});

I do not understand why I should select at least a character before and after the image so the containsNode returns true on the span element. Is this the expected behavior; the span element supposed to be selected whenever the img is selected, right?

Upvotes: 2

Views: 324

Answers (1)

trixn
trixn

Reputation: 16354

This is the expected behavior.

From the specification for the Selection API:

containsNode() method

The method must return false if the context object is empty or if node's root is not the document associated with the context object.

Otherwise, if allowPartialContainment is false, the method must return true if and only if start of its range is before or visually equivalent to the first boundary point in the node and end of its range is after or visually equivalent to the last boundary point in the node.

If allowPartialContainment is true, the method must return true if and only if start of its range is before or visually equivalent to the first boundary point in the node or end of its range is after or visually equivalent to the last boundary point in the node.

The interface for containsNode() is defined as:

boolean containsNode(Node node, optional boolean allowPartialContainment = false);

You have to provide true for the allowPartialContainment argument if you also want to have nodes that are only partially contained considered part of the selection:

const foundSpan = selection.containsNode(span, true); 

document.addEventListener("selectionchange", () => {
  const selection = window.getSelection();

  let span = document.querySelector("#_span");
  const isFullyContained = selection.containsNode(span);
  const isPartiallyContained = selection.containsNode(span, true);

  let y = document.querySelector("#y");
  y.toggleAttribute("hidden", !isPartiallyContained || isFullyContained);
  let z = document.querySelector("#z");
  z.toggleAttribute("hidden", !isFullyContained);
});
<h1>Select the text with the image</h1>
<div>
  We use the same configuration.
  <span id="_span"><img src="http://www.mandysam.com/img/random.jpg" width="40"></span>
  as Parcel to bundle this sandbox, you can find more info about Parcel.
  <h2 id="y" hidden=true>span partially contained</h2>
  <h2 id="z" hidden=true>span fully contained</h2>
</div>

It works with the image because img is is a void element (has no content) and therefore can't be only partially contained in a selection.

Upvotes: 4

Related Questions