@microsoft/fast-element 2.0.0-beta.9 → 2.0.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 (142) hide show
  1. package/.eslintrc.json +1 -1
  2. package/CHANGELOG.json +497 -0
  3. package/CHANGELOG.md +172 -1
  4. package/README.md +1 -9
  5. package/api-extractor.context.json +14 -0
  6. package/api-extractor.di.json +14 -0
  7. package/dist/context/context.api.json +1068 -0
  8. package/dist/di/di.api.json +4929 -0
  9. package/dist/dts/binding/binding.d.ts +49 -0
  10. package/dist/dts/binding/normalize.d.ts +9 -0
  11. package/dist/dts/binding/one-time.d.ts +11 -0
  12. package/dist/dts/binding/one-way.d.ts +20 -0
  13. package/dist/dts/{templating/binding-signal.d.ts → binding/signal.d.ts} +19 -4
  14. package/dist/dts/{templating/binding-two-way.d.ts → binding/two-way.d.ts} +9 -5
  15. package/dist/dts/components/attributes.d.ts +6 -0
  16. package/dist/dts/components/element-controller.d.ts +104 -8
  17. package/dist/dts/components/element-hydration.d.ts +2 -0
  18. package/dist/dts/components/fast-definitions.d.ts +6 -0
  19. package/dist/dts/components/hydration.d.ts +56 -0
  20. package/dist/dts/components/install-hydration.d.ts +1 -0
  21. package/dist/dts/context.d.ts +29 -15
  22. package/dist/dts/di/di.d.ts +0 -5
  23. package/dist/dts/dom-policy.d.ts +83 -0
  24. package/dist/dts/dom.d.ts +100 -0
  25. package/dist/dts/hydration/target-builder.d.ts +63 -0
  26. package/dist/dts/index.d.ts +33 -26
  27. package/dist/dts/index.rollup.d.ts +0 -1
  28. package/dist/dts/index.rollup.debug.d.ts +0 -1
  29. package/dist/dts/interfaces.d.ts +32 -82
  30. package/dist/dts/metadata.d.ts +6 -5
  31. package/dist/dts/observation/arrays.d.ts +1 -1
  32. package/dist/dts/observation/observable.bench.d.ts +18 -0
  33. package/dist/dts/observation/observable.d.ts +5 -5
  34. package/dist/dts/pending-task.d.ts +19 -7
  35. package/dist/dts/platform.d.ts +11 -2
  36. package/dist/dts/polyfills.d.ts +0 -8
  37. package/dist/dts/styles/css-binding-directive.d.ts +60 -0
  38. package/dist/dts/styles/css.d.ts +9 -7
  39. package/dist/dts/styles/element-styles.d.ts +1 -14
  40. package/dist/dts/styles/host.d.ts +2 -5
  41. package/dist/dts/styles/style-strategy.d.ts +42 -0
  42. package/dist/dts/templating/compiler.d.ts +11 -13
  43. package/dist/dts/templating/{binding.d.ts → html-binding-directive.d.ts} +21 -41
  44. package/dist/dts/templating/html-directive.d.ts +44 -140
  45. package/dist/dts/templating/install-hydratable-view-templates.d.ts +1 -0
  46. package/dist/dts/templating/node-observation.d.ts +11 -1
  47. package/dist/dts/templating/ref.d.ts +4 -0
  48. package/dist/dts/templating/render.bench.d.ts +3 -0
  49. package/dist/dts/templating/render.d.ts +49 -9
  50. package/dist/dts/templating/repeat-basic-reverse.bench.d.ts +3 -0
  51. package/dist/dts/templating/repeat-basic-shift.bench.d.ts +3 -0
  52. package/dist/dts/templating/repeat.d.ts +31 -9
  53. package/dist/dts/templating/template.d.ts +97 -12
  54. package/dist/dts/templating/view.d.ts +146 -29
  55. package/dist/dts/templating/when-basic.bench.d.ts +3 -0
  56. package/dist/dts/templating/when-conditional.bench.d.ts +3 -0
  57. package/dist/dts/templating/when-switch.bench.d.ts +3 -0
  58. package/dist/dts/templating/when.d.ts +3 -1
  59. package/dist/dts/testing/fakes.d.ts +12 -1
  60. package/dist/dts/tsdoc-metadata.json +1 -1
  61. package/dist/dts/utilities.d.ts +55 -1
  62. package/dist/esm/binding/binding.js +18 -0
  63. package/dist/esm/binding/normalize.js +17 -0
  64. package/dist/esm/binding/one-time.js +21 -0
  65. package/dist/esm/binding/one-way.js +30 -0
  66. package/dist/esm/{templating/binding-signal.js → binding/signal.js} +22 -6
  67. package/dist/esm/{templating/binding-two-way.js → binding/two-way.js} +18 -12
  68. package/dist/esm/components/attributes.js +16 -1
  69. package/dist/esm/components/element-controller.js +319 -49
  70. package/dist/esm/components/element-hydration.js +2 -0
  71. package/dist/esm/components/fast-definitions.js +12 -4
  72. package/dist/esm/components/fast-element.js +3 -1
  73. package/dist/esm/components/hydration.js +104 -0
  74. package/dist/esm/components/install-hydration.js +3 -0
  75. package/dist/esm/context.js +26 -4
  76. package/dist/esm/debug.js +8 -2
  77. package/dist/esm/di/di.js +9 -12
  78. package/dist/esm/dom-policy.js +345 -0
  79. package/dist/esm/dom.js +101 -0
  80. package/dist/esm/hydration/target-builder.js +175 -0
  81. package/dist/esm/index.js +34 -25
  82. package/dist/esm/index.rollup.debug.js +3 -1
  83. package/dist/esm/index.rollup.js +3 -1
  84. package/dist/esm/interfaces.js +51 -3
  85. package/dist/esm/metadata.js +11 -8
  86. package/dist/esm/observation/arrays.js +1 -1
  87. package/dist/esm/observation/observable.bench.js +79 -0
  88. package/dist/esm/observation/observable.js +20 -15
  89. package/dist/esm/observation/update-queue.js +2 -2
  90. package/dist/esm/pending-task.js +13 -1
  91. package/dist/esm/platform.js +12 -2
  92. package/dist/esm/polyfills.js +3 -61
  93. package/dist/esm/styles/css-binding-directive.js +76 -0
  94. package/dist/esm/styles/css.js +14 -7
  95. package/dist/esm/styles/element-styles.js +0 -33
  96. package/dist/esm/styles/style-strategy.js +1 -0
  97. package/dist/esm/templating/children.js +8 -4
  98. package/dist/esm/templating/compiler.js +37 -44
  99. package/dist/esm/templating/html-binding-directive.js +218 -0
  100. package/dist/esm/templating/html-directive.js +25 -152
  101. package/dist/esm/templating/install-hydratable-view-templates.js +17 -0
  102. package/dist/esm/templating/node-observation.js +14 -8
  103. package/dist/esm/templating/ref.js +1 -1
  104. package/dist/esm/templating/render.bench.js +56 -0
  105. package/dist/esm/templating/render.js +74 -30
  106. package/dist/esm/templating/repeat-basic-reverse.bench.js +43 -0
  107. package/dist/esm/templating/repeat-basic-shift.bench.js +43 -0
  108. package/dist/esm/templating/repeat.js +116 -17
  109. package/dist/esm/templating/template.js +135 -60
  110. package/dist/esm/templating/view.js +254 -34
  111. package/dist/esm/templating/when-basic.bench.js +36 -0
  112. package/dist/esm/templating/when-conditional.bench.js +39 -0
  113. package/dist/esm/templating/when-switch.bench.js +68 -0
  114. package/dist/esm/templating/when.js +12 -5
  115. package/dist/esm/testing/fakes.js +32 -1
  116. package/dist/esm/testing/fixture.js +1 -1
  117. package/dist/esm/utilities.js +97 -1
  118. package/dist/fast-element.api.json +9788 -5666
  119. package/dist/fast-element.d.ts +813 -2392
  120. package/dist/fast-element.debug.js +2785 -969
  121. package/dist/fast-element.debug.min.js +3 -1
  122. package/dist/fast-element.js +2638 -828
  123. package/dist/fast-element.min.js +3 -1
  124. package/dist/fast-element.untrimmed.d.ts +661 -313
  125. package/docs/{api-report.md → api-report.api.md} +238 -151
  126. package/docs/context/api-report.api.md +69 -0
  127. package/docs/di/api-report.api.md +315 -0
  128. package/karma.conf.cjs +2 -1
  129. package/package.json +59 -47
  130. package/scripts/run-api-extractor.js +51 -0
  131. package/scripts/run-benchmarks.js +46 -0
  132. package/tensile.config.js +12 -0
  133. package/dist/dts/templating/dom.d.ts +0 -41
  134. package/dist/esm/templating/binding.js +0 -282
  135. package/dist/esm/templating/dom.js +0 -49
  136. package/docs/guide/declaring-templates.md +0 -230
  137. package/docs/guide/defining-elements.md +0 -214
  138. package/docs/guide/leveraging-css.md +0 -253
  139. package/docs/guide/next-steps.md +0 -13
  140. package/docs/guide/observables-and-state.md +0 -213
  141. package/docs/guide/using-directives.md +0 -576
  142. package/docs/guide/working-with-shadow-dom.md +0 -296
@@ -1,10 +1,73 @@
1
+ let kernelMode;
2
+ const kernelAttr = "fast-kernel";
3
+ try {
4
+ if (document.currentScript) {
5
+ kernelMode = document.currentScript.getAttribute(kernelAttr);
6
+ }
7
+ else {
8
+ const scripts = document.getElementsByTagName("script");
9
+ const currentScript = scripts[scripts.length - 1];
10
+ kernelMode = currentScript.getAttribute(kernelAttr);
11
+ }
12
+ }
13
+ catch (e) {
14
+ kernelMode = "isolate";
15
+ }
16
+ let KernelServiceId;
17
+ switch (kernelMode) {
18
+ case "share": // share the kernel across major versions
19
+ KernelServiceId = Object.freeze({
20
+ updateQueue: 1,
21
+ observable: 2,
22
+ contextEvent: 3,
23
+ elementRegistry: 4,
24
+ });
25
+ break;
26
+ case "share-v2": // only share the kernel with other v2 instances
27
+ KernelServiceId = Object.freeze({
28
+ updateQueue: 1.2,
29
+ observable: 2.2,
30
+ contextEvent: 3.2,
31
+ elementRegistry: 4.2,
32
+ });
33
+ break;
34
+ default:
35
+ // fully isolate the kernel from all other FAST instances
36
+ const postfix = `-${Math.random().toString(36).substring(2, 8)}`;
37
+ KernelServiceId = Object.freeze({
38
+ updateQueue: `1.2${postfix}`,
39
+ observable: `2.2${postfix}`,
40
+ contextEvent: `3.2${postfix}`,
41
+ elementRegistry: `4.2${postfix}`,
42
+ });
43
+ break;
44
+ }
45
+ /**
46
+ * Determines whether or not an object is a function.
47
+ * @public
48
+ */
49
+ const isFunction = (object) => typeof object === "function";
50
+ /**
51
+ * Determines whether or not an object is a string.
52
+ * @public
53
+ */
54
+ const isString = (object) => typeof object === "string";
55
+ /**
56
+ * A function which does nothing.
57
+ * @public
58
+ */
59
+ const noop = () => void 0;
60
+
61
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
1
62
  (function ensureGlobalThis() {
2
63
  if (typeof globalThis !== "undefined") {
3
64
  // We're running in a modern environment.
4
65
  return;
5
66
  }
67
+ // @ts-ignore
6
68
  if (typeof global !== "undefined") {
7
69
  // We're running in NodeJS
70
+ // @ts-ignore
8
71
  global.globalThis = global;
9
72
  }
10
73
  else if (typeof self !== "undefined") {
@@ -22,69 +85,8 @@
22
85
  result.globalThis = result;
23
86
  }
24
87
  })();
25
- // API-only Polyfill for trustedTypes
26
- if (!globalThis.trustedTypes) {
27
- globalThis.trustedTypes = {
28
- createPolicy: (n, r) => r,
29
- };
30
- }
31
- // ensure FAST global - duplicated in platform.ts
32
- const propConfig$1 = {
33
- configurable: false,
34
- enumerable: false,
35
- writable: false,
36
- };
37
- if (globalThis.FAST === void 0) {
38
- Reflect.defineProperty(globalThis, "FAST", Object.assign({ value: Object.create(null) }, propConfig$1));
39
- }
40
- const FAST$1 = globalThis.FAST;
41
- if (FAST$1.getById === void 0) {
42
- const storage = Object.create(null);
43
- Reflect.defineProperty(FAST$1, "getById", Object.assign({ value(id, initialize) {
44
- let found = storage[id];
45
- if (found === void 0) {
46
- found = initialize ? (storage[id] = initialize()) : null;
47
- }
48
- return found;
49
- } }, propConfig$1));
50
- }
51
- // duplicated from DOM
52
- const supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
53
- "replace" in CSSStyleSheet.prototype;
54
- function usableStyleTarget(target) {
55
- return target === document ? document.body : target;
56
- }
57
- let id$1 = 0;
58
- const nextStyleId = () => `fast-${++id$1}`;
59
- class StyleElementStrategy {
60
- constructor(styles) {
61
- this.styles = styles;
62
- this.styleClass = nextStyleId();
63
- }
64
- addStylesTo(target) {
65
- target = usableStyleTarget(target);
66
- const styles = this.styles;
67
- const styleClass = this.styleClass;
68
- for (let i = 0; i < styles.length; i++) {
69
- const element = document.createElement("style");
70
- element.innerHTML = styles[i];
71
- element.className = styleClass;
72
- target.append(element);
73
- }
74
- }
75
- removeStylesFrom(target) {
76
- const styles = target.querySelectorAll(`.${this.styleClass}`);
77
- target = usableStyleTarget(target);
78
- for (let i = 0, ii = styles.length; i < ii; ++i) {
79
- target.removeChild(styles[i]);
80
- }
81
- }
82
- }
83
- if (!supportsAdoptedStyleSheets) {
84
- FAST$1.getById(/* KernelServiceId.styleSheetStrategy */ 5, () => StyleElementStrategy);
85
- }
86
88
 
87
- // ensure FAST global - duplicated in polyfills.ts and debug.ts
89
+ // ensure FAST global - duplicated debug.ts
88
90
  const propConfig = {
89
91
  configurable: false,
90
92
  enumerable: false,
@@ -95,7 +97,7 @@ if (globalThis.FAST === void 0) {
95
97
  }
96
98
  /**
97
99
  * The FAST global.
98
- * @internal
100
+ * @public
99
101
  */
100
102
  const FAST = globalThis.FAST;
101
103
  if (FAST.getById === void 0) {
@@ -171,21 +173,462 @@ function createMetadataLocator() {
171
173
  return metadata;
172
174
  };
173
175
  }
174
-
175
176
  /**
177
+ * Makes a type noop for JSON serialization.
178
+ * @param type - The type to make noop for JSON serialization.
176
179
  * @internal
177
180
  */
178
- const isFunction = (object) => typeof object === "function";
181
+ function makeSerializationNoop(type) {
182
+ type.prototype.toJSON = noop;
183
+ }
184
+
179
185
  /**
180
- * @internal
186
+ * The type of HTML aspect to target.
187
+ * @public
181
188
  */
182
- const isString = (object) => typeof object === "string";
189
+ const DOMAspect = Object.freeze({
190
+ /**
191
+ * Not aspected.
192
+ */
193
+ none: 0,
194
+ /**
195
+ * An attribute.
196
+ */
197
+ attribute: 1,
198
+ /**
199
+ * A boolean attribute.
200
+ */
201
+ booleanAttribute: 2,
202
+ /**
203
+ * A property.
204
+ */
205
+ property: 3,
206
+ /**
207
+ * Content
208
+ */
209
+ content: 4,
210
+ /**
211
+ * A token list.
212
+ */
213
+ tokenList: 5,
214
+ /**
215
+ * An event.
216
+ */
217
+ event: 6,
218
+ });
219
+ const createHTML$1 = html => html;
220
+ const fastTrustedType = globalThis.trustedTypes
221
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML: createHTML$1 })
222
+ : { createHTML: createHTML$1 };
223
+ let defaultPolicy = Object.freeze({
224
+ createHTML(value) {
225
+ return fastTrustedType.createHTML(value);
226
+ },
227
+ protect(tagName, aspect, aspectName, sink) {
228
+ return sink;
229
+ },
230
+ });
231
+ const fastPolicy = defaultPolicy;
232
+ /**
233
+ * Common DOM APIs.
234
+ * @public
235
+ */
236
+ const DOM = Object.freeze({
237
+ /**
238
+ * Gets the dom policy used by the templating system.
239
+ */
240
+ get policy() {
241
+ return defaultPolicy;
242
+ },
243
+ /**
244
+ * Sets the dom policy used by the templating system.
245
+ * @param policy - The policy to set.
246
+ * @remarks
247
+ * This API can only be called once, for security reasons. It should be
248
+ * called by the application developer at the start of their program.
249
+ */
250
+ setPolicy(value) {
251
+ if (defaultPolicy !== fastPolicy) {
252
+ throw FAST.error(1201 /* Message.onlySetDOMPolicyOnce */);
253
+ }
254
+ defaultPolicy = value;
255
+ },
256
+ /**
257
+ * Sets an attribute value on an element.
258
+ * @param element - The element to set the attribute value on.
259
+ * @param attributeName - The attribute name to set.
260
+ * @param value - The value of the attribute to set.
261
+ * @remarks
262
+ * If the value is `null` or `undefined`, the attribute is removed, otherwise
263
+ * it is set to the provided value using the standard `setAttribute` API.
264
+ */
265
+ setAttribute(element, attributeName, value) {
266
+ value === null || value === undefined
267
+ ? element.removeAttribute(attributeName)
268
+ : element.setAttribute(attributeName, value);
269
+ },
270
+ /**
271
+ * Sets a boolean attribute value.
272
+ * @param element - The element to set the boolean attribute value on.
273
+ * @param attributeName - The attribute name to set.
274
+ * @param value - The value of the attribute to set.
275
+ * @remarks
276
+ * If the value is true, the attribute is added; otherwise it is removed.
277
+ */
278
+ setBooleanAttribute(element, attributeName, value) {
279
+ value
280
+ ? element.setAttribute(attributeName, "")
281
+ : element.removeAttribute(attributeName);
282
+ },
283
+ });
284
+
285
+ function safeURL(tagName, aspect, aspectName, sink) {
286
+ return (target, name, value, ...rest) => {
287
+ if (isString(value)) {
288
+ value = value.replace(/(javascript:|vbscript:|data:)/, "");
289
+ }
290
+ sink(target, name, value, ...rest);
291
+ };
292
+ }
293
+ function block(tagName, aspect, aspectName, sink) {
294
+ throw FAST.error(1209 /* Message.blockedByDOMPolicy */, {
295
+ aspectName,
296
+ tagName: tagName !== null && tagName !== void 0 ? tagName : "text",
297
+ });
298
+ }
299
+ const defaultDOMElementGuards = {
300
+ a: {
301
+ [DOMAspect.attribute]: {
302
+ href: safeURL,
303
+ },
304
+ [DOMAspect.property]: {
305
+ href: safeURL,
306
+ },
307
+ },
308
+ area: {
309
+ [DOMAspect.attribute]: {
310
+ href: safeURL,
311
+ },
312
+ [DOMAspect.property]: {
313
+ href: safeURL,
314
+ },
315
+ },
316
+ button: {
317
+ [DOMAspect.attribute]: {
318
+ formaction: safeURL,
319
+ },
320
+ [DOMAspect.property]: {
321
+ formAction: safeURL,
322
+ },
323
+ },
324
+ embed: {
325
+ [DOMAspect.attribute]: {
326
+ src: block,
327
+ },
328
+ [DOMAspect.property]: {
329
+ src: block,
330
+ },
331
+ },
332
+ form: {
333
+ [DOMAspect.attribute]: {
334
+ action: safeURL,
335
+ },
336
+ [DOMAspect.property]: {
337
+ action: safeURL,
338
+ },
339
+ },
340
+ frame: {
341
+ [DOMAspect.attribute]: {
342
+ src: safeURL,
343
+ },
344
+ [DOMAspect.property]: {
345
+ src: safeURL,
346
+ },
347
+ },
348
+ iframe: {
349
+ [DOMAspect.attribute]: {
350
+ src: safeURL,
351
+ },
352
+ [DOMAspect.property]: {
353
+ src: safeURL,
354
+ srcdoc: block,
355
+ },
356
+ },
357
+ input: {
358
+ [DOMAspect.attribute]: {
359
+ formaction: safeURL,
360
+ },
361
+ [DOMAspect.property]: {
362
+ formAction: safeURL,
363
+ },
364
+ },
365
+ link: {
366
+ [DOMAspect.attribute]: {
367
+ href: block,
368
+ },
369
+ [DOMAspect.property]: {
370
+ href: block,
371
+ },
372
+ },
373
+ object: {
374
+ [DOMAspect.attribute]: {
375
+ codebase: block,
376
+ data: block,
377
+ },
378
+ [DOMAspect.property]: {
379
+ codeBase: block,
380
+ data: block,
381
+ },
382
+ },
383
+ script: {
384
+ [DOMAspect.attribute]: {
385
+ src: block,
386
+ text: block,
387
+ },
388
+ [DOMAspect.property]: {
389
+ src: block,
390
+ text: block,
391
+ innerText: block,
392
+ textContent: block,
393
+ },
394
+ },
395
+ style: {
396
+ [DOMAspect.property]: {
397
+ innerText: block,
398
+ textContent: block,
399
+ },
400
+ },
401
+ };
402
+ const blockedEvents = {
403
+ onabort: block,
404
+ onauxclick: block,
405
+ onbeforeinput: block,
406
+ onbeforematch: block,
407
+ onblur: block,
408
+ oncancel: block,
409
+ oncanplay: block,
410
+ oncanplaythrough: block,
411
+ onchange: block,
412
+ onclick: block,
413
+ onclose: block,
414
+ oncontextlost: block,
415
+ oncontextmenu: block,
416
+ oncontextrestored: block,
417
+ oncopy: block,
418
+ oncuechange: block,
419
+ oncut: block,
420
+ ondblclick: block,
421
+ ondrag: block,
422
+ ondragend: block,
423
+ ondragenter: block,
424
+ ondragleave: block,
425
+ ondragover: block,
426
+ ondragstart: block,
427
+ ondrop: block,
428
+ ondurationchange: block,
429
+ onemptied: block,
430
+ onended: block,
431
+ onerror: block,
432
+ onfocus: block,
433
+ onformdata: block,
434
+ oninput: block,
435
+ oninvalid: block,
436
+ onkeydown: block,
437
+ onkeypress: block,
438
+ onkeyup: block,
439
+ onload: block,
440
+ onloadeddata: block,
441
+ onloadedmetadata: block,
442
+ onloadstart: block,
443
+ onmousedown: block,
444
+ onmouseenter: block,
445
+ onmouseleave: block,
446
+ onmousemove: block,
447
+ onmouseout: block,
448
+ onmouseover: block,
449
+ onmouseup: block,
450
+ onpaste: block,
451
+ onpause: block,
452
+ onplay: block,
453
+ onplaying: block,
454
+ onprogress: block,
455
+ onratechange: block,
456
+ onreset: block,
457
+ onresize: block,
458
+ onscroll: block,
459
+ onsecuritypolicyviolation: block,
460
+ onseeked: block,
461
+ onseeking: block,
462
+ onselect: block,
463
+ onslotchange: block,
464
+ onstalled: block,
465
+ onsubmit: block,
466
+ onsuspend: block,
467
+ ontimeupdate: block,
468
+ ontoggle: block,
469
+ onvolumechange: block,
470
+ onwaiting: block,
471
+ onwebkitanimationend: block,
472
+ onwebkitanimationiteration: block,
473
+ onwebkitanimationstart: block,
474
+ onwebkittransitionend: block,
475
+ onwheel: block,
476
+ };
477
+ const defaultDOMGuards = {
478
+ elements: defaultDOMElementGuards,
479
+ aspects: {
480
+ [DOMAspect.attribute]: Object.assign({}, blockedEvents),
481
+ [DOMAspect.property]: Object.assign({ innerHTML: block }, blockedEvents),
482
+ [DOMAspect.event]: Object.assign({}, blockedEvents),
483
+ },
484
+ };
485
+ function createDomSinkGuards(config, defaults) {
486
+ const result = {};
487
+ for (const name in defaults) {
488
+ const overrideValue = config[name];
489
+ const defaultValue = defaults[name];
490
+ switch (overrideValue) {
491
+ case null:
492
+ // remove the default
493
+ break;
494
+ case undefined:
495
+ // keep the default
496
+ result[name] = defaultValue;
497
+ break;
498
+ default:
499
+ // override the default
500
+ result[name] = overrideValue;
501
+ break;
502
+ }
503
+ }
504
+ // add any new sinks that were not overrides
505
+ for (const name in config) {
506
+ if (!(name in result)) {
507
+ result[name] = config[name];
508
+ }
509
+ }
510
+ return Object.freeze(result);
511
+ }
512
+ function createDOMAspectGuards(config, defaults) {
513
+ const result = {};
514
+ for (const aspect in defaults) {
515
+ const overrideValue = config[aspect];
516
+ const defaultValue = defaults[aspect];
517
+ switch (overrideValue) {
518
+ case null:
519
+ // remove the default
520
+ break;
521
+ case undefined:
522
+ // keep the default
523
+ result[aspect] = createDomSinkGuards(defaultValue, {});
524
+ break;
525
+ default:
526
+ // override the default
527
+ result[aspect] = createDomSinkGuards(overrideValue, defaultValue);
528
+ break;
529
+ }
530
+ }
531
+ // add any new aspect guards that were not overrides
532
+ for (const aspect in config) {
533
+ if (!(aspect in result)) {
534
+ result[aspect] = createDomSinkGuards(config[aspect], {});
535
+ }
536
+ }
537
+ return Object.freeze(result);
538
+ }
539
+ function createElementGuards(config, defaults) {
540
+ const result = {};
541
+ for (const tag in defaults) {
542
+ const overrideValue = config[tag];
543
+ const defaultValue = defaults[tag];
544
+ switch (overrideValue) {
545
+ case null:
546
+ // remove the default
547
+ break;
548
+ case undefined:
549
+ // keep the default
550
+ result[tag] = createDOMAspectGuards(overrideValue, {});
551
+ break;
552
+ default:
553
+ // override the default aspects
554
+ result[tag] = createDOMAspectGuards(overrideValue, defaultValue);
555
+ break;
556
+ }
557
+ }
558
+ // Add any new element guards that were not overrides
559
+ for (const tag in config) {
560
+ if (!(tag in result)) {
561
+ result[tag] = createDOMAspectGuards(config[tag], {});
562
+ }
563
+ }
564
+ return Object.freeze(result);
565
+ }
566
+ function createDOMGuards(config, defaults) {
567
+ return Object.freeze({
568
+ elements: config.elements
569
+ ? createElementGuards(config.elements, defaults.elements)
570
+ : defaults.elements,
571
+ aspects: config.aspects
572
+ ? createDOMAspectGuards(config.aspects, defaults.aspects)
573
+ : defaults.aspects,
574
+ });
575
+ }
576
+ function createTrustedType() {
577
+ const createHTML = html => html;
578
+ return globalThis.trustedTypes
579
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML })
580
+ : { createHTML };
581
+ }
582
+ function tryGuard(aspectGuards, tagName, aspect, aspectName, sink) {
583
+ const sinkGuards = aspectGuards[aspect];
584
+ if (sinkGuards) {
585
+ const guard = sinkGuards[aspectName];
586
+ if (guard) {
587
+ return guard(tagName, aspect, aspectName, sink);
588
+ }
589
+ }
590
+ }
591
+ /**
592
+ * A helper for creating DOM policies.
593
+ * @public
594
+ */
595
+ const DOMPolicy = Object.freeze({
596
+ /**
597
+ * Creates a new DOM Policy object.
598
+ * @param options The options to use in creating the policy.
599
+ * @returns The newly created DOMPolicy.
600
+ */
601
+ create(options = {}) {
602
+ var _a, _b;
603
+ const trustedType = (_a = options.trustedType) !== null && _a !== void 0 ? _a : createTrustedType();
604
+ const guards = createDOMGuards((_b = options.guards) !== null && _b !== void 0 ? _b : {}, defaultDOMGuards);
605
+ return Object.freeze({
606
+ createHTML(value) {
607
+ return trustedType.createHTML(value);
608
+ },
609
+ protect(tagName, aspect, aspectName, sink) {
610
+ var _a;
611
+ // Check for element-specific guards.
612
+ const key = (tagName !== null && tagName !== void 0 ? tagName : "").toLowerCase();
613
+ const elementGuards = guards.elements[key];
614
+ if (elementGuards) {
615
+ const guard = tryGuard(elementGuards, tagName, aspect, aspectName, sink);
616
+ if (guard) {
617
+ return guard;
618
+ }
619
+ }
620
+ // Check for guards applicable to all nodes.
621
+ return ((_a = tryGuard(guards.aspects, tagName, aspect, aspectName, sink)) !== null && _a !== void 0 ? _a : sink);
622
+ },
623
+ });
624
+ },
625
+ });
183
626
 
184
627
  /**
185
628
  * The default UpdateQueue.
186
629
  * @public
187
630
  */
188
- const Updates = FAST.getById(1 /* KernelServiceId.updateQueue */, () => {
631
+ const Updates = FAST.getById(KernelServiceId.updateQueue, () => {
189
632
  const tasks = [];
190
633
  const pendingErrors = [];
191
634
  const rAF = globalThis.requestAnimationFrame;
@@ -431,9 +874,9 @@ const SourceLifetime = Object.freeze({
431
874
  * Common Observable APIs.
432
875
  * @public
433
876
  */
434
- const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
877
+ const Observable = FAST.getById(KernelServiceId.observable, () => {
435
878
  const queueUpdate = Updates.enqueue;
436
- const volatileRegex = /(:|&&|\|\||if)/;
879
+ const volatileRegex = /(:|&&|\|\||if|\?\.)/;
437
880
  const notifierLookup = new WeakMap();
438
881
  let watcher = void 0;
439
882
  let createArrayObserver = (array) => {
@@ -476,9 +919,9 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
476
919
  }
477
920
  }
478
921
  class ExpressionNotifierImplementation extends SubscriberSet {
479
- constructor(binding, initialSubscriber, isVolatileBinding = false) {
480
- super(binding, initialSubscriber);
481
- this.binding = binding;
922
+ constructor(expression, initialSubscriber, isVolatileBinding = false) {
923
+ super(expression, initialSubscriber);
924
+ this.expression = expression;
482
925
  this.isVolatileBinding = isVolatileBinding;
483
926
  this.needsRefresh = true;
484
927
  this.needsQueue = true;
@@ -518,13 +961,17 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
518
961
  this.needsRefresh = this.isVolatileBinding;
519
962
  let result;
520
963
  try {
521
- result = this.binding(source, context);
964
+ result = this.expression(source, context);
522
965
  }
523
966
  finally {
524
967
  watcher = previousWatcher;
525
968
  }
526
969
  return result;
527
970
  }
971
+ // backwards compat with v1 kernel
972
+ disconnect() {
973
+ this.dispose();
974
+ }
528
975
  dispose() {
529
976
  if (this.last !== null) {
530
977
  let current = this.first;
@@ -586,6 +1033,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
586
1033
  }
587
1034
  }
588
1035
  }
1036
+ makeSerializationNoop(ExpressionNotifierImplementation);
589
1037
  return Object.freeze({
590
1038
  /**
591
1039
  * @internal
@@ -653,20 +1101,20 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
653
1101
  /**
654
1102
  * Creates a {@link ExpressionNotifier} that can watch the
655
1103
  * provided {@link Expression} for changes.
656
- * @param binding - The binding to observe.
1104
+ * @param expression - The binding to observe.
657
1105
  * @param initialSubscriber - An initial subscriber to changes in the binding value.
658
1106
  * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
659
1107
  */
660
- binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) {
661
- return new ExpressionNotifierImplementation(binding, initialSubscriber, isVolatileBinding);
1108
+ binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
1109
+ return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
662
1110
  },
663
1111
  /**
664
1112
  * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
665
1113
  * on every evaluation of the value.
666
- * @param binding - The binding to inspect.
1114
+ * @param expression - The binding to inspect.
667
1115
  */
668
- isVolatileBinding(binding) {
669
- return volatileRegex.test(binding.toString());
1116
+ isVolatileBinding(expression) {
1117
+ return volatileRegex.test(expression.toString());
670
1118
  },
671
1119
  });
672
1120
  });
@@ -694,7 +1142,7 @@ function volatile(target, name, descriptor) {
694
1142
  },
695
1143
  });
696
1144
  }
697
- const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
1145
+ const contextEvent = FAST.getById(KernelServiceId.contextEvent, () => {
698
1146
  let current = null;
699
1147
  return {
700
1148
  get() {
@@ -1109,7 +1557,7 @@ let defaultSpliceStrategy = Object.freeze({
1109
1557
  if (changes === void 0) {
1110
1558
  return emptyArray;
1111
1559
  }
1112
- return changes.length > 1 ? project(current, changes) : changes;
1560
+ return project(current, changes);
1113
1561
  }
1114
1562
  return resetSplices;
1115
1563
  },
@@ -1309,21 +1757,102 @@ function lengthOf(array) {
1309
1757
  return array.length;
1310
1758
  }
1311
1759
 
1312
- const styleSheetCache = new Map();
1313
- let DefaultStyleStrategy;
1314
- function reduceStyles(styles) {
1315
- return styles
1316
- .map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
1317
- .reduce((prev, curr) => prev.concat(curr), []);
1318
- }
1319
1760
  /**
1320
- * Represents styles that can be applied to a custom element.
1761
+ * Captures a binding expression along with related information and capabilities.
1762
+ *
1321
1763
  * @public
1322
1764
  */
1323
- class ElementStyles {
1765
+ class Binding {
1324
1766
  /**
1325
- * Creates an instance of ElementStyles.
1326
- * @param styles - The styles that will be associated with elements.
1767
+ * Creates a binding.
1768
+ * @param evaluate - Evaluates the binding.
1769
+ * @param policy - The security policy to associate with this binding.
1770
+ * @param isVolatile - Indicates whether the binding is volatile.
1771
+ */
1772
+ constructor(evaluate, policy, isVolatile = false) {
1773
+ this.evaluate = evaluate;
1774
+ this.policy = policy;
1775
+ this.isVolatile = isVolatile;
1776
+ }
1777
+ }
1778
+
1779
+ class OneWayBinding extends Binding {
1780
+ createObserver(subscriber) {
1781
+ return Observable.binding(this.evaluate, subscriber, this.isVolatile);
1782
+ }
1783
+ }
1784
+ /**
1785
+ * Creates an standard binding.
1786
+ * @param expression - The binding to refresh when changed.
1787
+ * @param policy - The security policy to associate with th binding.
1788
+ * @param isVolatile - Indicates whether the binding is volatile or not.
1789
+ * @returns A binding configuration.
1790
+ * @public
1791
+ */
1792
+ function oneWay(expression, policy, isVolatile = Observable.isVolatileBinding(expression)) {
1793
+ return new OneWayBinding(expression, policy, isVolatile);
1794
+ }
1795
+ /**
1796
+ * Creates an event listener binding.
1797
+ * @param expression - The binding to invoke when the event is raised.
1798
+ * @param options - Event listener options.
1799
+ * @returns A binding configuration.
1800
+ * @public
1801
+ */
1802
+ function listener(expression, options) {
1803
+ const config = new OneWayBinding(expression);
1804
+ config.options = options;
1805
+ return config;
1806
+ }
1807
+
1808
+ class OneTimeBinding extends Binding {
1809
+ createObserver() {
1810
+ return this;
1811
+ }
1812
+ bind(controller) {
1813
+ return this.evaluate(controller.source, controller.context);
1814
+ }
1815
+ }
1816
+ makeSerializationNoop(OneTimeBinding);
1817
+ /**
1818
+ * Creates a one time binding
1819
+ * @param expression - The binding to refresh when signaled.
1820
+ * @param policy - The security policy to associate with th binding.
1821
+ * @returns A binding configuration.
1822
+ * @public
1823
+ */
1824
+ function oneTime(expression, policy) {
1825
+ return new OneTimeBinding(expression, policy);
1826
+ }
1827
+
1828
+ /**
1829
+ * Normalizes the input value into a binding.
1830
+ * @param value - The value to create the default binding for.
1831
+ * @returns A binding configuration for the provided value.
1832
+ * @public
1833
+ */
1834
+ function normalizeBinding$1(value) {
1835
+ return isFunction(value)
1836
+ ? oneWay(value)
1837
+ : value instanceof Binding
1838
+ ? value
1839
+ : oneTime(() => value);
1840
+ }
1841
+
1842
+ let DefaultStyleStrategy;
1843
+ function reduceStyles(styles) {
1844
+ return styles
1845
+ .map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
1846
+ .reduce((prev, curr) => prev.concat(curr), []);
1847
+ }
1848
+ /**
1849
+ * Represents styles that can be applied to a custom element.
1850
+ * @public
1851
+ */
1852
+ class ElementStyles {
1853
+ /**
1854
+ * Creates an instance of ElementStyles.
1855
+ * @param styles - The styles that will be associated with elements.
1327
1856
  */
1328
1857
  constructor(styles) {
1329
1858
  this.styles = styles;
@@ -1400,36 +1929,6 @@ class ElementStyles {
1400
1929
  */
1401
1930
  ElementStyles.supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
1402
1931
  "replace" in CSSStyleSheet.prototype;
1403
- /**
1404
- * https://wicg.github.io/construct-stylesheets/
1405
- * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
1406
- *
1407
- * @internal
1408
- */
1409
- class AdoptedStyleSheetsStrategy {
1410
- constructor(styles) {
1411
- this.sheets = styles.map((x) => {
1412
- if (x instanceof CSSStyleSheet) {
1413
- return x;
1414
- }
1415
- let sheet = styleSheetCache.get(x);
1416
- if (sheet === void 0) {
1417
- sheet = new CSSStyleSheet();
1418
- sheet.replaceSync(x);
1419
- styleSheetCache.set(x, sheet);
1420
- }
1421
- return sheet;
1422
- });
1423
- }
1424
- addStylesTo(target) {
1425
- target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...this.sheets];
1426
- }
1427
- removeStylesFrom(target) {
1428
- const sheets = this.sheets;
1429
- target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
1430
- }
1431
- }
1432
- ElementStyles.setDefaultStrategy(FAST.getById(5 /* KernelServiceId.styleSheetStrategy */, () => AdoptedStyleSheetsStrategy));
1433
1932
 
1434
1933
  const registry$1 = createTypeRegistry();
1435
1934
  /**
@@ -1468,6 +1967,85 @@ function cssDirective() {
1468
1967
  };
1469
1968
  }
1470
1969
 
1970
+ function handleChange(directive, controller, observer) {
1971
+ controller.source.style.setProperty(directive.targetAspect, observer.bind(controller));
1972
+ }
1973
+ /**
1974
+ * Enables bindings in CSS.
1975
+ *
1976
+ * @public
1977
+ */
1978
+ class CSSBindingDirective {
1979
+ /**
1980
+ * Creates an instance of CSSBindingDirective.
1981
+ * @param dataBinding - The binding to use in CSS.
1982
+ * @param targetAspect - The CSS property to target.
1983
+ */
1984
+ constructor(dataBinding, targetAspect) {
1985
+ this.dataBinding = dataBinding;
1986
+ this.targetAspect = targetAspect;
1987
+ }
1988
+ /**
1989
+ * Creates a CSS fragment to interpolate into the CSS document.
1990
+ * @returns - the string to interpolate into CSS
1991
+ */
1992
+ createCSS(add) {
1993
+ add(this);
1994
+ return `var(${this.targetAspect})`;
1995
+ }
1996
+ /**
1997
+ * Executed when this behavior is attached to a controller.
1998
+ * @param controller - Controls the behavior lifecycle.
1999
+ */
2000
+ addedCallback(controller) {
2001
+ var _a;
2002
+ const element = controller.source;
2003
+ if (!element.$cssBindings) {
2004
+ element.$cssBindings = new Map();
2005
+ const setAttribute = element.setAttribute;
2006
+ element.setAttribute = (attr, value) => {
2007
+ setAttribute.call(element, attr, value);
2008
+ if (attr === "style") {
2009
+ element.$cssBindings.forEach((v, k) => handleChange(k, v.controller, v.observer));
2010
+ }
2011
+ };
2012
+ }
2013
+ const observer = (_a = controller[this.targetAspect]) !== null && _a !== void 0 ? _a : (controller[this.targetAspect] = this.dataBinding.createObserver(this, this));
2014
+ observer.controller = controller;
2015
+ controller.source.$cssBindings.set(this, { controller, observer });
2016
+ }
2017
+ /**
2018
+ * Executed when this behavior's host is connected.
2019
+ * @param controller - Controls the behavior lifecycle.
2020
+ */
2021
+ connectedCallback(controller) {
2022
+ handleChange(this, controller, controller[this.targetAspect]);
2023
+ }
2024
+ /**
2025
+ * Executed when this behavior is detached from a controller.
2026
+ * @param controller - Controls the behavior lifecycle.
2027
+ */
2028
+ removedCallback(controller) {
2029
+ if (controller.source.$cssBindings) {
2030
+ controller.source.$cssBindings.delete(this);
2031
+ }
2032
+ }
2033
+ /**
2034
+ * Called when a subject this instance has subscribed to changes.
2035
+ * @param subject - The subject of the change.
2036
+ * @param args - The event args detailing the change that occurred.
2037
+ *
2038
+ * @internal
2039
+ */
2040
+ handleChange(_, observer) {
2041
+ handleChange(this, observer.controller, observer);
2042
+ }
2043
+ }
2044
+ CSSDirective.define(CSSBindingDirective);
2045
+
2046
+ const marker$1 = `${Math.random().toString(36).substring(2, 8)}`;
2047
+ let varId = 0;
2048
+ const nextCSSVariable = () => `--v${marker$1}${++varId}`;
1471
2049
  function collectStyles(strings, values) {
1472
2050
  const styles = [];
1473
2051
  let cssString = "";
@@ -1478,7 +2056,13 @@ function collectStyles(strings, values) {
1478
2056
  for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
1479
2057
  cssString += strings[i];
1480
2058
  let value = values[i];
1481
- if (CSSDirective.getForInstance(value) !== void 0) {
2059
+ if (isFunction(value)) {
2060
+ value = new CSSBindingDirective(oneWay(value), nextCSSVariable()).createCSS(add);
2061
+ }
2062
+ else if (value instanceof Binding) {
2063
+ value = new CSSBindingDirective(value, nextCSSVariable()).createCSS(add);
2064
+ }
2065
+ else if (CSSDirective.getForInstance(value) !== void 0) {
1482
2066
  value = value.createCSS(add);
1483
2067
  }
1484
2068
  if (value instanceof ElementStyles || value instanceof CSSStyleSheet) {
@@ -1550,68 +2134,119 @@ css.partial = (strings, ...values) => {
1550
2134
  const { styles, behaviors } = collectStyles(strings, values);
1551
2135
  return new CSSPartial(styles, behaviors);
1552
2136
  };
1553
- /**
1554
- * @deprecated Use css.partial instead.
1555
- * @public
1556
- */
1557
- const cssPartial = css.partial;
1558
2137
 
2138
+ const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
2139
+ const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
2140
+ const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
2141
+ const repeatViewEndMarker = /fe-repeat\$\$end\$\$(\d+)\$\$fe-repeat/;
2142
+ const elementBoundaryStartMarker = /^(?:.{0,1000})fe-eb\$\$start\$\$(.+?)\$\$fe-eb/;
2143
+ const elementBoundaryEndMarker = /fe-eb\$\$end\$\$(.{0,1000})\$\$fe-eb(?:.{0,1000})$/;
2144
+ function isComment$1(node) {
2145
+ return node && node.nodeType === Node.COMMENT_NODE;
2146
+ }
1559
2147
  /**
1560
- * Common DOM APIs.
1561
- * @public
2148
+ * Markup utilities to aid in template hydration.
2149
+ * @internal
1562
2150
  */
1563
- const DOM = Object.freeze({
1564
- /**
1565
- * @deprecated
1566
- * Use Updates.enqueue().
1567
- */
1568
- queueUpdate: Updates.enqueue,
2151
+ const HydrationMarkup = Object.freeze({
2152
+ attributeMarkerName: "data-fe-b",
2153
+ attributeBindingSeparator: " ",
2154
+ contentBindingStartMarker(index, uniqueId) {
2155
+ return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
2156
+ },
2157
+ contentBindingEndMarker(index, uniqueId) {
2158
+ return `fe-b$$end$$${index}$$${uniqueId}$$fe-b`;
2159
+ },
2160
+ repeatStartMarker(index) {
2161
+ return `fe-repeat$$start$$${index}$$fe-repeat`;
2162
+ },
2163
+ repeatEndMarker(index) {
2164
+ return `fe-repeat$$end$$${index}$$fe-repeat`;
2165
+ },
2166
+ isContentBindingStartMarker(content) {
2167
+ return bindingStartMarker.test(content);
2168
+ },
2169
+ isContentBindingEndMarker(content) {
2170
+ return bindingEndMarker.test(content);
2171
+ },
2172
+ isRepeatViewStartMarker(content) {
2173
+ return repeatViewStartMarker.test(content);
2174
+ },
2175
+ isRepeatViewEndMarker(content) {
2176
+ return repeatViewEndMarker.test(content);
2177
+ },
2178
+ isElementBoundaryStartMarker(node) {
2179
+ return isComment$1(node) && elementBoundaryStartMarker.test(node.data.trim());
2180
+ },
2181
+ isElementBoundaryEndMarker(node) {
2182
+ return isComment$1(node) && elementBoundaryEndMarker.test(node.data);
2183
+ },
1569
2184
  /**
1570
- * @deprecated
1571
- * Use Updates.next()
2185
+ * Returns the indexes of the ViewBehaviorFactories affecting
2186
+ * attributes for the element, or null if no factories were found.
1572
2187
  */
1573
- nextUpdate: Updates.next,
2188
+ parseAttributeBinding(node) {
2189
+ const attr = node.getAttribute(this.attributeMarkerName);
2190
+ return attr === null
2191
+ ? attr
2192
+ : attr.split(this.attributeBindingSeparator).map(i => parseInt(i));
2193
+ },
1574
2194
  /**
1575
- * @deprecated
1576
- * Use Updates.process()
2195
+ * Parses the ViewBehaviorFactory index from string data. Returns
2196
+ * the binding index or null if the index cannot be retrieved.
1577
2197
  */
1578
- processUpdates: Updates.process,
2198
+ parseContentBindingStartMarker(content) {
2199
+ return parseIndexAndIdMarker(bindingStartMarker, content);
2200
+ },
2201
+ parseContentBindingEndMarker(content) {
2202
+ return parseIndexAndIdMarker(bindingEndMarker, content);
2203
+ },
1579
2204
  /**
1580
- * Sets an attribute value on an element.
1581
- * @param element - The element to set the attribute value on.
1582
- * @param attributeName - The attribute name to set.
1583
- * @param value - The value of the attribute to set.
1584
- * @remarks
1585
- * If the value is `null` or `undefined`, the attribute is removed, otherwise
1586
- * it is set to the provided value using the standard `setAttribute` API.
2205
+ * Parses the index of a repeat directive from a content string.
1587
2206
  */
1588
- setAttribute(element, attributeName, value) {
1589
- value === null || value === undefined
1590
- ? element.removeAttribute(attributeName)
1591
- : element.setAttribute(attributeName, value);
2207
+ parseRepeatStartMarker(content) {
2208
+ return parseIntMarker(repeatViewStartMarker, content);
2209
+ },
2210
+ parseRepeatEndMarker(content) {
2211
+ return parseIntMarker(repeatViewEndMarker, content);
1592
2212
  },
1593
2213
  /**
1594
- * Sets a boolean attribute value.
1595
- * @param element - The element to set the boolean attribute value on.
1596
- * @param attributeName - The attribute name to set.
1597
- * @param value - The value of the attribute to set.
1598
- * @remarks
1599
- * If the value is true, the attribute is added; otherwise it is removed.
2214
+ * Parses element Id from element boundary markers
1600
2215
  */
1601
- setBooleanAttribute(element, attributeName, value) {
1602
- value
1603
- ? element.setAttribute(attributeName, "")
1604
- : element.removeAttribute(attributeName);
2216
+ parseElementBoundaryStartMarker(content) {
2217
+ return parseStringMarker(elementBoundaryStartMarker, content.trim());
2218
+ },
2219
+ parseElementBoundaryEndMarker(content) {
2220
+ return parseStringMarker(elementBoundaryEndMarker, content);
1605
2221
  },
1606
2222
  });
2223
+ function parseIntMarker(regex, content) {
2224
+ const match = regex.exec(content);
2225
+ return match === null ? match : parseInt(match[1]);
2226
+ }
2227
+ function parseStringMarker(regex, content) {
2228
+ const match = regex.exec(content);
2229
+ return match === null ? match : match[1];
2230
+ }
2231
+ function parseIndexAndIdMarker(regex, content) {
2232
+ const match = regex.exec(content);
2233
+ return match === null ? match : [parseInt(match[1]), match[2]];
2234
+ }
2235
+ /**
2236
+ * @internal
2237
+ */
2238
+ const Hydratable = Symbol.for("fe-hydration");
2239
+ function isHydratable(value) {
2240
+ return value[Hydratable] === Hydratable;
2241
+ }
1607
2242
 
1608
2243
  const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
1609
2244
  const interpolationStart = `${marker}{`;
1610
2245
  const interpolationEnd = `}${marker}`;
1611
2246
  const interpolationEndLength = interpolationEnd.length;
1612
- let id = 0;
2247
+ let id$1 = 0;
1613
2248
  /** @internal */
1614
- const nextId = () => `${marker}-${++id}`;
2249
+ const nextId = () => `${marker}-${++id$1}`;
1615
2250
  /**
1616
2251
  * Common APIs related to markup generation.
1617
2252
  * @public
@@ -1681,67 +2316,6 @@ const Parser = Object.freeze({
1681
2316
  },
1682
2317
  });
1683
2318
 
1684
- /**
1685
- * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
1686
- * control ViewBehaviors.
1687
- * @public
1688
- */
1689
- const ViewBehaviorOrchestrator = Object.freeze({
1690
- /**
1691
- * Creates a ViewBehaviorOrchestrator.
1692
- * @param source - The source to to associate behaviors with.
1693
- * @returns A ViewBehaviorOrchestrator.
1694
- */
1695
- create(source) {
1696
- const behaviors = [];
1697
- const targets = {};
1698
- let unbindables = null;
1699
- let isConnected = false;
1700
- return {
1701
- source,
1702
- context: ExecutionContext.default,
1703
- targets,
1704
- get isBound() {
1705
- return isConnected;
1706
- },
1707
- addBehaviorFactory(factory, target) {
1708
- const nodeId = factory.nodeId || (factory.nodeId = nextId());
1709
- factory.id || (factory.id = nextId());
1710
- this.addTarget(nodeId, target);
1711
- this.addBehavior(factory.createBehavior());
1712
- },
1713
- addTarget(nodeId, target) {
1714
- targets[nodeId] = target;
1715
- },
1716
- addBehavior(behavior) {
1717
- behaviors.push(behavior);
1718
- if (isConnected) {
1719
- behavior.bind(this);
1720
- }
1721
- },
1722
- onUnbind(unbindable) {
1723
- if (unbindables === null) {
1724
- unbindables = [];
1725
- }
1726
- unbindables.push(unbindable);
1727
- },
1728
- connectedCallback(controller) {
1729
- if (!isConnected) {
1730
- isConnected = true;
1731
- behaviors.forEach(x => x.bind(this));
1732
- }
1733
- },
1734
- disconnectedCallback(controller) {
1735
- if (isConnected) {
1736
- isConnected = false;
1737
- if (unbindables !== null) {
1738
- unbindables.forEach(x => x.unbind(this));
1739
- }
1740
- }
1741
- },
1742
- };
1743
- },
1744
- });
1745
2319
  const registry = createTypeRegistry();
1746
2320
  /**
1747
2321
  * Instructs the template engine to apply behavior to a node.
@@ -1769,67 +2343,6 @@ const HTMLDirective = Object.freeze({
1769
2343
  registry.register(options);
1770
2344
  return type;
1771
2345
  },
1772
- });
1773
- /**
1774
- * Decorator: Defines an HTMLDirective.
1775
- * @param options - Provides options that specify the directive's application.
1776
- * @public
1777
- */
1778
- function htmlDirective(options) {
1779
- /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
1780
- return function (type) {
1781
- HTMLDirective.define(type, options);
1782
- };
1783
- }
1784
- /**
1785
- * Captures a binding expression along with related information and capabilities.
1786
- *
1787
- * @public
1788
- */
1789
- class Binding {
1790
- /**
1791
- * Creates a binding.
1792
- * @param evaluate - Evaluates the binding.
1793
- * @param isVolatile - Indicates whether the binding is volatile.
1794
- */
1795
- constructor(evaluate, isVolatile = false) {
1796
- this.evaluate = evaluate;
1797
- this.isVolatile = isVolatile;
1798
- }
1799
- }
1800
- /**
1801
- * The type of HTML aspect to target.
1802
- * @public
1803
- */
1804
- const Aspect = Object.freeze({
1805
- /**
1806
- * Not aspected.
1807
- */
1808
- none: 0,
1809
- /**
1810
- * An attribute.
1811
- */
1812
- attribute: 1,
1813
- /**
1814
- * A boolean attribute.
1815
- */
1816
- booleanAttribute: 2,
1817
- /**
1818
- * A property.
1819
- */
1820
- property: 3,
1821
- /**
1822
- * Content
1823
- */
1824
- content: 4,
1825
- /**
1826
- * A token list.
1827
- */
1828
- tokenList: 5,
1829
- /**
1830
- * An event.
1831
- */
1832
- event: 6,
1833
2346
  /**
1834
2347
  *
1835
2348
  * @param directive - The directive to assign the aspect to.
@@ -1837,48 +2350,46 @@ const Aspect = Object.freeze({
1837
2350
  * @remarks
1838
2351
  * If a falsy value is provided, then the content aspect will be assigned.
1839
2352
  */
1840
- assign(directive, value) {
2353
+ assignAspect(directive, value) {
1841
2354
  if (!value) {
1842
- directive.aspectType = Aspect.content;
2355
+ directive.aspectType = DOMAspect.content;
1843
2356
  return;
1844
2357
  }
1845
2358
  directive.sourceAspect = value;
1846
2359
  switch (value[0]) {
1847
2360
  case ":":
1848
2361
  directive.targetAspect = value.substring(1);
1849
- switch (directive.targetAspect) {
1850
- case "innerHTML":
1851
- directive.aspectType = Aspect.property;
1852
- break;
1853
- case "classList":
1854
- directive.aspectType = Aspect.tokenList;
1855
- break;
1856
- default:
1857
- directive.aspectType = Aspect.property;
1858
- break;
1859
- }
2362
+ directive.aspectType =
2363
+ directive.targetAspect === "classList"
2364
+ ? DOMAspect.tokenList
2365
+ : DOMAspect.property;
1860
2366
  break;
1861
2367
  case "?":
1862
2368
  directive.targetAspect = value.substring(1);
1863
- directive.aspectType = Aspect.booleanAttribute;
2369
+ directive.aspectType = DOMAspect.booleanAttribute;
1864
2370
  break;
1865
2371
  case "@":
1866
2372
  directive.targetAspect = value.substring(1);
1867
- directive.aspectType = Aspect.event;
2373
+ directive.aspectType = DOMAspect.event;
1868
2374
  break;
1869
2375
  default:
1870
- if (value === "class") {
1871
- directive.targetAspect = "className";
1872
- directive.aspectType = Aspect.property;
1873
- }
1874
- else {
1875
- directive.targetAspect = value;
1876
- directive.aspectType = Aspect.attribute;
1877
- }
2376
+ directive.targetAspect = value;
2377
+ directive.aspectType = DOMAspect.attribute;
1878
2378
  break;
1879
2379
  }
1880
2380
  },
1881
2381
  });
2382
+ /**
2383
+ * Decorator: Defines an HTMLDirective.
2384
+ * @param options - Provides options that specify the directive's application.
2385
+ * @public
2386
+ */
2387
+ function htmlDirective(options) {
2388
+ /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
2389
+ return function (type) {
2390
+ HTMLDirective.define(type, options);
2391
+ };
2392
+ }
1882
2393
  /**
1883
2394
  * A base class used for attribute directives that don't need internal state.
1884
2395
  * @public
@@ -1890,10 +2401,6 @@ class StatelessAttachedAttributeDirective {
1890
2401
  */
1891
2402
  constructor(options) {
1892
2403
  this.options = options;
1893
- /**
1894
- * The unique id of the factory.
1895
- */
1896
- this.id = nextId();
1897
2404
  }
1898
2405
  /**
1899
2406
  * Creates a placeholder string based on the directive's index within the template.
@@ -1912,327 +2419,200 @@ class StatelessAttachedAttributeDirective {
1912
2419
  return this;
1913
2420
  }
1914
2421
  }
2422
+ makeSerializationNoop(StatelessAttachedAttributeDirective);
1915
2423
 
1916
- const createInnerHTMLBinding = globalThis.TrustedHTML
1917
- ? (binding) => (s, c) => {
1918
- const value = binding(s, c);
1919
- if (value instanceof TrustedHTML) {
1920
- return value;
1921
- }
1922
- throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
1923
- }
1924
- : (binding) => binding;
1925
- class OnChangeBinding extends Binding {
1926
- createObserver(_, subscriber) {
1927
- return Observable.binding(this.evaluate, subscriber, this.isVolatile);
2424
+ class HydrationTargetElementError extends Error {
2425
+ constructor(
2426
+ /**
2427
+ * The error message
2428
+ */
2429
+ message,
2430
+ /**
2431
+ * The Compiled View Behavior Factories that belong to the view.
2432
+ */
2433
+ factories,
2434
+ /**
2435
+ * The node to target factory.
2436
+ */
2437
+ node) {
2438
+ super(message);
2439
+ this.factories = factories;
2440
+ this.node = node;
1928
2441
  }
1929
2442
  }
1930
- class OneTimeBinding extends Binding {
1931
- createObserver() {
1932
- return this;
1933
- }
1934
- bind(controller) {
1935
- return this.evaluate(controller.source, controller.context);
1936
- }
2443
+ function isComment(node) {
2444
+ return node.nodeType === Node.COMMENT_NODE;
1937
2445
  }
1938
- function updateContent(target, aspect, value, controller) {
1939
- // If there's no actual value, then this equates to the
1940
- // empty string for the purposes of content bindings.
1941
- if (value === null || value === undefined) {
1942
- value = "";
1943
- }
1944
- // If the value has a "create" method, then it's a ContentTemplate.
1945
- if (value.create) {
1946
- target.textContent = "";
1947
- let view = target.$fastView;
1948
- // If there's no previous view that we might be able to
1949
- // reuse then create a new view from the template.
1950
- if (view === void 0) {
1951
- view = value.create();
1952
- }
1953
- else {
1954
- // If there is a previous view, but it wasn't created
1955
- // from the same template as the new value, then we
1956
- // need to remove the old view if it's still in the DOM
1957
- // and create a new view from the template.
1958
- if (target.$fastTemplate !== value) {
1959
- if (view.isComposed) {
1960
- view.remove();
1961
- view.unbind();
1962
- }
1963
- view = value.create();
1964
- }
1965
- }
1966
- // It's possible that the value is the same as the previous template
1967
- // and that there's actually no need to compose it.
1968
- if (!view.isComposed) {
1969
- view.isComposed = true;
1970
- view.bind(controller.source, controller.context);
1971
- view.insertBefore(target);
1972
- target.$fastView = view;
1973
- target.$fastTemplate = value;
1974
- }
1975
- else if (view.needsBindOnly) {
1976
- view.needsBindOnly = false;
1977
- view.bind(controller.source, controller.context);
1978
- }
1979
- }
1980
- else {
1981
- const view = target.$fastView;
1982
- // If there is a view and it's currently composed into
1983
- // the DOM, then we need to remove it.
1984
- if (view !== void 0 && view.isComposed) {
1985
- view.isComposed = false;
1986
- view.remove();
1987
- if (view.needsBindOnly) {
1988
- view.needsBindOnly = false;
2446
+ function isText(node) {
2447
+ return node.nodeType === Node.TEXT_NODE;
2448
+ }
2449
+ /**
2450
+ * Returns a range object inclusive of all nodes including and between the
2451
+ * provided first and last node.
2452
+ * @param first - The first node
2453
+ * @param last - This last node
2454
+ * @returns
2455
+ */
2456
+ function createRangeForNodes(first, last) {
2457
+ const range = document.createRange();
2458
+ range.setStart(first, 0);
2459
+ // The lastIndex should be inclusive of the end of the lastChild. Obtain offset based
2460
+ // on usageNotes: https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd#usage_notes
2461
+ range.setEnd(last, isComment(last) || isText(last) ? last.data.length : last.childNodes.length);
2462
+ return range;
2463
+ }
2464
+ function isShadowRoot(node) {
2465
+ return node instanceof DocumentFragment && "mode" in node;
2466
+ }
2467
+ /**
2468
+ * Maps {@link CompiledViewBehaviorFactory} ids to the corresponding node targets for the view.
2469
+ * @param firstNode - The first node of the view.
2470
+ * @param lastNode - The last node of the view.
2471
+ * @param factories - The Compiled View Behavior Factories that belong to the view.
2472
+ * @returns - A {@link ViewBehaviorTargets } object for the factories in the view.
2473
+ */
2474
+ function buildViewBindingTargets(firstNode, lastNode, factories) {
2475
+ const range = createRangeForNodes(firstNode, lastNode);
2476
+ const treeRoot = range.commonAncestorContainer;
2477
+ const walker = document.createTreeWalker(treeRoot, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_COMMENT + NodeFilter.SHOW_TEXT, {
2478
+ acceptNode(node) {
2479
+ return range.comparePoint(node, 0) === 0
2480
+ ? NodeFilter.FILTER_ACCEPT
2481
+ : NodeFilter.FILTER_REJECT;
2482
+ },
2483
+ });
2484
+ const targets = {};
2485
+ const boundaries = {};
2486
+ let node = (walker.currentNode = firstNode);
2487
+ while (node !== null) {
2488
+ switch (node.nodeType) {
2489
+ case Node.ELEMENT_NODE: {
2490
+ targetElement(node, factories, targets);
2491
+ break;
1989
2492
  }
1990
- else {
1991
- view.unbind();
2493
+ case Node.COMMENT_NODE: {
2494
+ targetComment(node, walker, factories, targets, boundaries);
2495
+ break;
1992
2496
  }
1993
2497
  }
1994
- target.textContent = value;
2498
+ node = walker.nextNode();
1995
2499
  }
2500
+ range.detach();
2501
+ return { targets, boundaries };
1996
2502
  }
1997
- function updateTokenList(target, aspect, value) {
1998
- var _a;
1999
- const lookup = `${this.id}-t`;
2000
- const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
2001
- const versions = state.v;
2002
- let currentVersion = state.c;
2003
- const tokenList = target[aspect];
2004
- // Add the classes, tracking the version at which they were added.
2005
- if (value !== null && value !== undefined && value.length) {
2006
- const names = value.split(/\s+/);
2007
- for (let i = 0, ii = names.length; i < ii; ++i) {
2008
- const currentName = names[i];
2009
- if (currentName === "") {
2010
- continue;
2503
+ function targetElement(node, factories, targets) {
2504
+ // Check for attributes and map any factories.
2505
+ const attrFactoryIds = HydrationMarkup.parseAttributeBinding(node);
2506
+ if (attrFactoryIds !== null) {
2507
+ for (const id of attrFactoryIds) {
2508
+ if (!factories[id]) {
2509
+ 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);
2011
2510
  }
2012
- versions[currentName] = currentVersion;
2013
- tokenList.add(currentName);
2511
+ targetFactory(factories[id], node, targets);
2014
2512
  }
2513
+ node.removeAttribute(HydrationMarkup.attributeMarkerName);
2015
2514
  }
2016
- state.v = currentVersion + 1;
2017
- // If this is the first call to add classes, there's no need to remove old ones.
2018
- if (currentVersion === 0) {
2515
+ }
2516
+ function targetComment(node, walker, factories, targets, boundaries) {
2517
+ if (HydrationMarkup.isElementBoundaryStartMarker(node)) {
2518
+ skipToElementBoundaryEndMarker(node, walker);
2019
2519
  return;
2020
2520
  }
2021
- // Remove classes from the previous version.
2022
- currentVersion -= 1;
2023
- for (const name in versions) {
2024
- if (versions[name] === currentVersion) {
2025
- tokenList.remove(name);
2521
+ if (HydrationMarkup.isContentBindingStartMarker(node.data)) {
2522
+ const parsed = HydrationMarkup.parseContentBindingStartMarker(node.data);
2523
+ if (parsed === null) {
2524
+ return;
2026
2525
  }
2027
- }
2028
- }
2029
- const setProperty = (t, a, v) => (t[a] = v);
2030
- const eventTarget = () => void 0;
2031
- /**
2032
- * A directive that applies bindings.
2033
- * @public
2034
- */
2035
- class HTMLBindingDirective {
2036
- /**
2037
- * Creates an instance of HTMLBindingDirective.
2038
- * @param dataBinding - The binding configuration to apply.
2039
- */
2040
- constructor(dataBinding) {
2041
- this.dataBinding = dataBinding;
2042
- this.updateTarget = null;
2043
- /**
2044
- * The unique id of the factory.
2045
- */
2046
- this.id = nextId();
2047
- /**
2048
- * The type of aspect to target.
2049
- */
2050
- this.aspectType = Aspect.content;
2051
- /** @internal */
2052
- this.bind = this.bindDefault;
2053
- this.data = `${this.id}-d`;
2054
- }
2055
- /**
2056
- * Creates HTML to be used within a template.
2057
- * @param add - Can be used to add behavior factories to a template.
2058
- */
2059
- createHTML(add) {
2060
- return Markup.interpolation(add(this));
2061
- }
2062
- /**
2063
- * Creates a behavior.
2064
- */
2065
- createBehavior() {
2066
- if (this.updateTarget === null) {
2067
- if (this.targetAspect === "innerHTML") {
2068
- this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
2069
- }
2070
- switch (this.aspectType) {
2071
- case 1:
2072
- this.updateTarget = DOM.setAttribute;
2073
- break;
2074
- case 2:
2075
- this.updateTarget = DOM.setBooleanAttribute;
2076
- break;
2077
- case 3:
2078
- this.updateTarget = setProperty;
2079
- break;
2080
- case 4:
2081
- this.bind = this.bindContent;
2082
- this.updateTarget = updateContent;
2083
- break;
2084
- case 5:
2085
- this.updateTarget = updateTokenList;
2526
+ const [index, id] = parsed;
2527
+ const factory = factories[index];
2528
+ const nodes = [];
2529
+ let current = walker.nextSibling();
2530
+ node.data = "";
2531
+ const first = current;
2532
+ // Search for the binding end marker that closes the binding.
2533
+ while (current !== null) {
2534
+ if (isComment(current)) {
2535
+ const parsed = HydrationMarkup.parseContentBindingEndMarker(current.data);
2536
+ if (parsed && parsed[1] === id) {
2086
2537
  break;
2087
- case 6:
2088
- this.bind = this.bindEvent;
2089
- this.updateTarget = eventTarget;
2090
- break;
2091
- default:
2092
- throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
2538
+ }
2093
2539
  }
2540
+ nodes.push(current);
2541
+ current = walker.nextSibling();
2094
2542
  }
2095
- return this;
2096
- }
2097
- /** @internal */
2098
- bindDefault(controller) {
2099
- var _a;
2100
- const target = controller.targets[this.nodeId];
2101
- const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
2102
- observer.target = target;
2103
- observer.controller = controller;
2104
- this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2105
- if (this.updateTarget === updateContent) {
2106
- controller.onUnbind(this);
2543
+ if (current === null) {
2544
+ const root = node.getRootNode();
2545
+ throw new Error(`Error hydrating Comment node inside "${isShadowRoot(root) ? root.host.nodeName : root.nodeName}".`);
2107
2546
  }
2108
- }
2109
- /** @internal */
2110
- bindContent(controller) {
2111
- this.bindDefault(controller);
2112
- controller.onUnbind(this);
2113
- }
2114
- /** @internal */
2115
- bindEvent(controller) {
2116
- const target = controller.targets[this.nodeId];
2117
- target[this.data] = controller;
2118
- target.addEventListener(this.targetAspect, this, this.dataBinding.options);
2119
- }
2120
- /** @internal */
2121
- unbind(controller) {
2122
- const target = controller.targets[this.nodeId];
2123
- const view = target.$fastView;
2124
- if (view !== void 0 && view.isComposed) {
2125
- view.unbind();
2126
- view.needsBindOnly = true;
2547
+ current.data = "";
2548
+ if (nodes.length === 1 && isText(nodes[0])) {
2549
+ targetFactory(factory, nodes[0], targets);
2127
2550
  }
2128
- }
2129
- /** @internal */
2130
- handleEvent(event) {
2131
- const target = event.currentTarget;
2132
- ExecutionContext.setEvent(event);
2133
- const controller = target[this.data];
2134
- const result = this.dataBinding.evaluate(controller.source, controller.context);
2135
- ExecutionContext.setEvent(null);
2136
- if (result !== true) {
2137
- event.preventDefault();
2551
+ else {
2552
+ // If current === first, it means there is no content in
2553
+ // the view. This happens when a `when` directive evaluates false,
2554
+ // or whenever a content binding returns null or undefined.
2555
+ // In that case, there will never be any content
2556
+ // to hydrate and Binding can simply create a HTMLView
2557
+ // whenever it needs to.
2558
+ if (current !== first && current.previousSibling !== null) {
2559
+ boundaries[factory.targetNodeId] = {
2560
+ first,
2561
+ last: current.previousSibling,
2562
+ };
2563
+ }
2564
+ // Binding evaluates to null / undefined or a template.
2565
+ // If binding revaluates to string, it will replace content in target
2566
+ // So we always insert a text node to ensure that
2567
+ // text content binding will be written to this text node instead of comment
2568
+ const dummyTextNode = current.parentNode.insertBefore(document.createTextNode(""), current);
2569
+ targetFactory(factory, dummyTextNode, targets);
2138
2570
  }
2139
2571
  }
2140
- /** @internal */
2141
- handleChange(binding, observer) {
2142
- const target = observer.target;
2143
- const controller = observer.controller;
2144
- this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2145
- }
2146
2572
  }
2147
- HTMLDirective.define(HTMLBindingDirective, { aspected: true });
2148
2573
  /**
2149
- * Creates an standard binding.
2150
- * @param binding - The binding to refresh when changed.
2151
- * @param isVolatile - Indicates whether the binding is volatile or not.
2152
- * @returns A binding configuration.
2153
- * @public
2154
- */
2155
- function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
2156
- return new OnChangeBinding(binding, isVolatile);
2157
- }
2158
- /**
2159
- * Creates a one time binding
2160
- * @param binding - The binding to refresh when signaled.
2161
- * @returns A binding configuration.
2162
- * @public
2163
- */
2164
- function oneTime(binding) {
2165
- return new OneTimeBinding(binding);
2166
- }
2167
- /**
2168
- * Creates an event listener binding.
2169
- * @param binding - The binding to invoke when the event is raised.
2170
- * @param options - Event listener options.
2171
- * @returns A binding configuration.
2172
- * @public
2574
+ * Moves TreeWalker to element boundary end marker
2575
+ * @param node - element boundary start marker node
2576
+ * @param walker - tree walker
2173
2577
  */
2174
- function listener(binding, options) {
2175
- const config = new OnChangeBinding(binding, false);
2176
- config.options = options;
2177
- return config;
2578
+ function skipToElementBoundaryEndMarker(node, walker) {
2579
+ const id = HydrationMarkup.parseElementBoundaryStartMarker(node.data);
2580
+ let current = walker.nextSibling();
2581
+ while (current !== null) {
2582
+ if (isComment(current)) {
2583
+ const parsed = HydrationMarkup.parseElementBoundaryEndMarker(current.data);
2584
+ if (parsed && parsed === id) {
2585
+ break;
2586
+ }
2587
+ }
2588
+ current = walker.nextSibling();
2589
+ }
2178
2590
  }
2179
- /**
2180
- * Normalizes the input value into a binding.
2181
- * @param value - The value to create the default binding for.
2182
- * @returns A binding configuration for the provided value.
2183
- * @public
2184
- */
2185
- function normalizeBinding(value) {
2186
- return isFunction(value)
2187
- ? bind(value)
2188
- : value instanceof Binding
2189
- ? value
2190
- : oneTime(() => value);
2591
+ function targetFactory(factory, node, targets) {
2592
+ if (factory.targetNodeId === undefined) {
2593
+ // Dev error, this shouldn't ever be thrown
2594
+ throw new Error("Factory could not be target to the node");
2595
+ }
2596
+ targets[factory.targetNodeId] = node;
2191
2597
  }
2192
2598
 
2599
+ var _a;
2193
2600
  function removeNodeSequence(firstNode, lastNode) {
2194
2601
  const parent = firstNode.parentNode;
2195
2602
  let current = firstNode;
2196
2603
  let next;
2197
2604
  while (current !== lastNode) {
2198
2605
  next = current.nextSibling;
2606
+ if (!next) {
2607
+ throw new Error(`Unmatched first/last child inside "${lastNode.getRootNode().host.nodeName}".`);
2608
+ }
2199
2609
  parent.removeChild(current);
2200
2610
  current = next;
2201
2611
  }
2202
2612
  parent.removeChild(lastNode);
2203
2613
  }
2204
- /**
2205
- * The standard View implementation, which also implements ElementView and SyntheticView.
2206
- * @public
2207
- */
2208
- class HTMLView {
2209
- /**
2210
- * Constructs an instance of HTMLView.
2211
- * @param fragment - The html fragment that contains the nodes for this view.
2212
- * @param behaviors - The behaviors to be applied to this view.
2213
- */
2214
- constructor(fragment, factories, targets) {
2215
- this.fragment = fragment;
2216
- this.factories = factories;
2217
- this.targets = targets;
2218
- this.behaviors = null;
2219
- this.unbindables = [];
2220
- /**
2221
- * The data that the view is bound to.
2222
- */
2223
- this.source = null;
2224
- /**
2225
- * Indicates whether the controller is bound.
2226
- */
2227
- this.isBound = false;
2228
- /**
2229
- * Indicates how the source's lifetime relates to the controller's lifetime.
2230
- */
2231
- this.sourceLifetime = SourceLifetime.unknown;
2232
- /**
2233
- * The execution context the view is running within.
2234
- */
2235
- this.context = this;
2614
+ class DefaultExecutionContext {
2615
+ constructor() {
2236
2616
  /**
2237
2617
  * The index of the current item within a repeat context.
2238
2618
  */
@@ -2241,8 +2621,6 @@ class HTMLView {
2241
2621
  * The length of the current collection within a repeat context.
2242
2622
  */
2243
2623
  this.length = 0;
2244
- this.firstChild = fragment.firstChild;
2245
- this.lastChild = fragment.lastChild;
2246
2624
  }
2247
2625
  /**
2248
2626
  * The current event within an event handler.
@@ -2297,6 +2675,43 @@ class HTMLView {
2297
2675
  eventTarget() {
2298
2676
  return this.event.target;
2299
2677
  }
2678
+ }
2679
+ /**
2680
+ * The standard View implementation, which also implements ElementView and SyntheticView.
2681
+ * @public
2682
+ */
2683
+ class HTMLView extends DefaultExecutionContext {
2684
+ /**
2685
+ * Constructs an instance of HTMLView.
2686
+ * @param fragment - The html fragment that contains the nodes for this view.
2687
+ * @param behaviors - The behaviors to be applied to this view.
2688
+ */
2689
+ constructor(fragment, factories, targets) {
2690
+ super();
2691
+ this.fragment = fragment;
2692
+ this.factories = factories;
2693
+ this.targets = targets;
2694
+ this.behaviors = null;
2695
+ this.unbindables = [];
2696
+ /**
2697
+ * The data that the view is bound to.
2698
+ */
2699
+ this.source = null;
2700
+ /**
2701
+ * Indicates whether the controller is bound.
2702
+ */
2703
+ this.isBound = false;
2704
+ /**
2705
+ * Indicates how the source's lifetime relates to the controller's lifetime.
2706
+ */
2707
+ this.sourceLifetime = SourceLifetime.unknown;
2708
+ /**
2709
+ * The execution context the view is running within.
2710
+ */
2711
+ this.context = this;
2712
+ this.firstChild = fragment.firstChild;
2713
+ this.lastChild = fragment.lastChild;
2714
+ }
2300
2715
  /**
2301
2716
  * Appends the view's DOM nodes to the referenced node.
2302
2717
  * @param node - The parent node to append the view's DOM nodes to.
@@ -2388,9 +2803,219 @@ class HTMLView {
2388
2803
  }
2389
2804
  this.isBound = true;
2390
2805
  }
2391
- /**
2392
- * Unbinds a view's behaviors from its binding source.
2393
- */
2806
+ /**
2807
+ * Unbinds a view's behaviors from its binding source.
2808
+ */
2809
+ unbind() {
2810
+ if (!this.isBound || this.source === null) {
2811
+ return;
2812
+ }
2813
+ this.evaluateUnbindables();
2814
+ this.source = null;
2815
+ this.context = this;
2816
+ this.isBound = false;
2817
+ }
2818
+ evaluateUnbindables() {
2819
+ const unbindables = this.unbindables;
2820
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
2821
+ unbindables[i].unbind(this);
2822
+ }
2823
+ unbindables.length = 0;
2824
+ }
2825
+ /**
2826
+ * Efficiently disposes of a contiguous range of synthetic view instances.
2827
+ * @param views - A contiguous range of views to be disposed.
2828
+ */
2829
+ static disposeContiguousBatch(views) {
2830
+ if (views.length === 0) {
2831
+ return;
2832
+ }
2833
+ removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
2834
+ for (let i = 0, ii = views.length; i < ii; ++i) {
2835
+ views[i].unbind();
2836
+ }
2837
+ }
2838
+ }
2839
+ makeSerializationNoop(HTMLView);
2840
+ Observable.defineProperty(HTMLView.prototype, "index");
2841
+ Observable.defineProperty(HTMLView.prototype, "length");
2842
+ const HydrationStage = {
2843
+ unhydrated: "unhydrated",
2844
+ hydrating: "hydrating",
2845
+ hydrated: "hydrated",
2846
+ };
2847
+ /** @public */
2848
+ class HydrationBindingError extends Error {
2849
+ constructor(
2850
+ /**
2851
+ * The error message
2852
+ */
2853
+ message,
2854
+ /**
2855
+ * The factory that was unable to be bound
2856
+ */
2857
+ factory,
2858
+ /**
2859
+ * A DocumentFragment containing a clone of the
2860
+ * view's Nodes.
2861
+ */
2862
+ fragment,
2863
+ /**
2864
+ * String representation of the HTML in the template that
2865
+ * threw the binding error.
2866
+ */
2867
+ templateString) {
2868
+ super(message);
2869
+ this.factory = factory;
2870
+ this.fragment = fragment;
2871
+ this.templateString = templateString;
2872
+ }
2873
+ }
2874
+ class HydrationView extends DefaultExecutionContext {
2875
+ constructor(firstChild, lastChild, sourceTemplate, hostBindingTarget) {
2876
+ super();
2877
+ this.firstChild = firstChild;
2878
+ this.lastChild = lastChild;
2879
+ this.sourceTemplate = sourceTemplate;
2880
+ this.hostBindingTarget = hostBindingTarget;
2881
+ this[_a] = Hydratable;
2882
+ this.context = this;
2883
+ this.source = null;
2884
+ this.isBound = false;
2885
+ this.sourceLifetime = SourceLifetime.unknown;
2886
+ this.unbindables = [];
2887
+ this.fragment = null;
2888
+ this.behaviors = null;
2889
+ this._hydrationStage = HydrationStage.unhydrated;
2890
+ this._bindingViewBoundaries = {};
2891
+ this._targets = {};
2892
+ this.factories = sourceTemplate.compile().factories;
2893
+ }
2894
+ get hydrationStage() {
2895
+ return this._hydrationStage;
2896
+ }
2897
+ get targets() {
2898
+ return this._targets;
2899
+ }
2900
+ get bindingViewBoundaries() {
2901
+ return this._bindingViewBoundaries;
2902
+ }
2903
+ /**
2904
+ * no-op. Hydrated views are don't need to be moved from a documentFragment
2905
+ * to the target node.
2906
+ */
2907
+ insertBefore(node) {
2908
+ // No-op in cases where this is called before the view is removed,
2909
+ // because the nodes will already be in the document and just need hydrating.
2910
+ if (this.fragment === null) {
2911
+ return;
2912
+ }
2913
+ if (this.fragment.hasChildNodes()) {
2914
+ node.parentNode.insertBefore(this.fragment, node);
2915
+ }
2916
+ else {
2917
+ const end = this.lastChild;
2918
+ if (node.previousSibling === end)
2919
+ return;
2920
+ const parentNode = node.parentNode;
2921
+ let current = this.firstChild;
2922
+ let next;
2923
+ while (current !== end) {
2924
+ next = current.nextSibling;
2925
+ parentNode.insertBefore(current, node);
2926
+ current = next;
2927
+ }
2928
+ parentNode.insertBefore(end, node);
2929
+ }
2930
+ }
2931
+ /**
2932
+ * Appends the view to a node. In cases where this is called before the
2933
+ * view has been removed, the method will no-op.
2934
+ * @param node - the node to append the view to.
2935
+ */
2936
+ appendTo(node) {
2937
+ if (this.fragment !== null) {
2938
+ node.appendChild(this.fragment);
2939
+ }
2940
+ }
2941
+ remove() {
2942
+ const fragment = this.fragment || (this.fragment = document.createDocumentFragment());
2943
+ const end = this.lastChild;
2944
+ let current = this.firstChild;
2945
+ let next;
2946
+ while (current !== end) {
2947
+ next = current.nextSibling;
2948
+ if (!next) {
2949
+ throw new Error(`Unmatched first/last child inside "${end.getRootNode().host.nodeName}".`);
2950
+ }
2951
+ fragment.appendChild(current);
2952
+ current = next;
2953
+ }
2954
+ fragment.appendChild(end);
2955
+ }
2956
+ bind(source, context = this) {
2957
+ var _b, _c;
2958
+ if (this.hydrationStage !== HydrationStage.hydrated) {
2959
+ this._hydrationStage = HydrationStage.hydrating;
2960
+ }
2961
+ if (this.source === source) {
2962
+ return;
2963
+ }
2964
+ let behaviors = this.behaviors;
2965
+ if (behaviors === null) {
2966
+ this.source = source;
2967
+ this.context = context;
2968
+ try {
2969
+ const { targets, boundaries } = buildViewBindingTargets(this.firstChild, this.lastChild, this.factories);
2970
+ this._targets = targets;
2971
+ this._bindingViewBoundaries = boundaries;
2972
+ }
2973
+ catch (error) {
2974
+ if (error instanceof HydrationTargetElementError) {
2975
+ let templateString = this.sourceTemplate.html;
2976
+ if (typeof templateString !== "string") {
2977
+ templateString = templateString.innerHTML;
2978
+ }
2979
+ error.templateString = templateString;
2980
+ }
2981
+ throw error;
2982
+ }
2983
+ this.behaviors = behaviors = new Array(this.factories.length);
2984
+ const factories = this.factories;
2985
+ for (let i = 0, ii = factories.length; i < ii; ++i) {
2986
+ const factory = factories[i];
2987
+ if (factory.targetNodeId === "h" && this.hostBindingTarget) {
2988
+ targetFactory(factory, this.hostBindingTarget, this._targets);
2989
+ }
2990
+ // If the binding has been targeted or it is a host binding and the view has a hostBindingTarget
2991
+ if (factory.targetNodeId in this.targets) {
2992
+ const behavior = factory.createBehavior();
2993
+ behavior.bind(this);
2994
+ behaviors[i] = behavior;
2995
+ }
2996
+ else {
2997
+ let templateString = this.sourceTemplate.html;
2998
+ if (typeof templateString !== "string") {
2999
+ templateString = templateString.innerHTML;
3000
+ }
3001
+ throw new HydrationBindingError(`HydrationView was unable to successfully target bindings inside "${(_c = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode()).host) === null || _c === void 0 ? void 0 : _c.nodeName}".`, factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
3002
+ }
3003
+ }
3004
+ }
3005
+ else {
3006
+ if (this.source !== null) {
3007
+ this.evaluateUnbindables();
3008
+ }
3009
+ this.isBound = false;
3010
+ this.source = source;
3011
+ this.context = context;
3012
+ for (let i = 0, ii = behaviors.length; i < ii; ++i) {
3013
+ behaviors[i].bind(this);
3014
+ }
3015
+ }
3016
+ this.isBound = true;
3017
+ this._hydrationStage = HydrationStage.hydrated;
3018
+ }
2394
3019
  unbind() {
2395
3020
  if (!this.isBound || this.source === null) {
2396
3021
  return;
@@ -2400,6 +3025,17 @@ class HTMLView {
2400
3025
  this.context = this;
2401
3026
  this.isBound = false;
2402
3027
  }
3028
+ /**
3029
+ * Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
3030
+ * Once a view has been disposed, it cannot be inserted or bound again.
3031
+ */
3032
+ dispose() {
3033
+ removeNodeSequence(this.firstChild, this.lastChild);
3034
+ this.unbind();
3035
+ }
3036
+ onUnbind(behavior) {
3037
+ this.unbindables.push(behavior);
3038
+ }
2403
3039
  evaluateUnbindables() {
2404
3040
  const unbindables = this.unbindables;
2405
3041
  for (let i = 0, ii = unbindables.length; i < ii; ++i) {
@@ -2407,22 +3043,220 @@ class HTMLView {
2407
3043
  }
2408
3044
  unbindables.length = 0;
2409
3045
  }
3046
+ }
3047
+ _a = Hydratable;
3048
+ makeSerializationNoop(HydrationView);
3049
+
3050
+ function isContentTemplate(value) {
3051
+ return value.create !== undefined;
3052
+ }
3053
+ function updateContent(target, aspect, value, controller) {
3054
+ // If there's no actual value, then this equates to the
3055
+ // empty string for the purposes of content bindings.
3056
+ if (value === null || value === undefined) {
3057
+ value = "";
3058
+ }
3059
+ // If the value has a "create" method, then it's a ContentTemplate.
3060
+ if (isContentTemplate(value)) {
3061
+ target.textContent = "";
3062
+ let view = target.$fastView;
3063
+ // If there's no previous view that we might be able to
3064
+ // reuse then create a new view from the template.
3065
+ if (view === void 0) {
3066
+ if (isHydratable(controller) &&
3067
+ isHydratable(value) &&
3068
+ controller.bindingViewBoundaries[this.targetNodeId] !== undefined &&
3069
+ controller.hydrationStage !== HydrationStage.hydrated) {
3070
+ const viewNodes = controller.bindingViewBoundaries[this.targetNodeId];
3071
+ view = value.hydrate(viewNodes.first, viewNodes.last);
3072
+ }
3073
+ else {
3074
+ view = value.create();
3075
+ }
3076
+ }
3077
+ else {
3078
+ // If there is a previous view, but it wasn't created
3079
+ // from the same template as the new value, then we
3080
+ // need to remove the old view if it's still in the DOM
3081
+ // and create a new view from the template.
3082
+ if (target.$fastTemplate !== value) {
3083
+ if (view.isComposed) {
3084
+ view.remove();
3085
+ view.unbind();
3086
+ }
3087
+ view = value.create();
3088
+ }
3089
+ }
3090
+ // It's possible that the value is the same as the previous template
3091
+ // and that there's actually no need to compose it.
3092
+ if (!view.isComposed) {
3093
+ view.isComposed = true;
3094
+ view.bind(controller.source, controller.context);
3095
+ view.insertBefore(target);
3096
+ target.$fastView = view;
3097
+ target.$fastTemplate = value;
3098
+ }
3099
+ else if (view.needsBindOnly) {
3100
+ view.needsBindOnly = false;
3101
+ view.bind(controller.source, controller.context);
3102
+ }
3103
+ }
3104
+ else {
3105
+ const view = target.$fastView;
3106
+ // If there is a view and it's currently composed into
3107
+ // the DOM, then we need to remove it.
3108
+ if (view !== void 0 && view.isComposed) {
3109
+ view.isComposed = false;
3110
+ view.remove();
3111
+ if (view.needsBindOnly) {
3112
+ view.needsBindOnly = false;
3113
+ }
3114
+ else {
3115
+ view.unbind();
3116
+ }
3117
+ }
3118
+ target.textContent = value;
3119
+ }
3120
+ }
3121
+ function updateTokenList(target, aspect, value) {
3122
+ var _a;
3123
+ const lookup = `${this.id}-t`;
3124
+ const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
3125
+ const classVersions = state.cv;
3126
+ let version = state.v;
3127
+ const tokenList = target[aspect];
3128
+ // Add the classes, tracking the version at which they were added.
3129
+ if (value !== null && value !== undefined && value.length) {
3130
+ const names = value.split(/\s+/);
3131
+ for (let i = 0, ii = names.length; i < ii; ++i) {
3132
+ const currentName = names[i];
3133
+ if (currentName === "") {
3134
+ continue;
3135
+ }
3136
+ classVersions[currentName] = version;
3137
+ tokenList.add(currentName);
3138
+ }
3139
+ }
3140
+ state.v = version + 1;
3141
+ // If this is the first call to add classes, there's no need to remove old ones.
3142
+ if (version === 0) {
3143
+ return;
3144
+ }
3145
+ // Remove classes from the previous version.
3146
+ version -= 1;
3147
+ for (const name in classVersions) {
3148
+ if (classVersions[name] === version) {
3149
+ tokenList.remove(name);
3150
+ }
3151
+ }
3152
+ }
3153
+ const sinkLookup = {
3154
+ [DOMAspect.attribute]: DOM.setAttribute,
3155
+ [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
3156
+ [DOMAspect.property]: (t, a, v) => (t[a] = v),
3157
+ [DOMAspect.content]: updateContent,
3158
+ [DOMAspect.tokenList]: updateTokenList,
3159
+ [DOMAspect.event]: () => void 0,
3160
+ };
3161
+ /**
3162
+ * A directive that applies bindings.
3163
+ * @public
3164
+ */
3165
+ class HTMLBindingDirective {
2410
3166
  /**
2411
- * Efficiently disposes of a contiguous range of synthetic view instances.
2412
- * @param views - A contiguous range of views to be disposed.
3167
+ * Creates an instance of HTMLBindingDirective.
3168
+ * @param dataBinding - The binding configuration to apply.
2413
3169
  */
2414
- static disposeContiguousBatch(views) {
2415
- if (views.length === 0) {
2416
- return;
3170
+ constructor(dataBinding) {
3171
+ this.dataBinding = dataBinding;
3172
+ this.updateTarget = null;
3173
+ /**
3174
+ * The type of aspect to target.
3175
+ */
3176
+ this.aspectType = DOMAspect.content;
3177
+ }
3178
+ /**
3179
+ * Creates HTML to be used within a template.
3180
+ * @param add - Can be used to add behavior factories to a template.
3181
+ */
3182
+ createHTML(add) {
3183
+ return Markup.interpolation(add(this));
3184
+ }
3185
+ /**
3186
+ * Creates a behavior.
3187
+ */
3188
+ createBehavior() {
3189
+ var _a;
3190
+ if (this.updateTarget === null) {
3191
+ const sink = sinkLookup[this.aspectType];
3192
+ const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
3193
+ if (!sink) {
3194
+ throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
3195
+ }
3196
+ this.data = `${this.id}-d`;
3197
+ this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
2417
3198
  }
2418
- removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
2419
- for (let i = 0, ii = views.length; i < ii; ++i) {
2420
- views[i].unbind();
3199
+ return this;
3200
+ }
3201
+ /** @internal */
3202
+ bind(controller) {
3203
+ var _a;
3204
+ const target = controller.targets[this.targetNodeId];
3205
+ const isHydrating = isHydratable(controller) &&
3206
+ controller.hydrationStage &&
3207
+ controller.hydrationStage !== HydrationStage.hydrated;
3208
+ switch (this.aspectType) {
3209
+ case DOMAspect.event:
3210
+ target[this.data] = controller;
3211
+ target.addEventListener(this.targetAspect, this, this.dataBinding.options);
3212
+ break;
3213
+ case DOMAspect.content:
3214
+ controller.onUnbind(this);
3215
+ // intentional fall through
3216
+ default:
3217
+ const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
3218
+ observer.target = target;
3219
+ observer.controller = controller;
3220
+ if (isHydrating &&
3221
+ (this.aspectType === DOMAspect.attribute ||
3222
+ this.aspectType === DOMAspect.booleanAttribute)) {
3223
+ observer.bind(controller);
3224
+ // Skip updating target during bind for attributes
3225
+ break;
3226
+ }
3227
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
3228
+ break;
3229
+ }
3230
+ }
3231
+ /** @internal */
3232
+ unbind(controller) {
3233
+ const target = controller.targets[this.targetNodeId];
3234
+ const view = target.$fastView;
3235
+ if (view !== void 0 && view.isComposed) {
3236
+ view.unbind();
3237
+ view.needsBindOnly = true;
2421
3238
  }
2422
3239
  }
3240
+ /** @internal */
3241
+ handleEvent(event) {
3242
+ const controller = event.currentTarget[this.data];
3243
+ if (controller.isBound) {
3244
+ ExecutionContext.setEvent(event);
3245
+ const result = this.dataBinding.evaluate(controller.source, controller.context);
3246
+ ExecutionContext.setEvent(null);
3247
+ if (result !== true) {
3248
+ event.preventDefault();
3249
+ }
3250
+ }
3251
+ }
3252
+ /** @internal */
3253
+ handleChange(binding, observer) {
3254
+ const target = observer.target;
3255
+ const controller = observer.controller;
3256
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
3257
+ }
2423
3258
  }
2424
- Observable.defineProperty(HTMLView.prototype, "index");
2425
- Observable.defineProperty(HTMLView.prototype, "length");
3259
+ HTMLDirective.define(HTMLBindingDirective, { aspected: true });
2426
3260
 
2427
3261
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
2428
3262
  const descriptorCache = {};
@@ -2448,20 +3282,25 @@ const warningHost = new Proxy(document.createElement("div"), {
2448
3282
  },
2449
3283
  });
2450
3284
  class CompilationContext {
2451
- constructor(fragment, directives) {
3285
+ constructor(fragment, directives, policy) {
2452
3286
  this.fragment = fragment;
2453
3287
  this.directives = directives;
3288
+ this.policy = policy;
2454
3289
  this.proto = null;
2455
3290
  this.nodeIds = new Set();
2456
3291
  this.descriptors = {};
2457
3292
  this.factories = [];
2458
3293
  }
2459
- addFactory(factory, parentId, nodeId, targetIndex) {
3294
+ addFactory(factory, parentId, nodeId, targetIndex, tagName) {
3295
+ var _a, _b;
2460
3296
  if (!this.nodeIds.has(nodeId)) {
2461
3297
  this.nodeIds.add(nodeId);
2462
3298
  this.addTargetDescriptor(parentId, nodeId, targetIndex);
2463
3299
  }
2464
- factory.nodeId = nodeId;
3300
+ factory.id = (_a = factory.id) !== null && _a !== void 0 ? _a : nextId();
3301
+ factory.targetNodeId = nodeId;
3302
+ factory.targetTagName = tagName;
3303
+ factory.policy = (_b = factory.policy) !== null && _b !== void 0 ? _b : this.policy;
2465
3304
  this.factories.push(factory);
2466
3305
  }
2467
3306
  freeze() {
@@ -2514,19 +3353,19 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
2514
3353
  let result = null;
2515
3354
  if (parseResult === null) {
2516
3355
  if (includeBasicValues) {
2517
- result = new HTMLBindingDirective(oneTime(() => attrValue));
2518
- Aspect.assign(result, attr.name);
3356
+ result = new HTMLBindingDirective(oneTime(() => attrValue, context.policy));
3357
+ HTMLDirective.assignAspect(result, attr.name);
2519
3358
  }
2520
3359
  }
2521
3360
  else {
2522
3361
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2523
- result = Compiler.aggregate(parseResult);
3362
+ result = Compiler.aggregate(parseResult, context.policy);
2524
3363
  }
2525
3364
  if (result !== null) {
2526
3365
  node.removeAttributeNode(attr);
2527
3366
  i--;
2528
3367
  ii--;
2529
- context.addFactory(result, parentId, nodeId, nodeIndex);
3368
+ context.addFactory(result, parentId, nodeId, nodeIndex, node.tagName);
2530
3369
  }
2531
3370
  }
2532
3371
  }
@@ -2551,8 +3390,8 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
2551
3390
  }
2552
3391
  else {
2553
3392
  currentNode.textContent = " ";
2554
- Aspect.assign(currentPart);
2555
- context.addFactory(currentPart, parentId, nodeId, nodeIndex);
3393
+ HTMLDirective.assignAspect(currentPart);
3394
+ context.addFactory(currentPart, parentId, nodeId, nodeIndex, null);
2556
3395
  }
2557
3396
  lastNode = currentNode;
2558
3397
  }
@@ -2584,7 +3423,7 @@ function compileNode(context, parentId, node, nodeIndex) {
2584
3423
  if (parts !== null) {
2585
3424
  context.addFactory(
2586
3425
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2587
- Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
3426
+ Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
2588
3427
  }
2589
3428
  break;
2590
3429
  }
@@ -2598,45 +3437,28 @@ function isMarker(node, directives) {
2598
3437
  Parser.parse(node.data, directives) !== null);
2599
3438
  }
2600
3439
  const templateTag = "TEMPLATE";
2601
- const policyOptions = { createHTML: html => html };
2602
- let htmlPolicy = globalThis.trustedTypes
2603
- ? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
2604
- : policyOptions;
2605
- const fastHTMLPolicy = htmlPolicy;
2606
3440
  /**
2607
3441
  * Common APIs related to compilation.
2608
3442
  * @public
2609
3443
  */
2610
3444
  const Compiler = {
2611
- /**
2612
- * Sets the HTML trusted types policy used by the compiler.
2613
- * @param policy - The policy to set for HTML.
2614
- * @remarks
2615
- * This API can only be called once, for security reasons. It should be
2616
- * called by the application developer at the start of their program.
2617
- */
2618
- setHTMLPolicy(policy) {
2619
- if (htmlPolicy !== fastHTMLPolicy) {
2620
- throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
2621
- }
2622
- htmlPolicy = policy;
2623
- },
2624
3445
  /**
2625
3446
  * Compiles a template and associated directives into a compilation
2626
3447
  * result which can be used to create views.
2627
3448
  * @param html - The html string or template element to compile.
2628
- * @param directives - The directives referenced by the template.
3449
+ * @param factories - The behavior factories referenced by the template.
3450
+ * @param policy - The security policy to compile the html with.
2629
3451
  * @remarks
2630
3452
  * The template that is provided for compilation is altered in-place
2631
3453
  * and cannot be compiled again. If the original template must be preserved,
2632
3454
  * it is recommended that you clone the original and pass the clone to this API.
2633
3455
  * @public
2634
3456
  */
2635
- compile(html, directives) {
3457
+ compile(html, factories, policy = DOM.policy) {
2636
3458
  let template;
2637
3459
  if (isString(html)) {
2638
3460
  template = document.createElement(templateTag);
2639
- template.innerHTML = htmlPolicy.createHTML(html);
3461
+ template.innerHTML = policy.createHTML(html);
2640
3462
  const fec = template.content.firstElementChild;
2641
3463
  if (fec !== null && fec.tagName === templateTag) {
2642
3464
  template = fec;
@@ -2645,20 +3467,23 @@ const Compiler = {
2645
3467
  else {
2646
3468
  template = html;
2647
3469
  }
3470
+ if (!template.content.firstChild && !template.content.lastChild) {
3471
+ template.content.appendChild(document.createComment(""));
3472
+ }
2648
3473
  // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
2649
3474
  const fragment = document.adoptNode(template.content);
2650
- const context = new CompilationContext(fragment, directives);
3475
+ const context = new CompilationContext(fragment, factories, policy);
2651
3476
  compileAttributes(context, "", template, /* host */ "h", 0, true);
2652
3477
  if (
2653
3478
  // If the first node in a fragment is a marker, that means it's an unstable first node,
2654
3479
  // because something like a when, repeat, etc. could add nodes before the marker.
2655
3480
  // To mitigate this, we insert a stable first node. However, if we insert a node,
2656
3481
  // that will alter the result of the TreeWalker. So, we also need to offset the target index.
2657
- isMarker(fragment.firstChild, directives) ||
3482
+ isMarker(fragment.firstChild, factories) ||
2658
3483
  // Or if there is only one node and a directive, it means the template's content
2659
3484
  // is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
2660
3485
  // the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
2661
- (fragment.childNodes.length === 1 && Object.keys(directives).length > 0)) {
3486
+ (fragment.childNodes.length === 1 && Object.keys(factories).length > 0)) {
2662
3487
  fragment.insertBefore(document.createComment(""), fragment.firstChild);
2663
3488
  }
2664
3489
  compileChildren(context, fragment, /* root */ "r");
@@ -2677,23 +3502,24 @@ const Compiler = {
2677
3502
  * Aggregates an array of strings and directives into a single directive.
2678
3503
  * @param parts - A heterogeneous array of static strings interspersed with
2679
3504
  * directives.
3505
+ * @param policy - The security policy to use with the aggregated bindings.
2680
3506
  * @returns A single inline directive that aggregates the behavior of all the parts.
2681
3507
  */
2682
- aggregate(parts) {
3508
+ aggregate(parts, policy = DOM.policy) {
2683
3509
  if (parts.length === 1) {
2684
3510
  return parts[0];
2685
3511
  }
2686
3512
  let sourceAspect;
2687
- let binding;
2688
3513
  let isVolatile = false;
3514
+ let bindingPolicy = void 0;
2689
3515
  const partCount = parts.length;
2690
3516
  const finalParts = parts.map((x) => {
2691
3517
  if (isString(x)) {
2692
3518
  return () => x;
2693
3519
  }
2694
3520
  sourceAspect = x.sourceAspect || sourceAspect;
2695
- binding = x.dataBinding || binding;
2696
3521
  isVolatile = isVolatile || x.dataBinding.isVolatile;
3522
+ bindingPolicy = bindingPolicy || x.dataBinding.policy;
2697
3523
  return x.dataBinding.evaluate;
2698
3524
  });
2699
3525
  const expression = (scope, context) => {
@@ -2703,14 +3529,56 @@ const Compiler = {
2703
3529
  }
2704
3530
  return output;
2705
3531
  };
2706
- binding.evaluate = expression;
2707
- binding.isVolatile = isVolatile;
2708
- const directive = new HTMLBindingDirective(binding);
2709
- Aspect.assign(directive, sourceAspect);
3532
+ const directive = new HTMLBindingDirective(oneWay(expression, bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy, isVolatile));
3533
+ HTMLDirective.assignAspect(directive, sourceAspect);
2710
3534
  return directive;
2711
3535
  },
2712
3536
  };
2713
3537
 
3538
+ // Much thanks to LitHTML for working this out!
3539
+ const lastAttributeNameRegex =
3540
+ /* eslint-disable-next-line no-control-regex, max-len */
3541
+ /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
3542
+ const noFactories = Object.create(null);
3543
+ /**
3544
+ * Inlines a template into another template.
3545
+ * @public
3546
+ */
3547
+ class InlineTemplateDirective {
3548
+ /**
3549
+ * Creates an instance of InlineTemplateDirective.
3550
+ * @param template - The template to inline.
3551
+ */
3552
+ constructor(html, factories = noFactories) {
3553
+ this.html = html;
3554
+ this.factories = factories;
3555
+ }
3556
+ /**
3557
+ * Creates HTML to be used within a template.
3558
+ * @param add - Can be used to add behavior factories to a template.
3559
+ */
3560
+ createHTML(add) {
3561
+ const factories = this.factories;
3562
+ for (const key in factories) {
3563
+ add(factories[key]);
3564
+ }
3565
+ return this.html;
3566
+ }
3567
+ }
3568
+ /**
3569
+ * An empty template partial.
3570
+ */
3571
+ InlineTemplateDirective.empty = new InlineTemplateDirective("");
3572
+ HTMLDirective.define(InlineTemplateDirective);
3573
+ function createHTML(value, prevString, add, definition = HTMLDirective.getForInstance(value)) {
3574
+ if (definition.aspected) {
3575
+ const match = lastAttributeNameRegex.exec(prevString);
3576
+ if (match !== null) {
3577
+ HTMLDirective.assignAspect(value, match[2]);
3578
+ }
3579
+ }
3580
+ return value.createHTML(add);
3581
+ }
2714
3582
  /**
2715
3583
  * A template capable of creating HTMLView instances or rendering directly to DOM.
2716
3584
  * @public
@@ -2720,21 +3588,53 @@ class ViewTemplate {
2720
3588
  * Creates an instance of ViewTemplate.
2721
3589
  * @param html - The html representing what this template will instantiate, including placeholders for directives.
2722
3590
  * @param factories - The directives that will be connected to placeholders in the html.
3591
+ * @param policy - The security policy to use when compiling this template.
2723
3592
  */
2724
- constructor(html, factories) {
3593
+ constructor(html, factories = {}, policy) {
3594
+ this.policy = policy;
2725
3595
  this.result = null;
2726
3596
  this.html = html;
2727
3597
  this.factories = factories;
2728
3598
  }
3599
+ /**
3600
+ * @internal
3601
+ */
3602
+ compile() {
3603
+ if (this.result === null) {
3604
+ this.result = Compiler.compile(this.html, this.factories, this.policy);
3605
+ }
3606
+ return this.result;
3607
+ }
2729
3608
  /**
2730
3609
  * Creates an HTMLView instance based on this template definition.
2731
3610
  * @param hostBindingTarget - The element that host behaviors will be bound to.
2732
3611
  */
2733
3612
  create(hostBindingTarget) {
2734
- if (this.result === null) {
2735
- this.result = Compiler.compile(this.html, this.factories);
3613
+ return this.compile().createView(hostBindingTarget);
3614
+ }
3615
+ /**
3616
+ * Returns a directive that can inline the template.
3617
+ */
3618
+ inline() {
3619
+ return new InlineTemplateDirective(isString(this.html) ? this.html : this.html.innerHTML, this.factories);
3620
+ }
3621
+ /**
3622
+ * Sets the DOMPolicy for this template.
3623
+ * @param policy - The policy to associated with this template.
3624
+ * @returns The modified template instance.
3625
+ * @remarks
3626
+ * The DOMPolicy can only be set once for a template and cannot be
3627
+ * set after the template is compiled.
3628
+ */
3629
+ withPolicy(policy) {
3630
+ if (this.result) {
3631
+ throw FAST.error(1208 /* Message.cannotSetTemplatePolicyAfterCompilation */);
2736
3632
  }
2737
- return this.result.createView(hostBindingTarget);
3633
+ if (this.policy) {
3634
+ throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
3635
+ }
3636
+ this.policy = policy;
3637
+ return this;
2738
3638
  }
2739
3639
  /**
2740
3640
  * Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
@@ -2749,18 +3649,49 @@ class ViewTemplate {
2749
3649
  view.appendTo(host);
2750
3650
  return view;
2751
3651
  }
2752
- }
2753
- // Much thanks to LitHTML for working this out!
2754
- const lastAttributeNameRegex =
2755
- /* eslint-disable-next-line no-control-regex */
2756
- /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
2757
- function createAspectedHTML(value, prevString, add) {
2758
- const match = lastAttributeNameRegex.exec(prevString);
2759
- if (match !== null) {
2760
- Aspect.assign(value, match[2]);
3652
+ /**
3653
+ * Creates a template based on a set of static strings and dynamic values.
3654
+ * @param strings - The static strings to create the template with.
3655
+ * @param values - The dynamic values to create the template with.
3656
+ * @param policy - The DOMPolicy to associated with the template.
3657
+ * @returns A ViewTemplate.
3658
+ * @remarks
3659
+ * This API should not be used directly under normal circumstances because constructing
3660
+ * a template in this way, if not done properly, can open up the application to XSS
3661
+ * attacks. When using this API, provide a strong DOMPolicy that can properly sanitize
3662
+ * and also be sure to manually sanitize all static strings particularly if they can
3663
+ * come from user input.
3664
+ */
3665
+ static create(strings, values, policy) {
3666
+ let html = "";
3667
+ const factories = Object.create(null);
3668
+ const add = (factory) => {
3669
+ var _a;
3670
+ const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
3671
+ factories[id] = factory;
3672
+ return id;
3673
+ };
3674
+ for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
3675
+ const currentString = strings[i];
3676
+ let currentValue = values[i];
3677
+ let definition;
3678
+ html += currentString;
3679
+ if (isFunction(currentValue)) {
3680
+ currentValue = new HTMLBindingDirective(oneWay(currentValue));
3681
+ }
3682
+ else if (currentValue instanceof Binding) {
3683
+ currentValue = new HTMLBindingDirective(currentValue);
3684
+ }
3685
+ else if (!(definition = HTMLDirective.getForInstance(currentValue))) {
3686
+ const staticValue = currentValue;
3687
+ currentValue = new HTMLBindingDirective(oneTime(() => staticValue));
3688
+ }
3689
+ html += createHTML(currentValue, currentString, add, definition);
3690
+ }
3691
+ return new ViewTemplate(html + strings[strings.length - 1], factories, policy);
2761
3692
  }
2762
- return value.createHTML(add);
2763
3693
  }
3694
+ makeSerializationNoop(ViewTemplate);
2764
3695
  /**
2765
3696
  * Transforms a template literal string into a ViewTemplate.
2766
3697
  * @param strings - The string fragments that are interpolated with the values.
@@ -2770,51 +3701,15 @@ function createAspectedHTML(value, prevString, add) {
2770
3701
  * other template instances, and Directive instances.
2771
3702
  * @public
2772
3703
  */
2773
- function html(strings, ...values) {
2774
- let html = "";
2775
- const factories = Object.create(null);
2776
- const add = (factory) => {
2777
- var _a;
2778
- const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
2779
- factories[id] = factory;
2780
- return id;
2781
- };
2782
- for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
2783
- const currentString = strings[i];
2784
- const currentValue = values[i];
2785
- let definition;
2786
- html += currentString;
2787
- if (isFunction(currentValue)) {
2788
- html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
2789
- }
2790
- else if (isString(currentValue)) {
2791
- const match = lastAttributeNameRegex.exec(currentString);
2792
- if (match !== null) {
2793
- const directive = new HTMLBindingDirective(oneTime(() => currentValue));
2794
- Aspect.assign(directive, match[2]);
2795
- html += directive.createHTML(add);
2796
- }
2797
- else {
2798
- html += currentValue;
2799
- }
2800
- }
2801
- else if (currentValue instanceof Binding) {
2802
- html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
2803
- }
2804
- else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
2805
- html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
2806
- }
2807
- else {
2808
- if (definition.aspected) {
2809
- html += createAspectedHTML(currentValue, currentString, add);
2810
- }
2811
- else {
2812
- html += currentValue.createHTML(add);
2813
- }
2814
- }
3704
+ const html = ((strings, ...values) => {
3705
+ if (Array.isArray(strings) && Array.isArray(strings.raw)) {
3706
+ return ViewTemplate.create(strings, values);
2815
3707
  }
2816
- return new ViewTemplate(html + strings[strings.length - 1], factories);
2817
- }
3708
+ throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
3709
+ });
3710
+ html.partial = (html) => {
3711
+ return new InlineTemplateDirective(html);
3712
+ };
2818
3713
 
2819
3714
  /**
2820
3715
  * The runtime behavior for template references.
@@ -2826,7 +3721,7 @@ class RefDirective extends StatelessAttachedAttributeDirective {
2826
3721
  * @param controller - The view controller that manages the lifecycle of this behavior.
2827
3722
  */
2828
3723
  bind(controller) {
2829
- controller.source[this.options] = controller.targets[this.nodeId];
3724
+ controller.source[this.options] = controller.targets[this.targetNodeId];
2830
3725
  }
2831
3726
  }
2832
3727
  HTMLDirective.define(RefDirective);
@@ -2837,19 +3732,26 @@ HTMLDirective.define(RefDirective);
2837
3732
  */
2838
3733
  const ref = (propertyName) => new RefDirective(propertyName);
2839
3734
 
3735
+ const noTemplate = () => null;
3736
+ function normalizeBinding(value) {
3737
+ return value === undefined ? noTemplate : isFunction(value) ? value : () => value;
3738
+ }
2840
3739
  /**
2841
3740
  * A directive that enables basic conditional rendering in a template.
2842
3741
  * @param condition - The condition to test for rendering.
2843
3742
  * @param templateOrTemplateBinding - The template or a binding that gets
2844
3743
  * the template to render when the condition is true.
3744
+ * @param elseTemplateOrTemplateBinding - Optional template or binding that that
3745
+ * gets the template to render when the conditional is false.
2845
3746
  * @public
2846
3747
  */
2847
- function when(condition, templateOrTemplateBinding) {
3748
+ function when(condition, templateOrTemplateBinding, elseTemplateOrTemplateBinding) {
2848
3749
  const dataBinding = isFunction(condition) ? condition : () => condition;
2849
- const templateBinding = isFunction(templateOrTemplateBinding)
2850
- ? templateOrTemplateBinding
2851
- : () => templateOrTemplateBinding;
2852
- return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
3750
+ const templateBinding = normalizeBinding(templateOrTemplateBinding);
3751
+ const elseBinding = normalizeBinding(elseTemplateOrTemplateBinding);
3752
+ return (source, context) => dataBinding(source, context)
3753
+ ? templateBinding(source, context)
3754
+ : elseBinding(source, context);
2853
3755
  }
2854
3756
 
2855
3757
  const defaultRepeatOptions = Object.freeze({
@@ -2868,6 +3770,19 @@ function bindWithPositioning(view, items, index, controller) {
2868
3770
  view.context.index = index;
2869
3771
  view.bind(items[index]);
2870
3772
  }
3773
+ function isCommentNode(node) {
3774
+ return node.nodeType === Node.COMMENT_NODE;
3775
+ }
3776
+ class HydrationRepeatError extends Error {
3777
+ constructor(
3778
+ /**
3779
+ * The error message
3780
+ */
3781
+ message, propertyBag) {
3782
+ super(message);
3783
+ this.propertyBag = propertyBag;
3784
+ }
3785
+ }
2871
3786
  /**
2872
3787
  * A behavior that renders a template for each item in an array.
2873
3788
  * @public
@@ -2884,12 +3799,13 @@ class RepeatBehavior {
2884
3799
  */
2885
3800
  constructor(directive) {
2886
3801
  this.directive = directive;
2887
- this.views = [];
2888
3802
  this.items = null;
2889
3803
  this.itemsObserver = null;
2890
3804
  this.bindView = bindWithoutPositioning;
2891
- this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
2892
- this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
3805
+ /** @internal */
3806
+ this.views = [];
3807
+ this.itemsBindingObserver = directive.dataBinding.createObserver(this, directive);
3808
+ this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
2893
3809
  if (directive.options.positioning) {
2894
3810
  this.bindView = bindWithPositioning;
2895
3811
  }
@@ -2899,12 +3815,19 @@ class RepeatBehavior {
2899
3815
  * @param controller - The view controller that manages the lifecycle of this behavior.
2900
3816
  */
2901
3817
  bind(controller) {
2902
- this.location = controller.targets[this.directive.nodeId];
3818
+ this.location = controller.targets[this.directive.targetNodeId];
2903
3819
  this.controller = controller;
2904
3820
  this.items = this.itemsBindingObserver.bind(controller);
2905
3821
  this.template = this.templateBindingObserver.bind(controller);
2906
3822
  this.observeItems(true);
2907
- this.refreshAllViews();
3823
+ if (isHydratable(this.template) &&
3824
+ isHydratable(controller) &&
3825
+ controller.hydrationStage !== HydrationStage.hydrated) {
3826
+ this.hydrateViews(this.template);
3827
+ }
3828
+ else {
3829
+ this.refreshAllViews();
3830
+ }
2908
3831
  controller.onUnbind(this);
2909
3832
  }
2910
3833
  /**
@@ -3005,10 +3928,10 @@ class RepeatBehavior {
3005
3928
  leftoverViews[i].dispose();
3006
3929
  }
3007
3930
  if (this.directive.options.positioning) {
3008
- for (let i = 0, ii = views.length; i < ii; ++i) {
3931
+ for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
3009
3932
  const context = views[i].context;
3010
- context.length = i;
3011
- context.index = ii;
3933
+ context.length = viewsLength;
3934
+ context.index = i;
3012
3935
  }
3013
3936
  }
3014
3937
  }
@@ -3042,6 +3965,18 @@ class RepeatBehavior {
3042
3965
  for (; i < itemsLength; ++i) {
3043
3966
  if (i < viewsLength) {
3044
3967
  const view = views[i];
3968
+ if (!view) {
3969
+ const serializer = new XMLSerializer();
3970
+ throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
3971
+ index: i,
3972
+ hydrationStage: this.controller
3973
+ .hydrationStage,
3974
+ itemsLength,
3975
+ viewsState: views.map(v => (v ? "hydrated" : "empty")),
3976
+ viewTemplateString: serializer.serializeToString(template.create().fragment),
3977
+ rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
3978
+ });
3979
+ }
3045
3980
  bindView(view, items, i, controller);
3046
3981
  }
3047
3982
  else {
@@ -3060,7 +3995,76 @@ class RepeatBehavior {
3060
3995
  unbindAllViews() {
3061
3996
  const views = this.views;
3062
3997
  for (let i = 0, ii = views.length; i < ii; ++i) {
3063
- views[i].unbind();
3998
+ const view = views[i];
3999
+ if (!view) {
4000
+ const serializer = new XMLSerializer();
4001
+ throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
4002
+ index: i,
4003
+ hydrationStage: this.controller.hydrationStage,
4004
+ viewsState: views.map(v => (v ? "hydrated" : "empty")),
4005
+ rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
4006
+ });
4007
+ }
4008
+ view.unbind();
4009
+ }
4010
+ }
4011
+ hydrateViews(template) {
4012
+ if (!this.items) {
4013
+ return;
4014
+ }
4015
+ this.views = new Array(this.items.length);
4016
+ let current = this.location.previousSibling;
4017
+ while (current !== null) {
4018
+ if (!isCommentNode(current)) {
4019
+ current = current.previousSibling;
4020
+ continue;
4021
+ }
4022
+ const index = HydrationMarkup.parseRepeatEndMarker(current.data);
4023
+ if (index === null) {
4024
+ current = current.previousSibling;
4025
+ continue;
4026
+ }
4027
+ current.data = "";
4028
+ // end of repeat is the previousSibling of end comment
4029
+ const end = current.previousSibling;
4030
+ if (!end) {
4031
+ throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": end should never be null.`);
4032
+ }
4033
+ // find start marker
4034
+ let start = end;
4035
+ // How many unmatched end markers we've encountered
4036
+ let unmatchedEndMarkers = 0;
4037
+ while (start !== null) {
4038
+ if (isCommentNode(start)) {
4039
+ if (HydrationMarkup.isRepeatViewEndMarker(start.data)) {
4040
+ unmatchedEndMarkers++;
4041
+ }
4042
+ else if (HydrationMarkup.isRepeatViewStartMarker(start.data)) {
4043
+ if (unmatchedEndMarkers) {
4044
+ unmatchedEndMarkers--;
4045
+ }
4046
+ else {
4047
+ if (HydrationMarkup.parseRepeatStartMarker(start.data) !==
4048
+ index) {
4049
+ throw new Error(`Error when hydrating inside "${this.location.getRootNode().host
4050
+ .nodeName}": Mismatched start and end markers.`);
4051
+ }
4052
+ start.data = "";
4053
+ current = start.previousSibling;
4054
+ // start of repeat content is the nextSibling of start comment
4055
+ start = start.nextSibling;
4056
+ const view = template.hydrate(start, end);
4057
+ this.views[index] = view;
4058
+ this.bindView(view, this.items, index, this.controller);
4059
+ break;
4060
+ }
4061
+ }
4062
+ }
4063
+ start = start.previousSibling;
4064
+ }
4065
+ if (!start) {
4066
+ throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": start should never be null.`);
4067
+ }
3064
4068
  }
3065
4069
  }
3066
4070
  }
@@ -3079,10 +4083,6 @@ class RepeatDirective {
3079
4083
  this.dataBinding = dataBinding;
3080
4084
  this.templateBinding = templateBinding;
3081
4085
  this.options = options;
3082
- /**
3083
- * The unique id of the factory.
3084
- */
3085
- this.id = nextId();
3086
4086
  ArrayObserver.enable();
3087
4087
  }
3088
4088
  /**
@@ -3110,8 +4110,8 @@ HTMLDirective.define(RepeatDirective);
3110
4110
  * @public
3111
4111
  */
3112
4112
  function repeat(items, template, options = defaultRepeatOptions) {
3113
- const dataBinding = normalizeBinding(items);
3114
- const templateBinding = normalizeBinding(template);
4113
+ const dataBinding = normalizeBinding$1(items);
4114
+ const templateBinding = normalizeBinding$1(template);
3115
4115
  return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
3116
4116
  }
3117
4117
 
@@ -3131,9 +4131,15 @@ const elements = (selector) => selector
3131
4131
  * Internally used by the SlottedDirective and the ChildrenDirective.
3132
4132
  */
3133
4133
  class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3134
- constructor() {
3135
- super(...arguments);
3136
- this.sourceProperty = `${this.id}-s`;
4134
+ /**
4135
+ * The unique id of the factory.
4136
+ */
4137
+ get id() {
4138
+ return this._id;
4139
+ }
4140
+ set id(value) {
4141
+ this._id = value;
4142
+ this._controllerProperty = `${value}-c`;
3137
4143
  }
3138
4144
  /**
3139
4145
  * Bind this behavior to the source.
@@ -3142,8 +4148,8 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3142
4148
  * @param targets - The targets that behaviors in a view can attach to.
3143
4149
  */
3144
4150
  bind(controller) {
3145
- const target = controller.targets[this.nodeId];
3146
- target[this.sourceProperty] = controller.source;
4151
+ const target = controller.targets[this.targetNodeId];
4152
+ target[this._controllerProperty] = controller;
3147
4153
  this.updateTarget(controller.source, this.computeNodes(target));
3148
4154
  this.observe(target);
3149
4155
  controller.onUnbind(this);
@@ -3155,10 +4161,10 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3155
4161
  * @param targets - The targets that behaviors in a view can attach to.
3156
4162
  */
3157
4163
  unbind(controller) {
3158
- const target = controller.targets[this.nodeId];
4164
+ const target = controller.targets[this.targetNodeId];
3159
4165
  this.updateTarget(controller.source, emptyArray);
3160
4166
  this.disconnect(target);
3161
- target[this.sourceProperty] = null;
4167
+ target[this._controllerProperty] = null;
3162
4168
  }
3163
4169
  /**
3164
4170
  * Gets the data source for the target.
@@ -3166,7 +4172,7 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3166
4172
  * @returns The source.
3167
4173
  */
3168
4174
  getSource(target) {
3169
- return target[this.sourceProperty];
4175
+ return target[this._controllerProperty].source;
3170
4176
  }
3171
4177
  /**
3172
4178
  * Updates the source property with the computed nodes.
@@ -3250,7 +4256,7 @@ class ChildrenDirective extends NodeObservationDirective {
3250
4256
  */
3251
4257
  constructor(options) {
3252
4258
  super(options);
3253
- this.observerProperty = `${this.id}-o`;
4259
+ this.observerProperty = Symbol();
3254
4260
  this.handleEvent = (mutations, observer) => {
3255
4261
  const target = observer.target;
3256
4262
  this.updateTarget(this.getSource(target), this.computeNodes(target));
@@ -3262,8 +4268,12 @@ class ChildrenDirective extends NodeObservationDirective {
3262
4268
  * @param target - The target to observe.
3263
4269
  */
3264
4270
  observe(target) {
3265
- var _a;
3266
- const observer = (_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = new MutationObserver(this.handleEvent));
4271
+ let observer = target[this.observerProperty];
4272
+ if (!observer) {
4273
+ observer = new MutationObserver(this.handleEvent);
4274
+ observer.toJSON = noop;
4275
+ target[this.observerProperty] = observer;
4276
+ }
3267
4277
  observer.target = target;
3268
4278
  observer.observe(target, this.options);
3269
4279
  }
@@ -3335,6 +4345,21 @@ const booleanConverter = {
3335
4345
  : true;
3336
4346
  },
3337
4347
  };
4348
+ /**
4349
+ * A {@link ValueConverter} that converts to and from `boolean` values. `null`, `undefined`, `""`,
4350
+ * and `void` values are converted to `null`.
4351
+ * @public
4352
+ */
4353
+ const nullableBooleanConverter = {
4354
+ toView(value) {
4355
+ return typeof value === "boolean" ? value.toString() : "";
4356
+ },
4357
+ fromView(value) {
4358
+ return [null, undefined, void 0].includes(value)
4359
+ ? null
4360
+ : booleanConverter.fromView(value);
4361
+ },
4362
+ };
3338
4363
  function toNumber(value) {
3339
4364
  if (value === null || value === undefined) {
3340
4365
  return null;
@@ -3500,7 +4525,8 @@ function attr(configOrTarget, prop) {
3500
4525
 
3501
4526
  const defaultShadowOptions = { mode: "open" };
3502
4527
  const defaultElementOptions = {};
3503
- const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */, () => createTypeRegistry());
4528
+ const fastElementBaseTypes = new Set();
4529
+ const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
3504
4530
  /**
3505
4531
  * Defines metadata for a FASTElement.
3506
4532
  * @public
@@ -3548,51 +4574,564 @@ class FASTElementDefinition {
3548
4574
  this.styles = ElementStyles.normalize(nameOrConfig.styles);
3549
4575
  fastElementRegistry.register(this);
3550
4576
  }
3551
- /**
3552
- * Indicates if this element has been defined in at least one registry.
3553
- */
3554
- get isDefined() {
3555
- return this.platformDefined;
4577
+ /**
4578
+ * Indicates if this element has been defined in at least one registry.
4579
+ */
4580
+ get isDefined() {
4581
+ return this.platformDefined;
4582
+ }
4583
+ /**
4584
+ * Defines a custom element based on this definition.
4585
+ * @param registry - The element registry to define the element in.
4586
+ * @remarks
4587
+ * This operation is idempotent per registry.
4588
+ */
4589
+ define(registry = this.registry) {
4590
+ const type = this.type;
4591
+ if (!registry.get(this.name)) {
4592
+ this.platformDefined = true;
4593
+ registry.define(this.name, type, this.elementOptions);
4594
+ }
4595
+ return this;
4596
+ }
4597
+ /**
4598
+ * Creates an instance of FASTElementDefinition.
4599
+ * @param type - The type this definition is being created for.
4600
+ * @param nameOrDef - The name of the element to define or a config object
4601
+ * that describes the element to define.
4602
+ */
4603
+ static compose(type, nameOrDef) {
4604
+ if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
4605
+ return new FASTElementDefinition(class extends type {
4606
+ }, nameOrDef);
4607
+ }
4608
+ return new FASTElementDefinition(type, nameOrDef);
4609
+ }
4610
+ /**
4611
+ * Registers a FASTElement base type.
4612
+ * @param type - The type to register as a base type.
4613
+ * @internal
4614
+ */
4615
+ static registerBaseType(type) {
4616
+ fastElementBaseTypes.add(type);
4617
+ }
4618
+ }
4619
+ /**
4620
+ * Gets the element definition associated with the specified type.
4621
+ * @param type - The custom element type to retrieve the definition for.
4622
+ */
4623
+ FASTElementDefinition.getByType = fastElementRegistry.getByType;
4624
+ /**
4625
+ * Gets the element definition associated with the instance.
4626
+ * @param instance - The custom element instance to retrieve the definition for.
4627
+ */
4628
+ FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
4629
+
4630
+ /**
4631
+ * A Behavior that enables advanced rendering.
4632
+ * @public
4633
+ */
4634
+ class RenderBehavior {
4635
+ /**
4636
+ * Creates an instance of RenderBehavior.
4637
+ * @param directive - The render directive that created this behavior.
4638
+ */
4639
+ constructor(directive) {
4640
+ this.directive = directive;
4641
+ this.location = null;
4642
+ this.controller = null;
4643
+ this.view = null;
4644
+ this.data = null;
4645
+ this.dataBindingObserver = directive.dataBinding.createObserver(this, directive);
4646
+ this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
4647
+ }
4648
+ /**
4649
+ * Bind this behavior.
4650
+ * @param controller - The view controller that manages the lifecycle of this behavior.
4651
+ */
4652
+ bind(controller) {
4653
+ this.location = controller.targets[this.directive.targetNodeId];
4654
+ this.controller = controller;
4655
+ this.data = this.dataBindingObserver.bind(controller);
4656
+ this.template = this.templateBindingObserver.bind(controller);
4657
+ controller.onUnbind(this);
4658
+ if (isHydratable(this.template) &&
4659
+ isHydratable(controller) &&
4660
+ controller.hydrationStage !== HydrationStage.hydrated &&
4661
+ !this.view) {
4662
+ const viewNodes = controller.bindingViewBoundaries[this.directive.targetNodeId];
4663
+ if (viewNodes) {
4664
+ this.view = this.template.hydrate(viewNodes.first, viewNodes.last);
4665
+ this.bindView(this.view);
4666
+ }
4667
+ }
4668
+ else {
4669
+ this.refreshView();
4670
+ }
4671
+ }
4672
+ /**
4673
+ * Unbinds this behavior.
4674
+ * @param controller - The view controller that manages the lifecycle of this behavior.
4675
+ */
4676
+ unbind(controller) {
4677
+ const view = this.view;
4678
+ if (view !== null && view.isComposed) {
4679
+ view.unbind();
4680
+ view.needsBindOnly = true;
4681
+ }
4682
+ }
4683
+ /** @internal */
4684
+ handleChange(source, observer) {
4685
+ if (observer === this.dataBindingObserver) {
4686
+ this.data = this.dataBindingObserver.bind(this.controller);
4687
+ }
4688
+ if (this.directive.templateBindingDependsOnData ||
4689
+ observer === this.templateBindingObserver) {
4690
+ this.template = this.templateBindingObserver.bind(this.controller);
4691
+ }
4692
+ this.refreshView();
4693
+ }
4694
+ bindView(view) {
4695
+ // It's possible that the value is the same as the previous template
4696
+ // and that there's actually no need to compose it.
4697
+ if (!view.isComposed) {
4698
+ view.isComposed = true;
4699
+ view.bind(this.data);
4700
+ view.insertBefore(this.location);
4701
+ view.$fastTemplate = this.template;
4702
+ }
4703
+ else if (view.needsBindOnly) {
4704
+ view.needsBindOnly = false;
4705
+ view.bind(this.data);
4706
+ }
4707
+ }
4708
+ refreshView() {
4709
+ let view = this.view;
4710
+ const template = this.template;
4711
+ if (view === null) {
4712
+ this.view = view = template.create();
4713
+ this.view.context.parent = this.controller.source;
4714
+ this.view.context.parentContext = this.controller.context;
4715
+ }
4716
+ else {
4717
+ // If there is a previous view, but it wasn't created
4718
+ // from the same template as the new value, then we
4719
+ // need to remove the old view if it's still in the DOM
4720
+ // and create a new view from the template.
4721
+ if (view.$fastTemplate !== template) {
4722
+ if (view.isComposed) {
4723
+ view.remove();
4724
+ view.unbind();
4725
+ }
4726
+ this.view = view = template.create();
4727
+ this.view.context.parent = this.controller.source;
4728
+ this.view.context.parentContext = this.controller.context;
4729
+ }
4730
+ }
4731
+ this.bindView(view);
4732
+ }
4733
+ }
4734
+ /**
4735
+ * A Directive that enables use of the RenderBehavior.
4736
+ * @public
4737
+ */
4738
+ class RenderDirective {
4739
+ /**
4740
+ * Creates an instance of RenderDirective.
4741
+ * @param dataBinding - A binding expression that returns the data to render.
4742
+ * @param templateBinding - A binding expression that returns the template to use to render the data.
4743
+ */
4744
+ constructor(dataBinding, templateBinding, templateBindingDependsOnData) {
4745
+ this.dataBinding = dataBinding;
4746
+ this.templateBinding = templateBinding;
4747
+ this.templateBindingDependsOnData = templateBindingDependsOnData;
4748
+ }
4749
+ /**
4750
+ * Creates HTML to be used within a template.
4751
+ * @param add - Can be used to add behavior factories to a template.
4752
+ */
4753
+ createHTML(add) {
4754
+ return Markup.comment(add(this));
4755
+ }
4756
+ /**
4757
+ * Creates a behavior.
4758
+ * @param targets - The targets available for behaviors to be attached to.
4759
+ */
4760
+ createBehavior() {
4761
+ return new RenderBehavior(this);
4762
+ }
4763
+ }
4764
+ HTMLDirective.define(RenderDirective);
4765
+ function isElementRenderOptions(object) {
4766
+ return !!object.element || !!object.tagName;
4767
+ }
4768
+ const typeToInstructionLookup = new Map();
4769
+ /* eslint @typescript-eslint/naming-convention: "off"*/
4770
+ const defaultAttributes = { ":model": (x) => x };
4771
+ const brand = Symbol("RenderInstruction");
4772
+ const defaultViewName = "default-view";
4773
+ const nullTemplate = html `
4774
+ &nbsp;
4775
+ `;
4776
+ function instructionToTemplate(def) {
4777
+ if (def === void 0) {
4778
+ return nullTemplate;
4779
+ }
4780
+ return def.template;
4781
+ }
4782
+ function createElementTemplate(tagName, options) {
4783
+ const markup = [];
4784
+ const values = [];
4785
+ const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
4786
+ markup.push(`<${tagName}`);
4787
+ if (attributes) {
4788
+ const attrNames = Object.getOwnPropertyNames(attributes);
4789
+ for (let i = 0, ii = attrNames.length; i < ii; ++i) {
4790
+ const name = attrNames[i];
4791
+ if (i === 0) {
4792
+ markup[0] = `${markup[0]} ${name}="`;
4793
+ }
4794
+ else {
4795
+ markup.push(`" ${name}="`);
4796
+ }
4797
+ values.push(attributes[name]);
4798
+ }
4799
+ markup.push(`"`);
4800
+ }
4801
+ if (directives) {
4802
+ markup[markup.length - 1] += " ";
4803
+ for (let i = 0, ii = directives.length; i < ii; ++i) {
4804
+ const directive = directives[i];
4805
+ markup.push(i > 0 ? "" : " ");
4806
+ values.push(directive);
4807
+ }
4808
+ }
4809
+ markup[markup.length - 1] += ">";
4810
+ if (content && isFunction(content.create)) {
4811
+ values.push(content);
4812
+ markup.push(`</${tagName}>`);
4813
+ }
4814
+ else {
4815
+ const lastIndex = markup.length - 1;
4816
+ markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
4817
+ }
4818
+ return ViewTemplate.create(markup, values, policy);
4819
+ }
4820
+ function create(options) {
4821
+ var _a;
4822
+ const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
4823
+ let template;
4824
+ if (isElementRenderOptions(options)) {
4825
+ let tagName = options.tagName;
4826
+ if (!tagName) {
4827
+ const def = FASTElementDefinition.getByType(options.element);
4828
+ if (def) {
4829
+ tagName = def.name;
4830
+ }
4831
+ else {
4832
+ throw new Error("Invalid element for model rendering.");
4833
+ }
4834
+ }
4835
+ if (!options.attributes) {
4836
+ options.attributes = defaultAttributes;
4837
+ }
4838
+ template = createElementTemplate(tagName, options);
4839
+ }
4840
+ else {
4841
+ template = options.template;
4842
+ }
4843
+ return {
4844
+ brand,
4845
+ type: options.type,
4846
+ name,
4847
+ template,
4848
+ };
4849
+ }
4850
+ function instanceOf(object) {
4851
+ return object && object.brand === brand;
4852
+ }
4853
+ function register(optionsOrInstruction) {
4854
+ let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
4855
+ if (lookup === void 0) {
4856
+ typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
4857
+ }
4858
+ const instruction = instanceOf(optionsOrInstruction)
4859
+ ? optionsOrInstruction
4860
+ : create(optionsOrInstruction);
4861
+ return (lookup[instruction.name] = instruction);
4862
+ }
4863
+ function getByType(type, name) {
4864
+ const entries = typeToInstructionLookup.get(type);
4865
+ if (entries === void 0) {
4866
+ return void 0;
4867
+ }
4868
+ return entries[name !== null && name !== void 0 ? name : defaultViewName];
4869
+ }
4870
+ function getForInstance(object, name) {
4871
+ if (object) {
4872
+ return getByType(object.constructor, name);
4873
+ }
4874
+ return void 0;
4875
+ }
4876
+ /**
4877
+ * Provides APIs for creating and interacting with render instructions.
4878
+ * @public
4879
+ */
4880
+ Object.freeze({
4881
+ /**
4882
+ * Checks whether the provided object is a RenderInstruction.
4883
+ * @param object - The object to check.
4884
+ * @returns true if the object is a RenderInstruction; false otherwise
4885
+ */
4886
+ instanceOf,
4887
+ /**
4888
+ * Creates a RenderInstruction for a set of options.
4889
+ * @param options - The options to use when creating the RenderInstruction.
4890
+ * @remarks
4891
+ * This API should be used with caution. When providing attributes or content,
4892
+ * if not done properly, you can open up the application to XSS attacks. When using this API,
4893
+ * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4894
+ * content and attribute values particularly if they can come from user input.
4895
+ */
4896
+ create,
4897
+ /**
4898
+ * Creates a template based on a tag name.
4899
+ * @param tagName - The tag name to use when creating the template.
4900
+ * @param attributes - The attributes to apply to the element.
4901
+ * @param content - The content to insert into the element.
4902
+ * @param policy - The DOMPolicy to create the template with.
4903
+ * @returns A template based on the provided specifications.
4904
+ * @remarks
4905
+ * This API should be used with caution. When providing attributes or content,
4906
+ * if not done properly, you can open up the application to XSS attacks. When using this API,
4907
+ * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4908
+ * content and attribute values particularly if they can come from user input.
4909
+ */
4910
+ createElementTemplate,
4911
+ /**
4912
+ * Creates and registers an instruction.
4913
+ * @param options The options to use when creating the RenderInstruction.
4914
+ * @remarks
4915
+ * A previously created RenderInstruction can also be registered.
4916
+ */
4917
+ register,
4918
+ /**
4919
+ * Finds a previously registered RenderInstruction by type and optionally by name.
4920
+ * @param type - The type to retrieve the RenderInstruction for.
4921
+ * @param name - An optional name used in differentiating between multiple registered instructions.
4922
+ * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4923
+ */
4924
+ getByType,
4925
+ /**
4926
+ * Finds a previously registered RenderInstruction for the instance's type and optionally by name.
4927
+ * @param object - The instance to retrieve the RenderInstruction for.
4928
+ * @param name - An optional name used in differentiating between multiple registered instructions.
4929
+ * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4930
+ */
4931
+ getForInstance,
4932
+ });
4933
+ /**
4934
+ * @internal
4935
+ */
4936
+ class NodeTemplate {
4937
+ constructor(node) {
4938
+ this.node = node;
4939
+ node.$fastTemplate = this;
4940
+ }
4941
+ get context() {
4942
+ // HACK
4943
+ return this;
4944
+ }
4945
+ bind(source) { }
4946
+ unbind() { }
4947
+ insertBefore(refNode) {
4948
+ refNode.parentNode.insertBefore(this.node, refNode);
4949
+ }
4950
+ remove() {
4951
+ this.node.parentNode.removeChild(this.node);
4952
+ }
4953
+ create() {
4954
+ return this;
4955
+ }
4956
+ hydrate(first, last) {
4957
+ return this;
4958
+ }
4959
+ }
4960
+ /**
4961
+ * Creates a RenderDirective for use in advanced rendering scenarios.
4962
+ * @param value - The binding expression that returns the data to be rendered. The expression
4963
+ * can also return a Node to render directly.
4964
+ * @param template - A template to render the data with
4965
+ * or a string to indicate which RenderInstruction to use when looking up a RenderInstruction.
4966
+ * Expressions can also be provided to dynamically determine either the template or the name.
4967
+ * @returns A RenderDirective suitable for use in a template.
4968
+ * @remarks
4969
+ * If no binding is provided, then a default binding that returns the source is created.
4970
+ * If no template is provided, then a binding is created that will use registered
4971
+ * RenderInstructions to determine the view.
4972
+ * If the template binding returns a string, then it will be used to look up a
4973
+ * RenderInstruction to determine the view.
4974
+ * @public
4975
+ */
4976
+ function render(value, template) {
4977
+ let dataBinding;
4978
+ if (value === void 0) {
4979
+ dataBinding = oneTime((source) => source);
4980
+ }
4981
+ else {
4982
+ dataBinding = normalizeBinding$1(value);
4983
+ }
4984
+ let templateBinding;
4985
+ let templateBindingDependsOnData = false;
4986
+ if (template === void 0) {
4987
+ templateBindingDependsOnData = true;
4988
+ templateBinding = oneTime((s, c) => {
4989
+ var _a;
4990
+ const data = dataBinding.evaluate(s, c);
4991
+ if (data instanceof Node) {
4992
+ return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
4993
+ }
4994
+ return instructionToTemplate(getForInstance(data));
4995
+ });
4996
+ }
4997
+ else if (isFunction(template)) {
4998
+ templateBinding = oneWay((s, c) => {
4999
+ var _a;
5000
+ let result = template(s, c);
5001
+ if (isString(result)) {
5002
+ result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
5003
+ }
5004
+ else if (result instanceof Node) {
5005
+ result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
5006
+ }
5007
+ return result;
5008
+ }, void 0, true);
5009
+ }
5010
+ else if (isString(template)) {
5011
+ templateBindingDependsOnData = true;
5012
+ templateBinding = oneTime((s, c) => {
5013
+ var _a;
5014
+ const data = dataBinding.evaluate(s, c);
5015
+ if (data instanceof Node) {
5016
+ return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
5017
+ }
5018
+ return instructionToTemplate(getForInstance(data, template));
5019
+ });
5020
+ }
5021
+ else if (template instanceof Binding) {
5022
+ const evaluateTemplate = template.evaluate;
5023
+ template.evaluate = (s, c) => {
5024
+ var _a;
5025
+ let result = evaluateTemplate(s, c);
5026
+ if (isString(result)) {
5027
+ result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
5028
+ }
5029
+ else if (result instanceof Node) {
5030
+ result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
5031
+ }
5032
+ return result;
5033
+ };
5034
+ templateBinding = template;
5035
+ }
5036
+ else {
5037
+ templateBinding = oneTime((s, c) => template);
3556
5038
  }
5039
+ return new RenderDirective(dataBinding, templateBinding, templateBindingDependsOnData);
5040
+ }
5041
+
5042
+ /**
5043
+ * An extension of MutationObserver that supports unobserving nodes.
5044
+ * @internal
5045
+ */
5046
+ class UnobservableMutationObserver extends MutationObserver {
3557
5047
  /**
3558
- * Defines a custom element based on this definition.
3559
- * @param registry - The element registry to define the element in.
3560
- * @remarks
3561
- * This operation is idempotent per registry.
5048
+ * Creates an instance of UnobservableMutationObserver.
5049
+ * @param callback - The callback to invoke when observed nodes are changed.
3562
5050
  */
3563
- define(registry = this.registry) {
3564
- const type = this.type;
3565
- if (!registry.get(this.name)) {
3566
- this.platformDefined = true;
3567
- registry.define(this.name, type, this.elementOptions);
5051
+ constructor(callback) {
5052
+ function handler(mutations) {
5053
+ this.callback.call(null, mutations.filter(record => this.observedNodes.has(record.target)));
3568
5054
  }
3569
- return this;
5055
+ super(handler);
5056
+ this.callback = callback;
5057
+ this.observedNodes = new Set();
3570
5058
  }
3571
- /**
3572
- * Creates an instance of FASTElementDefinition.
3573
- * @param type - The type this definition is being created for.
3574
- * @param nameOrDef - The name of the element to define or a config object
3575
- * that describes the element to define.
3576
- */
3577
- static compose(type, nameOrDef) {
3578
- const found = fastElementRegistry.getByType(type);
3579
- if (found) {
3580
- return new FASTElementDefinition(class extends type {
3581
- }, nameOrDef);
5059
+ observe(target, options) {
5060
+ this.observedNodes.add(target);
5061
+ super.observe(target, options);
5062
+ }
5063
+ unobserve(target) {
5064
+ this.observedNodes.delete(target);
5065
+ if (this.observedNodes.size < 1) {
5066
+ this.disconnect();
3582
5067
  }
3583
- return new FASTElementDefinition(type, nameOrDef);
3584
5068
  }
3585
5069
  }
3586
5070
  /**
3587
- * Gets the element definition associated with the specified type.
3588
- * @param type - The custom element type to retrieve the definition for.
3589
- */
3590
- FASTElementDefinition.getByType = fastElementRegistry.getByType;
3591
- /**
3592
- * Gets the element definition associated with the instance.
3593
- * @param instance - The custom element instance to retrieve the definition for.
5071
+ * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
5072
+ * control ViewBehaviors.
5073
+ * @public
3594
5074
  */
3595
- FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
5075
+ Object.freeze({
5076
+ /**
5077
+ * Creates a ViewBehaviorOrchestrator.
5078
+ * @param source - The source to to associate behaviors with.
5079
+ * @returns A ViewBehaviorOrchestrator.
5080
+ */
5081
+ create(source) {
5082
+ const behaviors = [];
5083
+ const targets = {};
5084
+ let unbindables = null;
5085
+ let isConnected = false;
5086
+ return {
5087
+ source,
5088
+ context: ExecutionContext.default,
5089
+ targets,
5090
+ get isBound() {
5091
+ return isConnected;
5092
+ },
5093
+ addBehaviorFactory(factory, target) {
5094
+ var _a, _b, _c, _d;
5095
+ const compiled = factory;
5096
+ compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
5097
+ compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
5098
+ compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
5099
+ compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
5100
+ this.addTarget(compiled.targetNodeId, target);
5101
+ this.addBehavior(compiled.createBehavior());
5102
+ },
5103
+ addTarget(nodeId, target) {
5104
+ targets[nodeId] = target;
5105
+ },
5106
+ addBehavior(behavior) {
5107
+ behaviors.push(behavior);
5108
+ if (isConnected) {
5109
+ behavior.bind(this);
5110
+ }
5111
+ },
5112
+ onUnbind(unbindable) {
5113
+ if (unbindables === null) {
5114
+ unbindables = [];
5115
+ }
5116
+ unbindables.push(unbindable);
5117
+ },
5118
+ connectedCallback(controller) {
5119
+ if (!isConnected) {
5120
+ isConnected = true;
5121
+ behaviors.forEach(x => x.bind(this));
5122
+ }
5123
+ },
5124
+ disconnectedCallback(controller) {
5125
+ if (isConnected) {
5126
+ isConnected = false;
5127
+ if (unbindables !== null) {
5128
+ unbindables.forEach(x => x.unbind(this));
5129
+ }
5130
+ }
5131
+ },
5132
+ };
5133
+ },
5134
+ });
3596
5135
 
3597
5136
  const defaultEventOptions = {
3598
5137
  bubbles: true,
@@ -3605,6 +5144,7 @@ function getShadowRoot(element) {
3605
5144
  var _a, _b;
3606
5145
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
3607
5146
  }
5147
+ let elementControllerStrategy;
3608
5148
  /**
3609
5149
  * Controls the lifecycle and rendering of a `FASTElement`.
3610
5150
  * @public
@@ -3623,8 +5163,19 @@ class ElementController extends PropertyChangeNotifier {
3623
5163
  this.needsInitialization = true;
3624
5164
  this.hasExistingShadowRoot = false;
3625
5165
  this._template = null;
3626
- this._isConnected = false;
5166
+ this.stage = 3 /* Stages.disconnected */;
5167
+ /**
5168
+ * A guard against connecting behaviors multiple times
5169
+ * during connect in scenarios where a behavior adds
5170
+ * another behavior during it's connectedCallback
5171
+ */
5172
+ this.guardBehaviorConnection = false;
3627
5173
  this.behaviors = null;
5174
+ /**
5175
+ * Tracks whether behaviors are connected so that
5176
+ * behaviors cant be connected multiple times
5177
+ */
5178
+ this.behaviorsConnected = false;
3628
5179
  this._mainStyles = null;
3629
5180
  /**
3630
5181
  * This allows Observable.getNotifier(...) to return the Controller
@@ -3679,11 +5230,28 @@ class ElementController extends PropertyChangeNotifier {
3679
5230
  */
3680
5231
  get isConnected() {
3681
5232
  Observable.track(this, isConnectedPropertyName);
3682
- return this._isConnected;
5233
+ return this.stage === 1 /* Stages.connected */;
3683
5234
  }
3684
- setIsConnected(value) {
3685
- this._isConnected = value;
3686
- Observable.notify(this, isConnectedPropertyName);
5235
+ /**
5236
+ * The context the expression is evaluated against.
5237
+ */
5238
+ get context() {
5239
+ var _a, _b;
5240
+ return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : ExecutionContext.default;
5241
+ }
5242
+ /**
5243
+ * Indicates whether the controller is bound.
5244
+ */
5245
+ get isBound() {
5246
+ var _a, _b;
5247
+ return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.isBound) !== null && _b !== void 0 ? _b : false;
5248
+ }
5249
+ /**
5250
+ * Indicates how the source's lifetime relates to the controller's lifetime.
5251
+ */
5252
+ get sourceLifetime() {
5253
+ var _a;
5254
+ return (_a = this.view) === null || _a === void 0 ? void 0 : _a.sourceLifetime;
3687
5255
  }
3688
5256
  /**
3689
5257
  * Gets/sets the template used to render the component.
@@ -3747,6 +5315,14 @@ class ElementController extends PropertyChangeNotifier {
3747
5315
  this.addStyles(value);
3748
5316
  }
3749
5317
  }
5318
+ /**
5319
+ * Registers an unbind handler with the controller.
5320
+ * @param behavior - An object to call when the controller unbinds.
5321
+ */
5322
+ onUnbind(behavior) {
5323
+ var _a;
5324
+ (_a = this.view) === null || _a === void 0 ? void 0 : _a.onUnbind(behavior);
5325
+ }
3750
5326
  /**
3751
5327
  * Adds the behavior to the component.
3752
5328
  * @param behavior - The behavior to add.
@@ -3758,7 +5334,9 @@ class ElementController extends PropertyChangeNotifier {
3758
5334
  if (count === 0) {
3759
5335
  targetBehaviors.set(behavior, 1);
3760
5336
  behavior.addedCallback && behavior.addedCallback(this);
3761
- if (behavior.connectedCallback && this.isConnected) {
5337
+ if (behavior.connectedCallback &&
5338
+ !this.guardBehaviorConnection &&
5339
+ (this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
3762
5340
  behavior.connectedCallback(this);
3763
5341
  }
3764
5342
  }
@@ -3782,7 +5360,7 @@ class ElementController extends PropertyChangeNotifier {
3782
5360
  }
3783
5361
  if (count === 1 || force) {
3784
5362
  targetBehaviors.delete(behavior);
3785
- if (behavior.disconnectedCallback && this.isConnected) {
5363
+ if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
3786
5364
  behavior.disconnectedCallback(this);
3787
5365
  }
3788
5366
  behavior.removedCallback && behavior.removedCallback(this);
@@ -3801,13 +5379,13 @@ class ElementController extends PropertyChangeNotifier {
3801
5379
  return;
3802
5380
  }
3803
5381
  const source = this.source;
3804
- const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
3805
5382
  if (styles instanceof HTMLElement) {
5383
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
3806
5384
  target.append(styles);
3807
5385
  }
3808
- else if (!styles.isAttachedTo(target)) {
5386
+ else if (!styles.isAttachedTo(source)) {
3809
5387
  const sourceBehaviors = styles.behaviors;
3810
- styles.addStylesTo(target);
5388
+ styles.addStylesTo(source);
3811
5389
  if (sourceBehaviors !== null) {
3812
5390
  for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
3813
5391
  this.addBehavior(sourceBehaviors[i]);
@@ -3825,16 +5403,16 @@ class ElementController extends PropertyChangeNotifier {
3825
5403
  return;
3826
5404
  }
3827
5405
  const source = this.source;
3828
- const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
3829
5406
  if (styles instanceof HTMLElement) {
5407
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
3830
5408
  target.removeChild(styles);
3831
5409
  }
3832
- else if (styles.isAttachedTo(target)) {
5410
+ else if (styles.isAttachedTo(source)) {
3833
5411
  const sourceBehaviors = styles.behaviors;
3834
- styles.removeStylesFrom(target);
5412
+ styles.removeStylesFrom(source);
3835
5413
  if (sourceBehaviors !== null) {
3836
5414
  for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
3837
- this.addBehavior(sourceBehaviors[i]);
5415
+ this.removeBehavior(sourceBehaviors[i]);
3838
5416
  }
3839
5417
  }
3840
5418
  }
@@ -3843,40 +5421,73 @@ class ElementController extends PropertyChangeNotifier {
3843
5421
  * Runs connected lifecycle behavior on the associated element.
3844
5422
  */
3845
5423
  connect() {
3846
- if (this._isConnected) {
5424
+ if (this.stage !== 3 /* Stages.disconnected */) {
3847
5425
  return;
3848
5426
  }
5427
+ this.stage = 0 /* Stages.connecting */;
5428
+ this.bindObservables();
5429
+ this.connectBehaviors();
3849
5430
  if (this.needsInitialization) {
3850
- this.finishInitialization();
5431
+ this.renderTemplate(this.template);
5432
+ this.addStyles(this.mainStyles);
5433
+ this.needsInitialization = false;
3851
5434
  }
3852
5435
  else if (this.view !== null) {
3853
5436
  this.view.bind(this.source);
3854
5437
  }
3855
- const behaviors = this.behaviors;
3856
- if (behaviors !== null) {
3857
- for (const key of behaviors.keys()) {
3858
- key.connectedCallback && key.connectedCallback(this);
5438
+ this.stage = 1 /* Stages.connected */;
5439
+ Observable.notify(this, isConnectedPropertyName);
5440
+ }
5441
+ bindObservables() {
5442
+ if (this.boundObservables !== null) {
5443
+ const element = this.source;
5444
+ const boundObservables = this.boundObservables;
5445
+ const propertyNames = Object.keys(boundObservables);
5446
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
5447
+ const propertyName = propertyNames[i];
5448
+ element[propertyName] = boundObservables[propertyName];
5449
+ }
5450
+ this.boundObservables = null;
5451
+ }
5452
+ }
5453
+ connectBehaviors() {
5454
+ if (this.behaviorsConnected === false) {
5455
+ const behaviors = this.behaviors;
5456
+ if (behaviors !== null) {
5457
+ this.guardBehaviorConnection = true;
5458
+ for (const key of behaviors.keys()) {
5459
+ key.connectedCallback && key.connectedCallback(this);
5460
+ }
5461
+ this.guardBehaviorConnection = false;
5462
+ }
5463
+ this.behaviorsConnected = true;
5464
+ }
5465
+ }
5466
+ disconnectBehaviors() {
5467
+ if (this.behaviorsConnected === true) {
5468
+ const behaviors = this.behaviors;
5469
+ if (behaviors !== null) {
5470
+ for (const key of behaviors.keys()) {
5471
+ key.disconnectedCallback && key.disconnectedCallback(this);
5472
+ }
3859
5473
  }
5474
+ this.behaviorsConnected = false;
3860
5475
  }
3861
- this.setIsConnected(true);
3862
5476
  }
3863
5477
  /**
3864
5478
  * Runs disconnected lifecycle behavior on the associated element.
3865
5479
  */
3866
5480
  disconnect() {
3867
- if (!this._isConnected) {
5481
+ if (this.stage !== 1 /* Stages.connected */) {
3868
5482
  return;
3869
5483
  }
3870
- this.setIsConnected(false);
5484
+ this.stage = 2 /* Stages.disconnecting */;
5485
+ Observable.notify(this, isConnectedPropertyName);
3871
5486
  if (this.view !== null) {
3872
5487
  this.view.unbind();
3873
5488
  }
3874
- const behaviors = this.behaviors;
3875
- if (behaviors !== null) {
3876
- for (const key of behaviors.keys()) {
3877
- key.disconnectedCallback && key.disconnectedCallback(this);
3878
- }
3879
- }
5489
+ this.disconnectBehaviors();
5490
+ this.stage = 3 /* Stages.disconnected */;
3880
5491
  }
3881
5492
  /**
3882
5493
  * Runs the attribute changed callback for the associated element.
@@ -3899,27 +5510,11 @@ class ElementController extends PropertyChangeNotifier {
3899
5510
  * Only emits events if connected.
3900
5511
  */
3901
5512
  emit(type, detail, options) {
3902
- if (this._isConnected) {
5513
+ if (this.stage === 1 /* Stages.connected */) {
3903
5514
  return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
3904
5515
  }
3905
5516
  return false;
3906
5517
  }
3907
- finishInitialization() {
3908
- const element = this.source;
3909
- const boundObservables = this.boundObservables;
3910
- // If we have any observables that were bound, re-apply their values.
3911
- if (boundObservables !== null) {
3912
- const propertyNames = Object.keys(boundObservables);
3913
- for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
3914
- const propertyName = propertyNames[i];
3915
- element[propertyName] = boundObservables[propertyName];
3916
- }
3917
- this.boundObservables = null;
3918
- }
3919
- this.renderTemplate(this.template);
3920
- this.addStyles(this.mainStyles);
3921
- this.needsInitialization = false;
3922
- }
3923
5518
  renderTemplate(template) {
3924
5519
  var _a;
3925
5520
  // When getting the host to render to, we start by looking
@@ -3963,13 +5558,224 @@ class ElementController extends PropertyChangeNotifier {
3963
5558
  if (definition === void 0) {
3964
5559
  throw FAST.error(1401 /* Message.missingElementDefinition */);
3965
5560
  }
3966
- return (element.$fastController = new ElementController(element, definition));
5561
+ return (element.$fastController = new elementControllerStrategy(element, definition));
5562
+ }
5563
+ /**
5564
+ * Sets the strategy that ElementController.forCustomElement uses to construct
5565
+ * ElementController instances for an element.
5566
+ * @param strategy - The strategy to use.
5567
+ */
5568
+ static setStrategy(strategy) {
5569
+ elementControllerStrategy = strategy;
5570
+ }
5571
+ }
5572
+ makeSerializationNoop(ElementController);
5573
+ // Set default strategy for ElementController
5574
+ ElementController.setStrategy(ElementController);
5575
+ /**
5576
+ * Converts a styleTarget into the operative target. When the provided target is an Element
5577
+ * that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
5578
+ * it will return the root node for the element.
5579
+ * @param target
5580
+ * @returns
5581
+ */
5582
+ function normalizeStyleTarget(target) {
5583
+ var _a;
5584
+ if ("adoptedStyleSheets" in target) {
5585
+ return target;
5586
+ }
5587
+ else {
5588
+ return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
5589
+ }
5590
+ }
5591
+ // Default StyleStrategy implementations are defined in this module because they
5592
+ // require access to element shadowRoots, and we don't want to leak shadowRoot
5593
+ // objects out of this module.
5594
+ /**
5595
+ * https://wicg.github.io/construct-stylesheets/
5596
+ * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
5597
+ *
5598
+ * @internal
5599
+ */
5600
+ class AdoptedStyleSheetsStrategy {
5601
+ constructor(styles) {
5602
+ const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
5603
+ this.sheets = styles.map((x) => {
5604
+ if (x instanceof CSSStyleSheet) {
5605
+ return x;
5606
+ }
5607
+ let sheet = styleSheetCache.get(x);
5608
+ if (sheet === void 0) {
5609
+ sheet = new CSSStyleSheet();
5610
+ sheet.replaceSync(x);
5611
+ styleSheetCache.set(x, sheet);
5612
+ }
5613
+ return sheet;
5614
+ });
5615
+ }
5616
+ addStylesTo(target) {
5617
+ addAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
5618
+ }
5619
+ removeStylesFrom(target) {
5620
+ removeAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
5621
+ }
5622
+ }
5623
+ AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
5624
+ let id = 0;
5625
+ const nextStyleId = () => `fast-${++id}`;
5626
+ function usableStyleTarget(target) {
5627
+ return target === document ? document.body : target;
5628
+ }
5629
+ /**
5630
+ * @internal
5631
+ */
5632
+ class StyleElementStrategy {
5633
+ constructor(styles) {
5634
+ this.styles = styles;
5635
+ this.styleClass = nextStyleId();
5636
+ }
5637
+ addStylesTo(target) {
5638
+ target = usableStyleTarget(normalizeStyleTarget(target));
5639
+ const styles = this.styles;
5640
+ const styleClass = this.styleClass;
5641
+ for (let i = 0; i < styles.length; i++) {
5642
+ const element = document.createElement("style");
5643
+ element.innerHTML = styles[i];
5644
+ element.className = styleClass;
5645
+ target.append(element);
5646
+ }
5647
+ }
5648
+ removeStylesFrom(target) {
5649
+ target = usableStyleTarget(normalizeStyleTarget(target));
5650
+ const styles = target.querySelectorAll(`.${this.styleClass}`);
5651
+ for (let i = 0, ii = styles.length; i < ii; ++i) {
5652
+ target.removeChild(styles[i]);
5653
+ }
5654
+ }
5655
+ }
5656
+ let addAdoptedStyleSheets = (target, sheets) => {
5657
+ target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...sheets];
5658
+ };
5659
+ let removeAdoptedStyleSheets = (target, sheets) => {
5660
+ target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
5661
+ };
5662
+ if (ElementStyles.supportsAdoptedStyleSheets) {
5663
+ try {
5664
+ // Test if browser implementation uses FrozenArray.
5665
+ // If not, use push / splice to alter the stylesheets
5666
+ // in place. This circumvents a bug in Safari 16.4 where
5667
+ // periodically, assigning the array would previously
5668
+ // cause sheets to be removed.
5669
+ document.adoptedStyleSheets.push();
5670
+ document.adoptedStyleSheets.splice();
5671
+ addAdoptedStyleSheets = (target, sheets) => {
5672
+ target.adoptedStyleSheets.push(...sheets);
5673
+ };
5674
+ removeAdoptedStyleSheets = (target, sheets) => {
5675
+ for (const sheet of sheets) {
5676
+ const index = target.adoptedStyleSheets.indexOf(sheet);
5677
+ if (index !== -1) {
5678
+ target.adoptedStyleSheets.splice(index, 1);
5679
+ }
5680
+ }
5681
+ };
5682
+ }
5683
+ catch (e) {
5684
+ // Do nothing if an error is thrown, the default
5685
+ // case handles FrozenArray.
5686
+ }
5687
+ ElementStyles.setDefaultStrategy(AdoptedStyleSheetsStrategy);
5688
+ }
5689
+ else {
5690
+ ElementStyles.setDefaultStrategy(StyleElementStrategy);
5691
+ }
5692
+ const deferHydrationAttribute = "defer-hydration";
5693
+ const needsHydrationAttribute = "needs-hydration";
5694
+ /**
5695
+ * An ElementController capable of hydrating FAST elements from
5696
+ * Declarative Shadow DOM.
5697
+ *
5698
+ * @beta
5699
+ */
5700
+ class HydratableElementController extends ElementController {
5701
+ static hydrationObserverHandler(records) {
5702
+ for (const record of records) {
5703
+ HydratableElementController.hydrationObserver.unobserve(record.target);
5704
+ record.target.$fastController.connect();
5705
+ }
5706
+ }
5707
+ connect() {
5708
+ var _a, _b;
5709
+ // Initialize needsHydration on first connect
5710
+ if (this.needsHydration === undefined) {
5711
+ this.needsHydration =
5712
+ this.source.getAttribute(needsHydrationAttribute) !== null;
5713
+ }
5714
+ // If the `defer-hydration` attribute exists on the source,
5715
+ // wait for it to be removed before continuing connection behavior.
5716
+ if (this.source.hasAttribute(deferHydrationAttribute)) {
5717
+ HydratableElementController.hydrationObserver.observe(this.source, {
5718
+ attributeFilter: [deferHydrationAttribute],
5719
+ });
5720
+ return;
5721
+ }
5722
+ // If the controller does not need to be hydrated, defer connection behavior
5723
+ // to the base-class. This case handles element re-connection and initial connection
5724
+ // of elements that did not get declarative shadow-dom emitted, as well as if an extending
5725
+ // class
5726
+ if (!this.needsHydration) {
5727
+ super.connect();
5728
+ return;
5729
+ }
5730
+ if (this.stage !== 3 /* Stages.disconnected */) {
5731
+ return;
5732
+ }
5733
+ this.stage = 0 /* Stages.connecting */;
5734
+ this.bindObservables();
5735
+ this.connectBehaviors();
5736
+ const element = this.source;
5737
+ const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
5738
+ if (this.template) {
5739
+ if (isHydratable(this.template)) {
5740
+ let firstChild = host.firstChild;
5741
+ let lastChild = host.lastChild;
5742
+ if (element.shadowRoot === null) {
5743
+ // handle element boundary markers when shadowRoot is not present
5744
+ if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
5745
+ firstChild.data = "";
5746
+ firstChild = firstChild.nextSibling;
5747
+ }
5748
+ if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
5749
+ lastChild.data = "";
5750
+ lastChild = lastChild.previousSibling;
5751
+ }
5752
+ }
5753
+ this.view = this.template.hydrate(firstChild, lastChild, element);
5754
+ (_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
5755
+ }
5756
+ else {
5757
+ this.renderTemplate(this.template);
5758
+ }
5759
+ }
5760
+ this.addStyles(this.mainStyles);
5761
+ this.stage = 1 /* Stages.connected */;
5762
+ this.source.removeAttribute(needsHydrationAttribute);
5763
+ this.needsInitialization = this.needsHydration = false;
5764
+ Observable.notify(this, isConnectedPropertyName);
5765
+ }
5766
+ disconnect() {
5767
+ super.disconnect();
5768
+ HydratableElementController.hydrationObserver.unobserve(this.source);
5769
+ }
5770
+ static install() {
5771
+ ElementController.setStrategy(HydratableElementController);
3967
5772
  }
3968
5773
  }
5774
+ HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
3969
5775
 
3970
5776
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3971
5777
  function createFASTElement(BaseType) {
3972
- return class extends BaseType {
5778
+ const type = class extends BaseType {
3973
5779
  constructor() {
3974
5780
  /* eslint-disable-next-line */
3975
5781
  super();
@@ -3988,6 +5794,8 @@ function createFASTElement(BaseType) {
3988
5794
  this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
3989
5795
  }
3990
5796
  };
5797
+ FASTElementDefinition.registerBaseType(type);
5798
+ return type;
3991
5799
  }
3992
5800
  function compose(type, nameOrDef) {
3993
5801
  if (isFunction(type)) {
@@ -4042,4 +5850,6 @@ function customElement(nameOrDef) {
4042
5850
  };
4043
5851
  }
4044
5852
 
4045
- export { AdoptedStyleSheetsStrategy, ArrayObserver, Aspect, AttributeConfiguration, AttributeDefinition, Binding, CSSDirective, ChildrenDirective, Compiler, DOM, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RepeatBehavior, RepeatDirective, SlottedDirective, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewBehaviorOrchestrator, ViewTemplate, attr, bind, booleanConverter, children, createMetadataLocator, createTypeRegistry, css, cssDirective, cssPartial, customElement, elements, emptyArray, html, htmlDirective, lengthOf, listener, normalizeBinding, nullableNumberConverter, observable, oneTime, ref, repeat, slotted, volatile, when };
5853
+ DOM.setPolicy(DOMPolicy.create());
5854
+
5855
+ export { ArrayObserver, AttributeConfiguration, AttributeDefinition, Binding, CSSBindingDirective, CSSDirective, ChildrenDirective, Compiler, DOM, DOMAspect, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, HydratableElementController, HydrationBindingError, InlineTemplateDirective, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RenderBehavior, RenderDirective, RepeatBehavior, RepeatDirective, SlottedDirective, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewTemplate, attr, booleanConverter, children, css, cssDirective, customElement, elements, emptyArray, html, htmlDirective, lengthOf, listener, normalizeBinding$1 as normalizeBinding, nullableBooleanConverter, nullableNumberConverter, observable, oneTime, oneWay, ref, render, repeat, slotted, volatile, when };