@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
@@ -2,17 +2,23 @@
2
2
  var _a;
3
3
  import { getSpecByTagName, resolveNamespace } from '@markuplint/ml-spec';
4
4
  import { pretenderNamespaceToURI } from '@markuplint/ml-config';
5
- import { getChildNamespace } from '@markuplint/parser-utils';
6
- import { matchSelector, typeNameMatches } from '@markuplint/selector';
5
+ import { ConfigParserError, getChildNamespace } from '@markuplint/parser-utils';
6
+ import { InvalidSelectorError, matchSelector, typeNameMatches } from '@markuplint/selector';
7
7
  import { getAccname, getAccnameVisits, getHostLanguageLabelSource, getReferencedTextAlternative, } from '../helper/accname.js';
8
+ import { literalToExpression } from '../helper/literal-values.js';
9
+ import { markPretendedAttr } from '../helper/pretended-attrs.js';
10
+ import { getAssignedNodes, getAssignedSlot, getFlatTreeSlot, getFlattenedSlottables, } from '../helper/slot-assignment.js';
11
+ import { getDeclarativeShadowRootAttachment, getDeclarativeShadowRootMode, getShadowRootTemplate, isHTMLTemplateElement, } from '../helper/tree-scope.js';
8
12
  import { after, before, nextElementSibling, previousElementSibling, remove, replaceWith, } from '../manipulations/child-node-methods.js';
9
13
  import { MLAttr } from './attr.js';
14
+ import { MLDocumentFragment } from './document-fragment.js';
10
15
  import { MLDomTokenList } from './dom-token-list.js';
11
16
  import { MLElementCloseTag } from './element-close-tag.js';
12
17
  import { toNamedNodeMap } from './named-node-map.js';
13
18
  import { toHTMLCollection, toNodeList } from './node-list.js';
14
19
  import { MLParentNode } from './parent-node.js';
15
- import { UnexpectedCallError, asciiLowerCase, vuePropKey } from '@markuplint/shared';
20
+ import { MLShadowRoot } from './shadow-root.js';
21
+ import { UnexpectedCallError, asciiLowerCase, isFatalError, vuePropKey } from '@markuplint/shared';
16
22
  const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
17
23
  /**
18
24
  * Represents a DOM Element node wrapper in the markuplint DOM tree.
@@ -830,14 +836,15 @@ export class MLElement extends MLParentNode {
830
836
  throw new UnexpectedCallError('Not supported "ariaValueText" property');
831
837
  }
832
838
  /**
833
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
839
+ * The slot of an open declarative shadow root that this element is assigned to, or `null` when no
840
+ * slot takes it, the root is closed (the DOM does not expose it), or the assignment is not known
841
+ * (see {@link MLElement.getFlatTreeSlot}).
834
842
  *
835
- * @unsupported
836
843
  * @implements DOM API: `Element`
837
844
  * @see https://dom.spec.whatwg.org/#dom-slotable-assignedslot
838
845
  */
839
846
  get assignedSlot() {
840
- throw new UnexpectedCallError('Not supported "assignedSlot" property');
847
+ return getAssignedSlot(this);
841
848
  }
842
849
  /**
843
850
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -918,7 +925,7 @@ export class MLElement extends MLParentNode {
918
925
  *
919
926
  * @unsupported
920
927
  * @implements DOM API: `Element`
921
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-clientheight-1
928
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-clientheight
922
929
  */
923
930
  get clientHeight() {
924
931
  throw new UnexpectedCallError('Not supported "clientHeight" property');
@@ -928,7 +935,7 @@ export class MLElement extends MLParentNode {
928
935
  *
929
936
  * @unsupported
930
937
  * @implements DOM API: `Element`
931
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-clientleft-1
938
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-clientleft
932
939
  */
933
940
  get clientLeft() {
934
941
  throw new UnexpectedCallError('Not supported "clientLeft" property');
@@ -938,7 +945,7 @@ export class MLElement extends MLParentNode {
938
945
  *
939
946
  * @unsupported
940
947
  * @implements DOM API: `Element`
941
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-clienttop-1
948
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-clienttop
942
949
  */
943
950
  get clientTop() {
944
951
  throw new UnexpectedCallError('Not supported "clientTop" property');
@@ -948,7 +955,7 @@ export class MLElement extends MLParentNode {
948
955
  *
949
956
  * @unsupported
950
957
  * @implements DOM API: `Element`
951
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-clientwidth-1
958
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-clientwidth
952
959
  */
953
960
  get clientWidth() {
954
961
  throw new UnexpectedCallError('Not supported "clientWidth" property');
@@ -1052,7 +1059,7 @@ export class MLElement extends MLParentNode {
1052
1059
  * @deprecated
1053
1060
  * @unsupported
1054
1061
  * @implements DOM API: `Element`
1055
- * @see https://html.spec.whatwg.org/multipage/interaction.html#dom-inert
1062
+ * @see https://html.spec.whatwg.org/multipage/dom.html#dom-inert
1056
1063
  */
1057
1064
  get inert() {
1058
1065
  throw new UnexpectedCallError('Not supported "inert" property');
@@ -1062,7 +1069,7 @@ export class MLElement extends MLParentNode {
1062
1069
  *
1063
1070
  * @unsupported
1064
1071
  * @implements DOM API: `Element`
1065
- * @see https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml
1072
+ * @see https://w3c.github.io/DOM-Parsing/#dom-innerhtml
1066
1073
  */
1067
1074
  get innerHTML() {
1068
1075
  throw new UnexpectedCallError('Not supported "innerHTML" property');
@@ -2304,7 +2311,7 @@ export class MLElement extends MLParentNode {
2304
2311
  *
2305
2312
  * @unsupported
2306
2313
  * @implements DOM API: `Element`
2307
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-scrollheight-1
2314
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-scrollheight
2308
2315
  */
2309
2316
  get scrollHeight() {
2310
2317
  throw new UnexpectedCallError('Not supported "scrollHeight" property');
@@ -2314,7 +2321,7 @@ export class MLElement extends MLParentNode {
2314
2321
  *
2315
2322
  * @unsupported
2316
2323
  * @implements DOM API: `Element`
2317
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-scrollleft-1
2324
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-scrollleft
2318
2325
  */
2319
2326
  get scrollLeft() {
2320
2327
  throw new UnexpectedCallError('Not supported "scrollLeft" property');
@@ -2324,7 +2331,7 @@ export class MLElement extends MLParentNode {
2324
2331
  *
2325
2332
  * @unsupported
2326
2333
  * @implements DOM API: `Element`
2327
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-scrolltop-1
2334
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-scrolltop
2328
2335
  */
2329
2336
  get scrollTop() {
2330
2337
  throw new UnexpectedCallError('Not supported "scrollTop" property');
@@ -2334,21 +2341,114 @@ export class MLElement extends MLParentNode {
2334
2341
  *
2335
2342
  * @unsupported
2336
2343
  * @implements DOM API: `Element`
2337
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-scrollwidth-1
2344
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-scrollwidth
2338
2345
  */
2339
2346
  get scrollWidth() {
2340
2347
  throw new UnexpectedCallError('Not supported "scrollWidth" property');
2341
2348
  }
2342
2349
  /**
2343
- * **IT THROWS AN ERROR WHEN CALLING THIS.**
2350
+ * The shadow root that a `template` element with `shadowrootmode="open"` declares on this element:
2351
+ * `null` for a `closed` one, which the DOM does not expose, and when there is none. See
2352
+ * {@link MLElement.declarativeShadowRoot} for one of either mode.
2344
2353
  *
2345
- * @unsupported
2346
2354
  * @implements DOM API: `Element`
2347
2355
  * @see https://dom.spec.whatwg.org/#ref-for-dom-element-shadowroot%E2%91%A0
2348
2356
  */
2349
2357
  get shadowRoot() {
2350
- throw new UnexpectedCallError('Not supported "shadowRoot" property');
2358
+ const shadowRoot = this.declarativeShadowRoot;
2359
+ return shadowRoot?.mode === 'open' ? shadowRoot : null;
2360
+ }
2361
+ /**
2362
+ * Whether this element is an HTML `template` element, which holds its children in {@link MLElement.content},
2363
+ * a tree of its own. A `template` of a framework that puts the children in the element itself
2364
+ * (`templateContent: 'in-place'` in the spec: React, Vue), a fragment, and a `template` with a
2365
+ * directive of a template engine are not.
2366
+ *
2367
+ * The getters about trees (this one, {@link MLElement.declarativeShadowRootAttachment},
2368
+ * {@link MLElement.content}, `getRootNode()` and the id lookups) keep what they first return: they
2369
+ * depend on the pretenders, the spec and the whole syntax tree, which are all in place once the
2370
+ * document is built, and the document does not change after that.
2371
+ *
2372
+ * @implements `@markuplint/ml-core` API: `MLElement`
2373
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#the-template-element
2374
+ */
2375
+ get isHTMLTemplateElement() {
2376
+ this.#isHTMLTemplateElement ??= isHTMLTemplateElement(this);
2377
+ return this.#isHTMLTemplateElement;
2378
+ }
2379
+ #isHTMLTemplateElement;
2380
+ /**
2381
+ * Whether this element is in no tree: an HTML `template` element that a declarative shadow root is
2382
+ * attached to, or that may have one (see {@link DeclarativeShadowRootAttachment}). The parser inserts
2383
+ * such an element into no tree, so it has no parent in a tree, is its own root, and is found by no
2384
+ * query of a tree. Unknown is treated as in no tree, as markuplint does not count what it cannot tell.
2385
+ *
2386
+ * @implements `@markuplint/ml-core` API: `MLElement`
2387
+ */
2388
+ get isInNoTree() {
2389
+ return this.isHTMLTemplateElement && this.declarativeShadowRootAttachment !== 'not-attached';
2390
+ }
2391
+ /**
2392
+ * The mode that the `shadowrootmode` attribute of an HTML `template` element asks for. `null` when the
2393
+ * element is not one, the attribute is missing or invalid, or its value is dynamic.
2394
+ *
2395
+ * @implements `@markuplint/ml-core` API: `MLElement`
2396
+ */
2397
+ get declarativeShadowRootMode() {
2398
+ return getDeclarativeShadowRootMode(this);
2399
+ }
2400
+ /**
2401
+ * Whether the HTML parser attaches a declarative shadow root to the parent of this `template`
2402
+ * element (see {@link DeclarativeShadowRootAttachment}).
2403
+ *
2404
+ * @implements `@markuplint/ml-core` API: `MLElement`
2405
+ */
2406
+ get declarativeShadowRootAttachment() {
2407
+ this.#declarativeShadowRootAttachment ??= getDeclarativeShadowRootAttachment(this);
2408
+ return this.#declarativeShadowRootAttachment;
2409
+ }
2410
+ #declarativeShadowRootAttachment;
2411
+ /**
2412
+ * The content of an HTML `template` element: a document fragment that is a tree of its own, holding
2413
+ * the same nodes as the children of the element. When a declarative shadow root is attached to the
2414
+ * parent of the `template`, it is the shadow root. `null` for an element that is not an HTML
2415
+ * `template` element.
2416
+ *
2417
+ * @implements DOM API: `HTMLTemplateElement`
2418
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#dom-template-content
2419
+ */
2420
+ get content() {
2421
+ if (this.#content !== undefined) {
2422
+ return this.#content;
2423
+ }
2424
+ if (!this.isHTMLTemplateElement) {
2425
+ this.#content = null;
2426
+ return null;
2427
+ }
2428
+ const astNode = { ...this._astToken, uuid: `${this.uuid}:content` };
2429
+ this.#content =
2430
+ this.declarativeShadowRootAttachment === 'attached'
2431
+ ? new MLShadowRoot(astNode, this.ownerMLDocument, this)
2432
+ : new MLDocumentFragment(astNode, this.ownerMLDocument);
2433
+ return this.#content;
2434
+ }
2435
+ #content;
2436
+ /**
2437
+ * The shadow root that a `template` element declares on this element, whatever its mode.
2438
+ *
2439
+ * @implements `@markuplint/ml-core` API: `MLElement`
2440
+ * @returns The shadow root, or `null` when none is attached to this element
2441
+ */
2442
+ get declarativeShadowRoot() {
2443
+ if (this.#declarativeShadowRoot !== undefined) {
2444
+ return this.#declarativeShadowRoot;
2445
+ }
2446
+ const template = getShadowRootTemplate(this);
2447
+ const content = template?.content;
2448
+ this.#declarativeShadowRoot = content instanceof MLShadowRoot ? content : null;
2449
+ return this.#declarativeShadowRoot;
2351
2450
  }
2451
+ #declarativeShadowRoot;
2352
2452
  /**
2353
2453
  * @implements DOM API: `Element`
2354
2454
  * @see https://dom.spec.whatwg.org/#ref-for-dom-element-slot%E2%91%A0
@@ -2468,13 +2568,43 @@ export class MLElement extends MLParentNode {
2468
2568
  throw new UnexpectedCallError('Not supported "animate" method');
2469
2569
  }
2470
2570
  /**
2571
+ * The nodes this `slot` places, in tree order, or with `flatten` what it renders in the flat tree. It
2572
+ * is empty when the assignment is not known; use {@link MLElement.getAssignedNodes} to tell the two apart.
2573
+ *
2574
+ * @implements DOM API: `HTMLSlotElement`
2471
2575
  * @see https://html.spec.whatwg.org/multipage/scripting.html#dom-slot-assignednodes
2472
2576
  */
2473
- assignedNodes() {
2577
+ assignedNodes(options) {
2474
2578
  if (this.localName !== 'slot') {
2475
2579
  throw new TypeError('assignedNodes is not a function');
2476
2580
  }
2477
- return [];
2581
+ return [...(options?.flatten ? getFlattenedSlottables(this) : (this.getAssignedNodes() ?? []))];
2582
+ }
2583
+ /**
2584
+ * The nodes this `slot` places, by the named slot assignment of the declarative shadow root it is in.
2585
+ * A slot that takes none has `[]`, and renders its own children. `null` when the assignment is not
2586
+ * known; what makes it so is in the module comment of `helper/slot-assignment.ts`.
2587
+ *
2588
+ * @implements `@markuplint/ml-core` API: `MLElement`
2589
+ * @see https://dom.spec.whatwg.org/#find-slotables
2590
+ */
2591
+ getAssignedNodes() {
2592
+ return getAssignedNodes(this);
2593
+ }
2594
+ /**
2595
+ * The slot that places this element in the flat tree: the slot of the shadow root of its parent that
2596
+ * takes it. `null` when its parent is a shadow host and no slot takes it, so that it is not rendered;
2597
+ * `undefined` when its parent is not a shadow host, or the assignment is not known
2598
+ * (see {@link MLElement.getAssignedNodes}).
2599
+ *
2600
+ * `getComposedParentElement` of `@markuplint/ml-spec` reads it as the parent that the accessibility
2601
+ * tree has for the element.
2602
+ *
2603
+ * @implements `@markuplint/ml-core` API: `MLElement`
2604
+ * @see https://dom.spec.whatwg.org/#find-a-slot
2605
+ */
2606
+ getFlatTreeSlot() {
2607
+ return getFlatTreeSlot(this);
2478
2608
  }
2479
2609
  /**
2480
2610
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -2549,7 +2679,7 @@ export class MLElement extends MLParentNode {
2549
2679
  if (el.matches(selectors)) {
2550
2680
  return el;
2551
2681
  }
2552
- el = el.parentElement;
2682
+ el = el.treeParentElement;
2553
2683
  } while (el !== null && el.is(el.ELEMENT_NODE));
2554
2684
  return null;
2555
2685
  }
@@ -2690,7 +2820,8 @@ export class MLElement extends MLParentNode {
2690
2820
  let first = null;
2691
2821
  let lastSetByCode = null;
2692
2822
  for (const attr of this.attributes) {
2693
- if (!attr.isNamedBy(attrName)) {
2823
+ // An attribute that the framework does not output (`disabled={false}`) is not an attribute of the element
2824
+ if (attr.isAbsent || !attr.isNamedBy(attrName)) {
2694
2825
  continue;
2695
2826
  }
2696
2827
  first ??= attr;
@@ -2720,7 +2851,7 @@ export class MLElement extends MLParentNode {
2720
2851
  * @see https://dom.spec.whatwg.org/#ref-for-dom-element-getattributenames%E2%91%A0
2721
2852
  */
2722
2853
  getAttributeNames() {
2723
- return [...this.attributes].map(attr => attr.name);
2854
+ return [...this.attributes].filter(attr => !attr.isAbsent).map(attr => attr.name);
2724
2855
  }
2725
2856
  /**
2726
2857
  * @implements DOM API: `Element`
@@ -2753,7 +2884,7 @@ export class MLElement extends MLParentNode {
2753
2884
  */
2754
2885
  getAttributePretended(attrName) {
2755
2886
  for (const attr of this.#attributes) {
2756
- if (attr.isNamedBy(attrName)) {
2887
+ if (!attr.isAbsent && attr.isNamedBy(attrName)) {
2757
2888
  return attr.value;
2758
2889
  }
2759
2890
  }
@@ -2761,7 +2892,8 @@ export class MLElement extends MLParentNode {
2761
2892
  }
2762
2893
  /**
2763
2894
  * Returns all attribute tokens matching the given name, including duplicates.
2764
- * Unlike `getAttribute`, this returns the full `MLAttr` token objects.
2895
+ * Unlike `getAttribute`, this returns the full `MLAttr` token objects. An attribute that the
2896
+ * framework does not output (`MLAttr#isAbsent`) is not one of them, as it is not for `getAttribute`.
2765
2897
  *
2766
2898
  * @implements `@markuplint/ml-core` API: `MLElement`
2767
2899
  * @param attrName - The attribute name to look up (see {@link MLAttr.isNamedBy} for how its case is compared)
@@ -2770,7 +2902,7 @@ export class MLElement extends MLParentNode {
2770
2902
  getAttributeToken(attrName) {
2771
2903
  const attrs = [];
2772
2904
  for (const attr of this.getAttributeTokens()) {
2773
- if (attr.isNamedBy(attrName)) {
2905
+ if (!attr.isAbsent && attr.isNamedBy(attrName)) {
2774
2906
  attrs.push(attr);
2775
2907
  }
2776
2908
  }
@@ -2779,6 +2911,9 @@ export class MLElement extends MLParentNode {
2779
2911
  /**
2780
2912
  * Returns all attribute tokens for this element, respecting the pretender context.
2781
2913
  * If the element is pretending to be another, returns the pretender's attributes.
2914
+ * It lists every attribute that is written, those that the framework does not output
2915
+ * (`MLAttr#isAbsent`) too, as a name that is misspelled is so whatever its value is; what is asked
2916
+ * of the DOM by name (`getAttributeToken`, `getAttribute`) does not find them.
2782
2917
  *
2783
2918
  * @implements `@markuplint/ml-core` API: `MLElement`
2784
2919
  * @returns A frozen array of all attribute tokens
@@ -2791,7 +2926,7 @@ export class MLElement extends MLParentNode {
2791
2926
  *
2792
2927
  * @unsupported
2793
2928
  * @implements DOM API: `Element`
2794
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-getboundingclientrect-1
2929
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-getboundingclientrect
2795
2930
  */
2796
2931
  getBoundingClientRect() {
2797
2932
  throw new UnexpectedCallError('Not supported "getBoundingClientRect" method');
@@ -2830,7 +2965,7 @@ export class MLElement extends MLParentNode {
2830
2965
  *
2831
2966
  * @unsupported
2832
2967
  * @implements DOM API: `Element`
2833
- * @see https://www.w3.org/TR/cssom-view-1/#ref-for-dom-element-getclientrects-1
2968
+ * @see https://www.w3.org/TR/cssom-view-1/#dom-element-getclientrects
2834
2969
  */
2835
2970
  getClientRects() {
2836
2971
  throw new UnexpectedCallError('Not supported "getClientRects" method');
@@ -2912,7 +3047,7 @@ export class MLElement extends MLParentNode {
2912
3047
  * @see https://dom.spec.whatwg.org/#dom-element-hasattributes
2913
3048
  */
2914
3049
  hasAttributes() {
2915
- return this.attributes.length > 0;
3050
+ return this.attributes.some(attr => !attr.isAbsent);
2916
3051
  }
2917
3052
  /**
2918
3053
  * Checks whether this element has any mutable attributes, such as spread
@@ -2926,16 +3061,37 @@ export class MLElement extends MLParentNode {
2926
3061
  if (!attr.nameNode) {
2927
3062
  return true;
2928
3063
  }
2929
- if (attr.isDynamicValue) {
3064
+ // An attribute that is known not to be output does not change
3065
+ if (attr.isDynamicValue && !attr.isAbsent) {
2930
3066
  return true;
2931
3067
  }
2932
3068
  }
2933
3069
  return false;
2934
3070
  }
3071
+ /**
3072
+ * Checks whether this element is a component whose rendered output is not known: the
3073
+ * parser sees it as a component (`elementType` is `'authored'`) and no pretender stands
3074
+ * for an element of HTML, which includes a pretender whose own target is a component
3075
+ * (the chain of `pretenders.auto` stopped at `Tabs.Root`).
3076
+ *
3077
+ * A web component is not one: it is the element that it is written as. The criterion of
3078
+ * `isTreatedAsCustom` in `@markuplint/rules` (`permitted-contents`) includes it, because
3079
+ * a content model accepts a custom element wherever it accepts flow content; change them
3080
+ * together.
3081
+ *
3082
+ * @implements `@markuplint/ml-core` API: `MLElement`
3083
+ * @returns True if what this element renders is not known
3084
+ */
3085
+ isUnresolvedComponent() {
3086
+ if (this.pretenderContext?.type === 'pretender') {
3087
+ return this.pretenderContext.as.elementType === 'authored';
3088
+ }
3089
+ return this.elementType === 'authored';
3090
+ }
2935
3091
  /**
2936
3092
  * Checks whether this element has children that are potentially mutable,
2937
3093
  * such as preprocessor-specific blocks, slot elements, or (optionally) elements
2938
- * with dynamic attributes.
3094
+ * with dynamic attributes and components that no pretender resolved.
2939
3095
  *
2940
3096
  * Blocks that carry a `blockBehavior` are not treated as mutable because
2941
3097
  * their branches are deterministically enumerable via
@@ -2943,11 +3099,29 @@ export class MLElement extends MLParentNode {
2943
3099
  * like `{value}`) can produce arbitrary content, so the children are
2944
3100
  * considered mutable.
2945
3101
  *
3102
+ * A component that no pretender resolved renders something that is not known, however its
3103
+ * attributes are written (`size={size}`, `size={16}` and `size="16"` tell the same): a rule that
3104
+ * asks about what the descendants are or have (the accessible name, the owned elements) cannot decide
3105
+ * on it, so it is mutable when `attr` is true ({@link MLElement.isUnresolvedComponent}). A component
3106
+ * that a pretender resolved is the element that it renders, which the rules read themselves.
3107
+ *
3108
+ * A rule that asks which children an element has (the required children, the elements that
3109
+ * satisfy a query) does not pass `attr`: an attribute that cannot be known does not decide whether
3110
+ * a child exists. It looks for the unresolved components among the children itself.
3111
+ *
3112
+ * Every `slot` is mutable by default, as a `slot` that is not in a declarative shadow root renders what
3113
+ * is given to it, and a rule that reads the children as they are written (`require-datetime`) cannot
3114
+ * know it. A rule that reads what a slot of a shadow root renders by `MLElement#getAssignedNodes` says
3115
+ * which slots are with `isMutableSlot`.
3116
+ *
3117
+ *
2946
3118
  * @implements `@markuplint/ml-core` API: `MLElement`
2947
- * @param attr - When true, also considers children with mutable attributes as mutable
3119
+ * @param attr - When true, also considers children with mutable attributes, and components that no
3120
+ * pretender resolved, as mutable
3121
+ * @param isMutableSlot - Whether a `slot` among the descendants may render what is not known
2948
3122
  * @returns True if this element has potentially mutable children
2949
3123
  */
2950
- hasMutableChildren(attr = false) {
3124
+ hasMutableChildren(attr = false, isMutableSlot = () => true) {
2951
3125
  for (const child of this.getPureChildNodes()) {
2952
3126
  if (child.is(child.MARKUPLINT_PREPROCESSOR_BLOCK)) {
2953
3127
  if (child.blockBehavior) {
@@ -2959,10 +3133,13 @@ export class MLElement extends MLParentNode {
2959
3133
  if (attr && child.hasMutableAttributes()) {
2960
3134
  return true;
2961
3135
  }
2962
- if (child.hasMutableChildren(attr)) {
3136
+ if (attr && child.isUnresolvedComponent()) {
3137
+ return true;
3138
+ }
3139
+ if (child.hasMutableChildren(attr, isMutableSlot)) {
2963
3140
  return true;
2964
3141
  }
2965
- if (child.localName === 'slot') {
3142
+ if (child.localName === 'slot' && isMutableSlot(child)) {
2966
3143
  return true;
2967
3144
  }
2968
3145
  }
@@ -3006,7 +3183,7 @@ export class MLElement extends MLParentNode {
3006
3183
  *
3007
3184
  * @unsupported
3008
3185
  * @implements DOM API: `Element`
3009
- * @see https://w3c.github.io/DOM-Parsing/#widl-Element-insertAdjacentHTML-void-DOMString-position-DOMString-text
3186
+ * @see https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml
3010
3187
  */
3011
3188
  insertAdjacentHTML(position, text) {
3012
3189
  throw new UnexpectedCallError('Does not implement "insertAdjacentHTML" method yet');
@@ -3221,7 +3398,19 @@ export class MLElement extends MLParentNode {
3221
3398
  if (this.elementType === 'html' && this.ownerMLDocument.isNativeTag(this.localName, this.namespaceURI)) {
3222
3399
  return;
3223
3400
  }
3224
- const pretenderConfig = pretenders?.find(option => this.matches(option.selector));
3401
+ const pretenderConfig = pretenders?.find(option => {
3402
+ try {
3403
+ return this.matches(option.selector);
3404
+ }
3405
+ catch (error) {
3406
+ // An entry that a scan or a file made names the file that declares its component, which is
3407
+ // where the user fixes it. `MLDocument` names the configuration for any other.
3408
+ if (!isFatalError(error) && error instanceof InvalidSelectorError && option.filePath) {
3409
+ throw new ConfigParserError(error.message, { filePath: option.filePath, raw: option.selector });
3410
+ }
3411
+ throw error;
3412
+ }
3413
+ });
3225
3414
  const asAttrValue = this.getAttribute('as');
3226
3415
  const pretenderElement = pretenderConfig?.as ??
3227
3416
  (this.elementType === 'html' || !asAttrValue
@@ -3286,9 +3475,19 @@ export class MLElement extends MLParentNode {
3286
3475
  };
3287
3476
  }
3288
3477
  /**
3289
- * Converts pretender attributes to AST attributes for a virtual element.
3478
+ * Converts pretender attributes to AST attributes for a virtual element. They are marked as made by
3479
+ * a pretender (`MLAttr#isPretended`): the position of their tokens is the element's, not the source's.
3480
+ *
3481
+ * `{ expression }` is the code that the attribute is written as in the component. It becomes an
3482
+ * attribute written as code (`{` and `}` are its quotes), which `MLAttr` reads as JavaScript
3483
+ * whatever the language of the document is: a literal in it is rendered as the framework renders
3484
+ * it, and any other code is dynamic.
3290
3485
  *
3291
- * `{ fromAttr }` reads this (the origin) element's attribute. When that attribute is an
3486
+ * `{ fromAttr }` reads this (the origin) element's attribute. When that attribute is a literal
3487
+ * (`kind={false}`, `kind`), the literal is rendered under the name of the attribute that the
3488
+ * virtual element gets, which is what the component does with the prop it forwards
3489
+ * (`<Button disabled={false}>` forwarded to `<button disabled={props.disabled}>` has no `disabled`,
3490
+ * and forwarded to `aria-label={props.disabled}` it has `aria-label="false"`). When it is any other
3292
3491
  * expression (`kind={kind}`), the result is dynamic as well: the value the component
3293
3492
  * receives is unknown, and the source text of the expression is not a value to validate.
3294
3493
  * When the attribute is not written and `omitIfMissing` is set, the virtual element gets
@@ -3302,9 +3501,14 @@ export class MLElement extends MLParentNode {
3302
3501
  return attrs.flatMap(({ name, value }, i) => {
3303
3502
  let isDynamic = value != null && typeof value === 'object' && 'dynamic' in value;
3304
3503
  let _value = '';
3504
+ // The code that the attribute is written as, when it is known
3505
+ let expression;
3305
3506
  if (typeof value === 'string') {
3306
3507
  _value = value;
3307
3508
  }
3509
+ else if (value != null && 'expression' in value) {
3510
+ expression = value.expression;
3511
+ }
3308
3512
  else if (value != null && 'fromAttr' in value) {
3309
3513
  // The attribute of the usage site that the prop is. Vue gives a prop the attribute by the camelCase
3310
3514
  // of its name (`my-label` and `myLabel` are the prop `myLabel`, and `mylabel` is another),
@@ -3316,15 +3520,21 @@ export class MLElement extends MLParentNode {
3316
3520
  if (!origin && value.omitIfMissing) {
3317
3521
  return [];
3318
3522
  }
3319
- if (origin?.isDynamicValue) {
3523
+ if (origin?.literal) {
3524
+ // The key of a literal is the code that it is written as (`true`, `"x"`, `0`), except for a number that
3525
+ // is not finite (`Infinity` is a name, which is not a literal)
3526
+ expression = literalToExpression(origin.literal);
3527
+ }
3528
+ else if (origin?.isDynamicValue) {
3320
3529
  isDynamic = true;
3321
3530
  }
3322
3531
  else {
3323
3532
  _value = origin?.value ?? '';
3324
3533
  }
3325
3534
  }
3535
+ const isExpression = expression !== undefined;
3326
3536
  return [
3327
- {
3537
+ markPretendedAttr({
3328
3538
  ...this._astToken,
3329
3539
  uuid: `${uuidBase}_attr_${i}`,
3330
3540
  type: 'attr',
@@ -3351,24 +3561,24 @@ export class MLElement extends MLParentNode {
3351
3561
  },
3352
3562
  startQuote: {
3353
3563
  ...this._astToken,
3354
- raw: '',
3564
+ raw: isExpression ? '{' : '',
3355
3565
  },
3356
3566
  value: {
3357
3567
  ...this._astToken,
3358
- raw: _value,
3568
+ raw: expression ?? _value,
3359
3569
  },
3360
3570
  endQuote: {
3361
3571
  ...this._astToken,
3362
- raw: '',
3572
+ raw: isExpression ? '}' : '',
3363
3573
  },
3364
3574
  isDuplicatable: true,
3365
- ...(isDynamic ? { isDynamicValue: true } : {}),
3575
+ ...(isDynamic || isExpression ? { isDynamicValue: true } : {}),
3366
3576
  parentNode: null,
3367
3577
  nextNode: null,
3368
3578
  prevNode: null,
3369
3579
  isFragment: false,
3370
3580
  isGhost: false,
3371
- },
3581
+ }),
3372
3582
  ];
3373
3583
  });
3374
3584
  }
@@ -3543,7 +3753,7 @@ export class MLElement extends MLParentNode {
3543
3753
  *
3544
3754
  * @unsupported
3545
3755
  * @implements DOM API: `Element`
3546
- * @see https://w3c.github.io/pointerlock/#dom-element-requestpointerlock
3756
+ * @see https://www.w3.org/TR/pointerlock-2/#dom-element-requestpointerlock
3547
3757
  */
3548
3758
  requestPointerLock(options) {
3549
3759
  throw new UnexpectedCallError('Not supported "requestPointerLock" method');
@@ -1,7 +1,8 @@
1
1
  import { UnexpectedCallError } from '@markuplint/shared';
2
2
  export class MLNamedNodeMap extends Array {
3
3
  getNamedItem(qualifiedName) {
4
- return this.find(attr => attr.isNamedBy(qualifiedName)) ?? null;
4
+ // An attribute that the framework does not output (`MLAttr#isAbsent`) is not found, as the DOM has none
5
+ return this.find(attr => !attr.isAbsent && attr.isNamedBy(qualifiedName)) ?? null;
5
6
  }
6
7
  /**
7
8
  * **IT THROWS AN ERROR WHEN CALLING THIS.**
@@ -10,10 +10,9 @@ class MLNodeList extends Array {
10
10
  *
11
11
  * It throws an error because it should return null but the `item` method of `NodeListOf` doesn't return null in the type definition of TypeScript.
12
12
  *
13
- * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-844377136
13
+ * @see https://dom.spec.whatwg.org/#dom-nodelist-item
14
14
  *
15
- * > item
16
- * > Returns the index-th item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null.
15
+ * > The item(index) method must return the indexth node in the collection. If there is no indexth node in the collection, then the method must return null.
17
16
  */
18
17
  throw new ReferenceError('Invalid index');
19
18
  }