register/zlayer/anchor/motion/density are pure cores over plain data, so most of the suite runs with no DOM at all; jsdom covers only what is genuinely DOM. 100 tests, zero elements. These five are why v4 had 11 hand-built modals and 7 hand-built popovers: there was nothing shared to reuse. register makes an element exist only because a file defined it; portal gives every overlay one stacking parent; zlayer owns the only z-index in the app; dismiss keeps one stack so Escape pops the top and not the modal underneath; anchor measures, flips and clamps once. Three bugs the tests found: unmount() called removeChild on null when no portal existed yet, focusables() checked aria-hidden on the element instead of its ancestors so a hidden subtree stayed tab-reachable, and the density ratios v4 hand-tuned are now derived from the gap they were meant to allow for.
39 lines
1.4 KiB
JavaScript
39 lines
1.4 KiB
JavaScript
// Module-supplied content is DATA, never markup.
|
|
//
|
|
// asText is the only way text reaches the DOM in this kit. It does not parse,
|
|
// sanitise or escape -- it refuses anything that is not a scalar, so "markup"
|
|
// is not a state the kit can be in. That is a stronger guarantee than escaping,
|
|
// which is a thing you can forget to do in one place.
|
|
|
|
const SCALAR = new Set(["string", "number", "boolean"]);
|
|
|
|
export function asText(value) {
|
|
if (value == null) return document.createTextNode("");
|
|
if (!SCALAR.has(typeof value)) {
|
|
throw new TypeError(
|
|
`Composer content must be a string, number or boolean; got ${describe(value)}. ` +
|
|
"Elements take data, not nodes or markup."
|
|
);
|
|
}
|
|
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
throw new TypeError(`Composer content must be a finite number; got ${value}.`);
|
|
}
|
|
return document.createTextNode(String(value));
|
|
}
|
|
|
|
/** Replace a node's content with `value`, rendered as text. */
|
|
export function setText(node, value) {
|
|
while (node.firstChild) node.removeChild(node.firstChild);
|
|
node.appendChild(asText(value));
|
|
return node;
|
|
}
|
|
|
|
function describe(value) {
|
|
if (typeof value === "object" && value !== null) {
|
|
if (typeof value.nodeType === "number") return `a DOM node (<${value.nodeName?.toLowerCase()}>)`;
|
|
if (Array.isArray(value)) return "an array";
|
|
return "an object";
|
|
}
|
|
return `a ${typeof value}`;
|
|
}
|