@markuplint/ml-core 5.4.0 → 5.6.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/ml-core.js +3 -0
  3. package/lib/ml-dom/helper/accname.d.ts +35 -0
  4. package/lib/ml-dom/helper/accname.js +69 -17
  5. package/lib/ml-dom/helper/debug.js +1 -1
  6. package/lib/ml-dom/helper/literal-values.d.ts +102 -0
  7. package/lib/ml-dom/helper/literal-values.js +193 -0
  8. package/lib/ml-dom/helper/pretended-attrs.d.ts +16 -0
  9. package/lib/ml-dom/helper/pretended-attrs.js +21 -0
  10. package/lib/ml-dom/helper/slot-assignment.d.ts +48 -0
  11. package/lib/ml-dom/helper/slot-assignment.js +292 -0
  12. package/lib/ml-dom/helper/tree-scope.d.ts +130 -0
  13. package/lib/ml-dom/helper/tree-scope.js +341 -0
  14. package/lib/ml-dom/helper/walkers.d.ts +15 -0
  15. package/lib/ml-dom/helper/walkers.js +34 -0
  16. package/lib/ml-dom/index.d.ts +4 -0
  17. package/lib/ml-dom/index.js +2 -0
  18. package/lib/ml-dom/node/attr.d.ts +77 -10
  19. package/lib/ml-dom/node/attr.js +209 -20
  20. package/lib/ml-dom/node/block.d.ts +15 -6
  21. package/lib/ml-dom/node/block.js +17 -8
  22. package/lib/ml-dom/node/document-fragment.d.ts +18 -6
  23. package/lib/ml-dom/node/document-fragment.js +19 -7
  24. package/lib/ml-dom/node/document.d.ts +36 -7
  25. package/lib/ml-dom/node/document.js +60 -17
  26. package/lib/ml-dom/node/dom-token-list.js +28 -3
  27. package/lib/ml-dom/node/element.d.ts +148 -24
  28. package/lib/ml-dom/node/element.js +258 -48
  29. package/lib/ml-dom/node/named-node-map.js +2 -1
  30. package/lib/ml-dom/node/node-list.js +2 -3
  31. package/lib/ml-dom/node/node.d.ts +55 -10
  32. package/lib/ml-dom/node/node.js +98 -13
  33. package/lib/ml-dom/node/parent-node.d.ts +39 -8
  34. package/lib/ml-dom/node/parent-node.js +79 -12
  35. package/lib/ml-dom/node/shadow-root.d.ts +162 -0
  36. package/lib/ml-dom/node/shadow-root.js +219 -0
  37. package/lib/ml-dom/node/text.d.ts +12 -3
  38. package/lib/ml-dom/node/text.js +14 -3
  39. package/lib/ml-rule/ml-rule.js +4 -1
  40. package/lib/test/index.js +3 -0
  41. package/lib/utils/index.d.ts +2 -0
  42. package/lib/utils/index.js +1 -0
  43. package/lib/utils/search-node-at-location.d.ts +39 -0
  44. package/lib/utils/search-node-at-location.js +53 -0
  45. package/package.json +12 -15
@@ -149,9 +149,14 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
149
149
  */
150
150
  get baseURI(): string;
151
151
  /**
152
- * The list of child nodes that contains `Element`, `Text`, and `Comment`.
153
- * Fragment nodes and preprocessor blocks with `each` or `end` block behavior
154
- * are transparent — their child nodes are flattened into this list.
152
+ * The list of child nodes. Fragment nodes and preprocessor blocks whose
153
+ * `blockBehavior.type` is `each` or `end` are flattened into this list.
154
+ * Conditional blocks (`if`, `switch`, `await`, and their clauses) and
155
+ * `each:empty` stay in this list: flattening a conditional block would
156
+ * present elements that never render together as siblings, and would
157
+ * remove the block nodes `conditionalChildNodes()` uses to enumerate
158
+ * each branch. A loop body is not an alternative branch, so `each` and
159
+ * `end` are flattened.
155
160
  *
156
161
  * @readonly
157
162
  * @implements DOM API: `Node`
@@ -244,9 +249,30 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
244
249
  * @see https://dom.spec.whatwg.org/#ref-for-dom-node-parentelement%E2%91%A0
245
250
  */
246
251
  get parentElement(): MLElement<T, O> | null;
252
+ /**
253
+ * The parent element in the tree of this node, which is what the DOM calls `parentElement`: `null`
254
+ * for what is at the top of the content of an HTML `template` element or of a declarative shadow
255
+ * root, whose {@link MLNode.parentElement} is the `template` element, as the syntax tree has it.
256
+ * A `template` element that is in no tree has none either.
257
+ *
258
+ * The selector engine and `closest()` read it, so that `section p` does not match a `p` in the
259
+ * content of a `template` in a `section`. {@link MLNode.parentElement} does not stop at that
260
+ * boundary because the content model of a `template` element is validated against what is in it.
261
+ *
262
+ * `getTreeParentElement` of `@markuplint/ml-spec`, which cannot use this getter, derives the same
263
+ * answer from `getRootNode()` for the algorithms of that package: a change of the boundary here
264
+ * belongs to it too.
265
+ *
266
+ * @implements `@markuplint/ml-core` API: `MLNode`
267
+ */
268
+ get treeParentElement(): MLElement<T, O> | null;
247
269
  /**
248
270
  * The parent node that may be `Element`, `Document`, `DocumentFragment`, and `null`.
249
271
  *
272
+ * A block of a template language is walked through (`MLBlock#isTransparent`): a node in a block
273
+ * has the node that contains the block as its parent. A node at the top of a fragment document
274
+ * has none.
275
+ *
250
276
  * ## HTML:
251
277
  *
252
278
  * ```html
@@ -267,10 +293,10 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
267
293
  *
268
294
  * ```jsx
269
295
  * <> // => null
270
- * <div> // => #document-fragment
296
+ * <div> // => <>
271
297
  * {items.map(item => {
272
298
  * return (
273
- * <span /> // => null
299
+ * <span /> // => <div>
274
300
  * )
275
301
  * })}
276
302
  * </div>
@@ -284,7 +310,7 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
284
310
  * div
285
311
  * //- <div>
286
312
  * if foo
287
- * //- null
313
+ * //- <div>
288
314
  * span
289
315
  * ```
290
316
  *
@@ -334,7 +360,7 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
334
360
  *
335
361
  * ```jsx
336
362
  * <> // => #document
337
- * <div> // => #document-fragment
363
+ * <div> // => <>
338
364
  * {items.map(item => {
339
365
  * return (
340
366
  * <span /> // => #ml-block
@@ -425,6 +451,13 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
425
451
  /**
426
452
  * Returns true if other is an inclusive descendant of node; otherwise false.
427
453
  *
454
+ * What an HTML `template` element holds is its `content`, a tree of its own, so the element does
455
+ * not contain it, and an element does not contain the content of a `template` under it, so
456
+ * `contains()` asked of what is outside a template about what is inside it is `false` as well.
457
+ * That differs from {@link MLNode.parentElement}, which stays the one of the syntax
458
+ * tree (the `template` element) because the content model of a `template` element is validated
459
+ * against what the syntax tree puts in it.
460
+ *
428
461
  * @implements DOM API: `Node`
429
462
  * @see https://dom.spec.whatwg.org/#ref-for-dom-node-contains%E2%91%A0
430
463
  */
@@ -438,7 +471,9 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
438
471
  */
439
472
  dispatchEvent(event: Event): boolean;
440
473
  /**
441
- * Finds subsequent nodes that match the given selector.
474
+ * Finds subsequent nodes that match the given selector, in the tree of this node: what follows in
475
+ * the markup but is in another tree (the content of an HTML `template` element) is not subsequent
476
+ * to it.
442
477
  *
443
478
  * @implements `@markuplint/ml-core` API: `MLNode`
444
479
  * @param selector - Optional selector to filter the nodes.
@@ -451,9 +486,16 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
451
486
  */
452
487
  getPureChildNodes(): NodeListOf<MLChildNode<T, O>>;
453
488
  /**
454
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
489
+ * The root of the tree of this node: the document, the content of the nearest HTML `template`
490
+ * element above it (a declarative shadow root when one is attached to the parent of the `template`),
491
+ * or this node itself for a document fragment, and for a `template` element that is in no tree.
492
+ * The syntax tree is followed through what a template engine renders away, so a block of a
493
+ * template language between the `template` element and this node does not hide the `template`.
455
494
  *
456
- * @unsupported
495
+ * With `composed`, a shadow root is followed to its host.
496
+ *
497
+ * @param options - `composed` follows a shadow root to its host
498
+ * @returns The root of the tree
457
499
  * @implements DOM API: `Node`
458
500
  * @see https://dom.spec.whatwg.org/#ref-for-dom-node-getrootnode%E2%91%A0
459
501
  */
@@ -502,6 +544,9 @@ export declare abstract class MLNode<T extends RuleConfigValue, O extends PlainD
502
544
  */
503
545
  isEqualNode(otherNode: Node | null): boolean;
504
546
  /**
547
+ * Whether the document of this node is a fragment of a document (a partial, a component) and
548
+ * not a whole one. The parser of the document decides it (`MLDocument#isFragment`).
549
+ *
505
550
  * @implements `@markuplint/ml-core` API: `MLNode`
506
551
  */
507
552
  isInFragmentDocument(): boolean;
@@ -174,9 +174,14 @@ export class MLNode extends MLToken {
174
174
  throw new UnexpectedCallError('Not supported "baseURI" property');
175
175
  }
176
176
  /**
177
- * The list of child nodes that contains `Element`, `Text`, and `Comment`.
178
- * Fragment nodes and preprocessor blocks with `each` or `end` block behavior
179
- * are transparent — their child nodes are flattened into this list.
177
+ * The list of child nodes. Fragment nodes and preprocessor blocks whose
178
+ * `blockBehavior.type` is `each` or `end` are flattened into this list.
179
+ * Conditional blocks (`if`, `switch`, `await`, and their clauses) and
180
+ * `each:empty` stay in this list: flattening a conditional block would
181
+ * present elements that never render together as siblings, and would
182
+ * remove the block nodes `conditionalChildNodes()` uses to enumerate
183
+ * each branch. A loop body is not an alternative branch, so `each` and
184
+ * `end` are flattened.
180
185
  *
181
186
  * @readonly
182
187
  * @implements DOM API: `Node`
@@ -321,9 +326,39 @@ export class MLNode extends MLToken {
321
326
  }
322
327
  return null;
323
328
  }
329
+ /**
330
+ * The parent element in the tree of this node, which is what the DOM calls `parentElement`: `null`
331
+ * for what is at the top of the content of an HTML `template` element or of a declarative shadow
332
+ * root, whose {@link MLNode.parentElement} is the `template` element, as the syntax tree has it.
333
+ * A `template` element that is in no tree has none either.
334
+ *
335
+ * The selector engine and `closest()` read it, so that `section p` does not match a `p` in the
336
+ * content of a `template` in a `section`. {@link MLNode.parentElement} does not stop at that
337
+ * boundary because the content model of a `template` element is validated against what is in it.
338
+ *
339
+ * `getTreeParentElement` of `@markuplint/ml-spec`, which cannot use this getter, derives the same
340
+ * answer from `getRootNode()` for the algorithms of that package: a change of the boundary here
341
+ * belongs to it too.
342
+ *
343
+ * @implements `@markuplint/ml-core` API: `MLNode`
344
+ */
345
+ get treeParentElement() {
346
+ const parent = this.parentElement;
347
+ if (!parent || parent.isHTMLTemplateElement) {
348
+ return null;
349
+ }
350
+ if (this.is(this.ELEMENT_NODE) && this.isInNoTree) {
351
+ return null;
352
+ }
353
+ return parent;
354
+ }
324
355
  /**
325
356
  * The parent node that may be `Element`, `Document`, `DocumentFragment`, and `null`.
326
357
  *
358
+ * A block of a template language is walked through (`MLBlock#isTransparent`): a node in a block
359
+ * has the node that contains the block as its parent. A node at the top of a fragment document
360
+ * has none.
361
+ *
327
362
  * ## HTML:
328
363
  *
329
364
  * ```html
@@ -344,10 +379,10 @@ export class MLNode extends MLToken {
344
379
  *
345
380
  * ```jsx
346
381
  * <> // => null
347
- * <div> // => #document-fragment
382
+ * <div> // => <>
348
383
  * {items.map(item => {
349
384
  * return (
350
- * <span /> // => null
385
+ * <span /> // => <div>
351
386
  * )
352
387
  * })}
353
388
  * </div>
@@ -361,7 +396,7 @@ export class MLNode extends MLToken {
361
396
  * div
362
397
  * //- <div>
363
398
  * if foo
364
- * //- null
399
+ * //- <div>
365
400
  * span
366
401
  * ```
367
402
  *
@@ -480,7 +515,7 @@ export class MLNode extends MLToken {
480
515
  *
481
516
  * ```jsx
482
517
  * <> // => #document
483
- * <div> // => #document-fragment
518
+ * <div> // => <>
484
519
  * {items.map(item => {
485
520
  * return (
486
521
  * <span /> // => #ml-block
@@ -655,12 +690,22 @@ export class MLNode extends MLToken {
655
690
  /**
656
691
  * Returns true if other is an inclusive descendant of node; otherwise false.
657
692
  *
693
+ * What an HTML `template` element holds is its `content`, a tree of its own, so the element does
694
+ * not contain it, and an element does not contain the content of a `template` under it, so
695
+ * `contains()` asked of what is outside a template about what is inside it is `false` as well.
696
+ * That differs from {@link MLNode.parentElement}, which stays the one of the syntax
697
+ * tree (the `template` element) because the content model of a `template` element is validated
698
+ * against what the syntax tree puts in it.
699
+ *
658
700
  * @implements DOM API: `Node`
659
701
  * @see https://dom.spec.whatwg.org/#ref-for-dom-node-contains%E2%91%A0
660
702
  */
661
703
  contains(
662
704
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
663
705
  other) {
706
+ if (this.is(this.ELEMENT_NODE) && this.isHTMLTemplateElement) {
707
+ return false;
708
+ }
664
709
  for (const childNode of this.childNodes) {
665
710
  if (other?.uuid === childNode.uuid || childNode.contains(other)) {
666
711
  return true;
@@ -681,7 +726,9 @@ export class MLNode extends MLToken {
681
726
  throw new UnexpectedCallError('Not supported "dispatchEvent" method');
682
727
  }
683
728
  /**
684
- * Finds subsequent nodes that match the given selector.
729
+ * Finds subsequent nodes that match the given selector, in the tree of this node: what follows in
730
+ * the markup but is in another tree (the content of an HTML `template` element) is not subsequent
731
+ * to it.
685
732
  *
686
733
  * @implements `@markuplint/ml-core` API: `MLNode`
687
734
  * @param selector - Optional selector to filter the nodes.
@@ -689,6 +736,7 @@ export class MLNode extends MLToken {
689
736
  */
690
737
  findSubsequentNodes(selector) {
691
738
  const matched = [];
739
+ const root = this.getRootNode();
692
740
  for (const node of this.ownerMLDocument.nodeList) {
693
741
  if (node.endOffset <= this.endOffset) {
694
742
  continue;
@@ -696,6 +744,9 @@ export class MLNode extends MLToken {
696
744
  if (this.contains(node)) {
697
745
  continue;
698
746
  }
747
+ if (node.getRootNode() !== root) {
748
+ continue;
749
+ }
699
750
  if (selector) {
700
751
  if (node.is(node.ELEMENT_NODE) && node.matches(selector)) {
701
752
  matched.push(node);
@@ -750,19 +801,50 @@ export class MLNode extends MLToken {
750
801
  return this.#pureChildNodesCache;
751
802
  }
752
803
  /**
753
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
804
+ * The root of the tree of this node: the document, the content of the nearest HTML `template`
805
+ * element above it (a declarative shadow root when one is attached to the parent of the `template`),
806
+ * or this node itself for a document fragment, and for a `template` element that is in no tree.
807
+ * The syntax tree is followed through what a template engine renders away, so a block of a
808
+ * template language between the `template` element and this node does not hide the `template`.
754
809
  *
755
- * @unsupported
810
+ * With `composed`, a shadow root is followed to its host.
811
+ *
812
+ * @param options - `composed` follows a shadow root to its host
813
+ * @returns The root of the tree
756
814
  * @implements DOM API: `Node`
757
815
  * @see https://dom.spec.whatwg.org/#ref-for-dom-node-getrootnode%E2%91%A0
758
816
  */
759
817
  getRootNode(
760
818
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
761
819
  options) {
762
- if (options) {
763
- throw new UnexpectedCallError('Not supported options');
820
+ const root = this.#getTreeRoot();
821
+ if (options?.composed && root.is(root.DOCUMENT_FRAGMENT_NODE) && 'host' in root) {
822
+ return root.host.getRootNode(options);
823
+ }
824
+ return root;
825
+ }
826
+ #treeRoot;
827
+ #getTreeRoot() {
828
+ if (this.#treeRoot) {
829
+ return this.#treeRoot;
830
+ }
831
+ this.#treeRoot = this.#findTreeRoot();
832
+ return this.#treeRoot;
833
+ }
834
+ #findTreeRoot() {
835
+ if (this.is(this.DOCUMENT_NODE) || this.is(this.DOCUMENT_FRAGMENT_NODE)) {
836
+ return this;
837
+ }
838
+ if (this.is(this.ELEMENT_NODE) && this.isInNoTree) {
839
+ return this;
840
+ }
841
+ let ancestor = this.syntacticalParentNode;
842
+ while (ancestor && !ancestor.is(ancestor.DOCUMENT_NODE)) {
843
+ if (ancestor.is(ancestor.ELEMENT_NODE) && ancestor.isHTMLTemplateElement) {
844
+ return ancestor.content ?? this.#ownerDocument;
845
+ }
846
+ ancestor = ancestor.syntacticalParentNode;
764
847
  }
765
- // The original DOM API returns a document fragment if the element is a fragment.
766
848
  return this.#ownerDocument;
767
849
  }
768
850
  /**
@@ -829,6 +911,9 @@ export class MLNode extends MLToken {
829
911
  throw new UnexpectedCallError('Not supported "isEqualNode" method');
830
912
  }
831
913
  /**
914
+ * Whether the document of this node is a fragment of a document (a partial, a component) and
915
+ * not a whole one. The parser of the document decides it (`MLDocument#isFragment`).
916
+ *
832
917
  * @implements `@markuplint/ml-core` API: `MLNode`
833
918
  */
834
919
  isInFragmentDocument() {
@@ -15,17 +15,17 @@ export declare abstract class MLParentNode<T extends RuleConfigValue, O extends
15
15
  get childElementCount(): number;
16
16
  /**
17
17
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
18
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-children%E2%91%A0
18
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-children
19
19
  */
20
20
  get children(): HTMLCollectionOf<MLElement<T, O>>;
21
21
  /**
22
22
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
23
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-firstelementchild%E2%91%A0
23
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild
24
24
  */
25
25
  get firstElementChild(): MLElement<T, O> | null;
26
26
  /**
27
27
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
28
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-lastelementchild%E2%91%A0
28
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild
29
29
  */
30
30
  get lastElementChild(): MLElement<T, O> | null;
31
31
  /**
@@ -33,7 +33,7 @@ export declare abstract class MLParentNode<T extends RuleConfigValue, O extends
33
33
  *
34
34
  * @unsupported
35
35
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
36
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-append%E2%91%A0
36
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-append
37
37
  */
38
38
  append(...nodes: (string | Node)[]): void;
39
39
  /**
@@ -41,26 +41,57 @@ export declare abstract class MLParentNode<T extends RuleConfigValue, O extends
41
41
  *
42
42
  * @unsupported
43
43
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
44
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-prepend%E2%91%A0
44
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-prepend
45
45
  */
46
46
  prepend(...nodes: (string | Node)[]): void;
47
47
  /**
48
48
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
49
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-queryselector%E2%91%A0
49
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-queryselector
50
50
  */
51
51
  querySelector(selectors: string): MLElement<T, O> | null;
52
52
  /**
53
+ * Selects the descendants in the tree order, within the tree of this node: not the elements in
54
+ * the content of an HTML `template` element, which belong to a tree of their own.
55
+ *
53
56
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
54
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-queryselectorall%E2%91%A0
57
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
55
58
  */
56
59
  querySelectorAll(selectors: string): NodeListOf<MLElement<T, O>>;
60
+ /**
61
+ * Selects the descendants in every tree under this node: the content of an HTML `template`
62
+ * element (and a declarative shadow root) at any depth as well, and a `template` element that is in
63
+ * no tree. A selector is matched in the tree of each element, so `section p` does not match a `p`
64
+ * in the content of a `template` that is in a `section`.
65
+ *
66
+ * It is for a rule that must look at every element whatever tree it is in (a restricted element,
67
+ * a table, a line break), where `querySelectorAll` would leave the content of a `template`
68
+ * unchecked.
69
+ *
70
+ * @implements `@markuplint/ml-core` API: `MLParentNode`
71
+ * @param selectors - A selector
72
+ * @returns The matched elements in the order of the markup
73
+ */
74
+ deepQuerySelectorAll(selectors: string): readonly MLElement<T, O>[];
75
+ /**
76
+ * The first element of the tree whose `id` is `elementId` (the string that the DOM has, with
77
+ * character references resolved), found through a map that is built once: a rule resolves one
78
+ * reference per attribute.
79
+ *
80
+ * @param elementId - The id
81
+ * @returns The element, or `null`
82
+ */
83
+ protected _getElementById(elementId: string): MLElement<T, O> | null;
57
84
  /**
58
85
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
59
86
  *
60
87
  * @unsupported
61
88
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
62
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-replacechildren%E2%91%A0
89
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
63
90
  */
64
91
  replaceChildren(...nodes: (string | Node)[]): void;
92
+ /**
93
+ * The descendants in the tree order, within the tree of this node (see {@link walkTree}). An
94
+ * HTML `template` element has none: what it holds is its `content`, a tree of its own.
95
+ */
65
96
  protected _descendantsToArray<N extends MLNode<T, O>>(filter?: (node: MLNode<T, O>) => N | null | void): ReadonlyArray<N>;
66
97
  }
@@ -1,4 +1,5 @@
1
- import { syncWalk } from '../helper/walkers.js';
1
+ import { getDOMValue } from '../helper/tree-scope.js';
2
+ import { walkTree } from '../helper/walkers.js';
2
3
  import { getChildren } from '../manipulations/get-children.js';
3
4
  import { toNodeList } from './node-list.js';
4
5
  import { MLNode } from './node.js';
@@ -10,6 +11,8 @@ import { UnexpectedCallError } from '@markuplint/shared';
10
11
  export class MLParentNode extends MLNode {
11
12
  #children = null;
12
13
  #selectedElements = new Map();
14
+ #deepSelectedElements = new Map();
15
+ #idMap = null;
13
16
  /**
14
17
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
15
18
  * @see https://dom.spec.whatwg.org/#dom-parentnode-childelementcount
@@ -19,7 +22,7 @@ export class MLParentNode extends MLNode {
19
22
  }
20
23
  /**
21
24
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
22
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-children%E2%91%A0
25
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-children
23
26
  */
24
27
  get children() {
25
28
  if (this.#children) {
@@ -31,24 +34,24 @@ export class MLParentNode extends MLNode {
31
34
  }
32
35
  /**
33
36
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
34
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-firstelementchild%E2%91%A0
37
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild
35
38
  */
36
39
  get firstElementChild() {
37
- return this.children[0] ?? null;
40
+ return this.children.item(0);
38
41
  }
39
42
  /**
40
43
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
41
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-lastelementchild%E2%91%A0
44
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild
42
45
  */
43
46
  get lastElementChild() {
44
- return this.children[0] ?? null;
47
+ return this.children.item(this.children.length - 1);
45
48
  }
46
49
  /**
47
50
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
48
51
  *
49
52
  * @unsupported
50
53
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
51
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-append%E2%91%A0
54
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-append
52
55
  */
53
56
  append(
54
57
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -60,7 +63,7 @@ export class MLParentNode extends MLNode {
60
63
  *
61
64
  * @unsupported
62
65
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
63
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-prepend%E2%91%A0
66
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-prepend
64
67
  */
65
68
  prepend(
66
69
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -69,15 +72,18 @@ export class MLParentNode extends MLNode {
69
72
  }
70
73
  /**
71
74
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
72
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-queryselector%E2%91%A0
75
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-queryselector
73
76
  */
74
77
  querySelector(selectors) {
75
78
  const selected = this.querySelectorAll(selectors);
76
79
  return selected[0] ?? null;
77
80
  }
78
81
  /**
82
+ * Selects the descendants in the tree order, within the tree of this node: not the elements in
83
+ * the content of an HTML `template` element, which belong to a tree of their own.
84
+ *
79
85
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
80
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-queryselectorall%E2%91%A0
86
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
81
87
  */
82
88
  querySelectorAll(selectors) {
83
89
  const selected = this.#selectedElements.get(selectors);
@@ -92,21 +98,82 @@ export class MLParentNode extends MLNode {
92
98
  this.#selectedElements.set(selectors, elements);
93
99
  return elements;
94
100
  }
101
+ /**
102
+ * Selects the descendants in every tree under this node: the content of an HTML `template`
103
+ * element (and a declarative shadow root) at any depth as well, and a `template` element that is in
104
+ * no tree. A selector is matched in the tree of each element, so `section p` does not match a `p`
105
+ * in the content of a `template` that is in a `section`.
106
+ *
107
+ * It is for a rule that must look at every element whatever tree it is in (a restricted element,
108
+ * a table, a line break), where `querySelectorAll` would leave the content of a `template`
109
+ * unchecked.
110
+ *
111
+ * @implements `@markuplint/ml-core` API: `MLParentNode`
112
+ * @param selectors - A selector
113
+ * @returns The matched elements in the order of the markup
114
+ */
115
+ deepQuerySelectorAll(selectors) {
116
+ const selected = this.#deepSelectedElements.get(selectors);
117
+ if (selected) {
118
+ return selected;
119
+ }
120
+ const elements = [];
121
+ walkTree([...this.childNodes], node => {
122
+ if (node.is(node.ELEMENT_NODE) && node.matches(selectors, this)) {
123
+ elements.push(node);
124
+ }
125
+ }, true);
126
+ const result = Object.freeze(elements);
127
+ this.#deepSelectedElements.set(selectors, result);
128
+ return result;
129
+ }
130
+ /**
131
+ * The first element of the tree whose `id` is `elementId` (the string that the DOM has, with
132
+ * character references resolved), found through a map that is built once: a rule resolves one
133
+ * reference per attribute.
134
+ *
135
+ * @param elementId - The id
136
+ * @returns The element, or `null`
137
+ */
138
+ _getElementById(elementId) {
139
+ if (!this.#idMap) {
140
+ const idMap = new Map();
141
+ for (const el of this._descendantsToArray(node => {
142
+ if (node.is(node.ELEMENT_NODE)) {
143
+ return node;
144
+ }
145
+ })) {
146
+ const id = getDOMValue(el.getAttributeNode('id'));
147
+ if (id != null && id !== '' && !idMap.has(id)) {
148
+ idMap.set(id, el);
149
+ }
150
+ }
151
+ this.#idMap = idMap;
152
+ }
153
+ return this.#idMap.get(elementId) ?? null;
154
+ }
95
155
  /**
96
156
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
97
157
  *
98
158
  * @unsupported
99
159
  * @implements DOM API: `Element`, `Document`, `DocumentFragment`
100
- * @see https://dom.spec.whatwg.org/#ref-for-dom-parentnode-replacechildren%E2%91%A0
160
+ * @see https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
101
161
  */
102
162
  replaceChildren(
103
163
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
104
164
  ...nodes) {
105
165
  throw new UnexpectedCallError('Not supported "replaceChildren" method');
106
166
  }
167
+ /**
168
+ * The descendants in the tree order, within the tree of this node (see {@link walkTree}). An
169
+ * HTML `template` element has none: what it holds is its `content`, a tree of its own.
170
+ */
107
171
  _descendantsToArray(filter) {
172
+ if (this.is(this.ELEMENT_NODE) && this.isHTMLTemplateElement) {
173
+ return Object.freeze([]);
174
+ }
108
175
  const nodeList = [];
109
- syncWalk([...this.childNodes], node => {
176
+ walkTree([...this.childNodes], node => {
110
177
  if (filter) {
111
178
  const filtered = filter(node);
112
179
  if (filtered) {