@microsoft/fast-element 2.8.5 → 2.9.1

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.json CHANGED
@@ -2,7 +2,37 @@
2
2
  "name": "@microsoft/fast-element",
3
3
  "entries": [
4
4
  {
5
- "date": "Tue, 02 Dec 2025 21:46:27 GMT",
5
+ "date": "Tue, 16 Dec 2025 21:27:19 GMT",
6
+ "version": "2.9.1",
7
+ "tag": "@microsoft/fast-element_v2.9.1",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "author": "863023+radium-v@users.noreply.github.com",
12
+ "package": "@microsoft/fast-element",
13
+ "commit": "2c8de46fbde0440593f559cd2ddd8d7c6248f68d",
14
+ "comment": "fix: correct hydration marker indexes for templates with host bindings"
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Mon, 08 Dec 2025 19:47:51 GMT",
21
+ "version": "2.9.0",
22
+ "tag": "@microsoft/fast-element_v2.9.0",
23
+ "comments": {
24
+ "minor": [
25
+ {
26
+ "author": "863023+radium-v@users.noreply.github.com",
27
+ "package": "@microsoft/fast-element",
28
+ "commit": "1e21fa3cac5f940f639bf76311af291e1a368da1",
29
+ "comment": "feat: add support for compact attribute binding markers"
30
+ }
31
+ ]
32
+ }
33
+ },
34
+ {
35
+ "date": "Tue, 02 Dec 2025 21:46:59 GMT",
6
36
  "version": "2.8.5",
7
37
  "tag": "@microsoft/fast-element_v2.8.5",
8
38
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,12 +1,28 @@
1
1
  # Change Log - @microsoft/fast-element
2
2
 
3
- <!-- This log was last generated on Tue, 02 Dec 2025 21:46:27 GMT and should not be manually modified. -->
3
+ <!-- This log was last generated on Tue, 16 Dec 2025 21:27:19 GMT and should not be manually modified. -->
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 2.9.1
8
+
9
+ Tue, 16 Dec 2025 21:27:19 GMT
10
+
11
+ ### Patches
12
+
13
+ - fix: correct hydration marker indexes for templates with host bindings (863023+radium-v@users.noreply.github.com)
14
+
15
+ ## 2.9.0
16
+
17
+ Mon, 08 Dec 2025 19:47:51 GMT
18
+
19
+ ### Minor changes
20
+
21
+ - feat: add support for compact attribute binding markers (863023+radium-v@users.noreply.github.com)
22
+
7
23
  ## 2.8.5
8
24
 
9
- Tue, 02 Dec 2025 21:46:27 GMT
25
+ Tue, 02 Dec 2025 21:46:59 GMT
10
26
 
11
27
  ### Patches
12
28
 
@@ -8,6 +8,7 @@ import type { HydrationView } from "../templating/view.js";
8
8
  */
9
9
  export declare const HydrationMarkup: Readonly<{
10
10
  attributeMarkerName: "data-fe-b";
11
+ compactAttributeMarkerName: "data-fe-c";
11
12
  attributeBindingSeparator: " ";
12
13
  contentBindingStartMarker(index: number, uniqueId: string): string;
13
14
  contentBindingEndMarker(index: number, uniqueId: string): string;
@@ -22,15 +23,25 @@ export declare const HydrationMarkup: Readonly<{
22
23
  /**
23
24
  * Returns the indexes of the ViewBehaviorFactories affecting
24
25
  * attributes for the element, or null if no factories were found.
26
+ *
27
+ * This method parses the space-separated format: `data-fe-b="0 1 2"`.
25
28
  */
26
29
  parseAttributeBinding(node: Element): null | number[];
27
30
  /**
28
31
  * Returns the indexes of the ViewBehaviorFactories affecting
29
32
  * attributes for the element, or null if no factories were found.
30
33
  *
31
- * Uses the alternative syntax of data-fe-b-<number>
34
+ * This method parses the enumerated format: `data-fe-b-0`, `data-fe-b-1`, `data-fe-b-2`.
35
+ * This is an alternative format that uses separate attributes for each binding index.
32
36
  */
33
37
  parseEnumeratedAttributeBinding(node: Element): null | number[];
38
+ /**
39
+ * Returns the indexes of the ViewBehaviorFactories affecting
40
+ * attributes for the element, or null if no factories were found.
41
+ *
42
+ * This method parses the compact format: `data-fe-c-{index}-{count}`.
43
+ */
44
+ parseCompactAttributeBinding(node: Element): null | number[];
34
45
  /**
35
46
  * Parses the ViewBehaviorFactory index from string data. Returns
36
47
  * the binding index or null if the index cannot be retrieved.
@@ -1,3 +1,4 @@
1
+ import { FAST } from "../platform.js";
1
2
  const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
2
3
  const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
3
4
  const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
@@ -13,6 +14,7 @@ function isComment(node) {
13
14
  */
14
15
  export const HydrationMarkup = Object.freeze({
15
16
  attributeMarkerName: "data-fe-b",
17
+ compactAttributeMarkerName: "data-fe-c",
16
18
  attributeBindingSeparator: " ",
17
19
  contentBindingStartMarker(index, uniqueId) {
18
20
  return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
@@ -47,6 +49,8 @@ export const HydrationMarkup = Object.freeze({
47
49
  /**
48
50
  * Returns the indexes of the ViewBehaviorFactories affecting
49
51
  * attributes for the element, or null if no factories were found.
52
+ *
53
+ * This method parses the space-separated format: `data-fe-b="0 1 2"`.
50
54
  */
51
55
  parseAttributeBinding(node) {
52
56
  const attr = node.getAttribute(this.attributeMarkerName);
@@ -58,7 +62,8 @@ export const HydrationMarkup = Object.freeze({
58
62
  * Returns the indexes of the ViewBehaviorFactories affecting
59
63
  * attributes for the element, or null if no factories were found.
60
64
  *
61
- * Uses the alternative syntax of data-fe-b-<number>
65
+ * This method parses the enumerated format: `data-fe-b-0`, `data-fe-b-1`, `data-fe-b-2`.
66
+ * This is an alternative format that uses separate attributes for each binding index.
62
67
  */
63
68
  parseEnumeratedAttributeBinding(node) {
64
69
  const attrs = [];
@@ -71,12 +76,47 @@ export const HydrationMarkup = Object.freeze({
71
76
  attrs.push(count);
72
77
  }
73
78
  else {
74
- throw new Error(`Invalid attribute marker name: ${attr}. Expected format is ${prefix}<number>.`);
79
+ throw FAST.error(1601 /* invalidAttributeMarkerName */, {
80
+ name: attr,
81
+ expectedFormat: `${prefix}<number>`,
82
+ });
75
83
  }
76
84
  }
77
85
  }
78
86
  return attrs.length === 0 ? null : attrs;
79
87
  },
88
+ /**
89
+ * Returns the indexes of the ViewBehaviorFactories affecting
90
+ * attributes for the element, or null if no factories were found.
91
+ *
92
+ * This method parses the compact format: `data-fe-c-{index}-{count}`.
93
+ */
94
+ parseCompactAttributeBinding(node) {
95
+ const prefix = `${this.compactAttributeMarkerName}-`;
96
+ const attrName = node.getAttributeNames().find(name => name.startsWith(prefix));
97
+ if (!attrName) {
98
+ return null;
99
+ }
100
+ const suffix = attrName.slice(prefix.length);
101
+ const parts = suffix.split("-");
102
+ const startIndex = parseInt(parts[0], 10);
103
+ const count = parseInt(parts[1], 10);
104
+ if (parts.length !== 2 ||
105
+ Number.isNaN(startIndex) ||
106
+ Number.isNaN(count) ||
107
+ startIndex < 0 ||
108
+ count < 1) {
109
+ throw FAST.error(1604 /* invalidCompactAttributeMarkerName */, {
110
+ name: attrName,
111
+ expectedFormat: `${this.compactAttributeMarkerName}-{index}-{count}`,
112
+ });
113
+ }
114
+ const indexes = [];
115
+ for (let i = 0; i < count; i++) {
116
+ indexes.push(startIndex + i);
117
+ }
118
+ return indexes;
119
+ },
80
120
  /**
81
121
  * Parses the ViewBehaviorFactory index from string data. Returns
82
122
  * the binding index or null if the index cannot be retrieved.
package/dist/esm/debug.js CHANGED
@@ -35,6 +35,9 @@ const debugMessages = {
35
35
  [1512 /* noDefaultResolver */]: "'${key}' not registered. Did you forget to add @singleton()?",
36
36
  [1513 /* cyclicDependency */]: "Cyclic dependency found '${name}'.",
37
37
  [1514 /* connectUpdateRequiresController */]: "Injected properties that are updated on changes to DOM connectivity require the target object to be an instance of FASTElement.",
38
+ [1601 /* invalidAttributeMarkerName */]: "Invalid attribute marker name: ${name}. Expected format is ${expectedFormat}.",
39
+ [1602 /* invalidCompactAttributeMarkerValues */]: "Invalid compact attribute marker values in ${markerName}. Both index and count must be positive integers.",
40
+ [1604 /* invalidCompactAttributeMarkerName */]: "Invalid compact attribute marker name: ${name}. Expected format is ${expectedFormat}.",
38
41
  };
39
42
  const allPlaceholders = /(\$\{\w+?})/g;
40
43
  const placeholder = /\$\{(\w+?)}/g;
@@ -52,6 +52,7 @@ function isShadowRoot(node) {
52
52
  export function buildViewBindingTargets(firstNode, lastNode, factories) {
53
53
  const range = createRangeForNodes(firstNode, lastNode);
54
54
  const treeRoot = range.commonAncestorContainer;
55
+ const hydrationIndexOffset = getHydrationIndexOffset(factories);
55
56
  const walker = document.createTreeWalker(treeRoot, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_COMMENT + NodeFilter.SHOW_TEXT, {
56
57
  acceptNode(node) {
57
58
  return range.comparePoint(node, 0) === 0
@@ -65,11 +66,11 @@ export function buildViewBindingTargets(firstNode, lastNode, factories) {
65
66
  while (node !== null) {
66
67
  switch (node.nodeType) {
67
68
  case Node.ELEMENT_NODE: {
68
- targetElement(node, factories, targets);
69
+ targetElement(node, factories, targets, hydrationIndexOffset);
69
70
  break;
70
71
  }
71
72
  case Node.COMMENT_NODE: {
72
- targetComment(node, walker, factories, targets, boundaries);
73
+ targetComment(node, walker, factories, targets, boundaries, hydrationIndexOffset);
73
74
  break;
74
75
  }
75
76
  }
@@ -78,21 +79,22 @@ export function buildViewBindingTargets(firstNode, lastNode, factories) {
78
79
  range.detach();
79
80
  return { targets, boundaries };
80
81
  }
81
- function targetElement(node, factories, targets) {
82
- var _a;
82
+ function targetElement(node, factories, targets, hydrationIndexOffset) {
83
+ var _a, _b;
83
84
  // Check for attributes and map any factories.
84
- const attrFactoryIds = (_a = HydrationMarkup.parseAttributeBinding(node)) !== null && _a !== void 0 ? _a : HydrationMarkup.parseEnumeratedAttributeBinding(node);
85
+ const attrFactoryIds = (_b = (_a = HydrationMarkup.parseAttributeBinding(node)) !== null && _a !== void 0 ? _a : HydrationMarkup.parseEnumeratedAttributeBinding(node)) !== null && _b !== void 0 ? _b : HydrationMarkup.parseCompactAttributeBinding(node);
85
86
  if (attrFactoryIds !== null) {
86
87
  for (const id of attrFactoryIds) {
87
- if (!factories[id]) {
88
+ const factory = factories[id + hydrationIndexOffset];
89
+ if (!factory) {
88
90
  throw new HydrationTargetElementError(`HydrationView was unable to successfully target factory on ${node.nodeName} inside ${node.getRootNode().host.nodeName}. This likely indicates a template mismatch between SSR rendering and hydration.`, factories, node);
89
91
  }
90
- targetFactory(factories[id], node, targets);
92
+ targetFactory(factory, node, targets);
91
93
  }
92
94
  node.removeAttribute(HydrationMarkup.attributeMarkerName);
93
95
  }
94
96
  }
95
- function targetComment(node, walker, factories, targets, boundaries) {
97
+ function targetComment(node, walker, factories, targets, boundaries, hydrationIndexOffset) {
96
98
  if (HydrationMarkup.isElementBoundaryStartMarker(node)) {
97
99
  skipToElementBoundaryEndMarker(node, walker);
98
100
  return;
@@ -103,7 +105,7 @@ function targetComment(node, walker, factories, targets, boundaries) {
103
105
  return;
104
106
  }
105
107
  const [index, id] = parsed;
106
- const factory = factories[index];
108
+ const factory = factories[index + hydrationIndexOffset];
107
109
  const nodes = [];
108
110
  let current = walker.nextSibling();
109
111
  node.data = "";
@@ -167,6 +169,18 @@ function skipToElementBoundaryEndMarker(node, walker) {
167
169
  current = walker.nextSibling();
168
170
  }
169
171
  }
172
+ function getHydrationIndexOffset(factories) {
173
+ let offset = 0;
174
+ for (let i = 0, ii = factories.length; i < ii; ++i) {
175
+ if (factories[i].targetNodeId === "h") {
176
+ offset++;
177
+ }
178
+ else {
179
+ break;
180
+ }
181
+ }
182
+ return offset;
183
+ }
170
184
  export function targetFactory(factory, node, targets) {
171
185
  if (factory.targetNodeId === undefined) {
172
186
  // Dev error, this shouldn't ever be thrown
@@ -35,6 +35,9 @@ const debugMessages = {
35
35
  [1512 /* noDefaultResolver */]: "'${key}' not registered. Did you forget to add @singleton()?",
36
36
  [1513 /* cyclicDependency */]: "Cyclic dependency found '${name}'.",
37
37
  [1514 /* connectUpdateRequiresController */]: "Injected properties that are updated on changes to DOM connectivity require the target object to be an instance of FASTElement.",
38
+ [1601 /* invalidAttributeMarkerName */]: "Invalid attribute marker name: ${name}. Expected format is ${expectedFormat}.",
39
+ [1602 /* invalidCompactAttributeMarkerValues */]: "Invalid compact attribute marker values in ${markerName}. Both index and count must be positive integers.",
40
+ [1604 /* invalidCompactAttributeMarkerName */]: "Invalid compact attribute marker name: ${name}. Expected format is ${expectedFormat}.",
38
41
  };
39
42
  const allPlaceholders = /(\$\{\w+?})/g;
40
43
  const placeholder = /\$\{(\w+?)}/g;
@@ -2333,6 +2336,7 @@ function isComment$1(node) {
2333
2336
  */
2334
2337
  const HydrationMarkup = Object.freeze({
2335
2338
  attributeMarkerName: "data-fe-b",
2339
+ compactAttributeMarkerName: "data-fe-c",
2336
2340
  attributeBindingSeparator: " ",
2337
2341
  contentBindingStartMarker(index, uniqueId) {
2338
2342
  return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
@@ -2367,6 +2371,8 @@ const HydrationMarkup = Object.freeze({
2367
2371
  /**
2368
2372
  * Returns the indexes of the ViewBehaviorFactories affecting
2369
2373
  * attributes for the element, or null if no factories were found.
2374
+ *
2375
+ * This method parses the space-separated format: `data-fe-b="0 1 2"`.
2370
2376
  */
2371
2377
  parseAttributeBinding(node) {
2372
2378
  const attr = node.getAttribute(this.attributeMarkerName);
@@ -2378,7 +2384,8 @@ const HydrationMarkup = Object.freeze({
2378
2384
  * Returns the indexes of the ViewBehaviorFactories affecting
2379
2385
  * attributes for the element, or null if no factories were found.
2380
2386
  *
2381
- * Uses the alternative syntax of data-fe-b-<number>
2387
+ * This method parses the enumerated format: `data-fe-b-0`, `data-fe-b-1`, `data-fe-b-2`.
2388
+ * This is an alternative format that uses separate attributes for each binding index.
2382
2389
  */
2383
2390
  parseEnumeratedAttributeBinding(node) {
2384
2391
  const attrs = [];
@@ -2391,12 +2398,47 @@ const HydrationMarkup = Object.freeze({
2391
2398
  attrs.push(count);
2392
2399
  }
2393
2400
  else {
2394
- throw new Error(`Invalid attribute marker name: ${attr}. Expected format is ${prefix}<number>.`);
2401
+ throw FAST.error(1601 /* invalidAttributeMarkerName */, {
2402
+ name: attr,
2403
+ expectedFormat: `${prefix}<number>`,
2404
+ });
2395
2405
  }
2396
2406
  }
2397
2407
  }
2398
2408
  return attrs.length === 0 ? null : attrs;
2399
2409
  },
2410
+ /**
2411
+ * Returns the indexes of the ViewBehaviorFactories affecting
2412
+ * attributes for the element, or null if no factories were found.
2413
+ *
2414
+ * This method parses the compact format: `data-fe-c-{index}-{count}`.
2415
+ */
2416
+ parseCompactAttributeBinding(node) {
2417
+ const prefix = `${this.compactAttributeMarkerName}-`;
2418
+ const attrName = node.getAttributeNames().find(name => name.startsWith(prefix));
2419
+ if (!attrName) {
2420
+ return null;
2421
+ }
2422
+ const suffix = attrName.slice(prefix.length);
2423
+ const parts = suffix.split("-");
2424
+ const startIndex = parseInt(parts[0], 10);
2425
+ const count = parseInt(parts[1], 10);
2426
+ if (parts.length !== 2 ||
2427
+ Number.isNaN(startIndex) ||
2428
+ Number.isNaN(count) ||
2429
+ startIndex < 0 ||
2430
+ count < 1) {
2431
+ throw FAST.error(1604 /* invalidCompactAttributeMarkerName */, {
2432
+ name: attrName,
2433
+ expectedFormat: `${this.compactAttributeMarkerName}-{index}-{count}`,
2434
+ });
2435
+ }
2436
+ const indexes = [];
2437
+ for (let i = 0; i < count; i++) {
2438
+ indexes.push(startIndex + i);
2439
+ }
2440
+ return indexes;
2441
+ },
2400
2442
  /**
2401
2443
  * Parses the ViewBehaviorFactory index from string data. Returns
2402
2444
  * the binding index or null if the index cannot be retrieved.
@@ -2686,6 +2728,7 @@ function isShadowRoot(node) {
2686
2728
  function buildViewBindingTargets(firstNode, lastNode, factories) {
2687
2729
  const range = createRangeForNodes(firstNode, lastNode);
2688
2730
  const treeRoot = range.commonAncestorContainer;
2731
+ const hydrationIndexOffset = getHydrationIndexOffset(factories);
2689
2732
  const walker = document.createTreeWalker(treeRoot, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_COMMENT + NodeFilter.SHOW_TEXT, {
2690
2733
  acceptNode(node) {
2691
2734
  return range.comparePoint(node, 0) === 0
@@ -2699,11 +2742,11 @@ function buildViewBindingTargets(firstNode, lastNode, factories) {
2699
2742
  while (node !== null) {
2700
2743
  switch (node.nodeType) {
2701
2744
  case Node.ELEMENT_NODE: {
2702
- targetElement(node, factories, targets);
2745
+ targetElement(node, factories, targets, hydrationIndexOffset);
2703
2746
  break;
2704
2747
  }
2705
2748
  case Node.COMMENT_NODE: {
2706
- targetComment(node, walker, factories, targets, boundaries);
2749
+ targetComment(node, walker, factories, targets, boundaries, hydrationIndexOffset);
2707
2750
  break;
2708
2751
  }
2709
2752
  }
@@ -2712,21 +2755,22 @@ function buildViewBindingTargets(firstNode, lastNode, factories) {
2712
2755
  range.detach();
2713
2756
  return { targets, boundaries };
2714
2757
  }
2715
- function targetElement(node, factories, targets) {
2716
- var _a;
2758
+ function targetElement(node, factories, targets, hydrationIndexOffset) {
2759
+ var _a, _b;
2717
2760
  // Check for attributes and map any factories.
2718
- const attrFactoryIds = (_a = HydrationMarkup.parseAttributeBinding(node)) !== null && _a !== void 0 ? _a : HydrationMarkup.parseEnumeratedAttributeBinding(node);
2761
+ const attrFactoryIds = (_b = (_a = HydrationMarkup.parseAttributeBinding(node)) !== null && _a !== void 0 ? _a : HydrationMarkup.parseEnumeratedAttributeBinding(node)) !== null && _b !== void 0 ? _b : HydrationMarkup.parseCompactAttributeBinding(node);
2719
2762
  if (attrFactoryIds !== null) {
2720
2763
  for (const id of attrFactoryIds) {
2721
- if (!factories[id]) {
2764
+ const factory = factories[id + hydrationIndexOffset];
2765
+ if (!factory) {
2722
2766
  throw new HydrationTargetElementError(`HydrationView was unable to successfully target factory on ${node.nodeName} inside ${node.getRootNode().host.nodeName}. This likely indicates a template mismatch between SSR rendering and hydration.`, factories, node);
2723
2767
  }
2724
- targetFactory(factories[id], node, targets);
2768
+ targetFactory(factory, node, targets);
2725
2769
  }
2726
2770
  node.removeAttribute(HydrationMarkup.attributeMarkerName);
2727
2771
  }
2728
2772
  }
2729
- function targetComment(node, walker, factories, targets, boundaries) {
2773
+ function targetComment(node, walker, factories, targets, boundaries, hydrationIndexOffset) {
2730
2774
  if (HydrationMarkup.isElementBoundaryStartMarker(node)) {
2731
2775
  skipToElementBoundaryEndMarker(node, walker);
2732
2776
  return;
@@ -2737,7 +2781,7 @@ function targetComment(node, walker, factories, targets, boundaries) {
2737
2781
  return;
2738
2782
  }
2739
2783
  const [index, id] = parsed;
2740
- const factory = factories[index];
2784
+ const factory = factories[index + hydrationIndexOffset];
2741
2785
  const nodes = [];
2742
2786
  let current = walker.nextSibling();
2743
2787
  node.data = "";
@@ -2801,6 +2845,18 @@ function skipToElementBoundaryEndMarker(node, walker) {
2801
2845
  current = walker.nextSibling();
2802
2846
  }
2803
2847
  }
2848
+ function getHydrationIndexOffset(factories) {
2849
+ let offset = 0;
2850
+ for (let i = 0, ii = factories.length; i < ii; ++i) {
2851
+ if (factories[i].targetNodeId === "h") {
2852
+ offset++;
2853
+ }
2854
+ else {
2855
+ break;
2856
+ }
2857
+ }
2858
+ return offset;
2859
+ }
2804
2860
  function targetFactory(factory, node, targets) {
2805
2861
  if (factory.targetNodeId === undefined) {
2806
2862
  // Dev error, this shouldn't ever be thrown