@siteimprove/alfa-dom 0.108.2 → 0.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @siteimprove/alfa-dom
2
2
 
3
+ ## 0.110.0
4
+
5
+ ### Patch Changes
6
+
7
+ - **Added:** The `inert` attribute is now supported. ([#1964](https://github.com/Siteimprove/alfa/pull/1964))
8
+
9
+ ## 0.109.0
10
+
11
+ ### Patch Changes
12
+
13
+ - **Added:** A new function `getTextDescendants` has been added to the `Query` namespace. It retrieves all text descendants of a node, with an option to group text under matching sub-trees into labeled `TextGroup` objects. ([#1972](https://github.com/Siteimprove/alfa/pull/1972))
14
+
15
+ - **Changed:** The `index()` method on nodes now accepts an optional predicate parameter to filter siblings when calculating the node's index position. ([#1980](https://github.com/Siteimprove/alfa/pull/1980))
16
+
3
17
  ## 0.108.2
4
18
 
5
19
  ## 0.108.1
@@ -28,7 +28,7 @@ export class Comment extends Node {
28
28
  .getOr("/");
29
29
  path += path === "/" ? "" : "/";
30
30
  path += "comment()";
31
- const index = this.preceding(options).count(Comment.isComment);
31
+ const index = this.index(options, Comment.isComment);
32
32
  path += `[${index + 1}]`;
33
33
  return path;
34
34
  }
@@ -11,6 +11,9 @@ import { Element } from "../../element.js";
11
11
  * @public
12
12
  */
13
13
  export function isSuggestedFocusable(element) {
14
+ if (element.isInert()) {
15
+ return false;
16
+ }
14
17
  switch (element.name) {
15
18
  case "a":
16
19
  case "link":
@@ -60,9 +60,20 @@ export declare class Element<N extends string = string> extends Node<"element">
60
60
  * {@link https://html.spec.whatwg.org/#dom-tabindex}
61
61
  */
62
62
  tabIndex(): Option<number>;
63
+ /**
64
+ * Computes inertness of an element based on the `inert` attribute.
65
+ *
66
+ * {@link https://html.spec.whatwg.org/#the-inert-attribute}
67
+ *
68
+ * @privateRemarks
69
+ * According to {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert}
70
+ * only open dialogs can escape inertness (except when they have the `inert` attribute).
71
+ */
72
+ isInert(): boolean;
63
73
  protected _inputType: helpers.InputType | undefined;
64
74
  protected _displaySize: number | undefined;
65
75
  protected _optionsList: Sequence<Element<"option">> | undefined;
76
+ private _isInert;
66
77
  /**
67
78
  * {@link https://dom.spec.whatwg.org/#dom-slotable-assignedslot}
68
79
  */
@@ -17,7 +17,7 @@ import { Slot } from "./slot.js";
17
17
  import { Slotable } from "./slotable.js";
18
18
  import * as predicate from "./element/predicate.js";
19
19
  const { isEmpty } = Iterable;
20
- const { not } = Predicate;
20
+ const { and, not, or, test } = Predicate;
21
21
  /**
22
22
  * @public
23
23
  */
@@ -171,6 +171,30 @@ export class Element extends Node {
171
171
  }
172
172
  return None;
173
173
  }
174
+ /**
175
+ * Computes inertness of an element based on the `inert` attribute.
176
+ *
177
+ * {@link https://html.spec.whatwg.org/#the-inert-attribute}
178
+ *
179
+ * @privateRemarks
180
+ * According to {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert}
181
+ * only open dialogs can escape inertness (except when they have the `inert` attribute).
182
+ */
183
+ isInert() {
184
+ if (this._isInert === undefined) {
185
+ this._isInert = test(or(
186
+ // Explicitly inert;
187
+ Element.hasAttribute("inert"), and(
188
+ // or not an open dialog,
189
+ not(and(Element.hasName("dialog"), Element.hasAttribute("open"))),
190
+ // and with an inert ancestor.
191
+ (element) => element
192
+ .parent(Node.flatTree)
193
+ .filter(Element.isElement)
194
+ .some((parent) => parent.isInert()))), this);
195
+ }
196
+ return this._isInert;
197
+ }
174
198
  /*
175
199
  * This collects caches for methods that are specific to some kind of elements.
176
200
  * The actual methods are declared in element/augment.ts to de-clutter this
@@ -181,6 +205,7 @@ export class Element extends Node {
181
205
  _inputType;
182
206
  _displaySize;
183
207
  _optionsList;
208
+ _isInert;
184
209
  /*
185
210
  * End of caches for methods specific to some kind of elements.
186
211
  */
@@ -205,9 +230,7 @@ export class Element extends Node {
205
230
  .getOr("/");
206
231
  path += path === "/" ? "" : "/";
207
232
  path += this._name;
208
- const index = this.preceding(options)
209
- .filter(Element.isElement)
210
- .count((element) => element._name === this._name);
233
+ const index = this.index(options, (node) => Element.isElement(node) && node._name === this._name);
211
234
  path += `[${index + 1}]`;
212
235
  return path;
213
236
  }
@@ -1,8 +1,9 @@
1
1
  import type { Predicate } from "@siteimprove/alfa-predicate";
2
2
  import type { Refinement } from "@siteimprove/alfa-refinement";
3
- import type { Sequence } from "@siteimprove/alfa-sequence";
3
+ import { Sequence } from "@siteimprove/alfa-sequence";
4
4
  import { Node } from "../../node.js";
5
5
  import { Element } from "../element.js";
6
+ import { Text } from "../text.js";
6
7
  /**
7
8
  * Get all descendants of a node that satisfy a given refinement.
8
9
  *
@@ -31,4 +32,36 @@ export declare const getElementDescendants: (node: Node, options?: Node.Traversa
31
32
  * @public
32
33
  */
33
34
  export declare function getInclusiveElementDescendants(node: Element, options?: Node.Traversal): Sequence<Element>;
35
+ /**
36
+ * A group of text nodes with an associated label.
37
+ *
38
+ * @public
39
+ */
40
+ export interface TextGroup {
41
+ label: string;
42
+ text: Sequence<Text>;
43
+ }
44
+ /**
45
+ * Options for grouping text descendants.
46
+ *
47
+ * @public
48
+ */
49
+ export interface TextGroupOptions<N extends Node = Node> {
50
+ startsGroup: Refinement<Node, N>;
51
+ getLabel: (node: N) => string;
52
+ }
53
+ /**
54
+ * Get all text descendants of a node, optionally grouping some into labeled groups.
55
+ *
56
+ * @remarks
57
+ * When a descendant matches `startsGroup`, all of its text descendants are collected
58
+ * into a {@link TextGroup} with a label from `getLabel`. Text nodes outside such
59
+ * sub-trees are returned as plain {@link Text} nodes.
60
+ *
61
+ * Groups are not nested: if a `startsGroup` node contains another `startsGroup` node,
62
+ * the inner node's text is included in the outer group, not as a separate group.
63
+ *
64
+ * @public
65
+ */
66
+ export declare function getTextDescendants<N extends Node = Node>(textOptions?: TextGroupOptions<N>): (node: Node, options?: Node.Traversal) => Sequence<Text | TextGroup>;
34
67
  //# sourceMappingURL=descendants.d.ts.map
@@ -1,6 +1,8 @@
1
1
  import { Cache } from "@siteimprove/alfa-cache";
2
+ import { Sequence } from "@siteimprove/alfa-sequence";
2
3
  import { Node } from "../../node.js";
3
4
  import { Element } from "../element.js";
5
+ import { Text } from "../text.js";
4
6
  const _descendantsCache = Cache.empty();
5
7
  export function getDescendants(predicate) {
6
8
  return (node, options = Node.Traversal.empty) => {
@@ -23,4 +25,51 @@ export const getElementDescendants = getDescendants(Element.isElement);
23
25
  export function getInclusiveElementDescendants(node, options = Node.Traversal.empty) {
24
26
  return getElementDescendants(node, options).prepend(node);
25
27
  }
28
+ const _textCache = Cache.empty();
29
+ const defaultTextOptions = {
30
+ startsGroup: (node) => false,
31
+ getLabel: () => "",
32
+ };
33
+ /**
34
+ * Get all text descendants of a node, optionally grouping some into labeled groups.
35
+ *
36
+ * @remarks
37
+ * When a descendant matches `startsGroup`, all of its text descendants are collected
38
+ * into a {@link TextGroup} with a label from `getLabel`. Text nodes outside such
39
+ * sub-trees are returned as plain {@link Text} nodes.
40
+ *
41
+ * Groups are not nested: if a `startsGroup` node contains another `startsGroup` node,
42
+ * the inner node's text is included in the outer group, not as a separate group.
43
+ *
44
+ * @public
45
+ */
46
+ export function getTextDescendants(textOptions = defaultTextOptions) {
47
+ return (node, options = Node.Traversal.empty) => {
48
+ const optionsMap = _textCache
49
+ .get(textOptions, Cache.empty)
50
+ .get(node, () => []);
51
+ if (optionsMap[options.value] === undefined) {
52
+ optionsMap[options.value] = Sequence.from(_getTextDescendants(node, textOptions, options));
53
+ }
54
+ return optionsMap[options.value];
55
+ };
56
+ }
57
+ function* _getTextDescendants(node, textOptions, traversalOptions) {
58
+ const { startsGroup, getLabel } = textOptions;
59
+ for (const child of node.children(traversalOptions)) {
60
+ if (startsGroup(child)) {
61
+ const groupText = getDescendants(Text.isText)(child, traversalOptions);
62
+ yield {
63
+ label: getLabel(child),
64
+ text: groupText,
65
+ };
66
+ }
67
+ else if (Text.isText(child)) {
68
+ yield child;
69
+ }
70
+ else {
71
+ yield* _getTextDescendants(child, textOptions, traversalOptions);
72
+ }
73
+ }
74
+ }
26
75
  //# sourceMappingURL=descendants.js.map
@@ -1,12 +1,16 @@
1
1
  import * as descendants from "./descendants.js";
2
2
  import * as elementIdMap from "./element-id-map.js";
3
+ import type { Node } from "../../node.js";
3
4
  /**
4
5
  * @public
5
6
  */
6
7
  export declare namespace Query {
7
8
  const getDescendants: typeof descendants.getDescendants;
8
- const getElementDescendants: (node: import("../../node.js").Node, options?: import("../../node.js").Node.Traversal) => import("@siteimprove/alfa-sequence").Sequence<import("../element.js").Element<string>>;
9
- const getElementIdMap: typeof elementIdMap.getElementIdMap;
9
+ const getElementDescendants: (node: Node, options?: Node.Traversal) => import("@siteimprove/alfa-sequence").Sequence<import("../element.js").Element<string>>;
10
10
  const getInclusiveElementDescendants: typeof descendants.getInclusiveElementDescendants;
11
+ const getTextDescendants: typeof descendants.getTextDescendants;
12
+ const getElementIdMap: typeof elementIdMap.getElementIdMap;
13
+ type TextGroup = descendants.TextGroup;
14
+ type TextGroupOptions<N extends Node = Node> = descendants.TextGroupOptions<N>;
11
15
  }
12
16
  //# sourceMappingURL=index.d.ts.map
@@ -7,7 +7,8 @@ export var Query;
7
7
  (function (Query) {
8
8
  Query.getDescendants = descendants.getDescendants;
9
9
  Query.getElementDescendants = descendants.getElementDescendants;
10
- Query.getElementIdMap = elementIdMap.getElementIdMap;
11
10
  Query.getInclusiveElementDescendants = descendants.getInclusiveElementDescendants;
11
+ Query.getTextDescendants = descendants.getTextDescendants;
12
+ Query.getElementIdMap = elementIdMap.getElementIdMap;
12
13
  })(Query || (Query = {}));
13
14
  //# sourceMappingURL=index.js.map
package/dist/node/text.js CHANGED
@@ -44,7 +44,7 @@ export class Text extends Node {
44
44
  .getOr("/");
45
45
  path += path === "/" ? "" : "/";
46
46
  path += "text()";
47
- const index = this.preceding(options).count(Text.isText);
47
+ const index = this.index(options, Text.isText);
48
48
  path += `[${index + 1}]`;
49
49
  return path;
50
50
  }
package/dist/node.d.ts CHANGED
@@ -73,7 +73,7 @@ export interface Node {
73
73
  last(options?: Node.Traversal): Option<Node>;
74
74
  previous(options?: Node.Traversal): Option<Node>;
75
75
  next(options?: Node.Traversal): Option<Node>;
76
- index(options?: Node.Traversal): number;
76
+ index(options?: Node.Traversal, predicate?: Predicate<Node>): number;
77
77
  closest<T extends Node>(refinement: Refinement<Node, T>, options?: Node.Traversal): Option<T>;
78
78
  closest(predicate: Predicate<Node>, options?: Node.Traversal): Option<Node>;
79
79
  }
package/dist/node.js CHANGED
@@ -183,13 +183,10 @@ export class Node extends tree.Node {
183
183
  // we accept the risk of caching the value assuming that it will only be
184
184
  // computed on fully frozen trees.
185
185
  path(options = Node.Traversal.empty) {
186
- if (this._path[options.value] !== undefined) {
187
- return this._path[options.value];
188
- }
189
- else {
186
+ if (this._path[options.value] == undefined) {
190
187
  this._path[options.value] = this._internalPath(options);
191
- return this._internalPath(options);
192
188
  }
189
+ return this._path[options.value];
193
190
  }
194
191
  equals(value) {
195
192
  return value === this;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json.schemastore.org/package",
3
3
  "name": "@siteimprove/alfa-dom",
4
4
  "homepage": "https://alfa.siteimprove.com",
5
- "version": "0.108.2",
5
+ "version": "0.110.0",
6
6
  "license": "MIT",
7
7
  "description": "Implementations of the core DOM and CSSOM node types",
8
8
  "repository": {
@@ -35,37 +35,37 @@
35
35
  "dist/**/*.d.ts"
36
36
  ],
37
37
  "dependencies": {
38
- "@siteimprove/alfa-array": "^0.108.2",
39
- "@siteimprove/alfa-cache": "^0.108.2",
40
- "@siteimprove/alfa-comparable": "^0.108.2",
41
- "@siteimprove/alfa-css": "^0.108.2",
42
- "@siteimprove/alfa-css-feature": "^0.108.2",
43
- "@siteimprove/alfa-device": "^0.108.2",
44
- "@siteimprove/alfa-earl": "^0.108.2",
45
- "@siteimprove/alfa-equatable": "^0.108.2",
46
- "@siteimprove/alfa-flags": "^0.108.2",
47
- "@siteimprove/alfa-iterable": "^0.108.2",
48
- "@siteimprove/alfa-json": "^0.108.2",
49
- "@siteimprove/alfa-lazy": "^0.108.2",
50
- "@siteimprove/alfa-map": "^0.108.2",
51
- "@siteimprove/alfa-option": "^0.108.2",
52
- "@siteimprove/alfa-parser": "^0.108.2",
53
- "@siteimprove/alfa-predicate": "^0.108.2",
54
- "@siteimprove/alfa-rectangle": "^0.108.2",
55
- "@siteimprove/alfa-refinement": "^0.108.2",
56
- "@siteimprove/alfa-result": "^0.108.2",
57
- "@siteimprove/alfa-sarif": "^0.108.2",
58
- "@siteimprove/alfa-selective": "^0.108.2",
59
- "@siteimprove/alfa-sequence": "^0.108.2",
60
- "@siteimprove/alfa-slice": "^0.108.2",
61
- "@siteimprove/alfa-string": "^0.108.2",
62
- "@siteimprove/alfa-trampoline": "^0.108.2",
63
- "@siteimprove/alfa-tree": "^0.108.2"
38
+ "@siteimprove/alfa-array": "^0.110.0",
39
+ "@siteimprove/alfa-cache": "^0.110.0",
40
+ "@siteimprove/alfa-comparable": "^0.110.0",
41
+ "@siteimprove/alfa-css": "^0.110.0",
42
+ "@siteimprove/alfa-css-feature": "^0.110.0",
43
+ "@siteimprove/alfa-device": "^0.110.0",
44
+ "@siteimprove/alfa-earl": "^0.110.0",
45
+ "@siteimprove/alfa-equatable": "^0.110.0",
46
+ "@siteimprove/alfa-flags": "^0.110.0",
47
+ "@siteimprove/alfa-iterable": "^0.110.0",
48
+ "@siteimprove/alfa-json": "^0.110.0",
49
+ "@siteimprove/alfa-lazy": "^0.110.0",
50
+ "@siteimprove/alfa-map": "^0.110.0",
51
+ "@siteimprove/alfa-option": "^0.110.0",
52
+ "@siteimprove/alfa-parser": "^0.110.0",
53
+ "@siteimprove/alfa-predicate": "^0.110.0",
54
+ "@siteimprove/alfa-rectangle": "^0.110.0",
55
+ "@siteimprove/alfa-refinement": "^0.110.0",
56
+ "@siteimprove/alfa-result": "^0.110.0",
57
+ "@siteimprove/alfa-sarif": "^0.110.0",
58
+ "@siteimprove/alfa-selective": "^0.110.0",
59
+ "@siteimprove/alfa-sequence": "^0.110.0",
60
+ "@siteimprove/alfa-slice": "^0.110.0",
61
+ "@siteimprove/alfa-string": "^0.110.0",
62
+ "@siteimprove/alfa-trampoline": "^0.110.0",
63
+ "@siteimprove/alfa-tree": "^0.110.0"
64
64
  },
65
65
  "devDependencies": {
66
- "@siteimprove/alfa-test": "^0.108.2",
66
+ "@siteimprove/alfa-test": "^0.110.0",
67
67
  "@types/jsdom": "^27.0.0",
68
- "jsdom": "^27.2.0"
68
+ "jsdom": "^27.4.0"
69
69
  },
70
70
  "publishConfig": {
71
71
  "access": "public",