@microsoft/fast-element 2.0.0-beta.9 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.eslintrc.json +1 -1
  2. package/CHANGELOG.json +518 -0
  3. package/CHANGELOG.md +181 -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 +7 -1
  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 +19 -6
  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 +9789 -5667
  119. package/dist/fast-element.d.ts +813 -2392
  120. package/dist/fast-element.debug.js +2788 -974
  121. package/dist/fast-element.debug.min.js +3 -1
  122. package/dist/fast-element.js +2641 -833
  123. package/dist/fast-element.min.js +3 -1
  124. package/dist/fast-element.untrimmed.d.ts +662 -314
  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,89 +1,3 @@
1
- (function ensureGlobalThis() {
2
- if (typeof globalThis !== "undefined") {
3
- // We're running in a modern environment.
4
- return;
5
- }
6
- if (typeof global !== "undefined") {
7
- // We're running in NodeJS
8
- global.globalThis = global;
9
- }
10
- else if (typeof self !== "undefined") {
11
- self.globalThis = self;
12
- }
13
- else if (typeof window !== "undefined") {
14
- // We're running in the browser's main thread.
15
- window.globalThis = window;
16
- }
17
- else {
18
- // Hopefully we never get here...
19
- // Not all environments allow eval and Function. Use only as a last resort:
20
- // eslint-disable-next-line no-new-func
21
- const result = new Function("return this")();
22
- result.globalThis = result;
23
- }
24
- })();
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$2 = globalThis.FAST;
41
- if (FAST$2.getById === void 0) {
42
- const storage = Object.create(null);
43
- Reflect.defineProperty(FAST$2, "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$2.getById(/* KernelServiceId.styleSheetStrategy */ 5, () => StyleElementStrategy);
85
- }
86
-
87
1
  if (globalThis.FAST === void 0) {
88
2
  Reflect.defineProperty(globalThis, "FAST", {
89
3
  value: Object.create(null),
@@ -95,18 +9,24 @@ if (globalThis.FAST === void 0) {
95
9
  const FAST$1 = globalThis.FAST;
96
10
  const debugMessages = {
97
11
  [1101 /* needsArrayObservation */]: "Must call enableArrayObservation before observing arrays.",
98
- [1201 /* onlySetHTMLPolicyOnce */]: "The HTML policy can only be set once.",
12
+ [1201 /* onlySetDOMPolicyOnce */]: "The DOM Policy can only be set once.",
99
13
  [1202 /* bindingInnerHTMLRequiresTrustedTypes */]: "To bind innerHTML, you must use a TrustedTypesPolicy.",
100
14
  [1203 /* twoWayBindingRequiresObservables */]: "View=>Model update skipped. To use twoWay binding, the target property must be observable.",
101
15
  [1204 /* hostBindingWithoutHost */]: "No host element is present. Cannot bind host with ${name}.",
102
16
  [1205 /* unsupportedBindingBehavior */]: "The requested binding behavior is not supported by the binding engine.",
17
+ [1206 /* directCallToHTMLTagNotAllowed */]: "Calling html`` as a normal function invalidates the security guarantees provided by FAST.",
18
+ [1207 /* onlySetTemplatePolicyOnce */]: "The DOM Policy for an HTML template can only be set once.",
19
+ [1208 /* cannotSetTemplatePolicyAfterCompilation */]: "The DOM Policy cannot be set after a template is compiled.",
20
+ [1209 /* blockedByDOMPolicy */]: "'${aspectName}' on '${tagName}' is blocked by the current DOMPolicy.",
103
21
  [1401 /* missingElementDefinition */]: "Missing FASTElement definition.",
104
22
  [1501 /* noRegistrationForContext */]: "No registration for Context/Interface '${name}'.",
105
23
  [1502 /* noFactoryForResolver */]: "Dependency injection resolver for '${key}' returned a null factory.",
106
24
  [1503 /* invalidResolverStrategy */]: "Invalid dependency injection resolver strategy specified '${strategy}'.",
107
25
  [1504 /* cannotAutoregisterDependency */]: "Unable to autoregister dependency.",
108
26
  [1505 /* cannotResolveKey */]: "Unable to resolve dependency injection key '${key}'.",
109
- [1506 /* cannotConstructNativeFunction */]: "'${name}' is a native function and therefore cannot be safely constructed by DI. If this is intentional, please use a callback or cachedCallback resolver.",
27
+ [1506 /* cannotConstructNativeFunction */]:
28
+ /* eslint-disable-next-line max-len */
29
+ "'${name}' is a native function and therefore cannot be safely constructed by DI. If this is intentional, please use a callback or cachedCallback resolver.",
110
30
  [1507 /* cannotJITRegisterNonConstructor */]: "Attempted to jitRegister something that is not a constructor '${value}'. Did you forget to register this dependency?",
111
31
  [1508 /* cannotJITRegisterIntrinsic */]: "Attempted to jitRegister an intrinsic type '${value}'. Did you forget to add @inject(Key)?",
112
32
  [1509 /* cannotJITRegisterInterface */]: "Attempted to jitRegister an interface '${value}'.",
@@ -145,7 +65,95 @@ Object.assign(FAST$1, {
145
65
  },
146
66
  });
147
67
 
148
- // ensure FAST global - duplicated in polyfills.ts and debug.ts
68
+ let kernelMode;
69
+ const kernelAttr = "fast-kernel";
70
+ try {
71
+ if (document.currentScript) {
72
+ kernelMode = document.currentScript.getAttribute(kernelAttr);
73
+ }
74
+ else {
75
+ const scripts = document.getElementsByTagName("script");
76
+ const currentScript = scripts[scripts.length - 1];
77
+ kernelMode = currentScript.getAttribute(kernelAttr);
78
+ }
79
+ }
80
+ catch (e) {
81
+ kernelMode = "isolate";
82
+ }
83
+ let KernelServiceId;
84
+ switch (kernelMode) {
85
+ case "share": // share the kernel across major versions
86
+ KernelServiceId = Object.freeze({
87
+ updateQueue: 1,
88
+ observable: 2,
89
+ contextEvent: 3,
90
+ elementRegistry: 4,
91
+ });
92
+ break;
93
+ case "share-v2": // only share the kernel with other v2 instances
94
+ KernelServiceId = Object.freeze({
95
+ updateQueue: 1.2,
96
+ observable: 2.2,
97
+ contextEvent: 3.2,
98
+ elementRegistry: 4.2,
99
+ });
100
+ break;
101
+ default:
102
+ // fully isolate the kernel from all other FAST instances
103
+ const postfix = `-${Math.random().toString(36).substring(2, 8)}`;
104
+ KernelServiceId = Object.freeze({
105
+ updateQueue: `1.2${postfix}`,
106
+ observable: `2.2${postfix}`,
107
+ contextEvent: `3.2${postfix}`,
108
+ elementRegistry: `4.2${postfix}`,
109
+ });
110
+ break;
111
+ }
112
+ /**
113
+ * Determines whether or not an object is a function.
114
+ * @public
115
+ */
116
+ const isFunction = (object) => typeof object === "function";
117
+ /**
118
+ * Determines whether or not an object is a string.
119
+ * @public
120
+ */
121
+ const isString = (object) => typeof object === "string";
122
+ /**
123
+ * A function which does nothing.
124
+ * @public
125
+ */
126
+ const noop = () => void 0;
127
+
128
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
129
+ (function ensureGlobalThis() {
130
+ if (typeof globalThis !== "undefined") {
131
+ // We're running in a modern environment.
132
+ return;
133
+ }
134
+ // @ts-ignore
135
+ if (typeof global !== "undefined") {
136
+ // We're running in NodeJS
137
+ // @ts-ignore
138
+ global.globalThis = global;
139
+ }
140
+ else if (typeof self !== "undefined") {
141
+ self.globalThis = self;
142
+ }
143
+ else if (typeof window !== "undefined") {
144
+ // We're running in the browser's main thread.
145
+ window.globalThis = window;
146
+ }
147
+ else {
148
+ // Hopefully we never get here...
149
+ // Not all environments allow eval and Function. Use only as a last resort:
150
+ // eslint-disable-next-line no-new-func
151
+ const result = new Function("return this")();
152
+ result.globalThis = result;
153
+ }
154
+ })();
155
+
156
+ // ensure FAST global - duplicated debug.ts
149
157
  const propConfig = {
150
158
  configurable: false,
151
159
  enumerable: false,
@@ -156,7 +164,7 @@ if (globalThis.FAST === void 0) {
156
164
  }
157
165
  /**
158
166
  * The FAST global.
159
- * @internal
167
+ * @public
160
168
  */
161
169
  const FAST = globalThis.FAST;
162
170
  if (FAST.getById === void 0) {
@@ -232,122 +240,563 @@ function createMetadataLocator() {
232
240
  return metadata;
233
241
  };
234
242
  }
235
-
236
- /**
237
- * @internal
238
- */
239
- const isFunction = (object) => typeof object === "function";
240
243
  /**
244
+ * Makes a type noop for JSON serialization.
245
+ * @param type - The type to make noop for JSON serialization.
241
246
  * @internal
242
247
  */
243
- const isString = (object) => typeof object === "string";
248
+ function makeSerializationNoop(type) {
249
+ type.prototype.toJSON = noop;
250
+ }
244
251
 
245
252
  /**
246
- * The default UpdateQueue.
253
+ * The type of HTML aspect to target.
247
254
  * @public
248
255
  */
249
- const Updates = FAST.getById(1 /* KernelServiceId.updateQueue */, () => {
250
- const tasks = [];
251
- const pendingErrors = [];
252
- const rAF = globalThis.requestAnimationFrame;
253
- let updateAsync = true;
254
- function throwFirstError() {
255
- if (pendingErrors.length) {
256
- throw pendingErrors.shift();
257
- }
258
- }
259
- function tryRunTask(task) {
260
- try {
261
- task.call();
262
- }
263
- catch (error) {
264
- if (updateAsync) {
265
- pendingErrors.push(error);
266
- setTimeout(throwFirstError, 0);
267
- }
268
- else {
269
- tasks.length = 0;
270
- throw error;
271
- }
272
- }
273
- }
274
- function process() {
275
- const capacity = 1024;
276
- let index = 0;
277
- while (index < tasks.length) {
278
- tryRunTask(tasks[index]);
279
- index++;
280
- // Prevent leaking memory for long chains of recursive calls to `enqueue`.
281
- // If we call `enqueue` within a task scheduled by `enqueue`, the queue will
282
- // grow, but to avoid an O(n) walk for every task we execute, we don't
283
- // shift tasks off the queue after they have been executed.
284
- // Instead, we periodically shift 1024 tasks off the queue.
285
- if (index > capacity) {
286
- // Manually shift all values starting at the index back to the
287
- // beginning of the queue.
288
- for (let scan = 0, newLength = tasks.length - index; scan < newLength; scan++) {
289
- tasks[scan] = tasks[scan + index];
290
- }
291
- tasks.length -= index;
292
- index = 0;
293
- }
294
- }
295
- tasks.length = 0;
296
- }
297
- function enqueue(callable) {
298
- tasks.push(callable);
299
- if (tasks.length < 2) {
300
- updateAsync ? rAF(process) : process();
301
- }
302
- }
303
- return Object.freeze({
304
- enqueue,
305
- next: () => new Promise(enqueue),
306
- process,
307
- setMode: (isAsync) => (updateAsync = isAsync),
308
- });
256
+ const DOMAspect = Object.freeze({
257
+ /**
258
+ * Not aspected.
259
+ */
260
+ none: 0,
261
+ /**
262
+ * An attribute.
263
+ */
264
+ attribute: 1,
265
+ /**
266
+ * A boolean attribute.
267
+ */
268
+ booleanAttribute: 2,
269
+ /**
270
+ * A property.
271
+ */
272
+ property: 3,
273
+ /**
274
+ * Content
275
+ */
276
+ content: 4,
277
+ /**
278
+ * A token list.
279
+ */
280
+ tokenList: 5,
281
+ /**
282
+ * An event.
283
+ */
284
+ event: 6,
309
285
  });
310
-
286
+ const createHTML$1 = html => html;
287
+ const fastTrustedType = globalThis.trustedTypes
288
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML: createHTML$1 })
289
+ : { createHTML: createHTML$1 };
290
+ let defaultPolicy = Object.freeze({
291
+ createHTML(value) {
292
+ return fastTrustedType.createHTML(value);
293
+ },
294
+ protect(tagName, aspect, aspectName, sink) {
295
+ return sink;
296
+ },
297
+ });
298
+ const fastPolicy = defaultPolicy;
311
299
  /**
312
- * An implementation of {@link Notifier} that efficiently keeps track of
313
- * subscribers interested in a specific change notification on an
314
- * observable subject.
315
- *
316
- * @remarks
317
- * This set is optimized for the most common scenario of 1 or 2 subscribers.
318
- * With this in mind, it can store a subscriber in an internal field, allowing it to avoid Array#push operations.
319
- * If the set ever exceeds two subscribers, it upgrades to an array automatically.
300
+ * Common DOM APIs.
320
301
  * @public
321
302
  */
322
- class SubscriberSet {
303
+ const DOM = Object.freeze({
323
304
  /**
324
- * Creates an instance of SubscriberSet for the specified subject.
325
- * @param subject - The subject that subscribers will receive notifications from.
326
- * @param initialSubscriber - An initial subscriber to changes.
305
+ * Gets the dom policy used by the templating system.
327
306
  */
328
- constructor(subject, initialSubscriber) {
329
- this.sub1 = void 0;
330
- this.sub2 = void 0;
331
- this.spillover = void 0;
332
- this.subject = subject;
333
- this.sub1 = initialSubscriber;
334
- }
307
+ get policy() {
308
+ return defaultPolicy;
309
+ },
335
310
  /**
336
- * Checks whether the provided subscriber has been added to this set.
337
- * @param subscriber - The subscriber to test for inclusion in this set.
311
+ * Sets the dom policy used by the templating system.
312
+ * @param policy - The policy to set.
313
+ * @remarks
314
+ * This API can only be called once, for security reasons. It should be
315
+ * called by the application developer at the start of their program.
338
316
  */
339
- has(subscriber) {
340
- return this.spillover === void 0
341
- ? this.sub1 === subscriber || this.sub2 === subscriber
342
- : this.spillover.indexOf(subscriber) !== -1;
343
- }
317
+ setPolicy(value) {
318
+ if (defaultPolicy !== fastPolicy) {
319
+ throw FAST.error(1201 /* Message.onlySetDOMPolicyOnce */);
320
+ }
321
+ defaultPolicy = value;
322
+ },
344
323
  /**
345
- * Subscribes to notification of changes in an object's state.
346
- * @param subscriber - The object that is subscribing for change notification.
347
- */
348
- subscribe(subscriber) {
349
- const spillover = this.spillover;
350
- if (spillover === void 0) {
324
+ * Sets an attribute value on an element.
325
+ * @param element - The element to set the attribute value on.
326
+ * @param attributeName - The attribute name to set.
327
+ * @param value - The value of the attribute to set.
328
+ * @remarks
329
+ * If the value is `null` or `undefined`, the attribute is removed, otherwise
330
+ * it is set to the provided value using the standard `setAttribute` API.
331
+ */
332
+ setAttribute(element, attributeName, value) {
333
+ value === null || value === undefined
334
+ ? element.removeAttribute(attributeName)
335
+ : element.setAttribute(attributeName, value);
336
+ },
337
+ /**
338
+ * Sets a boolean attribute value.
339
+ * @param element - The element to set the boolean attribute value on.
340
+ * @param attributeName - The attribute name to set.
341
+ * @param value - The value of the attribute to set.
342
+ * @remarks
343
+ * If the value is true, the attribute is added; otherwise it is removed.
344
+ */
345
+ setBooleanAttribute(element, attributeName, value) {
346
+ value
347
+ ? element.setAttribute(attributeName, "")
348
+ : element.removeAttribute(attributeName);
349
+ },
350
+ });
351
+
352
+ function safeURL(tagName, aspect, aspectName, sink) {
353
+ return (target, name, value, ...rest) => {
354
+ if (isString(value)) {
355
+ value = value.replace(/(javascript:|vbscript:|data:)/, "");
356
+ }
357
+ sink(target, name, value, ...rest);
358
+ };
359
+ }
360
+ function block(tagName, aspect, aspectName, sink) {
361
+ throw FAST.error(1209 /* Message.blockedByDOMPolicy */, {
362
+ aspectName,
363
+ tagName: tagName !== null && tagName !== void 0 ? tagName : "text",
364
+ });
365
+ }
366
+ const defaultDOMElementGuards = {
367
+ a: {
368
+ [DOMAspect.attribute]: {
369
+ href: safeURL,
370
+ },
371
+ [DOMAspect.property]: {
372
+ href: safeURL,
373
+ },
374
+ },
375
+ area: {
376
+ [DOMAspect.attribute]: {
377
+ href: safeURL,
378
+ },
379
+ [DOMAspect.property]: {
380
+ href: safeURL,
381
+ },
382
+ },
383
+ button: {
384
+ [DOMAspect.attribute]: {
385
+ formaction: safeURL,
386
+ },
387
+ [DOMAspect.property]: {
388
+ formAction: safeURL,
389
+ },
390
+ },
391
+ embed: {
392
+ [DOMAspect.attribute]: {
393
+ src: block,
394
+ },
395
+ [DOMAspect.property]: {
396
+ src: block,
397
+ },
398
+ },
399
+ form: {
400
+ [DOMAspect.attribute]: {
401
+ action: safeURL,
402
+ },
403
+ [DOMAspect.property]: {
404
+ action: safeURL,
405
+ },
406
+ },
407
+ frame: {
408
+ [DOMAspect.attribute]: {
409
+ src: safeURL,
410
+ },
411
+ [DOMAspect.property]: {
412
+ src: safeURL,
413
+ },
414
+ },
415
+ iframe: {
416
+ [DOMAspect.attribute]: {
417
+ src: safeURL,
418
+ },
419
+ [DOMAspect.property]: {
420
+ src: safeURL,
421
+ srcdoc: block,
422
+ },
423
+ },
424
+ input: {
425
+ [DOMAspect.attribute]: {
426
+ formaction: safeURL,
427
+ },
428
+ [DOMAspect.property]: {
429
+ formAction: safeURL,
430
+ },
431
+ },
432
+ link: {
433
+ [DOMAspect.attribute]: {
434
+ href: block,
435
+ },
436
+ [DOMAspect.property]: {
437
+ href: block,
438
+ },
439
+ },
440
+ object: {
441
+ [DOMAspect.attribute]: {
442
+ codebase: block,
443
+ data: block,
444
+ },
445
+ [DOMAspect.property]: {
446
+ codeBase: block,
447
+ data: block,
448
+ },
449
+ },
450
+ script: {
451
+ [DOMAspect.attribute]: {
452
+ src: block,
453
+ text: block,
454
+ },
455
+ [DOMAspect.property]: {
456
+ src: block,
457
+ text: block,
458
+ innerText: block,
459
+ textContent: block,
460
+ },
461
+ },
462
+ style: {
463
+ [DOMAspect.property]: {
464
+ innerText: block,
465
+ textContent: block,
466
+ },
467
+ },
468
+ };
469
+ const blockedEvents = {
470
+ onabort: block,
471
+ onauxclick: block,
472
+ onbeforeinput: block,
473
+ onbeforematch: block,
474
+ onblur: block,
475
+ oncancel: block,
476
+ oncanplay: block,
477
+ oncanplaythrough: block,
478
+ onchange: block,
479
+ onclick: block,
480
+ onclose: block,
481
+ oncontextlost: block,
482
+ oncontextmenu: block,
483
+ oncontextrestored: block,
484
+ oncopy: block,
485
+ oncuechange: block,
486
+ oncut: block,
487
+ ondblclick: block,
488
+ ondrag: block,
489
+ ondragend: block,
490
+ ondragenter: block,
491
+ ondragleave: block,
492
+ ondragover: block,
493
+ ondragstart: block,
494
+ ondrop: block,
495
+ ondurationchange: block,
496
+ onemptied: block,
497
+ onended: block,
498
+ onerror: block,
499
+ onfocus: block,
500
+ onformdata: block,
501
+ oninput: block,
502
+ oninvalid: block,
503
+ onkeydown: block,
504
+ onkeypress: block,
505
+ onkeyup: block,
506
+ onload: block,
507
+ onloadeddata: block,
508
+ onloadedmetadata: block,
509
+ onloadstart: block,
510
+ onmousedown: block,
511
+ onmouseenter: block,
512
+ onmouseleave: block,
513
+ onmousemove: block,
514
+ onmouseout: block,
515
+ onmouseover: block,
516
+ onmouseup: block,
517
+ onpaste: block,
518
+ onpause: block,
519
+ onplay: block,
520
+ onplaying: block,
521
+ onprogress: block,
522
+ onratechange: block,
523
+ onreset: block,
524
+ onresize: block,
525
+ onscroll: block,
526
+ onsecuritypolicyviolation: block,
527
+ onseeked: block,
528
+ onseeking: block,
529
+ onselect: block,
530
+ onslotchange: block,
531
+ onstalled: block,
532
+ onsubmit: block,
533
+ onsuspend: block,
534
+ ontimeupdate: block,
535
+ ontoggle: block,
536
+ onvolumechange: block,
537
+ onwaiting: block,
538
+ onwebkitanimationend: block,
539
+ onwebkitanimationiteration: block,
540
+ onwebkitanimationstart: block,
541
+ onwebkittransitionend: block,
542
+ onwheel: block,
543
+ };
544
+ const defaultDOMGuards = {
545
+ elements: defaultDOMElementGuards,
546
+ aspects: {
547
+ [DOMAspect.attribute]: Object.assign({}, blockedEvents),
548
+ [DOMAspect.property]: Object.assign({ innerHTML: block }, blockedEvents),
549
+ [DOMAspect.event]: Object.assign({}, blockedEvents),
550
+ },
551
+ };
552
+ function createDomSinkGuards(config, defaults) {
553
+ const result = {};
554
+ for (const name in defaults) {
555
+ const overrideValue = config[name];
556
+ const defaultValue = defaults[name];
557
+ switch (overrideValue) {
558
+ case null:
559
+ // remove the default
560
+ break;
561
+ case undefined:
562
+ // keep the default
563
+ result[name] = defaultValue;
564
+ break;
565
+ default:
566
+ // override the default
567
+ result[name] = overrideValue;
568
+ break;
569
+ }
570
+ }
571
+ // add any new sinks that were not overrides
572
+ for (const name in config) {
573
+ if (!(name in result)) {
574
+ result[name] = config[name];
575
+ }
576
+ }
577
+ return Object.freeze(result);
578
+ }
579
+ function createDOMAspectGuards(config, defaults) {
580
+ const result = {};
581
+ for (const aspect in defaults) {
582
+ const overrideValue = config[aspect];
583
+ const defaultValue = defaults[aspect];
584
+ switch (overrideValue) {
585
+ case null:
586
+ // remove the default
587
+ break;
588
+ case undefined:
589
+ // keep the default
590
+ result[aspect] = createDomSinkGuards(defaultValue, {});
591
+ break;
592
+ default:
593
+ // override the default
594
+ result[aspect] = createDomSinkGuards(overrideValue, defaultValue);
595
+ break;
596
+ }
597
+ }
598
+ // add any new aspect guards that were not overrides
599
+ for (const aspect in config) {
600
+ if (!(aspect in result)) {
601
+ result[aspect] = createDomSinkGuards(config[aspect], {});
602
+ }
603
+ }
604
+ return Object.freeze(result);
605
+ }
606
+ function createElementGuards(config, defaults) {
607
+ const result = {};
608
+ for (const tag in defaults) {
609
+ const overrideValue = config[tag];
610
+ const defaultValue = defaults[tag];
611
+ switch (overrideValue) {
612
+ case null:
613
+ // remove the default
614
+ break;
615
+ case undefined:
616
+ // keep the default
617
+ result[tag] = createDOMAspectGuards(overrideValue, {});
618
+ break;
619
+ default:
620
+ // override the default aspects
621
+ result[tag] = createDOMAspectGuards(overrideValue, defaultValue);
622
+ break;
623
+ }
624
+ }
625
+ // Add any new element guards that were not overrides
626
+ for (const tag in config) {
627
+ if (!(tag in result)) {
628
+ result[tag] = createDOMAspectGuards(config[tag], {});
629
+ }
630
+ }
631
+ return Object.freeze(result);
632
+ }
633
+ function createDOMGuards(config, defaults) {
634
+ return Object.freeze({
635
+ elements: config.elements
636
+ ? createElementGuards(config.elements, defaults.elements)
637
+ : defaults.elements,
638
+ aspects: config.aspects
639
+ ? createDOMAspectGuards(config.aspects, defaults.aspects)
640
+ : defaults.aspects,
641
+ });
642
+ }
643
+ function createTrustedType() {
644
+ const createHTML = html => html;
645
+ return globalThis.trustedTypes
646
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML })
647
+ : { createHTML };
648
+ }
649
+ function tryGuard(aspectGuards, tagName, aspect, aspectName, sink) {
650
+ const sinkGuards = aspectGuards[aspect];
651
+ if (sinkGuards) {
652
+ const guard = sinkGuards[aspectName];
653
+ if (guard) {
654
+ return guard(tagName, aspect, aspectName, sink);
655
+ }
656
+ }
657
+ }
658
+ /**
659
+ * A helper for creating DOM policies.
660
+ * @public
661
+ */
662
+ const DOMPolicy = Object.freeze({
663
+ /**
664
+ * Creates a new DOM Policy object.
665
+ * @param options The options to use in creating the policy.
666
+ * @returns The newly created DOMPolicy.
667
+ */
668
+ create(options = {}) {
669
+ var _a, _b;
670
+ const trustedType = (_a = options.trustedType) !== null && _a !== void 0 ? _a : createTrustedType();
671
+ const guards = createDOMGuards((_b = options.guards) !== null && _b !== void 0 ? _b : {}, defaultDOMGuards);
672
+ return Object.freeze({
673
+ createHTML(value) {
674
+ return trustedType.createHTML(value);
675
+ },
676
+ protect(tagName, aspect, aspectName, sink) {
677
+ var _a;
678
+ // Check for element-specific guards.
679
+ const key = (tagName !== null && tagName !== void 0 ? tagName : "").toLowerCase();
680
+ const elementGuards = guards.elements[key];
681
+ if (elementGuards) {
682
+ const guard = tryGuard(elementGuards, tagName, aspect, aspectName, sink);
683
+ if (guard) {
684
+ return guard;
685
+ }
686
+ }
687
+ // Check for guards applicable to all nodes.
688
+ return ((_a = tryGuard(guards.aspects, tagName, aspect, aspectName, sink)) !== null && _a !== void 0 ? _a : sink);
689
+ },
690
+ });
691
+ },
692
+ });
693
+
694
+ /**
695
+ * The default UpdateQueue.
696
+ * @public
697
+ */
698
+ const Updates = FAST.getById(KernelServiceId.updateQueue, () => {
699
+ const tasks = [];
700
+ const pendingErrors = [];
701
+ const rAF = globalThis.requestAnimationFrame;
702
+ let updateAsync = true;
703
+ function throwFirstError() {
704
+ if (pendingErrors.length) {
705
+ throw pendingErrors.shift();
706
+ }
707
+ }
708
+ function tryRunTask(task) {
709
+ try {
710
+ task.call();
711
+ }
712
+ catch (error) {
713
+ if (updateAsync) {
714
+ pendingErrors.push(error);
715
+ setTimeout(throwFirstError, 0);
716
+ }
717
+ else {
718
+ tasks.length = 0;
719
+ throw error;
720
+ }
721
+ }
722
+ }
723
+ function process() {
724
+ const capacity = 1024;
725
+ let index = 0;
726
+ while (index < tasks.length) {
727
+ tryRunTask(tasks[index]);
728
+ index++;
729
+ // Prevent leaking memory for long chains of recursive calls to `enqueue`.
730
+ // If we call `enqueue` within a task scheduled by `enqueue`, the queue will
731
+ // grow, but to avoid an O(n) walk for every task we execute, we don't
732
+ // shift tasks off the queue after they have been executed.
733
+ // Instead, we periodically shift 1024 tasks off the queue.
734
+ if (index > capacity) {
735
+ // Manually shift all values starting at the index back to the
736
+ // beginning of the queue.
737
+ for (let scan = 0, newLength = tasks.length - index; scan < newLength; scan++) {
738
+ tasks[scan] = tasks[scan + index];
739
+ }
740
+ tasks.length -= index;
741
+ index = 0;
742
+ }
743
+ }
744
+ tasks.length = 0;
745
+ }
746
+ function enqueue(callable) {
747
+ tasks.push(callable);
748
+ if (tasks.length < 2) {
749
+ updateAsync ? rAF(process) : process();
750
+ }
751
+ }
752
+ return Object.freeze({
753
+ enqueue,
754
+ next: () => new Promise(enqueue),
755
+ process,
756
+ setMode: (isAsync) => (updateAsync = isAsync),
757
+ });
758
+ });
759
+
760
+ /**
761
+ * An implementation of {@link Notifier} that efficiently keeps track of
762
+ * subscribers interested in a specific change notification on an
763
+ * observable subject.
764
+ *
765
+ * @remarks
766
+ * This set is optimized for the most common scenario of 1 or 2 subscribers.
767
+ * With this in mind, it can store a subscriber in an internal field, allowing it to avoid Array#push operations.
768
+ * If the set ever exceeds two subscribers, it upgrades to an array automatically.
769
+ * @public
770
+ */
771
+ class SubscriberSet {
772
+ /**
773
+ * Creates an instance of SubscriberSet for the specified subject.
774
+ * @param subject - The subject that subscribers will receive notifications from.
775
+ * @param initialSubscriber - An initial subscriber to changes.
776
+ */
777
+ constructor(subject, initialSubscriber) {
778
+ this.sub1 = void 0;
779
+ this.sub2 = void 0;
780
+ this.spillover = void 0;
781
+ this.subject = subject;
782
+ this.sub1 = initialSubscriber;
783
+ }
784
+ /**
785
+ * Checks whether the provided subscriber has been added to this set.
786
+ * @param subscriber - The subscriber to test for inclusion in this set.
787
+ */
788
+ has(subscriber) {
789
+ return this.spillover === void 0
790
+ ? this.sub1 === subscriber || this.sub2 === subscriber
791
+ : this.spillover.indexOf(subscriber) !== -1;
792
+ }
793
+ /**
794
+ * Subscribes to notification of changes in an object's state.
795
+ * @param subscriber - The object that is subscribing for change notification.
796
+ */
797
+ subscribe(subscriber) {
798
+ const spillover = this.spillover;
799
+ if (spillover === void 0) {
351
800
  if (this.has(subscriber)) {
352
801
  return;
353
802
  }
@@ -492,9 +941,9 @@ const SourceLifetime = Object.freeze({
492
941
  * Common Observable APIs.
493
942
  * @public
494
943
  */
495
- const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
944
+ const Observable = FAST.getById(KernelServiceId.observable, () => {
496
945
  const queueUpdate = Updates.enqueue;
497
- const volatileRegex = /(:|&&|\|\||if)/;
946
+ const volatileRegex = /(:|&&|\|\||if|\?\.)/;
498
947
  const notifierLookup = new WeakMap();
499
948
  let watcher = void 0;
500
949
  let createArrayObserver = (array) => {
@@ -537,9 +986,9 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
537
986
  }
538
987
  }
539
988
  class ExpressionNotifierImplementation extends SubscriberSet {
540
- constructor(binding, initialSubscriber, isVolatileBinding = false) {
541
- super(binding, initialSubscriber);
542
- this.binding = binding;
989
+ constructor(expression, initialSubscriber, isVolatileBinding = false) {
990
+ super(expression, initialSubscriber);
991
+ this.expression = expression;
543
992
  this.isVolatileBinding = isVolatileBinding;
544
993
  this.needsRefresh = true;
545
994
  this.needsQueue = true;
@@ -579,13 +1028,17 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
579
1028
  this.needsRefresh = this.isVolatileBinding;
580
1029
  let result;
581
1030
  try {
582
- result = this.binding(source, context);
1031
+ result = this.expression(source, context);
583
1032
  }
584
1033
  finally {
585
1034
  watcher = previousWatcher;
586
1035
  }
587
1036
  return result;
588
1037
  }
1038
+ // backwards compat with v1 kernel
1039
+ disconnect() {
1040
+ this.dispose();
1041
+ }
589
1042
  dispose() {
590
1043
  if (this.last !== null) {
591
1044
  let current = this.first;
@@ -647,6 +1100,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
647
1100
  }
648
1101
  }
649
1102
  }
1103
+ makeSerializationNoop(ExpressionNotifierImplementation);
650
1104
  return Object.freeze({
651
1105
  /**
652
1106
  * @internal
@@ -714,20 +1168,20 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
714
1168
  /**
715
1169
  * Creates a {@link ExpressionNotifier} that can watch the
716
1170
  * provided {@link Expression} for changes.
717
- * @param binding - The binding to observe.
1171
+ * @param expression - The binding to observe.
718
1172
  * @param initialSubscriber - An initial subscriber to changes in the binding value.
719
1173
  * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
720
1174
  */
721
- binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) {
722
- return new ExpressionNotifierImplementation(binding, initialSubscriber, isVolatileBinding);
1175
+ binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
1176
+ return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
723
1177
  },
724
1178
  /**
725
1179
  * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
726
1180
  * on every evaluation of the value.
727
- * @param binding - The binding to inspect.
1181
+ * @param expression - The binding to inspect.
728
1182
  */
729
- isVolatileBinding(binding) {
730
- return volatileRegex.test(binding.toString());
1183
+ isVolatileBinding(expression) {
1184
+ return volatileRegex.test(expression.toString());
731
1185
  },
732
1186
  });
733
1187
  });
@@ -755,7 +1209,7 @@ function volatile(target, name, descriptor) {
755
1209
  },
756
1210
  });
757
1211
  }
758
- const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
1212
+ const contextEvent = FAST.getById(KernelServiceId.contextEvent, () => {
759
1213
  let current = null;
760
1214
  return {
761
1215
  get() {
@@ -1170,7 +1624,7 @@ let defaultSpliceStrategy = Object.freeze({
1170
1624
  if (changes === void 0) {
1171
1625
  return emptyArray;
1172
1626
  }
1173
- return changes.length > 1 ? project(current, changes) : changes;
1627
+ return project(current, changes);
1174
1628
  }
1175
1629
  return resetSplices;
1176
1630
  },
@@ -1370,34 +1824,115 @@ function lengthOf(array) {
1370
1824
  return array.length;
1371
1825
  }
1372
1826
 
1373
- const styleSheetCache = new Map();
1374
- let DefaultStyleStrategy;
1375
- function reduceStyles(styles) {
1376
- return styles
1377
- .map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
1378
- .reduce((prev, curr) => prev.concat(curr), []);
1379
- }
1380
1827
  /**
1381
- * Represents styles that can be applied to a custom element.
1828
+ * Captures a binding expression along with related information and capabilities.
1829
+ *
1382
1830
  * @public
1383
1831
  */
1384
- class ElementStyles {
1832
+ class Binding {
1385
1833
  /**
1386
- * Creates an instance of ElementStyles.
1387
- * @param styles - The styles that will be associated with elements.
1834
+ * Creates a binding.
1835
+ * @param evaluate - Evaluates the binding.
1836
+ * @param policy - The security policy to associate with this binding.
1837
+ * @param isVolatile - Indicates whether the binding is volatile.
1388
1838
  */
1389
- constructor(styles) {
1390
- this.styles = styles;
1391
- this.targets = new WeakSet();
1392
- this._strategy = null;
1393
- this.behaviors = styles
1394
- .map((x) => x instanceof ElementStyles ? x.behaviors : null)
1395
- .reduce((prev, curr) => (curr === null ? prev : prev === null ? curr : prev.concat(curr)), null);
1839
+ constructor(evaluate, policy, isVolatile = false) {
1840
+ this.evaluate = evaluate;
1841
+ this.policy = policy;
1842
+ this.isVolatile = isVolatile;
1396
1843
  }
1397
- /**
1398
- * Gets the StyleStrategy associated with these element styles.
1399
- */
1400
- get strategy() {
1844
+ }
1845
+
1846
+ class OneWayBinding extends Binding {
1847
+ createObserver(subscriber) {
1848
+ return Observable.binding(this.evaluate, subscriber, this.isVolatile);
1849
+ }
1850
+ }
1851
+ /**
1852
+ * Creates an standard binding.
1853
+ * @param expression - The binding to refresh when changed.
1854
+ * @param policy - The security policy to associate with th binding.
1855
+ * @param isVolatile - Indicates whether the binding is volatile or not.
1856
+ * @returns A binding configuration.
1857
+ * @public
1858
+ */
1859
+ function oneWay(expression, policy, isVolatile = Observable.isVolatileBinding(expression)) {
1860
+ return new OneWayBinding(expression, policy, isVolatile);
1861
+ }
1862
+ /**
1863
+ * Creates an event listener binding.
1864
+ * @param expression - The binding to invoke when the event is raised.
1865
+ * @param options - Event listener options.
1866
+ * @returns A binding configuration.
1867
+ * @public
1868
+ */
1869
+ function listener(expression, options) {
1870
+ const config = new OneWayBinding(expression);
1871
+ config.options = options;
1872
+ return config;
1873
+ }
1874
+
1875
+ class OneTimeBinding extends Binding {
1876
+ createObserver() {
1877
+ return this;
1878
+ }
1879
+ bind(controller) {
1880
+ return this.evaluate(controller.source, controller.context);
1881
+ }
1882
+ }
1883
+ makeSerializationNoop(OneTimeBinding);
1884
+ /**
1885
+ * Creates a one time binding
1886
+ * @param expression - The binding to refresh when signaled.
1887
+ * @param policy - The security policy to associate with th binding.
1888
+ * @returns A binding configuration.
1889
+ * @public
1890
+ */
1891
+ function oneTime(expression, policy) {
1892
+ return new OneTimeBinding(expression, policy);
1893
+ }
1894
+
1895
+ /**
1896
+ * Normalizes the input value into a binding.
1897
+ * @param value - The value to create the default binding for.
1898
+ * @returns A binding configuration for the provided value.
1899
+ * @public
1900
+ */
1901
+ function normalizeBinding$1(value) {
1902
+ return isFunction(value)
1903
+ ? oneWay(value)
1904
+ : value instanceof Binding
1905
+ ? value
1906
+ : oneTime(() => value);
1907
+ }
1908
+
1909
+ let DefaultStyleStrategy;
1910
+ function reduceStyles(styles) {
1911
+ return styles
1912
+ .map((x) => x instanceof ElementStyles ? reduceStyles(x.styles) : [x])
1913
+ .reduce((prev, curr) => prev.concat(curr), []);
1914
+ }
1915
+ /**
1916
+ * Represents styles that can be applied to a custom element.
1917
+ * @public
1918
+ */
1919
+ class ElementStyles {
1920
+ /**
1921
+ * Creates an instance of ElementStyles.
1922
+ * @param styles - The styles that will be associated with elements.
1923
+ */
1924
+ constructor(styles) {
1925
+ this.styles = styles;
1926
+ this.targets = new WeakSet();
1927
+ this._strategy = null;
1928
+ this.behaviors = styles
1929
+ .map((x) => x instanceof ElementStyles ? x.behaviors : null)
1930
+ .reduce((prev, curr) => (curr === null ? prev : prev === null ? curr : prev.concat(curr)), null);
1931
+ }
1932
+ /**
1933
+ * Gets the StyleStrategy associated with these element styles.
1934
+ */
1935
+ get strategy() {
1401
1936
  if (this._strategy === null) {
1402
1937
  this.withStrategy(DefaultStyleStrategy);
1403
1938
  }
@@ -1461,36 +1996,6 @@ class ElementStyles {
1461
1996
  */
1462
1997
  ElementStyles.supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
1463
1998
  "replace" in CSSStyleSheet.prototype;
1464
- /**
1465
- * https://wicg.github.io/construct-stylesheets/
1466
- * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
1467
- *
1468
- * @internal
1469
- */
1470
- class AdoptedStyleSheetsStrategy {
1471
- constructor(styles) {
1472
- this.sheets = styles.map((x) => {
1473
- if (x instanceof CSSStyleSheet) {
1474
- return x;
1475
- }
1476
- let sheet = styleSheetCache.get(x);
1477
- if (sheet === void 0) {
1478
- sheet = new CSSStyleSheet();
1479
- sheet.replaceSync(x);
1480
- styleSheetCache.set(x, sheet);
1481
- }
1482
- return sheet;
1483
- });
1484
- }
1485
- addStylesTo(target) {
1486
- target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...this.sheets];
1487
- }
1488
- removeStylesFrom(target) {
1489
- const sheets = this.sheets;
1490
- target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
1491
- }
1492
- }
1493
- ElementStyles.setDefaultStrategy(FAST.getById(5 /* KernelServiceId.styleSheetStrategy */, () => AdoptedStyleSheetsStrategy));
1494
1999
 
1495
2000
  const registry$1 = createTypeRegistry();
1496
2001
  /**
@@ -1529,6 +2034,85 @@ function cssDirective() {
1529
2034
  };
1530
2035
  }
1531
2036
 
2037
+ function handleChange(directive, controller, observer) {
2038
+ controller.source.style.setProperty(directive.targetAspect, observer.bind(controller));
2039
+ }
2040
+ /**
2041
+ * Enables bindings in CSS.
2042
+ *
2043
+ * @public
2044
+ */
2045
+ class CSSBindingDirective {
2046
+ /**
2047
+ * Creates an instance of CSSBindingDirective.
2048
+ * @param dataBinding - The binding to use in CSS.
2049
+ * @param targetAspect - The CSS property to target.
2050
+ */
2051
+ constructor(dataBinding, targetAspect) {
2052
+ this.dataBinding = dataBinding;
2053
+ this.targetAspect = targetAspect;
2054
+ }
2055
+ /**
2056
+ * Creates a CSS fragment to interpolate into the CSS document.
2057
+ * @returns - the string to interpolate into CSS
2058
+ */
2059
+ createCSS(add) {
2060
+ add(this);
2061
+ return `var(${this.targetAspect})`;
2062
+ }
2063
+ /**
2064
+ * Executed when this behavior is attached to a controller.
2065
+ * @param controller - Controls the behavior lifecycle.
2066
+ */
2067
+ addedCallback(controller) {
2068
+ var _a;
2069
+ const element = controller.source;
2070
+ if (!element.$cssBindings) {
2071
+ element.$cssBindings = new Map();
2072
+ const setAttribute = element.setAttribute;
2073
+ element.setAttribute = (attr, value) => {
2074
+ setAttribute.call(element, attr, value);
2075
+ if (attr === "style") {
2076
+ element.$cssBindings.forEach((v, k) => handleChange(k, v.controller, v.observer));
2077
+ }
2078
+ };
2079
+ }
2080
+ const observer = (_a = controller[this.targetAspect]) !== null && _a !== void 0 ? _a : (controller[this.targetAspect] = this.dataBinding.createObserver(this, this));
2081
+ observer.controller = controller;
2082
+ controller.source.$cssBindings.set(this, { controller, observer });
2083
+ }
2084
+ /**
2085
+ * Executed when this behavior's host is connected.
2086
+ * @param controller - Controls the behavior lifecycle.
2087
+ */
2088
+ connectedCallback(controller) {
2089
+ handleChange(this, controller, controller[this.targetAspect]);
2090
+ }
2091
+ /**
2092
+ * Executed when this behavior is detached from a controller.
2093
+ * @param controller - Controls the behavior lifecycle.
2094
+ */
2095
+ removedCallback(controller) {
2096
+ if (controller.source.$cssBindings) {
2097
+ controller.source.$cssBindings.delete(this);
2098
+ }
2099
+ }
2100
+ /**
2101
+ * Called when a subject this instance has subscribed to changes.
2102
+ * @param subject - The subject of the change.
2103
+ * @param args - The event args detailing the change that occurred.
2104
+ *
2105
+ * @internal
2106
+ */
2107
+ handleChange(_, observer) {
2108
+ handleChange(this, observer.controller, observer);
2109
+ }
2110
+ }
2111
+ CSSDirective.define(CSSBindingDirective);
2112
+
2113
+ const marker$1 = `${Math.random().toString(36).substring(2, 8)}`;
2114
+ let varId = 0;
2115
+ const nextCSSVariable = () => `--v${marker$1}${++varId}`;
1532
2116
  function collectStyles(strings, values) {
1533
2117
  const styles = [];
1534
2118
  let cssString = "";
@@ -1539,7 +2123,13 @@ function collectStyles(strings, values) {
1539
2123
  for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
1540
2124
  cssString += strings[i];
1541
2125
  let value = values[i];
1542
- if (CSSDirective.getForInstance(value) !== void 0) {
2126
+ if (isFunction(value)) {
2127
+ value = new CSSBindingDirective(oneWay(value), nextCSSVariable()).createCSS(add);
2128
+ }
2129
+ else if (value instanceof Binding) {
2130
+ value = new CSSBindingDirective(value, nextCSSVariable()).createCSS(add);
2131
+ }
2132
+ else if (CSSDirective.getForInstance(value) !== void 0) {
1543
2133
  value = value.createCSS(add);
1544
2134
  }
1545
2135
  if (value instanceof ElementStyles || value instanceof CSSStyleSheet) {
@@ -1611,68 +2201,119 @@ css.partial = (strings, ...values) => {
1611
2201
  const { styles, behaviors } = collectStyles(strings, values);
1612
2202
  return new CSSPartial(styles, behaviors);
1613
2203
  };
1614
- /**
1615
- * @deprecated Use css.partial instead.
1616
- * @public
1617
- */
1618
- const cssPartial = css.partial;
1619
2204
 
2205
+ const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
2206
+ const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
2207
+ const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
2208
+ const repeatViewEndMarker = /fe-repeat\$\$end\$\$(\d+)\$\$fe-repeat/;
2209
+ const elementBoundaryStartMarker = /^(?:.{0,1000})fe-eb\$\$start\$\$(.+?)\$\$fe-eb/;
2210
+ const elementBoundaryEndMarker = /fe-eb\$\$end\$\$(.{0,1000})\$\$fe-eb(?:.{0,1000})$/;
2211
+ function isComment$1(node) {
2212
+ return node && node.nodeType === Node.COMMENT_NODE;
2213
+ }
1620
2214
  /**
1621
- * Common DOM APIs.
1622
- * @public
2215
+ * Markup utilities to aid in template hydration.
2216
+ * @internal
1623
2217
  */
1624
- const DOM = Object.freeze({
1625
- /**
1626
- * @deprecated
1627
- * Use Updates.enqueue().
1628
- */
1629
- queueUpdate: Updates.enqueue,
2218
+ const HydrationMarkup = Object.freeze({
2219
+ attributeMarkerName: "data-fe-b",
2220
+ attributeBindingSeparator: " ",
2221
+ contentBindingStartMarker(index, uniqueId) {
2222
+ return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
2223
+ },
2224
+ contentBindingEndMarker(index, uniqueId) {
2225
+ return `fe-b$$end$$${index}$$${uniqueId}$$fe-b`;
2226
+ },
2227
+ repeatStartMarker(index) {
2228
+ return `fe-repeat$$start$$${index}$$fe-repeat`;
2229
+ },
2230
+ repeatEndMarker(index) {
2231
+ return `fe-repeat$$end$$${index}$$fe-repeat`;
2232
+ },
2233
+ isContentBindingStartMarker(content) {
2234
+ return bindingStartMarker.test(content);
2235
+ },
2236
+ isContentBindingEndMarker(content) {
2237
+ return bindingEndMarker.test(content);
2238
+ },
2239
+ isRepeatViewStartMarker(content) {
2240
+ return repeatViewStartMarker.test(content);
2241
+ },
2242
+ isRepeatViewEndMarker(content) {
2243
+ return repeatViewEndMarker.test(content);
2244
+ },
2245
+ isElementBoundaryStartMarker(node) {
2246
+ return isComment$1(node) && elementBoundaryStartMarker.test(node.data.trim());
2247
+ },
2248
+ isElementBoundaryEndMarker(node) {
2249
+ return isComment$1(node) && elementBoundaryEndMarker.test(node.data);
2250
+ },
1630
2251
  /**
1631
- * @deprecated
1632
- * Use Updates.next()
2252
+ * Returns the indexes of the ViewBehaviorFactories affecting
2253
+ * attributes for the element, or null if no factories were found.
1633
2254
  */
1634
- nextUpdate: Updates.next,
2255
+ parseAttributeBinding(node) {
2256
+ const attr = node.getAttribute(this.attributeMarkerName);
2257
+ return attr === null
2258
+ ? attr
2259
+ : attr.split(this.attributeBindingSeparator).map(i => parseInt(i));
2260
+ },
1635
2261
  /**
1636
- * @deprecated
1637
- * Use Updates.process()
2262
+ * Parses the ViewBehaviorFactory index from string data. Returns
2263
+ * the binding index or null if the index cannot be retrieved.
1638
2264
  */
1639
- processUpdates: Updates.process,
2265
+ parseContentBindingStartMarker(content) {
2266
+ return parseIndexAndIdMarker(bindingStartMarker, content);
2267
+ },
2268
+ parseContentBindingEndMarker(content) {
2269
+ return parseIndexAndIdMarker(bindingEndMarker, content);
2270
+ },
1640
2271
  /**
1641
- * Sets an attribute value on an element.
1642
- * @param element - The element to set the attribute value on.
1643
- * @param attributeName - The attribute name to set.
1644
- * @param value - The value of the attribute to set.
1645
- * @remarks
1646
- * If the value is `null` or `undefined`, the attribute is removed, otherwise
1647
- * it is set to the provided value using the standard `setAttribute` API.
2272
+ * Parses the index of a repeat directive from a content string.
1648
2273
  */
1649
- setAttribute(element, attributeName, value) {
1650
- value === null || value === undefined
1651
- ? element.removeAttribute(attributeName)
1652
- : element.setAttribute(attributeName, value);
2274
+ parseRepeatStartMarker(content) {
2275
+ return parseIntMarker(repeatViewStartMarker, content);
2276
+ },
2277
+ parseRepeatEndMarker(content) {
2278
+ return parseIntMarker(repeatViewEndMarker, content);
1653
2279
  },
1654
2280
  /**
1655
- * Sets a boolean attribute value.
1656
- * @param element - The element to set the boolean attribute value on.
1657
- * @param attributeName - The attribute name to set.
1658
- * @param value - The value of the attribute to set.
1659
- * @remarks
1660
- * If the value is true, the attribute is added; otherwise it is removed.
2281
+ * Parses element Id from element boundary markers
1661
2282
  */
1662
- setBooleanAttribute(element, attributeName, value) {
1663
- value
1664
- ? element.setAttribute(attributeName, "")
1665
- : element.removeAttribute(attributeName);
2283
+ parseElementBoundaryStartMarker(content) {
2284
+ return parseStringMarker(elementBoundaryStartMarker, content.trim());
2285
+ },
2286
+ parseElementBoundaryEndMarker(content) {
2287
+ return parseStringMarker(elementBoundaryEndMarker, content);
1666
2288
  },
1667
2289
  });
2290
+ function parseIntMarker(regex, content) {
2291
+ const match = regex.exec(content);
2292
+ return match === null ? match : parseInt(match[1]);
2293
+ }
2294
+ function parseStringMarker(regex, content) {
2295
+ const match = regex.exec(content);
2296
+ return match === null ? match : match[1];
2297
+ }
2298
+ function parseIndexAndIdMarker(regex, content) {
2299
+ const match = regex.exec(content);
2300
+ return match === null ? match : [parseInt(match[1]), match[2]];
2301
+ }
2302
+ /**
2303
+ * @internal
2304
+ */
2305
+ const Hydratable = Symbol.for("fe-hydration");
2306
+ function isHydratable(value) {
2307
+ return value[Hydratable] === Hydratable;
2308
+ }
1668
2309
 
1669
2310
  const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
1670
2311
  const interpolationStart = `${marker}{`;
1671
2312
  const interpolationEnd = `}${marker}`;
1672
2313
  const interpolationEndLength = interpolationEnd.length;
1673
- let id = 0;
2314
+ let id$1 = 0;
1674
2315
  /** @internal */
1675
- const nextId = () => `${marker}-${++id}`;
2316
+ const nextId = () => `${marker}-${++id$1}`;
1676
2317
  /**
1677
2318
  * Common APIs related to markup generation.
1678
2319
  * @public
@@ -1742,67 +2383,6 @@ const Parser = Object.freeze({
1742
2383
  },
1743
2384
  });
1744
2385
 
1745
- /**
1746
- * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
1747
- * control ViewBehaviors.
1748
- * @public
1749
- */
1750
- const ViewBehaviorOrchestrator = Object.freeze({
1751
- /**
1752
- * Creates a ViewBehaviorOrchestrator.
1753
- * @param source - The source to to associate behaviors with.
1754
- * @returns A ViewBehaviorOrchestrator.
1755
- */
1756
- create(source) {
1757
- const behaviors = [];
1758
- const targets = {};
1759
- let unbindables = null;
1760
- let isConnected = false;
1761
- return {
1762
- source,
1763
- context: ExecutionContext.default,
1764
- targets,
1765
- get isBound() {
1766
- return isConnected;
1767
- },
1768
- addBehaviorFactory(factory, target) {
1769
- const nodeId = factory.nodeId || (factory.nodeId = nextId());
1770
- factory.id || (factory.id = nextId());
1771
- this.addTarget(nodeId, target);
1772
- this.addBehavior(factory.createBehavior());
1773
- },
1774
- addTarget(nodeId, target) {
1775
- targets[nodeId] = target;
1776
- },
1777
- addBehavior(behavior) {
1778
- behaviors.push(behavior);
1779
- if (isConnected) {
1780
- behavior.bind(this);
1781
- }
1782
- },
1783
- onUnbind(unbindable) {
1784
- if (unbindables === null) {
1785
- unbindables = [];
1786
- }
1787
- unbindables.push(unbindable);
1788
- },
1789
- connectedCallback(controller) {
1790
- if (!isConnected) {
1791
- isConnected = true;
1792
- behaviors.forEach(x => x.bind(this));
1793
- }
1794
- },
1795
- disconnectedCallback(controller) {
1796
- if (isConnected) {
1797
- isConnected = false;
1798
- if (unbindables !== null) {
1799
- unbindables.forEach(x => x.unbind(this));
1800
- }
1801
- }
1802
- },
1803
- };
1804
- },
1805
- });
1806
2386
  const registry = createTypeRegistry();
1807
2387
  /**
1808
2388
  * Instructs the template engine to apply behavior to a node.
@@ -1830,67 +2410,6 @@ const HTMLDirective = Object.freeze({
1830
2410
  registry.register(options);
1831
2411
  return type;
1832
2412
  },
1833
- });
1834
- /**
1835
- * Decorator: Defines an HTMLDirective.
1836
- * @param options - Provides options that specify the directive's application.
1837
- * @public
1838
- */
1839
- function htmlDirective(options) {
1840
- /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
1841
- return function (type) {
1842
- HTMLDirective.define(type, options);
1843
- };
1844
- }
1845
- /**
1846
- * Captures a binding expression along with related information and capabilities.
1847
- *
1848
- * @public
1849
- */
1850
- class Binding {
1851
- /**
1852
- * Creates a binding.
1853
- * @param evaluate - Evaluates the binding.
1854
- * @param isVolatile - Indicates whether the binding is volatile.
1855
- */
1856
- constructor(evaluate, isVolatile = false) {
1857
- this.evaluate = evaluate;
1858
- this.isVolatile = isVolatile;
1859
- }
1860
- }
1861
- /**
1862
- * The type of HTML aspect to target.
1863
- * @public
1864
- */
1865
- const Aspect = Object.freeze({
1866
- /**
1867
- * Not aspected.
1868
- */
1869
- none: 0,
1870
- /**
1871
- * An attribute.
1872
- */
1873
- attribute: 1,
1874
- /**
1875
- * A boolean attribute.
1876
- */
1877
- booleanAttribute: 2,
1878
- /**
1879
- * A property.
1880
- */
1881
- property: 3,
1882
- /**
1883
- * Content
1884
- */
1885
- content: 4,
1886
- /**
1887
- * A token list.
1888
- */
1889
- tokenList: 5,
1890
- /**
1891
- * An event.
1892
- */
1893
- event: 6,
1894
2413
  /**
1895
2414
  *
1896
2415
  * @param directive - The directive to assign the aspect to.
@@ -1898,48 +2417,46 @@ const Aspect = Object.freeze({
1898
2417
  * @remarks
1899
2418
  * If a falsy value is provided, then the content aspect will be assigned.
1900
2419
  */
1901
- assign(directive, value) {
2420
+ assignAspect(directive, value) {
1902
2421
  if (!value) {
1903
- directive.aspectType = Aspect.content;
2422
+ directive.aspectType = DOMAspect.content;
1904
2423
  return;
1905
2424
  }
1906
2425
  directive.sourceAspect = value;
1907
2426
  switch (value[0]) {
1908
2427
  case ":":
1909
2428
  directive.targetAspect = value.substring(1);
1910
- switch (directive.targetAspect) {
1911
- case "innerHTML":
1912
- directive.aspectType = Aspect.property;
1913
- break;
1914
- case "classList":
1915
- directive.aspectType = Aspect.tokenList;
1916
- break;
1917
- default:
1918
- directive.aspectType = Aspect.property;
1919
- break;
1920
- }
2429
+ directive.aspectType =
2430
+ directive.targetAspect === "classList"
2431
+ ? DOMAspect.tokenList
2432
+ : DOMAspect.property;
1921
2433
  break;
1922
2434
  case "?":
1923
2435
  directive.targetAspect = value.substring(1);
1924
- directive.aspectType = Aspect.booleanAttribute;
2436
+ directive.aspectType = DOMAspect.booleanAttribute;
1925
2437
  break;
1926
2438
  case "@":
1927
2439
  directive.targetAspect = value.substring(1);
1928
- directive.aspectType = Aspect.event;
2440
+ directive.aspectType = DOMAspect.event;
1929
2441
  break;
1930
2442
  default:
1931
- if (value === "class") {
1932
- directive.targetAspect = "className";
1933
- directive.aspectType = Aspect.property;
1934
- }
1935
- else {
1936
- directive.targetAspect = value;
1937
- directive.aspectType = Aspect.attribute;
1938
- }
2443
+ directive.targetAspect = value;
2444
+ directive.aspectType = DOMAspect.attribute;
1939
2445
  break;
1940
2446
  }
1941
2447
  },
1942
2448
  });
2449
+ /**
2450
+ * Decorator: Defines an HTMLDirective.
2451
+ * @param options - Provides options that specify the directive's application.
2452
+ * @public
2453
+ */
2454
+ function htmlDirective(options) {
2455
+ /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
2456
+ return function (type) {
2457
+ HTMLDirective.define(type, options);
2458
+ };
2459
+ }
1943
2460
  /**
1944
2461
  * A base class used for attribute directives that don't need internal state.
1945
2462
  * @public
@@ -1951,10 +2468,6 @@ class StatelessAttachedAttributeDirective {
1951
2468
  */
1952
2469
  constructor(options) {
1953
2470
  this.options = options;
1954
- /**
1955
- * The unique id of the factory.
1956
- */
1957
- this.id = nextId();
1958
2471
  }
1959
2472
  /**
1960
2473
  * Creates a placeholder string based on the directive's index within the template.
@@ -1973,327 +2486,200 @@ class StatelessAttachedAttributeDirective {
1973
2486
  return this;
1974
2487
  }
1975
2488
  }
2489
+ makeSerializationNoop(StatelessAttachedAttributeDirective);
1976
2490
 
1977
- const createInnerHTMLBinding = globalThis.TrustedHTML
1978
- ? (binding) => (s, c) => {
1979
- const value = binding(s, c);
1980
- if (value instanceof TrustedHTML) {
1981
- return value;
1982
- }
1983
- throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
1984
- }
1985
- : (binding) => binding;
1986
- class OnChangeBinding extends Binding {
1987
- createObserver(_, subscriber) {
1988
- return Observable.binding(this.evaluate, subscriber, this.isVolatile);
1989
- }
1990
- }
1991
- class OneTimeBinding extends Binding {
1992
- createObserver() {
1993
- return this;
1994
- }
1995
- bind(controller) {
1996
- return this.evaluate(controller.source, controller.context);
1997
- }
1998
- }
1999
- function updateContent(target, aspect, value, controller) {
2000
- // If there's no actual value, then this equates to the
2001
- // empty string for the purposes of content bindings.
2002
- if (value === null || value === undefined) {
2003
- value = "";
2004
- }
2005
- // If the value has a "create" method, then it's a ContentTemplate.
2006
- if (value.create) {
2007
- target.textContent = "";
2008
- let view = target.$fastView;
2009
- // If there's no previous view that we might be able to
2010
- // reuse then create a new view from the template.
2011
- if (view === void 0) {
2012
- view = value.create();
2013
- }
2014
- else {
2015
- // If there is a previous view, but it wasn't created
2016
- // from the same template as the new value, then we
2017
- // need to remove the old view if it's still in the DOM
2018
- // and create a new view from the template.
2019
- if (target.$fastTemplate !== value) {
2020
- if (view.isComposed) {
2021
- view.remove();
2022
- view.unbind();
2023
- }
2024
- view = value.create();
2025
- }
2026
- }
2027
- // It's possible that the value is the same as the previous template
2028
- // and that there's actually no need to compose it.
2029
- if (!view.isComposed) {
2030
- view.isComposed = true;
2031
- view.bind(controller.source, controller.context);
2032
- view.insertBefore(target);
2033
- target.$fastView = view;
2034
- target.$fastTemplate = value;
2035
- }
2036
- else if (view.needsBindOnly) {
2037
- view.needsBindOnly = false;
2038
- view.bind(controller.source, controller.context);
2039
- }
2040
- }
2041
- else {
2042
- const view = target.$fastView;
2043
- // If there is a view and it's currently composed into
2044
- // the DOM, then we need to remove it.
2045
- if (view !== void 0 && view.isComposed) {
2046
- view.isComposed = false;
2047
- view.remove();
2048
- if (view.needsBindOnly) {
2049
- view.needsBindOnly = false;
2050
- }
2051
- else {
2052
- view.unbind();
2053
- }
2054
- }
2055
- target.textContent = value;
2056
- }
2057
- }
2058
- function updateTokenList(target, aspect, value) {
2059
- var _a;
2060
- const lookup = `${this.id}-t`;
2061
- const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
2062
- const versions = state.v;
2063
- let currentVersion = state.c;
2064
- const tokenList = target[aspect];
2065
- // Add the classes, tracking the version at which they were added.
2066
- if (value !== null && value !== undefined && value.length) {
2067
- const names = value.split(/\s+/);
2068
- for (let i = 0, ii = names.length; i < ii; ++i) {
2069
- const currentName = names[i];
2070
- if (currentName === "") {
2071
- continue;
2072
- }
2073
- versions[currentName] = currentVersion;
2074
- tokenList.add(currentName);
2075
- }
2076
- }
2077
- state.v = currentVersion + 1;
2078
- // If this is the first call to add classes, there's no need to remove old ones.
2079
- if (currentVersion === 0) {
2080
- return;
2081
- }
2082
- // Remove classes from the previous version.
2083
- currentVersion -= 1;
2084
- for (const name in versions) {
2085
- if (versions[name] === currentVersion) {
2086
- tokenList.remove(name);
2087
- }
2088
- }
2089
- }
2090
- const setProperty = (t, a, v) => (t[a] = v);
2091
- const eventTarget = () => void 0;
2092
- /**
2093
- * A directive that applies bindings.
2094
- * @public
2095
- */
2096
- class HTMLBindingDirective {
2491
+ class HydrationTargetElementError extends Error {
2492
+ constructor(
2097
2493
  /**
2098
- * Creates an instance of HTMLBindingDirective.
2099
- * @param dataBinding - The binding configuration to apply.
2494
+ * The error message
2100
2495
  */
2101
- constructor(dataBinding) {
2102
- this.dataBinding = dataBinding;
2103
- this.updateTarget = null;
2104
- /**
2105
- * The unique id of the factory.
2106
- */
2107
- this.id = nextId();
2108
- /**
2109
- * The type of aspect to target.
2110
- */
2111
- this.aspectType = Aspect.content;
2112
- /** @internal */
2113
- this.bind = this.bindDefault;
2114
- this.data = `${this.id}-d`;
2115
- }
2496
+ message,
2116
2497
  /**
2117
- * Creates HTML to be used within a template.
2118
- * @param add - Can be used to add behavior factories to a template.
2498
+ * The Compiled View Behavior Factories that belong to the view.
2119
2499
  */
2120
- createHTML(add) {
2121
- return Markup.interpolation(add(this));
2122
- }
2500
+ factories,
2123
2501
  /**
2124
- * Creates a behavior.
2502
+ * The node to target factory.
2125
2503
  */
2126
- createBehavior() {
2127
- if (this.updateTarget === null) {
2128
- if (this.targetAspect === "innerHTML") {
2129
- this.dataBinding.evaluate = createInnerHTMLBinding(this.dataBinding.evaluate);
2130
- }
2131
- switch (this.aspectType) {
2132
- case 1:
2133
- this.updateTarget = DOM.setAttribute;
2134
- break;
2135
- case 2:
2136
- this.updateTarget = DOM.setBooleanAttribute;
2137
- break;
2138
- case 3:
2139
- this.updateTarget = setProperty;
2140
- break;
2141
- case 4:
2142
- this.bind = this.bindContent;
2143
- this.updateTarget = updateContent;
2144
- break;
2145
- case 5:
2146
- this.updateTarget = updateTokenList;
2147
- break;
2148
- case 6:
2149
- this.bind = this.bindEvent;
2150
- this.updateTarget = eventTarget;
2151
- break;
2152
- default:
2153
- throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
2154
- }
2155
- }
2156
- return this;
2157
- }
2158
- /** @internal */
2159
- bindDefault(controller) {
2160
- var _a;
2161
- const target = controller.targets[this.nodeId];
2162
- const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
2163
- observer.target = target;
2164
- observer.controller = controller;
2165
- this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2166
- if (this.updateTarget === updateContent) {
2167
- controller.onUnbind(this);
2168
- }
2169
- }
2170
- /** @internal */
2171
- bindContent(controller) {
2172
- this.bindDefault(controller);
2173
- controller.onUnbind(this);
2174
- }
2175
- /** @internal */
2176
- bindEvent(controller) {
2177
- const target = controller.targets[this.nodeId];
2178
- target[this.data] = controller;
2179
- target.addEventListener(this.targetAspect, this, this.dataBinding.options);
2180
- }
2181
- /** @internal */
2182
- unbind(controller) {
2183
- const target = controller.targets[this.nodeId];
2184
- const view = target.$fastView;
2185
- if (view !== void 0 && view.isComposed) {
2186
- view.unbind();
2187
- view.needsBindOnly = true;
2188
- }
2189
- }
2190
- /** @internal */
2191
- handleEvent(event) {
2192
- const target = event.currentTarget;
2193
- ExecutionContext.setEvent(event);
2194
- const controller = target[this.data];
2195
- const result = this.dataBinding.evaluate(controller.source, controller.context);
2196
- ExecutionContext.setEvent(null);
2197
- if (result !== true) {
2198
- event.preventDefault();
2199
- }
2200
- }
2201
- /** @internal */
2202
- handleChange(binding, observer) {
2203
- const target = observer.target;
2204
- const controller = observer.controller;
2205
- this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2504
+ node) {
2505
+ super(message);
2506
+ this.factories = factories;
2507
+ this.node = node;
2206
2508
  }
2207
2509
  }
2208
- HTMLDirective.define(HTMLBindingDirective, { aspected: true });
2209
- /**
2210
- * Creates an standard binding.
2211
- * @param binding - The binding to refresh when changed.
2212
- * @param isVolatile - Indicates whether the binding is volatile or not.
2213
- * @returns A binding configuration.
2214
- * @public
2215
- */
2216
- function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
2217
- return new OnChangeBinding(binding, isVolatile);
2510
+ function isComment(node) {
2511
+ return node.nodeType === Node.COMMENT_NODE;
2512
+ }
2513
+ function isText(node) {
2514
+ return node.nodeType === Node.TEXT_NODE;
2218
2515
  }
2219
2516
  /**
2220
- * Creates a one time binding
2221
- * @param binding - The binding to refresh when signaled.
2222
- * @returns A binding configuration.
2223
- * @public
2517
+ * Returns a range object inclusive of all nodes including and between the
2518
+ * provided first and last node.
2519
+ * @param first - The first node
2520
+ * @param last - This last node
2521
+ * @returns
2224
2522
  */
2225
- function oneTime(binding) {
2226
- return new OneTimeBinding(binding);
2523
+ function createRangeForNodes(first, last) {
2524
+ const range = document.createRange();
2525
+ range.setStart(first, 0);
2526
+ // The lastIndex should be inclusive of the end of the lastChild. Obtain offset based
2527
+ // on usageNotes: https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd#usage_notes
2528
+ range.setEnd(last, isComment(last) || isText(last) ? last.data.length : last.childNodes.length);
2529
+ return range;
2530
+ }
2531
+ function isShadowRoot(node) {
2532
+ return node instanceof DocumentFragment && "mode" in node;
2227
2533
  }
2228
2534
  /**
2229
- * Creates an event listener binding.
2230
- * @param binding - The binding to invoke when the event is raised.
2231
- * @param options - Event listener options.
2232
- * @returns A binding configuration.
2233
- * @public
2535
+ * Maps {@link CompiledViewBehaviorFactory} ids to the corresponding node targets for the view.
2536
+ * @param firstNode - The first node of the view.
2537
+ * @param lastNode - The last node of the view.
2538
+ * @param factories - The Compiled View Behavior Factories that belong to the view.
2539
+ * @returns - A {@link ViewBehaviorTargets } object for the factories in the view.
2234
2540
  */
2235
- function listener(binding, options) {
2236
- const config = new OnChangeBinding(binding, false);
2237
- config.options = options;
2238
- return config;
2541
+ function buildViewBindingTargets(firstNode, lastNode, factories) {
2542
+ const range = createRangeForNodes(firstNode, lastNode);
2543
+ const treeRoot = range.commonAncestorContainer;
2544
+ const walker = document.createTreeWalker(treeRoot, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_COMMENT + NodeFilter.SHOW_TEXT, {
2545
+ acceptNode(node) {
2546
+ return range.comparePoint(node, 0) === 0
2547
+ ? NodeFilter.FILTER_ACCEPT
2548
+ : NodeFilter.FILTER_REJECT;
2549
+ },
2550
+ });
2551
+ const targets = {};
2552
+ const boundaries = {};
2553
+ let node = (walker.currentNode = firstNode);
2554
+ while (node !== null) {
2555
+ switch (node.nodeType) {
2556
+ case Node.ELEMENT_NODE: {
2557
+ targetElement(node, factories, targets);
2558
+ break;
2559
+ }
2560
+ case Node.COMMENT_NODE: {
2561
+ targetComment(node, walker, factories, targets, boundaries);
2562
+ break;
2563
+ }
2564
+ }
2565
+ node = walker.nextNode();
2566
+ }
2567
+ range.detach();
2568
+ return { targets, boundaries };
2569
+ }
2570
+ function targetElement(node, factories, targets) {
2571
+ // Check for attributes and map any factories.
2572
+ const attrFactoryIds = HydrationMarkup.parseAttributeBinding(node);
2573
+ if (attrFactoryIds !== null) {
2574
+ for (const id of attrFactoryIds) {
2575
+ if (!factories[id]) {
2576
+ 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);
2577
+ }
2578
+ targetFactory(factories[id], node, targets);
2579
+ }
2580
+ node.removeAttribute(HydrationMarkup.attributeMarkerName);
2581
+ }
2582
+ }
2583
+ function targetComment(node, walker, factories, targets, boundaries) {
2584
+ if (HydrationMarkup.isElementBoundaryStartMarker(node)) {
2585
+ skipToElementBoundaryEndMarker(node, walker);
2586
+ return;
2587
+ }
2588
+ if (HydrationMarkup.isContentBindingStartMarker(node.data)) {
2589
+ const parsed = HydrationMarkup.parseContentBindingStartMarker(node.data);
2590
+ if (parsed === null) {
2591
+ return;
2592
+ }
2593
+ const [index, id] = parsed;
2594
+ const factory = factories[index];
2595
+ const nodes = [];
2596
+ let current = walker.nextSibling();
2597
+ node.data = "";
2598
+ const first = current;
2599
+ // Search for the binding end marker that closes the binding.
2600
+ while (current !== null) {
2601
+ if (isComment(current)) {
2602
+ const parsed = HydrationMarkup.parseContentBindingEndMarker(current.data);
2603
+ if (parsed && parsed[1] === id) {
2604
+ break;
2605
+ }
2606
+ }
2607
+ nodes.push(current);
2608
+ current = walker.nextSibling();
2609
+ }
2610
+ if (current === null) {
2611
+ const root = node.getRootNode();
2612
+ throw new Error(`Error hydrating Comment node inside "${isShadowRoot(root) ? root.host.nodeName : root.nodeName}".`);
2613
+ }
2614
+ current.data = "";
2615
+ if (nodes.length === 1 && isText(nodes[0])) {
2616
+ targetFactory(factory, nodes[0], targets);
2617
+ }
2618
+ else {
2619
+ // If current === first, it means there is no content in
2620
+ // the view. This happens when a `when` directive evaluates false,
2621
+ // or whenever a content binding returns null or undefined.
2622
+ // In that case, there will never be any content
2623
+ // to hydrate and Binding can simply create a HTMLView
2624
+ // whenever it needs to.
2625
+ if (current !== first && current.previousSibling !== null) {
2626
+ boundaries[factory.targetNodeId] = {
2627
+ first,
2628
+ last: current.previousSibling,
2629
+ };
2630
+ }
2631
+ // Binding evaluates to null / undefined or a template.
2632
+ // If binding revaluates to string, it will replace content in target
2633
+ // So we always insert a text node to ensure that
2634
+ // text content binding will be written to this text node instead of comment
2635
+ const dummyTextNode = current.parentNode.insertBefore(document.createTextNode(""), current);
2636
+ targetFactory(factory, dummyTextNode, targets);
2637
+ }
2638
+ }
2239
2639
  }
2240
2640
  /**
2241
- * Normalizes the input value into a binding.
2242
- * @param value - The value to create the default binding for.
2243
- * @returns A binding configuration for the provided value.
2244
- * @public
2641
+ * Moves TreeWalker to element boundary end marker
2642
+ * @param node - element boundary start marker node
2643
+ * @param walker - tree walker
2245
2644
  */
2246
- function normalizeBinding(value) {
2247
- return isFunction(value)
2248
- ? bind(value)
2249
- : value instanceof Binding
2250
- ? value
2251
- : oneTime(() => value);
2645
+ function skipToElementBoundaryEndMarker(node, walker) {
2646
+ const id = HydrationMarkup.parseElementBoundaryStartMarker(node.data);
2647
+ let current = walker.nextSibling();
2648
+ while (current !== null) {
2649
+ if (isComment(current)) {
2650
+ const parsed = HydrationMarkup.parseElementBoundaryEndMarker(current.data);
2651
+ if (parsed && parsed === id) {
2652
+ break;
2653
+ }
2654
+ }
2655
+ current = walker.nextSibling();
2656
+ }
2657
+ }
2658
+ function targetFactory(factory, node, targets) {
2659
+ if (factory.targetNodeId === undefined) {
2660
+ // Dev error, this shouldn't ever be thrown
2661
+ throw new Error("Factory could not be target to the node");
2662
+ }
2663
+ targets[factory.targetNodeId] = node;
2252
2664
  }
2253
2665
 
2666
+ var _a;
2254
2667
  function removeNodeSequence(firstNode, lastNode) {
2255
2668
  const parent = firstNode.parentNode;
2256
2669
  let current = firstNode;
2257
2670
  let next;
2258
2671
  while (current !== lastNode) {
2259
2672
  next = current.nextSibling;
2673
+ if (!next) {
2674
+ throw new Error(`Unmatched first/last child inside "${lastNode.getRootNode().host.nodeName}".`);
2675
+ }
2260
2676
  parent.removeChild(current);
2261
2677
  current = next;
2262
2678
  }
2263
2679
  parent.removeChild(lastNode);
2264
2680
  }
2265
- /**
2266
- * The standard View implementation, which also implements ElementView and SyntheticView.
2267
- * @public
2268
- */
2269
- class HTMLView {
2270
- /**
2271
- * Constructs an instance of HTMLView.
2272
- * @param fragment - The html fragment that contains the nodes for this view.
2273
- * @param behaviors - The behaviors to be applied to this view.
2274
- */
2275
- constructor(fragment, factories, targets) {
2276
- this.fragment = fragment;
2277
- this.factories = factories;
2278
- this.targets = targets;
2279
- this.behaviors = null;
2280
- this.unbindables = [];
2281
- /**
2282
- * The data that the view is bound to.
2283
- */
2284
- this.source = null;
2285
- /**
2286
- * Indicates whether the controller is bound.
2287
- */
2288
- this.isBound = false;
2289
- /**
2290
- * Indicates how the source's lifetime relates to the controller's lifetime.
2291
- */
2292
- this.sourceLifetime = SourceLifetime.unknown;
2293
- /**
2294
- * The execution context the view is running within.
2295
- */
2296
- this.context = this;
2681
+ class DefaultExecutionContext {
2682
+ constructor() {
2297
2683
  /**
2298
2684
  * The index of the current item within a repeat context.
2299
2685
  */
@@ -2302,8 +2688,6 @@ class HTMLView {
2302
2688
  * The length of the current collection within a repeat context.
2303
2689
  */
2304
2690
  this.length = 0;
2305
- this.firstChild = fragment.firstChild;
2306
- this.lastChild = fragment.lastChild;
2307
2691
  }
2308
2692
  /**
2309
2693
  * The current event within an event handler.
@@ -2358,6 +2742,43 @@ class HTMLView {
2358
2742
  eventTarget() {
2359
2743
  return this.event.target;
2360
2744
  }
2745
+ }
2746
+ /**
2747
+ * The standard View implementation, which also implements ElementView and SyntheticView.
2748
+ * @public
2749
+ */
2750
+ class HTMLView extends DefaultExecutionContext {
2751
+ /**
2752
+ * Constructs an instance of HTMLView.
2753
+ * @param fragment - The html fragment that contains the nodes for this view.
2754
+ * @param behaviors - The behaviors to be applied to this view.
2755
+ */
2756
+ constructor(fragment, factories, targets) {
2757
+ super();
2758
+ this.fragment = fragment;
2759
+ this.factories = factories;
2760
+ this.targets = targets;
2761
+ this.behaviors = null;
2762
+ this.unbindables = [];
2763
+ /**
2764
+ * The data that the view is bound to.
2765
+ */
2766
+ this.source = null;
2767
+ /**
2768
+ * Indicates whether the controller is bound.
2769
+ */
2770
+ this.isBound = false;
2771
+ /**
2772
+ * Indicates how the source's lifetime relates to the controller's lifetime.
2773
+ */
2774
+ this.sourceLifetime = SourceLifetime.unknown;
2775
+ /**
2776
+ * The execution context the view is running within.
2777
+ */
2778
+ this.context = this;
2779
+ this.firstChild = fragment.firstChild;
2780
+ this.lastChild = fragment.lastChild;
2781
+ }
2361
2782
  /**
2362
2783
  * Appends the view's DOM nodes to the referenced node.
2363
2784
  * @param node - The parent node to append the view's DOM nodes to.
@@ -2449,9 +2870,219 @@ class HTMLView {
2449
2870
  }
2450
2871
  this.isBound = true;
2451
2872
  }
2452
- /**
2453
- * Unbinds a view's behaviors from its binding source.
2454
- */
2873
+ /**
2874
+ * Unbinds a view's behaviors from its binding source.
2875
+ */
2876
+ unbind() {
2877
+ if (!this.isBound || this.source === null) {
2878
+ return;
2879
+ }
2880
+ this.evaluateUnbindables();
2881
+ this.source = null;
2882
+ this.context = this;
2883
+ this.isBound = false;
2884
+ }
2885
+ evaluateUnbindables() {
2886
+ const unbindables = this.unbindables;
2887
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
2888
+ unbindables[i].unbind(this);
2889
+ }
2890
+ unbindables.length = 0;
2891
+ }
2892
+ /**
2893
+ * Efficiently disposes of a contiguous range of synthetic view instances.
2894
+ * @param views - A contiguous range of views to be disposed.
2895
+ */
2896
+ static disposeContiguousBatch(views) {
2897
+ if (views.length === 0) {
2898
+ return;
2899
+ }
2900
+ removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
2901
+ for (let i = 0, ii = views.length; i < ii; ++i) {
2902
+ views[i].unbind();
2903
+ }
2904
+ }
2905
+ }
2906
+ makeSerializationNoop(HTMLView);
2907
+ Observable.defineProperty(HTMLView.prototype, "index");
2908
+ Observable.defineProperty(HTMLView.prototype, "length");
2909
+ const HydrationStage = {
2910
+ unhydrated: "unhydrated",
2911
+ hydrating: "hydrating",
2912
+ hydrated: "hydrated",
2913
+ };
2914
+ /** @public */
2915
+ class HydrationBindingError extends Error {
2916
+ constructor(
2917
+ /**
2918
+ * The error message
2919
+ */
2920
+ message,
2921
+ /**
2922
+ * The factory that was unable to be bound
2923
+ */
2924
+ factory,
2925
+ /**
2926
+ * A DocumentFragment containing a clone of the
2927
+ * view's Nodes.
2928
+ */
2929
+ fragment,
2930
+ /**
2931
+ * String representation of the HTML in the template that
2932
+ * threw the binding error.
2933
+ */
2934
+ templateString) {
2935
+ super(message);
2936
+ this.factory = factory;
2937
+ this.fragment = fragment;
2938
+ this.templateString = templateString;
2939
+ }
2940
+ }
2941
+ class HydrationView extends DefaultExecutionContext {
2942
+ constructor(firstChild, lastChild, sourceTemplate, hostBindingTarget) {
2943
+ super();
2944
+ this.firstChild = firstChild;
2945
+ this.lastChild = lastChild;
2946
+ this.sourceTemplate = sourceTemplate;
2947
+ this.hostBindingTarget = hostBindingTarget;
2948
+ this[_a] = Hydratable;
2949
+ this.context = this;
2950
+ this.source = null;
2951
+ this.isBound = false;
2952
+ this.sourceLifetime = SourceLifetime.unknown;
2953
+ this.unbindables = [];
2954
+ this.fragment = null;
2955
+ this.behaviors = null;
2956
+ this._hydrationStage = HydrationStage.unhydrated;
2957
+ this._bindingViewBoundaries = {};
2958
+ this._targets = {};
2959
+ this.factories = sourceTemplate.compile().factories;
2960
+ }
2961
+ get hydrationStage() {
2962
+ return this._hydrationStage;
2963
+ }
2964
+ get targets() {
2965
+ return this._targets;
2966
+ }
2967
+ get bindingViewBoundaries() {
2968
+ return this._bindingViewBoundaries;
2969
+ }
2970
+ /**
2971
+ * no-op. Hydrated views are don't need to be moved from a documentFragment
2972
+ * to the target node.
2973
+ */
2974
+ insertBefore(node) {
2975
+ // No-op in cases where this is called before the view is removed,
2976
+ // because the nodes will already be in the document and just need hydrating.
2977
+ if (this.fragment === null) {
2978
+ return;
2979
+ }
2980
+ if (this.fragment.hasChildNodes()) {
2981
+ node.parentNode.insertBefore(this.fragment, node);
2982
+ }
2983
+ else {
2984
+ const end = this.lastChild;
2985
+ if (node.previousSibling === end)
2986
+ return;
2987
+ const parentNode = node.parentNode;
2988
+ let current = this.firstChild;
2989
+ let next;
2990
+ while (current !== end) {
2991
+ next = current.nextSibling;
2992
+ parentNode.insertBefore(current, node);
2993
+ current = next;
2994
+ }
2995
+ parentNode.insertBefore(end, node);
2996
+ }
2997
+ }
2998
+ /**
2999
+ * Appends the view to a node. In cases where this is called before the
3000
+ * view has been removed, the method will no-op.
3001
+ * @param node - the node to append the view to.
3002
+ */
3003
+ appendTo(node) {
3004
+ if (this.fragment !== null) {
3005
+ node.appendChild(this.fragment);
3006
+ }
3007
+ }
3008
+ remove() {
3009
+ const fragment = this.fragment || (this.fragment = document.createDocumentFragment());
3010
+ const end = this.lastChild;
3011
+ let current = this.firstChild;
3012
+ let next;
3013
+ while (current !== end) {
3014
+ next = current.nextSibling;
3015
+ if (!next) {
3016
+ throw new Error(`Unmatched first/last child inside "${end.getRootNode().host.nodeName}".`);
3017
+ }
3018
+ fragment.appendChild(current);
3019
+ current = next;
3020
+ }
3021
+ fragment.appendChild(end);
3022
+ }
3023
+ bind(source, context = this) {
3024
+ var _b, _c;
3025
+ if (this.hydrationStage !== HydrationStage.hydrated) {
3026
+ this._hydrationStage = HydrationStage.hydrating;
3027
+ }
3028
+ if (this.source === source) {
3029
+ return;
3030
+ }
3031
+ let behaviors = this.behaviors;
3032
+ if (behaviors === null) {
3033
+ this.source = source;
3034
+ this.context = context;
3035
+ try {
3036
+ const { targets, boundaries } = buildViewBindingTargets(this.firstChild, this.lastChild, this.factories);
3037
+ this._targets = targets;
3038
+ this._bindingViewBoundaries = boundaries;
3039
+ }
3040
+ catch (error) {
3041
+ if (error instanceof HydrationTargetElementError) {
3042
+ let templateString = this.sourceTemplate.html;
3043
+ if (typeof templateString !== "string") {
3044
+ templateString = templateString.innerHTML;
3045
+ }
3046
+ error.templateString = templateString;
3047
+ }
3048
+ throw error;
3049
+ }
3050
+ this.behaviors = behaviors = new Array(this.factories.length);
3051
+ const factories = this.factories;
3052
+ for (let i = 0, ii = factories.length; i < ii; ++i) {
3053
+ const factory = factories[i];
3054
+ if (factory.targetNodeId === "h" && this.hostBindingTarget) {
3055
+ targetFactory(factory, this.hostBindingTarget, this._targets);
3056
+ }
3057
+ // If the binding has been targeted or it is a host binding and the view has a hostBindingTarget
3058
+ if (factory.targetNodeId in this.targets) {
3059
+ const behavior = factory.createBehavior();
3060
+ behavior.bind(this);
3061
+ behaviors[i] = behavior;
3062
+ }
3063
+ else {
3064
+ let templateString = this.sourceTemplate.html;
3065
+ if (typeof templateString !== "string") {
3066
+ templateString = templateString.innerHTML;
3067
+ }
3068
+ 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);
3069
+ }
3070
+ }
3071
+ }
3072
+ else {
3073
+ if (this.source !== null) {
3074
+ this.evaluateUnbindables();
3075
+ }
3076
+ this.isBound = false;
3077
+ this.source = source;
3078
+ this.context = context;
3079
+ for (let i = 0, ii = behaviors.length; i < ii; ++i) {
3080
+ behaviors[i].bind(this);
3081
+ }
3082
+ }
3083
+ this.isBound = true;
3084
+ this._hydrationStage = HydrationStage.hydrated;
3085
+ }
2455
3086
  unbind() {
2456
3087
  if (!this.isBound || this.source === null) {
2457
3088
  return;
@@ -2461,6 +3092,17 @@ class HTMLView {
2461
3092
  this.context = this;
2462
3093
  this.isBound = false;
2463
3094
  }
3095
+ /**
3096
+ * Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
3097
+ * Once a view has been disposed, it cannot be inserted or bound again.
3098
+ */
3099
+ dispose() {
3100
+ removeNodeSequence(this.firstChild, this.lastChild);
3101
+ this.unbind();
3102
+ }
3103
+ onUnbind(behavior) {
3104
+ this.unbindables.push(behavior);
3105
+ }
2464
3106
  evaluateUnbindables() {
2465
3107
  const unbindables = this.unbindables;
2466
3108
  for (let i = 0, ii = unbindables.length; i < ii; ++i) {
@@ -2468,22 +3110,220 @@ class HTMLView {
2468
3110
  }
2469
3111
  unbindables.length = 0;
2470
3112
  }
3113
+ }
3114
+ _a = Hydratable;
3115
+ makeSerializationNoop(HydrationView);
3116
+
3117
+ function isContentTemplate(value) {
3118
+ return value.create !== undefined;
3119
+ }
3120
+ function updateContent(target, aspect, value, controller) {
3121
+ // If there's no actual value, then this equates to the
3122
+ // empty string for the purposes of content bindings.
3123
+ if (value === null || value === undefined) {
3124
+ value = "";
3125
+ }
3126
+ // If the value has a "create" method, then it's a ContentTemplate.
3127
+ if (isContentTemplate(value)) {
3128
+ target.textContent = "";
3129
+ let view = target.$fastView;
3130
+ // If there's no previous view that we might be able to
3131
+ // reuse then create a new view from the template.
3132
+ if (view === void 0) {
3133
+ if (isHydratable(controller) &&
3134
+ isHydratable(value) &&
3135
+ controller.bindingViewBoundaries[this.targetNodeId] !== undefined &&
3136
+ controller.hydrationStage !== HydrationStage.hydrated) {
3137
+ const viewNodes = controller.bindingViewBoundaries[this.targetNodeId];
3138
+ view = value.hydrate(viewNodes.first, viewNodes.last);
3139
+ }
3140
+ else {
3141
+ view = value.create();
3142
+ }
3143
+ }
3144
+ else {
3145
+ // If there is a previous view, but it wasn't created
3146
+ // from the same template as the new value, then we
3147
+ // need to remove the old view if it's still in the DOM
3148
+ // and create a new view from the template.
3149
+ if (target.$fastTemplate !== value) {
3150
+ if (view.isComposed) {
3151
+ view.remove();
3152
+ view.unbind();
3153
+ }
3154
+ view = value.create();
3155
+ }
3156
+ }
3157
+ // It's possible that the value is the same as the previous template
3158
+ // and that there's actually no need to compose it.
3159
+ if (!view.isComposed) {
3160
+ view.isComposed = true;
3161
+ view.bind(controller.source, controller.context);
3162
+ view.insertBefore(target);
3163
+ target.$fastView = view;
3164
+ target.$fastTemplate = value;
3165
+ }
3166
+ else if (view.needsBindOnly) {
3167
+ view.needsBindOnly = false;
3168
+ view.bind(controller.source, controller.context);
3169
+ }
3170
+ }
3171
+ else {
3172
+ const view = target.$fastView;
3173
+ // If there is a view and it's currently composed into
3174
+ // the DOM, then we need to remove it.
3175
+ if (view !== void 0 && view.isComposed) {
3176
+ view.isComposed = false;
3177
+ view.remove();
3178
+ if (view.needsBindOnly) {
3179
+ view.needsBindOnly = false;
3180
+ }
3181
+ else {
3182
+ view.unbind();
3183
+ }
3184
+ }
3185
+ target.textContent = value;
3186
+ }
3187
+ }
3188
+ function updateTokenList(target, aspect, value) {
3189
+ var _a;
3190
+ const lookup = `${this.id}-t`;
3191
+ const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
3192
+ const classVersions = state.cv;
3193
+ let version = state.v;
3194
+ const tokenList = target[aspect];
3195
+ // Add the classes, tracking the version at which they were added.
3196
+ if (value !== null && value !== undefined && value.length) {
3197
+ const names = value.split(/\s+/);
3198
+ for (let i = 0, ii = names.length; i < ii; ++i) {
3199
+ const currentName = names[i];
3200
+ if (currentName === "") {
3201
+ continue;
3202
+ }
3203
+ classVersions[currentName] = version;
3204
+ tokenList.add(currentName);
3205
+ }
3206
+ }
3207
+ state.v = version + 1;
3208
+ // If this is the first call to add classes, there's no need to remove old ones.
3209
+ if (version === 0) {
3210
+ return;
3211
+ }
3212
+ // Remove classes from the previous version.
3213
+ version -= 1;
3214
+ for (const name in classVersions) {
3215
+ if (classVersions[name] === version) {
3216
+ tokenList.remove(name);
3217
+ }
3218
+ }
3219
+ }
3220
+ const sinkLookup = {
3221
+ [DOMAspect.attribute]: DOM.setAttribute,
3222
+ [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
3223
+ [DOMAspect.property]: (t, a, v) => (t[a] = v),
3224
+ [DOMAspect.content]: updateContent,
3225
+ [DOMAspect.tokenList]: updateTokenList,
3226
+ [DOMAspect.event]: () => void 0,
3227
+ };
3228
+ /**
3229
+ * A directive that applies bindings.
3230
+ * @public
3231
+ */
3232
+ class HTMLBindingDirective {
2471
3233
  /**
2472
- * Efficiently disposes of a contiguous range of synthetic view instances.
2473
- * @param views - A contiguous range of views to be disposed.
3234
+ * Creates an instance of HTMLBindingDirective.
3235
+ * @param dataBinding - The binding configuration to apply.
2474
3236
  */
2475
- static disposeContiguousBatch(views) {
2476
- if (views.length === 0) {
2477
- return;
3237
+ constructor(dataBinding) {
3238
+ this.dataBinding = dataBinding;
3239
+ this.updateTarget = null;
3240
+ /**
3241
+ * The type of aspect to target.
3242
+ */
3243
+ this.aspectType = DOMAspect.content;
3244
+ }
3245
+ /**
3246
+ * Creates HTML to be used within a template.
3247
+ * @param add - Can be used to add behavior factories to a template.
3248
+ */
3249
+ createHTML(add) {
3250
+ return Markup.interpolation(add(this));
3251
+ }
3252
+ /**
3253
+ * Creates a behavior.
3254
+ */
3255
+ createBehavior() {
3256
+ var _a;
3257
+ if (this.updateTarget === null) {
3258
+ const sink = sinkLookup[this.aspectType];
3259
+ const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
3260
+ if (!sink) {
3261
+ throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
3262
+ }
3263
+ this.data = `${this.id}-d`;
3264
+ this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
2478
3265
  }
2479
- removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
2480
- for (let i = 0, ii = views.length; i < ii; ++i) {
2481
- views[i].unbind();
3266
+ return this;
3267
+ }
3268
+ /** @internal */
3269
+ bind(controller) {
3270
+ var _a;
3271
+ const target = controller.targets[this.targetNodeId];
3272
+ const isHydrating = isHydratable(controller) &&
3273
+ controller.hydrationStage &&
3274
+ controller.hydrationStage !== HydrationStage.hydrated;
3275
+ switch (this.aspectType) {
3276
+ case DOMAspect.event:
3277
+ target[this.data] = controller;
3278
+ target.addEventListener(this.targetAspect, this, this.dataBinding.options);
3279
+ break;
3280
+ case DOMAspect.content:
3281
+ controller.onUnbind(this);
3282
+ // intentional fall through
3283
+ default:
3284
+ const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
3285
+ observer.target = target;
3286
+ observer.controller = controller;
3287
+ if (isHydrating &&
3288
+ (this.aspectType === DOMAspect.attribute ||
3289
+ this.aspectType === DOMAspect.booleanAttribute)) {
3290
+ observer.bind(controller);
3291
+ // Skip updating target during bind for attributes
3292
+ break;
3293
+ }
3294
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
3295
+ break;
3296
+ }
3297
+ }
3298
+ /** @internal */
3299
+ unbind(controller) {
3300
+ const target = controller.targets[this.targetNodeId];
3301
+ const view = target.$fastView;
3302
+ if (view !== void 0 && view.isComposed) {
3303
+ view.unbind();
3304
+ view.needsBindOnly = true;
2482
3305
  }
2483
3306
  }
3307
+ /** @internal */
3308
+ handleEvent(event) {
3309
+ const controller = event.currentTarget[this.data];
3310
+ if (controller.isBound) {
3311
+ ExecutionContext.setEvent(event);
3312
+ const result = this.dataBinding.evaluate(controller.source, controller.context);
3313
+ ExecutionContext.setEvent(null);
3314
+ if (result !== true) {
3315
+ event.preventDefault();
3316
+ }
3317
+ }
3318
+ }
3319
+ /** @internal */
3320
+ handleChange(binding, observer) {
3321
+ const target = observer.target;
3322
+ const controller = observer.controller;
3323
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
3324
+ }
2484
3325
  }
2485
- Observable.defineProperty(HTMLView.prototype, "index");
2486
- Observable.defineProperty(HTMLView.prototype, "length");
3326
+ HTMLDirective.define(HTMLBindingDirective, { aspected: true });
2487
3327
 
2488
3328
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
2489
3329
  const descriptorCache = {};
@@ -2509,20 +3349,25 @@ const warningHost = new Proxy(document.createElement("div"), {
2509
3349
  },
2510
3350
  });
2511
3351
  class CompilationContext {
2512
- constructor(fragment, directives) {
3352
+ constructor(fragment, directives, policy) {
2513
3353
  this.fragment = fragment;
2514
3354
  this.directives = directives;
3355
+ this.policy = policy;
2515
3356
  this.proto = null;
2516
3357
  this.nodeIds = new Set();
2517
3358
  this.descriptors = {};
2518
3359
  this.factories = [];
2519
3360
  }
2520
- addFactory(factory, parentId, nodeId, targetIndex) {
3361
+ addFactory(factory, parentId, nodeId, targetIndex, tagName) {
3362
+ var _a, _b;
2521
3363
  if (!this.nodeIds.has(nodeId)) {
2522
3364
  this.nodeIds.add(nodeId);
2523
3365
  this.addTargetDescriptor(parentId, nodeId, targetIndex);
2524
3366
  }
2525
- factory.nodeId = nodeId;
3367
+ factory.id = (_a = factory.id) !== null && _a !== void 0 ? _a : nextId();
3368
+ factory.targetNodeId = nodeId;
3369
+ factory.targetTagName = tagName;
3370
+ factory.policy = (_b = factory.policy) !== null && _b !== void 0 ? _b : this.policy;
2526
3371
  this.factories.push(factory);
2527
3372
  }
2528
3373
  freeze() {
@@ -2575,19 +3420,19 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
2575
3420
  let result = null;
2576
3421
  if (parseResult === null) {
2577
3422
  if (includeBasicValues) {
2578
- result = new HTMLBindingDirective(oneTime(() => attrValue));
2579
- Aspect.assign(result, attr.name);
3423
+ result = new HTMLBindingDirective(oneTime(() => attrValue, context.policy));
3424
+ HTMLDirective.assignAspect(result, attr.name);
2580
3425
  }
2581
3426
  }
2582
3427
  else {
2583
3428
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2584
- result = Compiler.aggregate(parseResult);
3429
+ result = Compiler.aggregate(parseResult, context.policy);
2585
3430
  }
2586
3431
  if (result !== null) {
2587
3432
  node.removeAttributeNode(attr);
2588
3433
  i--;
2589
3434
  ii--;
2590
- context.addFactory(result, parentId, nodeId, nodeIndex);
3435
+ context.addFactory(result, parentId, nodeId, nodeIndex, node.tagName);
2591
3436
  }
2592
3437
  }
2593
3438
  }
@@ -2612,8 +3457,8 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
2612
3457
  }
2613
3458
  else {
2614
3459
  currentNode.textContent = " ";
2615
- Aspect.assign(currentPart);
2616
- context.addFactory(currentPart, parentId, nodeId, nodeIndex);
3460
+ HTMLDirective.assignAspect(currentPart);
3461
+ context.addFactory(currentPart, parentId, nodeId, nodeIndex, null);
2617
3462
  }
2618
3463
  lastNode = currentNode;
2619
3464
  }
@@ -2645,7 +3490,7 @@ function compileNode(context, parentId, node, nodeIndex) {
2645
3490
  if (parts !== null) {
2646
3491
  context.addFactory(
2647
3492
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2648
- Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
3493
+ Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
2649
3494
  }
2650
3495
  break;
2651
3496
  }
@@ -2659,45 +3504,28 @@ function isMarker(node, directives) {
2659
3504
  Parser.parse(node.data, directives) !== null);
2660
3505
  }
2661
3506
  const templateTag = "TEMPLATE";
2662
- const policyOptions = { createHTML: html => html };
2663
- let htmlPolicy = globalThis.trustedTypes
2664
- ? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
2665
- : policyOptions;
2666
- const fastHTMLPolicy = htmlPolicy;
2667
3507
  /**
2668
3508
  * Common APIs related to compilation.
2669
3509
  * @public
2670
3510
  */
2671
3511
  const Compiler = {
2672
- /**
2673
- * Sets the HTML trusted types policy used by the compiler.
2674
- * @param policy - The policy to set for HTML.
2675
- * @remarks
2676
- * This API can only be called once, for security reasons. It should be
2677
- * called by the application developer at the start of their program.
2678
- */
2679
- setHTMLPolicy(policy) {
2680
- if (htmlPolicy !== fastHTMLPolicy) {
2681
- throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
2682
- }
2683
- htmlPolicy = policy;
2684
- },
2685
3512
  /**
2686
3513
  * Compiles a template and associated directives into a compilation
2687
3514
  * result which can be used to create views.
2688
3515
  * @param html - The html string or template element to compile.
2689
- * @param directives - The directives referenced by the template.
3516
+ * @param factories - The behavior factories referenced by the template.
3517
+ * @param policy - The security policy to compile the html with.
2690
3518
  * @remarks
2691
3519
  * The template that is provided for compilation is altered in-place
2692
3520
  * and cannot be compiled again. If the original template must be preserved,
2693
3521
  * it is recommended that you clone the original and pass the clone to this API.
2694
3522
  * @public
2695
3523
  */
2696
- compile(html, directives) {
3524
+ compile(html, factories, policy = DOM.policy) {
2697
3525
  let template;
2698
3526
  if (isString(html)) {
2699
3527
  template = document.createElement(templateTag);
2700
- template.innerHTML = htmlPolicy.createHTML(html);
3528
+ template.innerHTML = policy.createHTML(html);
2701
3529
  const fec = template.content.firstElementChild;
2702
3530
  if (fec !== null && fec.tagName === templateTag) {
2703
3531
  template = fec;
@@ -2706,20 +3534,23 @@ const Compiler = {
2706
3534
  else {
2707
3535
  template = html;
2708
3536
  }
3537
+ if (!template.content.firstChild && !template.content.lastChild) {
3538
+ template.content.appendChild(document.createComment(""));
3539
+ }
2709
3540
  // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
2710
3541
  const fragment = document.adoptNode(template.content);
2711
- const context = new CompilationContext(fragment, directives);
3542
+ const context = new CompilationContext(fragment, factories, policy);
2712
3543
  compileAttributes(context, "", template, /* host */ "h", 0, true);
2713
3544
  if (
2714
3545
  // If the first node in a fragment is a marker, that means it's an unstable first node,
2715
3546
  // because something like a when, repeat, etc. could add nodes before the marker.
2716
3547
  // To mitigate this, we insert a stable first node. However, if we insert a node,
2717
3548
  // that will alter the result of the TreeWalker. So, we also need to offset the target index.
2718
- isMarker(fragment.firstChild, directives) ||
3549
+ isMarker(fragment.firstChild, factories) ||
2719
3550
  // Or if there is only one node and a directive, it means the template's content
2720
3551
  // is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
2721
3552
  // the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
2722
- (fragment.childNodes.length === 1 && Object.keys(directives).length > 0)) {
3553
+ (fragment.childNodes.length === 1 && Object.keys(factories).length > 0)) {
2723
3554
  fragment.insertBefore(document.createComment(""), fragment.firstChild);
2724
3555
  }
2725
3556
  compileChildren(context, fragment, /* root */ "r");
@@ -2738,23 +3569,24 @@ const Compiler = {
2738
3569
  * Aggregates an array of strings and directives into a single directive.
2739
3570
  * @param parts - A heterogeneous array of static strings interspersed with
2740
3571
  * directives.
3572
+ * @param policy - The security policy to use with the aggregated bindings.
2741
3573
  * @returns A single inline directive that aggregates the behavior of all the parts.
2742
3574
  */
2743
- aggregate(parts) {
3575
+ aggregate(parts, policy = DOM.policy) {
2744
3576
  if (parts.length === 1) {
2745
3577
  return parts[0];
2746
3578
  }
2747
3579
  let sourceAspect;
2748
- let binding;
2749
3580
  let isVolatile = false;
3581
+ let bindingPolicy = void 0;
2750
3582
  const partCount = parts.length;
2751
3583
  const finalParts = parts.map((x) => {
2752
3584
  if (isString(x)) {
2753
3585
  return () => x;
2754
3586
  }
2755
3587
  sourceAspect = x.sourceAspect || sourceAspect;
2756
- binding = x.dataBinding || binding;
2757
3588
  isVolatile = isVolatile || x.dataBinding.isVolatile;
3589
+ bindingPolicy = bindingPolicy || x.dataBinding.policy;
2758
3590
  return x.dataBinding.evaluate;
2759
3591
  });
2760
3592
  const expression = (scope, context) => {
@@ -2764,14 +3596,56 @@ const Compiler = {
2764
3596
  }
2765
3597
  return output;
2766
3598
  };
2767
- binding.evaluate = expression;
2768
- binding.isVolatile = isVolatile;
2769
- const directive = new HTMLBindingDirective(binding);
2770
- Aspect.assign(directive, sourceAspect);
3599
+ const directive = new HTMLBindingDirective(oneWay(expression, bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy, isVolatile));
3600
+ HTMLDirective.assignAspect(directive, sourceAspect);
2771
3601
  return directive;
2772
3602
  },
2773
3603
  };
2774
3604
 
3605
+ // Much thanks to LitHTML for working this out!
3606
+ const lastAttributeNameRegex =
3607
+ /* eslint-disable-next-line no-control-regex, max-len */
3608
+ /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
3609
+ const noFactories = Object.create(null);
3610
+ /**
3611
+ * Inlines a template into another template.
3612
+ * @public
3613
+ */
3614
+ class InlineTemplateDirective {
3615
+ /**
3616
+ * Creates an instance of InlineTemplateDirective.
3617
+ * @param template - The template to inline.
3618
+ */
3619
+ constructor(html, factories = noFactories) {
3620
+ this.html = html;
3621
+ this.factories = factories;
3622
+ }
3623
+ /**
3624
+ * Creates HTML to be used within a template.
3625
+ * @param add - Can be used to add behavior factories to a template.
3626
+ */
3627
+ createHTML(add) {
3628
+ const factories = this.factories;
3629
+ for (const key in factories) {
3630
+ add(factories[key]);
3631
+ }
3632
+ return this.html;
3633
+ }
3634
+ }
3635
+ /**
3636
+ * An empty template partial.
3637
+ */
3638
+ InlineTemplateDirective.empty = new InlineTemplateDirective("");
3639
+ HTMLDirective.define(InlineTemplateDirective);
3640
+ function createHTML(value, prevString, add, definition = HTMLDirective.getForInstance(value)) {
3641
+ if (definition.aspected) {
3642
+ const match = lastAttributeNameRegex.exec(prevString);
3643
+ if (match !== null) {
3644
+ HTMLDirective.assignAspect(value, match[2]);
3645
+ }
3646
+ }
3647
+ return value.createHTML(add);
3648
+ }
2775
3649
  /**
2776
3650
  * A template capable of creating HTMLView instances or rendering directly to DOM.
2777
3651
  * @public
@@ -2781,21 +3655,53 @@ class ViewTemplate {
2781
3655
  * Creates an instance of ViewTemplate.
2782
3656
  * @param html - The html representing what this template will instantiate, including placeholders for directives.
2783
3657
  * @param factories - The directives that will be connected to placeholders in the html.
3658
+ * @param policy - The security policy to use when compiling this template.
2784
3659
  */
2785
- constructor(html, factories) {
3660
+ constructor(html, factories = {}, policy) {
3661
+ this.policy = policy;
2786
3662
  this.result = null;
2787
3663
  this.html = html;
2788
3664
  this.factories = factories;
2789
3665
  }
3666
+ /**
3667
+ * @internal
3668
+ */
3669
+ compile() {
3670
+ if (this.result === null) {
3671
+ this.result = Compiler.compile(this.html, this.factories, this.policy);
3672
+ }
3673
+ return this.result;
3674
+ }
2790
3675
  /**
2791
3676
  * Creates an HTMLView instance based on this template definition.
2792
3677
  * @param hostBindingTarget - The element that host behaviors will be bound to.
2793
3678
  */
2794
3679
  create(hostBindingTarget) {
2795
- if (this.result === null) {
2796
- this.result = Compiler.compile(this.html, this.factories);
3680
+ return this.compile().createView(hostBindingTarget);
3681
+ }
3682
+ /**
3683
+ * Returns a directive that can inline the template.
3684
+ */
3685
+ inline() {
3686
+ return new InlineTemplateDirective(isString(this.html) ? this.html : this.html.innerHTML, this.factories);
3687
+ }
3688
+ /**
3689
+ * Sets the DOMPolicy for this template.
3690
+ * @param policy - The policy to associated with this template.
3691
+ * @returns The modified template instance.
3692
+ * @remarks
3693
+ * The DOMPolicy can only be set once for a template and cannot be
3694
+ * set after the template is compiled.
3695
+ */
3696
+ withPolicy(policy) {
3697
+ if (this.result) {
3698
+ throw FAST.error(1208 /* Message.cannotSetTemplatePolicyAfterCompilation */);
2797
3699
  }
2798
- return this.result.createView(hostBindingTarget);
3700
+ if (this.policy) {
3701
+ throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
3702
+ }
3703
+ this.policy = policy;
3704
+ return this;
2799
3705
  }
2800
3706
  /**
2801
3707
  * Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
@@ -2810,18 +3716,49 @@ class ViewTemplate {
2810
3716
  view.appendTo(host);
2811
3717
  return view;
2812
3718
  }
2813
- }
2814
- // Much thanks to LitHTML for working this out!
2815
- const lastAttributeNameRegex =
2816
- /* eslint-disable-next-line no-control-regex */
2817
- /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
2818
- function createAspectedHTML(value, prevString, add) {
2819
- const match = lastAttributeNameRegex.exec(prevString);
2820
- if (match !== null) {
2821
- Aspect.assign(value, match[2]);
3719
+ /**
3720
+ * Creates a template based on a set of static strings and dynamic values.
3721
+ * @param strings - The static strings to create the template with.
3722
+ * @param values - The dynamic values to create the template with.
3723
+ * @param policy - The DOMPolicy to associated with the template.
3724
+ * @returns A ViewTemplate.
3725
+ * @remarks
3726
+ * This API should not be used directly under normal circumstances because constructing
3727
+ * a template in this way, if not done properly, can open up the application to XSS
3728
+ * attacks. When using this API, provide a strong DOMPolicy that can properly sanitize
3729
+ * and also be sure to manually sanitize all static strings particularly if they can
3730
+ * come from user input.
3731
+ */
3732
+ static create(strings, values, policy) {
3733
+ let html = "";
3734
+ const factories = Object.create(null);
3735
+ const add = (factory) => {
3736
+ var _a;
3737
+ const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
3738
+ factories[id] = factory;
3739
+ return id;
3740
+ };
3741
+ for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
3742
+ const currentString = strings[i];
3743
+ let currentValue = values[i];
3744
+ let definition;
3745
+ html += currentString;
3746
+ if (isFunction(currentValue)) {
3747
+ currentValue = new HTMLBindingDirective(oneWay(currentValue));
3748
+ }
3749
+ else if (currentValue instanceof Binding) {
3750
+ currentValue = new HTMLBindingDirective(currentValue);
3751
+ }
3752
+ else if (!(definition = HTMLDirective.getForInstance(currentValue))) {
3753
+ const staticValue = currentValue;
3754
+ currentValue = new HTMLBindingDirective(oneTime(() => staticValue));
3755
+ }
3756
+ html += createHTML(currentValue, currentString, add, definition);
3757
+ }
3758
+ return new ViewTemplate(html + strings[strings.length - 1], factories, policy);
2822
3759
  }
2823
- return value.createHTML(add);
2824
3760
  }
3761
+ makeSerializationNoop(ViewTemplate);
2825
3762
  /**
2826
3763
  * Transforms a template literal string into a ViewTemplate.
2827
3764
  * @param strings - The string fragments that are interpolated with the values.
@@ -2831,51 +3768,15 @@ function createAspectedHTML(value, prevString, add) {
2831
3768
  * other template instances, and Directive instances.
2832
3769
  * @public
2833
3770
  */
2834
- function html(strings, ...values) {
2835
- let html = "";
2836
- const factories = Object.create(null);
2837
- const add = (factory) => {
2838
- var _a;
2839
- const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
2840
- factories[id] = factory;
2841
- return id;
2842
- };
2843
- for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
2844
- const currentString = strings[i];
2845
- const currentValue = values[i];
2846
- let definition;
2847
- html += currentString;
2848
- if (isFunction(currentValue)) {
2849
- html += createAspectedHTML(new HTMLBindingDirective(bind(currentValue)), currentString, add);
2850
- }
2851
- else if (isString(currentValue)) {
2852
- const match = lastAttributeNameRegex.exec(currentString);
2853
- if (match !== null) {
2854
- const directive = new HTMLBindingDirective(oneTime(() => currentValue));
2855
- Aspect.assign(directive, match[2]);
2856
- html += directive.createHTML(add);
2857
- }
2858
- else {
2859
- html += currentValue;
2860
- }
2861
- }
2862
- else if (currentValue instanceof Binding) {
2863
- html += createAspectedHTML(new HTMLBindingDirective(currentValue), currentString, add);
2864
- }
2865
- else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
2866
- html += createAspectedHTML(new HTMLBindingDirective(oneTime(() => currentValue)), currentString, add);
2867
- }
2868
- else {
2869
- if (definition.aspected) {
2870
- html += createAspectedHTML(currentValue, currentString, add);
2871
- }
2872
- else {
2873
- html += currentValue.createHTML(add);
2874
- }
2875
- }
3771
+ const html = ((strings, ...values) => {
3772
+ if (Array.isArray(strings) && Array.isArray(strings.raw)) {
3773
+ return ViewTemplate.create(strings, values);
2876
3774
  }
2877
- return new ViewTemplate(html + strings[strings.length - 1], factories);
2878
- }
3775
+ throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
3776
+ });
3777
+ html.partial = (html) => {
3778
+ return new InlineTemplateDirective(html);
3779
+ };
2879
3780
 
2880
3781
  /**
2881
3782
  * The runtime behavior for template references.
@@ -2887,7 +3788,7 @@ class RefDirective extends StatelessAttachedAttributeDirective {
2887
3788
  * @param controller - The view controller that manages the lifecycle of this behavior.
2888
3789
  */
2889
3790
  bind(controller) {
2890
- controller.source[this.options] = controller.targets[this.nodeId];
3791
+ controller.source[this.options] = controller.targets[this.targetNodeId];
2891
3792
  }
2892
3793
  }
2893
3794
  HTMLDirective.define(RefDirective);
@@ -2898,19 +3799,26 @@ HTMLDirective.define(RefDirective);
2898
3799
  */
2899
3800
  const ref = (propertyName) => new RefDirective(propertyName);
2900
3801
 
3802
+ const noTemplate = () => null;
3803
+ function normalizeBinding(value) {
3804
+ return value === undefined ? noTemplate : isFunction(value) ? value : () => value;
3805
+ }
2901
3806
  /**
2902
3807
  * A directive that enables basic conditional rendering in a template.
2903
3808
  * @param condition - The condition to test for rendering.
2904
3809
  * @param templateOrTemplateBinding - The template or a binding that gets
2905
3810
  * the template to render when the condition is true.
3811
+ * @param elseTemplateOrTemplateBinding - Optional template or binding that that
3812
+ * gets the template to render when the conditional is false.
2906
3813
  * @public
2907
3814
  */
2908
- function when(condition, templateOrTemplateBinding) {
3815
+ function when(condition, templateOrTemplateBinding, elseTemplateOrTemplateBinding) {
2909
3816
  const dataBinding = isFunction(condition) ? condition : () => condition;
2910
- const templateBinding = isFunction(templateOrTemplateBinding)
2911
- ? templateOrTemplateBinding
2912
- : () => templateOrTemplateBinding;
2913
- return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
3817
+ const templateBinding = normalizeBinding(templateOrTemplateBinding);
3818
+ const elseBinding = normalizeBinding(elseTemplateOrTemplateBinding);
3819
+ return (source, context) => dataBinding(source, context)
3820
+ ? templateBinding(source, context)
3821
+ : elseBinding(source, context);
2914
3822
  }
2915
3823
 
2916
3824
  const defaultRepeatOptions = Object.freeze({
@@ -2929,6 +3837,19 @@ function bindWithPositioning(view, items, index, controller) {
2929
3837
  view.context.index = index;
2930
3838
  view.bind(items[index]);
2931
3839
  }
3840
+ function isCommentNode(node) {
3841
+ return node.nodeType === Node.COMMENT_NODE;
3842
+ }
3843
+ class HydrationRepeatError extends Error {
3844
+ constructor(
3845
+ /**
3846
+ * The error message
3847
+ */
3848
+ message, propertyBag) {
3849
+ super(message);
3850
+ this.propertyBag = propertyBag;
3851
+ }
3852
+ }
2932
3853
  /**
2933
3854
  * A behavior that renders a template for each item in an array.
2934
3855
  * @public
@@ -2945,12 +3866,13 @@ class RepeatBehavior {
2945
3866
  */
2946
3867
  constructor(directive) {
2947
3868
  this.directive = directive;
2948
- this.views = [];
2949
3869
  this.items = null;
2950
3870
  this.itemsObserver = null;
2951
3871
  this.bindView = bindWithoutPositioning;
2952
- this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
2953
- this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
3872
+ /** @internal */
3873
+ this.views = [];
3874
+ this.itemsBindingObserver = directive.dataBinding.createObserver(this, directive);
3875
+ this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
2954
3876
  if (directive.options.positioning) {
2955
3877
  this.bindView = bindWithPositioning;
2956
3878
  }
@@ -2960,12 +3882,19 @@ class RepeatBehavior {
2960
3882
  * @param controller - The view controller that manages the lifecycle of this behavior.
2961
3883
  */
2962
3884
  bind(controller) {
2963
- this.location = controller.targets[this.directive.nodeId];
3885
+ this.location = controller.targets[this.directive.targetNodeId];
2964
3886
  this.controller = controller;
2965
3887
  this.items = this.itemsBindingObserver.bind(controller);
2966
3888
  this.template = this.templateBindingObserver.bind(controller);
2967
3889
  this.observeItems(true);
2968
- this.refreshAllViews();
3890
+ if (isHydratable(this.template) &&
3891
+ isHydratable(controller) &&
3892
+ controller.hydrationStage !== HydrationStage.hydrated) {
3893
+ this.hydrateViews(this.template);
3894
+ }
3895
+ else {
3896
+ this.refreshAllViews();
3897
+ }
2969
3898
  controller.onUnbind(this);
2970
3899
  }
2971
3900
  /**
@@ -3066,10 +3995,10 @@ class RepeatBehavior {
3066
3995
  leftoverViews[i].dispose();
3067
3996
  }
3068
3997
  if (this.directive.options.positioning) {
3069
- for (let i = 0, ii = views.length; i < ii; ++i) {
3998
+ for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
3070
3999
  const context = views[i].context;
3071
- context.length = i;
3072
- context.index = ii;
4000
+ context.length = viewsLength;
4001
+ context.index = i;
3073
4002
  }
3074
4003
  }
3075
4004
  }
@@ -3103,6 +4032,18 @@ class RepeatBehavior {
3103
4032
  for (; i < itemsLength; ++i) {
3104
4033
  if (i < viewsLength) {
3105
4034
  const view = views[i];
4035
+ if (!view) {
4036
+ const serializer = new XMLSerializer();
4037
+ throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
4038
+ index: i,
4039
+ hydrationStage: this.controller
4040
+ .hydrationStage,
4041
+ itemsLength,
4042
+ viewsState: views.map(v => (v ? "hydrated" : "empty")),
4043
+ viewTemplateString: serializer.serializeToString(template.create().fragment),
4044
+ rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
4045
+ });
4046
+ }
3106
4047
  bindView(view, items, i, controller);
3107
4048
  }
3108
4049
  else {
@@ -3121,7 +4062,76 @@ class RepeatBehavior {
3121
4062
  unbindAllViews() {
3122
4063
  const views = this.views;
3123
4064
  for (let i = 0, ii = views.length; i < ii; ++i) {
3124
- views[i].unbind();
4065
+ const view = views[i];
4066
+ if (!view) {
4067
+ const serializer = new XMLSerializer();
4068
+ throw new HydrationRepeatError(`View is null or undefined inside "${this.location.getRootNode().host.nodeName}".`, {
4069
+ index: i,
4070
+ hydrationStage: this.controller.hydrationStage,
4071
+ viewsState: views.map(v => (v ? "hydrated" : "empty")),
4072
+ rootNodeContent: serializer.serializeToString(this.location.getRootNode()),
4073
+ });
4074
+ }
4075
+ view.unbind();
4076
+ }
4077
+ }
4078
+ hydrateViews(template) {
4079
+ if (!this.items) {
4080
+ return;
4081
+ }
4082
+ this.views = new Array(this.items.length);
4083
+ let current = this.location.previousSibling;
4084
+ while (current !== null) {
4085
+ if (!isCommentNode(current)) {
4086
+ current = current.previousSibling;
4087
+ continue;
4088
+ }
4089
+ const index = HydrationMarkup.parseRepeatEndMarker(current.data);
4090
+ if (index === null) {
4091
+ current = current.previousSibling;
4092
+ continue;
4093
+ }
4094
+ current.data = "";
4095
+ // end of repeat is the previousSibling of end comment
4096
+ const end = current.previousSibling;
4097
+ if (!end) {
4098
+ throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": end should never be null.`);
4099
+ }
4100
+ // find start marker
4101
+ let start = end;
4102
+ // How many unmatched end markers we've encountered
4103
+ let unmatchedEndMarkers = 0;
4104
+ while (start !== null) {
4105
+ if (isCommentNode(start)) {
4106
+ if (HydrationMarkup.isRepeatViewEndMarker(start.data)) {
4107
+ unmatchedEndMarkers++;
4108
+ }
4109
+ else if (HydrationMarkup.isRepeatViewStartMarker(start.data)) {
4110
+ if (unmatchedEndMarkers) {
4111
+ unmatchedEndMarkers--;
4112
+ }
4113
+ else {
4114
+ if (HydrationMarkup.parseRepeatStartMarker(start.data) !==
4115
+ index) {
4116
+ throw new Error(`Error when hydrating inside "${this.location.getRootNode().host
4117
+ .nodeName}": Mismatched start and end markers.`);
4118
+ }
4119
+ start.data = "";
4120
+ current = start.previousSibling;
4121
+ // start of repeat content is the nextSibling of start comment
4122
+ start = start.nextSibling;
4123
+ const view = template.hydrate(start, end);
4124
+ this.views[index] = view;
4125
+ this.bindView(view, this.items, index, this.controller);
4126
+ break;
4127
+ }
4128
+ }
4129
+ }
4130
+ start = start.previousSibling;
4131
+ }
4132
+ if (!start) {
4133
+ throw new Error(`Error when hydrating inside "${this.location.getRootNode().host.nodeName}": start should never be null.`);
4134
+ }
3125
4135
  }
3126
4136
  }
3127
4137
  }
@@ -3140,10 +4150,6 @@ class RepeatDirective {
3140
4150
  this.dataBinding = dataBinding;
3141
4151
  this.templateBinding = templateBinding;
3142
4152
  this.options = options;
3143
- /**
3144
- * The unique id of the factory.
3145
- */
3146
- this.id = nextId();
3147
4153
  ArrayObserver.enable();
3148
4154
  }
3149
4155
  /**
@@ -3171,8 +4177,8 @@ HTMLDirective.define(RepeatDirective);
3171
4177
  * @public
3172
4178
  */
3173
4179
  function repeat(items, template, options = defaultRepeatOptions) {
3174
- const dataBinding = normalizeBinding(items);
3175
- const templateBinding = normalizeBinding(template);
4180
+ const dataBinding = normalizeBinding$1(items);
4181
+ const templateBinding = normalizeBinding$1(template);
3176
4182
  return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
3177
4183
  }
3178
4184
 
@@ -3192,9 +4198,15 @@ const elements = (selector) => selector
3192
4198
  * Internally used by the SlottedDirective and the ChildrenDirective.
3193
4199
  */
3194
4200
  class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3195
- constructor() {
3196
- super(...arguments);
3197
- this.sourceProperty = `${this.id}-s`;
4201
+ /**
4202
+ * The unique id of the factory.
4203
+ */
4204
+ get id() {
4205
+ return this._id;
4206
+ }
4207
+ set id(value) {
4208
+ this._id = value;
4209
+ this._controllerProperty = `${value}-c`;
3198
4210
  }
3199
4211
  /**
3200
4212
  * Bind this behavior to the source.
@@ -3203,8 +4215,8 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3203
4215
  * @param targets - The targets that behaviors in a view can attach to.
3204
4216
  */
3205
4217
  bind(controller) {
3206
- const target = controller.targets[this.nodeId];
3207
- target[this.sourceProperty] = controller.source;
4218
+ const target = controller.targets[this.targetNodeId];
4219
+ target[this._controllerProperty] = controller;
3208
4220
  this.updateTarget(controller.source, this.computeNodes(target));
3209
4221
  this.observe(target);
3210
4222
  controller.onUnbind(this);
@@ -3216,10 +4228,10 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3216
4228
  * @param targets - The targets that behaviors in a view can attach to.
3217
4229
  */
3218
4230
  unbind(controller) {
3219
- const target = controller.targets[this.nodeId];
4231
+ const target = controller.targets[this.targetNodeId];
3220
4232
  this.updateTarget(controller.source, emptyArray);
3221
4233
  this.disconnect(target);
3222
- target[this.sourceProperty] = null;
4234
+ target[this._controllerProperty] = null;
3223
4235
  }
3224
4236
  /**
3225
4237
  * Gets the data source for the target.
@@ -3227,7 +4239,7 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
3227
4239
  * @returns The source.
3228
4240
  */
3229
4241
  getSource(target) {
3230
- return target[this.sourceProperty];
4242
+ return target[this._controllerProperty].source;
3231
4243
  }
3232
4244
  /**
3233
4245
  * Updates the source property with the computed nodes.
@@ -3311,7 +4323,7 @@ class ChildrenDirective extends NodeObservationDirective {
3311
4323
  */
3312
4324
  constructor(options) {
3313
4325
  super(options);
3314
- this.observerProperty = `${this.id}-o`;
4326
+ this.observerProperty = Symbol();
3315
4327
  this.handleEvent = (mutations, observer) => {
3316
4328
  const target = observer.target;
3317
4329
  this.updateTarget(this.getSource(target), this.computeNodes(target));
@@ -3323,8 +4335,12 @@ class ChildrenDirective extends NodeObservationDirective {
3323
4335
  * @param target - The target to observe.
3324
4336
  */
3325
4337
  observe(target) {
3326
- var _a;
3327
- const observer = (_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = new MutationObserver(this.handleEvent));
4338
+ let observer = target[this.observerProperty];
4339
+ if (!observer) {
4340
+ observer = new MutationObserver(this.handleEvent);
4341
+ observer.toJSON = noop;
4342
+ target[this.observerProperty] = observer;
4343
+ }
3328
4344
  observer.target = target;
3329
4345
  observer.observe(target, this.options);
3330
4346
  }
@@ -3387,13 +4403,26 @@ const booleanConverter = {
3387
4403
  return value ? "true" : "false";
3388
4404
  },
3389
4405
  fromView(value) {
3390
- return value === null ||
4406
+ return !(value === null ||
3391
4407
  value === void 0 ||
3392
4408
  value === "false" ||
3393
4409
  value === false ||
3394
- value === 0
3395
- ? false
3396
- : true;
4410
+ value === 0);
4411
+ },
4412
+ };
4413
+ /**
4414
+ * A {@link ValueConverter} that converts to and from `boolean` values. `null`, `undefined`, `""`,
4415
+ * and `void` values are converted to `null`.
4416
+ * @public
4417
+ */
4418
+ const nullableBooleanConverter = {
4419
+ toView(value) {
4420
+ return typeof value === "boolean" ? value.toString() : "";
4421
+ },
4422
+ fromView(value) {
4423
+ return [null, undefined, void 0].includes(value)
4424
+ ? null
4425
+ : booleanConverter.fromView(value);
3397
4426
  },
3398
4427
  };
3399
4428
  function toNumber(value) {
@@ -3450,7 +4479,7 @@ class AttributeDefinition {
3450
4479
  /**
3451
4480
  * Sets the value of the attribute/property on the source element.
3452
4481
  * @param source - The source element to access.
3453
- * @param value - The value to set the attribute/property to.
4482
+ * @param newValue - The value to set the attribute/property to.
3454
4483
  */
3455
4484
  setValue(source, newValue) {
3456
4485
  const oldValue = source[this.fieldName];
@@ -3561,7 +4590,8 @@ function attr(configOrTarget, prop) {
3561
4590
 
3562
4591
  const defaultShadowOptions = { mode: "open" };
3563
4592
  const defaultElementOptions = {};
3564
- const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */, () => createTypeRegistry());
4593
+ const fastElementBaseTypes = new Set();
4594
+ const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
3565
4595
  /**
3566
4596
  * Defines metadata for a FASTElement.
3567
4597
  * @public
@@ -3609,51 +4639,564 @@ class FASTElementDefinition {
3609
4639
  this.styles = ElementStyles.normalize(nameOrConfig.styles);
3610
4640
  fastElementRegistry.register(this);
3611
4641
  }
3612
- /**
3613
- * Indicates if this element has been defined in at least one registry.
3614
- */
3615
- get isDefined() {
3616
- return this.platformDefined;
4642
+ /**
4643
+ * Indicates if this element has been defined in at least one registry.
4644
+ */
4645
+ get isDefined() {
4646
+ return this.platformDefined;
4647
+ }
4648
+ /**
4649
+ * Defines a custom element based on this definition.
4650
+ * @param registry - The element registry to define the element in.
4651
+ * @remarks
4652
+ * This operation is idempotent per registry.
4653
+ */
4654
+ define(registry = this.registry) {
4655
+ const type = this.type;
4656
+ if (!registry.get(this.name)) {
4657
+ this.platformDefined = true;
4658
+ registry.define(this.name, type, this.elementOptions);
4659
+ }
4660
+ return this;
4661
+ }
4662
+ /**
4663
+ * Creates an instance of FASTElementDefinition.
4664
+ * @param type - The type this definition is being created for.
4665
+ * @param nameOrDef - The name of the element to define or a config object
4666
+ * that describes the element to define.
4667
+ */
4668
+ static compose(type, nameOrDef) {
4669
+ if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
4670
+ return new FASTElementDefinition(class extends type {
4671
+ }, nameOrDef);
4672
+ }
4673
+ return new FASTElementDefinition(type, nameOrDef);
4674
+ }
4675
+ /**
4676
+ * Registers a FASTElement base type.
4677
+ * @param type - The type to register as a base type.
4678
+ * @internal
4679
+ */
4680
+ static registerBaseType(type) {
4681
+ fastElementBaseTypes.add(type);
4682
+ }
4683
+ }
4684
+ /**
4685
+ * Gets the element definition associated with the specified type.
4686
+ * @param type - The custom element type to retrieve the definition for.
4687
+ */
4688
+ FASTElementDefinition.getByType = fastElementRegistry.getByType;
4689
+ /**
4690
+ * Gets the element definition associated with the instance.
4691
+ * @param instance - The custom element instance to retrieve the definition for.
4692
+ */
4693
+ FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
4694
+
4695
+ /**
4696
+ * A Behavior that enables advanced rendering.
4697
+ * @public
4698
+ */
4699
+ class RenderBehavior {
4700
+ /**
4701
+ * Creates an instance of RenderBehavior.
4702
+ * @param directive - The render directive that created this behavior.
4703
+ */
4704
+ constructor(directive) {
4705
+ this.directive = directive;
4706
+ this.location = null;
4707
+ this.controller = null;
4708
+ this.view = null;
4709
+ this.data = null;
4710
+ this.dataBindingObserver = directive.dataBinding.createObserver(this, directive);
4711
+ this.templateBindingObserver = directive.templateBinding.createObserver(this, directive);
4712
+ }
4713
+ /**
4714
+ * Bind this behavior.
4715
+ * @param controller - The view controller that manages the lifecycle of this behavior.
4716
+ */
4717
+ bind(controller) {
4718
+ this.location = controller.targets[this.directive.targetNodeId];
4719
+ this.controller = controller;
4720
+ this.data = this.dataBindingObserver.bind(controller);
4721
+ this.template = this.templateBindingObserver.bind(controller);
4722
+ controller.onUnbind(this);
4723
+ if (isHydratable(this.template) &&
4724
+ isHydratable(controller) &&
4725
+ controller.hydrationStage !== HydrationStage.hydrated &&
4726
+ !this.view) {
4727
+ const viewNodes = controller.bindingViewBoundaries[this.directive.targetNodeId];
4728
+ if (viewNodes) {
4729
+ this.view = this.template.hydrate(viewNodes.first, viewNodes.last);
4730
+ this.bindView(this.view);
4731
+ }
4732
+ }
4733
+ else {
4734
+ this.refreshView();
4735
+ }
4736
+ }
4737
+ /**
4738
+ * Unbinds this behavior.
4739
+ * @param controller - The view controller that manages the lifecycle of this behavior.
4740
+ */
4741
+ unbind(controller) {
4742
+ const view = this.view;
4743
+ if (view !== null && view.isComposed) {
4744
+ view.unbind();
4745
+ view.needsBindOnly = true;
4746
+ }
4747
+ }
4748
+ /** @internal */
4749
+ handleChange(source, observer) {
4750
+ if (observer === this.dataBindingObserver) {
4751
+ this.data = this.dataBindingObserver.bind(this.controller);
4752
+ }
4753
+ if (this.directive.templateBindingDependsOnData ||
4754
+ observer === this.templateBindingObserver) {
4755
+ this.template = this.templateBindingObserver.bind(this.controller);
4756
+ }
4757
+ this.refreshView();
4758
+ }
4759
+ bindView(view) {
4760
+ // It's possible that the value is the same as the previous template
4761
+ // and that there's actually no need to compose it.
4762
+ if (!view.isComposed) {
4763
+ view.isComposed = true;
4764
+ view.bind(this.data);
4765
+ view.insertBefore(this.location);
4766
+ view.$fastTemplate = this.template;
4767
+ }
4768
+ else if (view.needsBindOnly) {
4769
+ view.needsBindOnly = false;
4770
+ view.bind(this.data);
4771
+ }
4772
+ }
4773
+ refreshView() {
4774
+ let view = this.view;
4775
+ const template = this.template;
4776
+ if (view === null) {
4777
+ this.view = view = template.create();
4778
+ this.view.context.parent = this.controller.source;
4779
+ this.view.context.parentContext = this.controller.context;
4780
+ }
4781
+ else {
4782
+ // If there is a previous view, but it wasn't created
4783
+ // from the same template as the new value, then we
4784
+ // need to remove the old view if it's still in the DOM
4785
+ // and create a new view from the template.
4786
+ if (view.$fastTemplate !== template) {
4787
+ if (view.isComposed) {
4788
+ view.remove();
4789
+ view.unbind();
4790
+ }
4791
+ this.view = view = template.create();
4792
+ this.view.context.parent = this.controller.source;
4793
+ this.view.context.parentContext = this.controller.context;
4794
+ }
4795
+ }
4796
+ this.bindView(view);
4797
+ }
4798
+ }
4799
+ /**
4800
+ * A Directive that enables use of the RenderBehavior.
4801
+ * @public
4802
+ */
4803
+ class RenderDirective {
4804
+ /**
4805
+ * Creates an instance of RenderDirective.
4806
+ * @param dataBinding - A binding expression that returns the data to render.
4807
+ * @param templateBinding - A binding expression that returns the template to use to render the data.
4808
+ */
4809
+ constructor(dataBinding, templateBinding, templateBindingDependsOnData) {
4810
+ this.dataBinding = dataBinding;
4811
+ this.templateBinding = templateBinding;
4812
+ this.templateBindingDependsOnData = templateBindingDependsOnData;
4813
+ }
4814
+ /**
4815
+ * Creates HTML to be used within a template.
4816
+ * @param add - Can be used to add behavior factories to a template.
4817
+ */
4818
+ createHTML(add) {
4819
+ return Markup.comment(add(this));
4820
+ }
4821
+ /**
4822
+ * Creates a behavior.
4823
+ * @param targets - The targets available for behaviors to be attached to.
4824
+ */
4825
+ createBehavior() {
4826
+ return new RenderBehavior(this);
4827
+ }
4828
+ }
4829
+ HTMLDirective.define(RenderDirective);
4830
+ function isElementRenderOptions(object) {
4831
+ return !!object.element || !!object.tagName;
4832
+ }
4833
+ const typeToInstructionLookup = new Map();
4834
+ /* eslint @typescript-eslint/naming-convention: "off"*/
4835
+ const defaultAttributes = { ":model": (x) => x };
4836
+ const brand = Symbol("RenderInstruction");
4837
+ const defaultViewName = "default-view";
4838
+ const nullTemplate = html `
4839
+ &nbsp;
4840
+ `;
4841
+ function instructionToTemplate(def) {
4842
+ if (def === void 0) {
4843
+ return nullTemplate;
4844
+ }
4845
+ return def.template;
4846
+ }
4847
+ function createElementTemplate(tagName, options) {
4848
+ const markup = [];
4849
+ const values = [];
4850
+ const { attributes, directives, content, policy } = options !== null && options !== void 0 ? options : {};
4851
+ markup.push(`<${tagName}`);
4852
+ if (attributes) {
4853
+ const attrNames = Object.getOwnPropertyNames(attributes);
4854
+ for (let i = 0, ii = attrNames.length; i < ii; ++i) {
4855
+ const name = attrNames[i];
4856
+ if (i === 0) {
4857
+ markup[0] = `${markup[0]} ${name}="`;
4858
+ }
4859
+ else {
4860
+ markup.push(`" ${name}="`);
4861
+ }
4862
+ values.push(attributes[name]);
4863
+ }
4864
+ markup.push(`"`);
4865
+ }
4866
+ if (directives) {
4867
+ markup[markup.length - 1] += " ";
4868
+ for (let i = 0, ii = directives.length; i < ii; ++i) {
4869
+ const directive = directives[i];
4870
+ markup.push(i > 0 ? "" : " ");
4871
+ values.push(directive);
4872
+ }
4873
+ }
4874
+ markup[markup.length - 1] += ">";
4875
+ if (content && isFunction(content.create)) {
4876
+ values.push(content);
4877
+ markup.push(`</${tagName}>`);
4878
+ }
4879
+ else {
4880
+ const lastIndex = markup.length - 1;
4881
+ markup[lastIndex] = `${markup[lastIndex]}${content !== null && content !== void 0 ? content : ""}</${tagName}>`;
4882
+ }
4883
+ return ViewTemplate.create(markup, values, policy);
4884
+ }
4885
+ function create(options) {
4886
+ var _a;
4887
+ const name = (_a = options.name) !== null && _a !== void 0 ? _a : defaultViewName;
4888
+ let template;
4889
+ if (isElementRenderOptions(options)) {
4890
+ let tagName = options.tagName;
4891
+ if (!tagName) {
4892
+ const def = FASTElementDefinition.getByType(options.element);
4893
+ if (def) {
4894
+ tagName = def.name;
4895
+ }
4896
+ else {
4897
+ throw new Error("Invalid element for model rendering.");
4898
+ }
4899
+ }
4900
+ if (!options.attributes) {
4901
+ options.attributes = defaultAttributes;
4902
+ }
4903
+ template = createElementTemplate(tagName, options);
4904
+ }
4905
+ else {
4906
+ template = options.template;
4907
+ }
4908
+ return {
4909
+ brand,
4910
+ type: options.type,
4911
+ name,
4912
+ template,
4913
+ };
4914
+ }
4915
+ function instanceOf(object) {
4916
+ return object && object.brand === brand;
4917
+ }
4918
+ function register(optionsOrInstruction) {
4919
+ let lookup = typeToInstructionLookup.get(optionsOrInstruction.type);
4920
+ if (lookup === void 0) {
4921
+ typeToInstructionLookup.set(optionsOrInstruction.type, (lookup = Object.create(null)));
4922
+ }
4923
+ const instruction = instanceOf(optionsOrInstruction)
4924
+ ? optionsOrInstruction
4925
+ : create(optionsOrInstruction);
4926
+ return (lookup[instruction.name] = instruction);
4927
+ }
4928
+ function getByType(type, name) {
4929
+ const entries = typeToInstructionLookup.get(type);
4930
+ if (entries === void 0) {
4931
+ return void 0;
4932
+ }
4933
+ return entries[name !== null && name !== void 0 ? name : defaultViewName];
4934
+ }
4935
+ function getForInstance(object, name) {
4936
+ if (object) {
4937
+ return getByType(object.constructor, name);
4938
+ }
4939
+ return void 0;
4940
+ }
4941
+ /**
4942
+ * Provides APIs for creating and interacting with render instructions.
4943
+ * @public
4944
+ */
4945
+ Object.freeze({
4946
+ /**
4947
+ * Checks whether the provided object is a RenderInstruction.
4948
+ * @param object - The object to check.
4949
+ * @returns true if the object is a RenderInstruction; false otherwise
4950
+ */
4951
+ instanceOf,
4952
+ /**
4953
+ * Creates a RenderInstruction for a set of options.
4954
+ * @param options - The options to use when creating the RenderInstruction.
4955
+ * @remarks
4956
+ * This API should be used with caution. When providing attributes or content,
4957
+ * if not done properly, you can open up the application to XSS attacks. When using this API,
4958
+ * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4959
+ * content and attribute values particularly if they can come from user input.
4960
+ */
4961
+ create,
4962
+ /**
4963
+ * Creates a template based on a tag name.
4964
+ * @param tagName - The tag name to use when creating the template.
4965
+ * @param attributes - The attributes to apply to the element.
4966
+ * @param content - The content to insert into the element.
4967
+ * @param policy - The DOMPolicy to create the template with.
4968
+ * @returns A template based on the provided specifications.
4969
+ * @remarks
4970
+ * This API should be used with caution. When providing attributes or content,
4971
+ * if not done properly, you can open up the application to XSS attacks. When using this API,
4972
+ * provide a strong DOMPolicy that can properly sanitize and also be sure to manually sanitize
4973
+ * content and attribute values particularly if they can come from user input.
4974
+ */
4975
+ createElementTemplate,
4976
+ /**
4977
+ * Creates and registers an instruction.
4978
+ * @param options The options to use when creating the RenderInstruction.
4979
+ * @remarks
4980
+ * A previously created RenderInstruction can also be registered.
4981
+ */
4982
+ register,
4983
+ /**
4984
+ * Finds a previously registered RenderInstruction by type and optionally by name.
4985
+ * @param type - The type to retrieve the RenderInstruction for.
4986
+ * @param name - An optional name used in differentiating between multiple registered instructions.
4987
+ * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4988
+ */
4989
+ getByType,
4990
+ /**
4991
+ * Finds a previously registered RenderInstruction for the instance's type and optionally by name.
4992
+ * @param object - The instance to retrieve the RenderInstruction for.
4993
+ * @param name - An optional name used in differentiating between multiple registered instructions.
4994
+ * @returns The located RenderInstruction that matches the criteria or undefined if none is found.
4995
+ */
4996
+ getForInstance,
4997
+ });
4998
+ /**
4999
+ * @internal
5000
+ */
5001
+ class NodeTemplate {
5002
+ constructor(node) {
5003
+ this.node = node;
5004
+ node.$fastTemplate = this;
5005
+ }
5006
+ get context() {
5007
+ // HACK
5008
+ return this;
5009
+ }
5010
+ bind(source) { }
5011
+ unbind() { }
5012
+ insertBefore(refNode) {
5013
+ refNode.parentNode.insertBefore(this.node, refNode);
5014
+ }
5015
+ remove() {
5016
+ this.node.parentNode.removeChild(this.node);
5017
+ }
5018
+ create() {
5019
+ return this;
5020
+ }
5021
+ hydrate(first, last) {
5022
+ return this;
5023
+ }
5024
+ }
5025
+ /**
5026
+ * Creates a RenderDirective for use in advanced rendering scenarios.
5027
+ * @param value - The binding expression that returns the data to be rendered. The expression
5028
+ * can also return a Node to render directly.
5029
+ * @param template - A template to render the data with
5030
+ * or a string to indicate which RenderInstruction to use when looking up a RenderInstruction.
5031
+ * Expressions can also be provided to dynamically determine either the template or the name.
5032
+ * @returns A RenderDirective suitable for use in a template.
5033
+ * @remarks
5034
+ * If no binding is provided, then a default binding that returns the source is created.
5035
+ * If no template is provided, then a binding is created that will use registered
5036
+ * RenderInstructions to determine the view.
5037
+ * If the template binding returns a string, then it will be used to look up a
5038
+ * RenderInstruction to determine the view.
5039
+ * @public
5040
+ */
5041
+ function render(value, template) {
5042
+ let dataBinding;
5043
+ if (value === void 0) {
5044
+ dataBinding = oneTime((source) => source);
5045
+ }
5046
+ else {
5047
+ dataBinding = normalizeBinding$1(value);
5048
+ }
5049
+ let templateBinding;
5050
+ let templateBindingDependsOnData = false;
5051
+ if (template === void 0) {
5052
+ templateBindingDependsOnData = true;
5053
+ templateBinding = oneTime((s, c) => {
5054
+ var _a;
5055
+ const data = dataBinding.evaluate(s, c);
5056
+ if (data instanceof Node) {
5057
+ return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
5058
+ }
5059
+ return instructionToTemplate(getForInstance(data));
5060
+ });
5061
+ }
5062
+ else if (isFunction(template)) {
5063
+ templateBinding = oneWay((s, c) => {
5064
+ var _a;
5065
+ let result = template(s, c);
5066
+ if (isString(result)) {
5067
+ result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
5068
+ }
5069
+ else if (result instanceof Node) {
5070
+ result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
5071
+ }
5072
+ return result;
5073
+ }, void 0, true);
5074
+ }
5075
+ else if (isString(template)) {
5076
+ templateBindingDependsOnData = true;
5077
+ templateBinding = oneTime((s, c) => {
5078
+ var _a;
5079
+ const data = dataBinding.evaluate(s, c);
5080
+ if (data instanceof Node) {
5081
+ return (_a = data.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(data);
5082
+ }
5083
+ return instructionToTemplate(getForInstance(data, template));
5084
+ });
5085
+ }
5086
+ else if (template instanceof Binding) {
5087
+ const evaluateTemplate = template.evaluate;
5088
+ template.evaluate = (s, c) => {
5089
+ var _a;
5090
+ let result = evaluateTemplate(s, c);
5091
+ if (isString(result)) {
5092
+ result = instructionToTemplate(getForInstance(dataBinding.evaluate(s, c), result));
5093
+ }
5094
+ else if (result instanceof Node) {
5095
+ result = (_a = result.$fastTemplate) !== null && _a !== void 0 ? _a : new NodeTemplate(result);
5096
+ }
5097
+ return result;
5098
+ };
5099
+ templateBinding = template;
5100
+ }
5101
+ else {
5102
+ templateBinding = oneTime((s, c) => template);
3617
5103
  }
5104
+ return new RenderDirective(dataBinding, templateBinding, templateBindingDependsOnData);
5105
+ }
5106
+
5107
+ /**
5108
+ * An extension of MutationObserver that supports unobserving nodes.
5109
+ * @internal
5110
+ */
5111
+ class UnobservableMutationObserver extends MutationObserver {
3618
5112
  /**
3619
- * Defines a custom element based on this definition.
3620
- * @param registry - The element registry to define the element in.
3621
- * @remarks
3622
- * This operation is idempotent per registry.
5113
+ * Creates an instance of UnobservableMutationObserver.
5114
+ * @param callback - The callback to invoke when observed nodes are changed.
3623
5115
  */
3624
- define(registry = this.registry) {
3625
- const type = this.type;
3626
- if (!registry.get(this.name)) {
3627
- this.platformDefined = true;
3628
- registry.define(this.name, type, this.elementOptions);
5116
+ constructor(callback) {
5117
+ function handler(mutations) {
5118
+ this.callback.call(null, mutations.filter(record => this.observedNodes.has(record.target)));
3629
5119
  }
3630
- return this;
5120
+ super(handler);
5121
+ this.callback = callback;
5122
+ this.observedNodes = new Set();
3631
5123
  }
3632
- /**
3633
- * Creates an instance of FASTElementDefinition.
3634
- * @param type - The type this definition is being created for.
3635
- * @param nameOrDef - The name of the element to define or a config object
3636
- * that describes the element to define.
3637
- */
3638
- static compose(type, nameOrDef) {
3639
- const found = fastElementRegistry.getByType(type);
3640
- if (found) {
3641
- return new FASTElementDefinition(class extends type {
3642
- }, nameOrDef);
5124
+ observe(target, options) {
5125
+ this.observedNodes.add(target);
5126
+ super.observe(target, options);
5127
+ }
5128
+ unobserve(target) {
5129
+ this.observedNodes.delete(target);
5130
+ if (this.observedNodes.size < 1) {
5131
+ this.disconnect();
3643
5132
  }
3644
- return new FASTElementDefinition(type, nameOrDef);
3645
5133
  }
3646
5134
  }
3647
5135
  /**
3648
- * Gets the element definition associated with the specified type.
3649
- * @param type - The custom element type to retrieve the definition for.
3650
- */
3651
- FASTElementDefinition.getByType = fastElementRegistry.getByType;
3652
- /**
3653
- * Gets the element definition associated with the instance.
3654
- * @param instance - The custom element instance to retrieve the definition for.
5136
+ * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
5137
+ * control ViewBehaviors.
5138
+ * @public
3655
5139
  */
3656
- FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
5140
+ Object.freeze({
5141
+ /**
5142
+ * Creates a ViewBehaviorOrchestrator.
5143
+ * @param source - The source to to associate behaviors with.
5144
+ * @returns A ViewBehaviorOrchestrator.
5145
+ */
5146
+ create(source) {
5147
+ const behaviors = [];
5148
+ const targets = {};
5149
+ let unbindables = null;
5150
+ let isConnected = false;
5151
+ return {
5152
+ source,
5153
+ context: ExecutionContext.default,
5154
+ targets,
5155
+ get isBound() {
5156
+ return isConnected;
5157
+ },
5158
+ addBehaviorFactory(factory, target) {
5159
+ var _a, _b, _c, _d;
5160
+ const compiled = factory;
5161
+ compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
5162
+ compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
5163
+ compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
5164
+ compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
5165
+ this.addTarget(compiled.targetNodeId, target);
5166
+ this.addBehavior(compiled.createBehavior());
5167
+ },
5168
+ addTarget(nodeId, target) {
5169
+ targets[nodeId] = target;
5170
+ },
5171
+ addBehavior(behavior) {
5172
+ behaviors.push(behavior);
5173
+ if (isConnected) {
5174
+ behavior.bind(this);
5175
+ }
5176
+ },
5177
+ onUnbind(unbindable) {
5178
+ if (unbindables === null) {
5179
+ unbindables = [];
5180
+ }
5181
+ unbindables.push(unbindable);
5182
+ },
5183
+ connectedCallback(controller) {
5184
+ if (!isConnected) {
5185
+ isConnected = true;
5186
+ behaviors.forEach(x => x.bind(this));
5187
+ }
5188
+ },
5189
+ disconnectedCallback(controller) {
5190
+ if (isConnected) {
5191
+ isConnected = false;
5192
+ if (unbindables !== null) {
5193
+ unbindables.forEach(x => x.unbind(this));
5194
+ }
5195
+ }
5196
+ },
5197
+ };
5198
+ },
5199
+ });
3657
5200
 
3658
5201
  const defaultEventOptions = {
3659
5202
  bubbles: true,
@@ -3666,6 +5209,7 @@ function getShadowRoot(element) {
3666
5209
  var _a, _b;
3667
5210
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
3668
5211
  }
5212
+ let elementControllerStrategy;
3669
5213
  /**
3670
5214
  * Controls the lifecycle and rendering of a `FASTElement`.
3671
5215
  * @public
@@ -3684,8 +5228,19 @@ class ElementController extends PropertyChangeNotifier {
3684
5228
  this.needsInitialization = true;
3685
5229
  this.hasExistingShadowRoot = false;
3686
5230
  this._template = null;
3687
- this._isConnected = false;
5231
+ this.stage = 3 /* Stages.disconnected */;
5232
+ /**
5233
+ * A guard against connecting behaviors multiple times
5234
+ * during connect in scenarios where a behavior adds
5235
+ * another behavior during it's connectedCallback
5236
+ */
5237
+ this.guardBehaviorConnection = false;
3688
5238
  this.behaviors = null;
5239
+ /**
5240
+ * Tracks whether behaviors are connected so that
5241
+ * behaviors cant be connected multiple times
5242
+ */
5243
+ this.behaviorsConnected = false;
3689
5244
  this._mainStyles = null;
3690
5245
  /**
3691
5246
  * This allows Observable.getNotifier(...) to return the Controller
@@ -3740,11 +5295,28 @@ class ElementController extends PropertyChangeNotifier {
3740
5295
  */
3741
5296
  get isConnected() {
3742
5297
  Observable.track(this, isConnectedPropertyName);
3743
- return this._isConnected;
5298
+ return this.stage === 1 /* Stages.connected */;
3744
5299
  }
3745
- setIsConnected(value) {
3746
- this._isConnected = value;
3747
- Observable.notify(this, isConnectedPropertyName);
5300
+ /**
5301
+ * The context the expression is evaluated against.
5302
+ */
5303
+ get context() {
5304
+ var _a, _b;
5305
+ return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : ExecutionContext.default;
5306
+ }
5307
+ /**
5308
+ * Indicates whether the controller is bound.
5309
+ */
5310
+ get isBound() {
5311
+ var _a, _b;
5312
+ return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.isBound) !== null && _b !== void 0 ? _b : false;
5313
+ }
5314
+ /**
5315
+ * Indicates how the source's lifetime relates to the controller's lifetime.
5316
+ */
5317
+ get sourceLifetime() {
5318
+ var _a;
5319
+ return (_a = this.view) === null || _a === void 0 ? void 0 : _a.sourceLifetime;
3748
5320
  }
3749
5321
  /**
3750
5322
  * Gets/sets the template used to render the component.
@@ -3808,6 +5380,14 @@ class ElementController extends PropertyChangeNotifier {
3808
5380
  this.addStyles(value);
3809
5381
  }
3810
5382
  }
5383
+ /**
5384
+ * Registers an unbind handler with the controller.
5385
+ * @param behavior - An object to call when the controller unbinds.
5386
+ */
5387
+ onUnbind(behavior) {
5388
+ var _a;
5389
+ (_a = this.view) === null || _a === void 0 ? void 0 : _a.onUnbind(behavior);
5390
+ }
3811
5391
  /**
3812
5392
  * Adds the behavior to the component.
3813
5393
  * @param behavior - The behavior to add.
@@ -3819,7 +5399,9 @@ class ElementController extends PropertyChangeNotifier {
3819
5399
  if (count === 0) {
3820
5400
  targetBehaviors.set(behavior, 1);
3821
5401
  behavior.addedCallback && behavior.addedCallback(this);
3822
- if (behavior.connectedCallback && this.isConnected) {
5402
+ if (behavior.connectedCallback &&
5403
+ !this.guardBehaviorConnection &&
5404
+ (this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
3823
5405
  behavior.connectedCallback(this);
3824
5406
  }
3825
5407
  }
@@ -3843,7 +5425,7 @@ class ElementController extends PropertyChangeNotifier {
3843
5425
  }
3844
5426
  if (count === 1 || force) {
3845
5427
  targetBehaviors.delete(behavior);
3846
- if (behavior.disconnectedCallback && this.isConnected) {
5428
+ if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
3847
5429
  behavior.disconnectedCallback(this);
3848
5430
  }
3849
5431
  behavior.removedCallback && behavior.removedCallback(this);
@@ -3862,13 +5444,13 @@ class ElementController extends PropertyChangeNotifier {
3862
5444
  return;
3863
5445
  }
3864
5446
  const source = this.source;
3865
- const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
3866
5447
  if (styles instanceof HTMLElement) {
5448
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
3867
5449
  target.append(styles);
3868
5450
  }
3869
- else if (!styles.isAttachedTo(target)) {
5451
+ else if (!styles.isAttachedTo(source)) {
3870
5452
  const sourceBehaviors = styles.behaviors;
3871
- styles.addStylesTo(target);
5453
+ styles.addStylesTo(source);
3872
5454
  if (sourceBehaviors !== null) {
3873
5455
  for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
3874
5456
  this.addBehavior(sourceBehaviors[i]);
@@ -3886,16 +5468,16 @@ class ElementController extends PropertyChangeNotifier {
3886
5468
  return;
3887
5469
  }
3888
5470
  const source = this.source;
3889
- const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
3890
5471
  if (styles instanceof HTMLElement) {
5472
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
3891
5473
  target.removeChild(styles);
3892
5474
  }
3893
- else if (styles.isAttachedTo(target)) {
5475
+ else if (styles.isAttachedTo(source)) {
3894
5476
  const sourceBehaviors = styles.behaviors;
3895
- styles.removeStylesFrom(target);
5477
+ styles.removeStylesFrom(source);
3896
5478
  if (sourceBehaviors !== null) {
3897
5479
  for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
3898
- this.addBehavior(sourceBehaviors[i]);
5480
+ this.removeBehavior(sourceBehaviors[i]);
3899
5481
  }
3900
5482
  }
3901
5483
  }
@@ -3904,40 +5486,73 @@ class ElementController extends PropertyChangeNotifier {
3904
5486
  * Runs connected lifecycle behavior on the associated element.
3905
5487
  */
3906
5488
  connect() {
3907
- if (this._isConnected) {
5489
+ if (this.stage !== 3 /* Stages.disconnected */) {
3908
5490
  return;
3909
5491
  }
5492
+ this.stage = 0 /* Stages.connecting */;
5493
+ this.bindObservables();
5494
+ this.connectBehaviors();
3910
5495
  if (this.needsInitialization) {
3911
- this.finishInitialization();
5496
+ this.renderTemplate(this.template);
5497
+ this.addStyles(this.mainStyles);
5498
+ this.needsInitialization = false;
3912
5499
  }
3913
5500
  else if (this.view !== null) {
3914
5501
  this.view.bind(this.source);
3915
5502
  }
3916
- const behaviors = this.behaviors;
3917
- if (behaviors !== null) {
3918
- for (const key of behaviors.keys()) {
3919
- key.connectedCallback && key.connectedCallback(this);
5503
+ this.stage = 1 /* Stages.connected */;
5504
+ Observable.notify(this, isConnectedPropertyName);
5505
+ }
5506
+ bindObservables() {
5507
+ if (this.boundObservables !== null) {
5508
+ const element = this.source;
5509
+ const boundObservables = this.boundObservables;
5510
+ const propertyNames = Object.keys(boundObservables);
5511
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
5512
+ const propertyName = propertyNames[i];
5513
+ element[propertyName] = boundObservables[propertyName];
5514
+ }
5515
+ this.boundObservables = null;
5516
+ }
5517
+ }
5518
+ connectBehaviors() {
5519
+ if (this.behaviorsConnected === false) {
5520
+ const behaviors = this.behaviors;
5521
+ if (behaviors !== null) {
5522
+ this.guardBehaviorConnection = true;
5523
+ for (const key of behaviors.keys()) {
5524
+ key.connectedCallback && key.connectedCallback(this);
5525
+ }
5526
+ this.guardBehaviorConnection = false;
5527
+ }
5528
+ this.behaviorsConnected = true;
5529
+ }
5530
+ }
5531
+ disconnectBehaviors() {
5532
+ if (this.behaviorsConnected === true) {
5533
+ const behaviors = this.behaviors;
5534
+ if (behaviors !== null) {
5535
+ for (const key of behaviors.keys()) {
5536
+ key.disconnectedCallback && key.disconnectedCallback(this);
5537
+ }
3920
5538
  }
5539
+ this.behaviorsConnected = false;
3921
5540
  }
3922
- this.setIsConnected(true);
3923
5541
  }
3924
5542
  /**
3925
5543
  * Runs disconnected lifecycle behavior on the associated element.
3926
5544
  */
3927
5545
  disconnect() {
3928
- if (!this._isConnected) {
5546
+ if (this.stage !== 1 /* Stages.connected */) {
3929
5547
  return;
3930
5548
  }
3931
- this.setIsConnected(false);
5549
+ this.stage = 2 /* Stages.disconnecting */;
5550
+ Observable.notify(this, isConnectedPropertyName);
3932
5551
  if (this.view !== null) {
3933
5552
  this.view.unbind();
3934
5553
  }
3935
- const behaviors = this.behaviors;
3936
- if (behaviors !== null) {
3937
- for (const key of behaviors.keys()) {
3938
- key.disconnectedCallback && key.disconnectedCallback(this);
3939
- }
3940
- }
5554
+ this.disconnectBehaviors();
5555
+ this.stage = 3 /* Stages.disconnected */;
3941
5556
  }
3942
5557
  /**
3943
5558
  * Runs the attribute changed callback for the associated element.
@@ -3960,27 +5575,11 @@ class ElementController extends PropertyChangeNotifier {
3960
5575
  * Only emits events if connected.
3961
5576
  */
3962
5577
  emit(type, detail, options) {
3963
- if (this._isConnected) {
5578
+ if (this.stage === 1 /* Stages.connected */) {
3964
5579
  return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
3965
5580
  }
3966
5581
  return false;
3967
5582
  }
3968
- finishInitialization() {
3969
- const element = this.source;
3970
- const boundObservables = this.boundObservables;
3971
- // If we have any observables that were bound, re-apply their values.
3972
- if (boundObservables !== null) {
3973
- const propertyNames = Object.keys(boundObservables);
3974
- for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
3975
- const propertyName = propertyNames[i];
3976
- element[propertyName] = boundObservables[propertyName];
3977
- }
3978
- this.boundObservables = null;
3979
- }
3980
- this.renderTemplate(this.template);
3981
- this.addStyles(this.mainStyles);
3982
- this.needsInitialization = false;
3983
- }
3984
5583
  renderTemplate(template) {
3985
5584
  var _a;
3986
5585
  // When getting the host to render to, we start by looking
@@ -4024,13 +5623,224 @@ class ElementController extends PropertyChangeNotifier {
4024
5623
  if (definition === void 0) {
4025
5624
  throw FAST.error(1401 /* Message.missingElementDefinition */);
4026
5625
  }
4027
- return (element.$fastController = new ElementController(element, definition));
5626
+ return (element.$fastController = new elementControllerStrategy(element, definition));
5627
+ }
5628
+ /**
5629
+ * Sets the strategy that ElementController.forCustomElement uses to construct
5630
+ * ElementController instances for an element.
5631
+ * @param strategy - The strategy to use.
5632
+ */
5633
+ static setStrategy(strategy) {
5634
+ elementControllerStrategy = strategy;
5635
+ }
5636
+ }
5637
+ makeSerializationNoop(ElementController);
5638
+ // Set default strategy for ElementController
5639
+ ElementController.setStrategy(ElementController);
5640
+ /**
5641
+ * Converts a styleTarget into the operative target. When the provided target is an Element
5642
+ * that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
5643
+ * it will return the root node for the element.
5644
+ * @param target
5645
+ * @returns
5646
+ */
5647
+ function normalizeStyleTarget(target) {
5648
+ var _a;
5649
+ if ("adoptedStyleSheets" in target) {
5650
+ return target;
5651
+ }
5652
+ else {
5653
+ return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
5654
+ }
5655
+ }
5656
+ // Default StyleStrategy implementations are defined in this module because they
5657
+ // require access to element shadowRoots, and we don't want to leak shadowRoot
5658
+ // objects out of this module.
5659
+ /**
5660
+ * https://wicg.github.io/construct-stylesheets/
5661
+ * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
5662
+ *
5663
+ * @internal
5664
+ */
5665
+ class AdoptedStyleSheetsStrategy {
5666
+ constructor(styles) {
5667
+ const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
5668
+ this.sheets = styles.map((x) => {
5669
+ if (x instanceof CSSStyleSheet) {
5670
+ return x;
5671
+ }
5672
+ let sheet = styleSheetCache.get(x);
5673
+ if (sheet === void 0) {
5674
+ sheet = new CSSStyleSheet();
5675
+ sheet.replaceSync(x);
5676
+ styleSheetCache.set(x, sheet);
5677
+ }
5678
+ return sheet;
5679
+ });
5680
+ }
5681
+ addStylesTo(target) {
5682
+ addAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
5683
+ }
5684
+ removeStylesFrom(target) {
5685
+ removeAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
5686
+ }
5687
+ }
5688
+ AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
5689
+ let id = 0;
5690
+ const nextStyleId = () => `fast-${++id}`;
5691
+ function usableStyleTarget(target) {
5692
+ return target === document ? document.body : target;
5693
+ }
5694
+ /**
5695
+ * @internal
5696
+ */
5697
+ class StyleElementStrategy {
5698
+ constructor(styles) {
5699
+ this.styles = styles;
5700
+ this.styleClass = nextStyleId();
5701
+ }
5702
+ addStylesTo(target) {
5703
+ target = usableStyleTarget(normalizeStyleTarget(target));
5704
+ const styles = this.styles;
5705
+ const styleClass = this.styleClass;
5706
+ for (let i = 0; i < styles.length; i++) {
5707
+ const element = document.createElement("style");
5708
+ element.innerHTML = styles[i];
5709
+ element.className = styleClass;
5710
+ target.append(element);
5711
+ }
5712
+ }
5713
+ removeStylesFrom(target) {
5714
+ target = usableStyleTarget(normalizeStyleTarget(target));
5715
+ const styles = target.querySelectorAll(`.${this.styleClass}`);
5716
+ for (let i = 0, ii = styles.length; i < ii; ++i) {
5717
+ target.removeChild(styles[i]);
5718
+ }
5719
+ }
5720
+ }
5721
+ let addAdoptedStyleSheets = (target, sheets) => {
5722
+ target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...sheets];
5723
+ };
5724
+ let removeAdoptedStyleSheets = (target, sheets) => {
5725
+ target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
5726
+ };
5727
+ if (ElementStyles.supportsAdoptedStyleSheets) {
5728
+ try {
5729
+ // Test if browser implementation uses FrozenArray.
5730
+ // If not, use push / splice to alter the stylesheets
5731
+ // in place. This circumvents a bug in Safari 16.4 where
5732
+ // periodically, assigning the array would previously
5733
+ // cause sheets to be removed.
5734
+ document.adoptedStyleSheets.push();
5735
+ document.adoptedStyleSheets.splice();
5736
+ addAdoptedStyleSheets = (target, sheets) => {
5737
+ target.adoptedStyleSheets.push(...sheets);
5738
+ };
5739
+ removeAdoptedStyleSheets = (target, sheets) => {
5740
+ for (const sheet of sheets) {
5741
+ const index = target.adoptedStyleSheets.indexOf(sheet);
5742
+ if (index !== -1) {
5743
+ target.adoptedStyleSheets.splice(index, 1);
5744
+ }
5745
+ }
5746
+ };
5747
+ }
5748
+ catch (e) {
5749
+ // Do nothing if an error is thrown, the default
5750
+ // case handles FrozenArray.
5751
+ }
5752
+ ElementStyles.setDefaultStrategy(AdoptedStyleSheetsStrategy);
5753
+ }
5754
+ else {
5755
+ ElementStyles.setDefaultStrategy(StyleElementStrategy);
5756
+ }
5757
+ const deferHydrationAttribute = "defer-hydration";
5758
+ const needsHydrationAttribute = "needs-hydration";
5759
+ /**
5760
+ * An ElementController capable of hydrating FAST elements from
5761
+ * Declarative Shadow DOM.
5762
+ *
5763
+ * @beta
5764
+ */
5765
+ class HydratableElementController extends ElementController {
5766
+ static hydrationObserverHandler(records) {
5767
+ for (const record of records) {
5768
+ HydratableElementController.hydrationObserver.unobserve(record.target);
5769
+ record.target.$fastController.connect();
5770
+ }
5771
+ }
5772
+ connect() {
5773
+ var _a, _b;
5774
+ // Initialize needsHydration on first connect
5775
+ if (this.needsHydration === undefined) {
5776
+ this.needsHydration =
5777
+ this.source.getAttribute(needsHydrationAttribute) !== null;
5778
+ }
5779
+ // If the `defer-hydration` attribute exists on the source,
5780
+ // wait for it to be removed before continuing connection behavior.
5781
+ if (this.source.hasAttribute(deferHydrationAttribute)) {
5782
+ HydratableElementController.hydrationObserver.observe(this.source, {
5783
+ attributeFilter: [deferHydrationAttribute],
5784
+ });
5785
+ return;
5786
+ }
5787
+ // If the controller does not need to be hydrated, defer connection behavior
5788
+ // to the base-class. This case handles element re-connection and initial connection
5789
+ // of elements that did not get declarative shadow-dom emitted, as well as if an extending
5790
+ // class
5791
+ if (!this.needsHydration) {
5792
+ super.connect();
5793
+ return;
5794
+ }
5795
+ if (this.stage !== 3 /* Stages.disconnected */) {
5796
+ return;
5797
+ }
5798
+ this.stage = 0 /* Stages.connecting */;
5799
+ this.bindObservables();
5800
+ this.connectBehaviors();
5801
+ const element = this.source;
5802
+ const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
5803
+ if (this.template) {
5804
+ if (isHydratable(this.template)) {
5805
+ let firstChild = host.firstChild;
5806
+ let lastChild = host.lastChild;
5807
+ if (element.shadowRoot === null) {
5808
+ // handle element boundary markers when shadowRoot is not present
5809
+ if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
5810
+ firstChild.data = "";
5811
+ firstChild = firstChild.nextSibling;
5812
+ }
5813
+ if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
5814
+ lastChild.data = "";
5815
+ lastChild = lastChild.previousSibling;
5816
+ }
5817
+ }
5818
+ this.view = this.template.hydrate(firstChild, lastChild, element);
5819
+ (_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
5820
+ }
5821
+ else {
5822
+ this.renderTemplate(this.template);
5823
+ }
5824
+ }
5825
+ this.addStyles(this.mainStyles);
5826
+ this.stage = 1 /* Stages.connected */;
5827
+ this.source.removeAttribute(needsHydrationAttribute);
5828
+ this.needsInitialization = this.needsHydration = false;
5829
+ Observable.notify(this, isConnectedPropertyName);
5830
+ }
5831
+ disconnect() {
5832
+ super.disconnect();
5833
+ HydratableElementController.hydrationObserver.unobserve(this.source);
5834
+ }
5835
+ static install() {
5836
+ ElementController.setStrategy(HydratableElementController);
4028
5837
  }
4029
5838
  }
5839
+ HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
4030
5840
 
4031
5841
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
4032
5842
  function createFASTElement(BaseType) {
4033
- return class extends BaseType {
5843
+ const type = class extends BaseType {
4034
5844
  constructor() {
4035
5845
  /* eslint-disable-next-line */
4036
5846
  super();
@@ -4049,6 +5859,8 @@ function createFASTElement(BaseType) {
4049
5859
  this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
4050
5860
  }
4051
5861
  };
5862
+ FASTElementDefinition.registerBaseType(type);
5863
+ return type;
4052
5864
  }
4053
5865
  function compose(type, nameOrDef) {
4054
5866
  if (isFunction(type)) {
@@ -4103,4 +5915,6 @@ function customElement(nameOrDef) {
4103
5915
  };
4104
5916
  }
4105
5917
 
4106
- 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 };
5918
+ DOM.setPolicy(DOMPolicy.create());
5919
+
5920
+ 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 };