@microsoft/fast-element 2.0.0-beta.2 → 2.0.0-beta.20

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 (113) hide show
  1. package/CHANGELOG.json +488 -0
  2. package/CHANGELOG.md +180 -1
  3. package/dist/dts/components/attributes.d.ts +15 -0
  4. package/dist/dts/components/{controller.d.ts → element-controller.d.ts} +74 -28
  5. package/dist/dts/components/fast-definitions.d.ts +41 -9
  6. package/dist/dts/components/fast-element.d.ts +14 -26
  7. package/dist/dts/components/hydration.d.ts +14 -0
  8. package/dist/{esm/observation/behavior.js → dts/components/install-hydration.d.ts} +0 -0
  9. package/dist/dts/context.d.ts +1 -1
  10. package/dist/dts/di/di.d.ts +894 -0
  11. package/dist/dts/dom-policy.d.ts +68 -0
  12. package/dist/dts/dom.d.ts +100 -0
  13. package/dist/dts/index.d.ts +5 -4
  14. package/dist/dts/index.rollup.d.ts +0 -1
  15. package/dist/dts/index.rollup.debug.d.ts +0 -1
  16. package/dist/dts/interfaces.d.ts +60 -79
  17. package/dist/dts/observation/observable.d.ts +99 -54
  18. package/dist/dts/pending-task.d.ts +20 -0
  19. package/dist/dts/platform.d.ts +7 -0
  20. package/dist/dts/polyfills.d.ts +0 -8
  21. package/dist/dts/state/exports.d.ts +3 -0
  22. package/dist/dts/state/reactive.d.ts +8 -0
  23. package/dist/dts/state/state.d.ts +141 -0
  24. package/dist/dts/state/visitor.d.ts +6 -0
  25. package/dist/dts/state/watch.d.ts +10 -0
  26. package/dist/dts/styles/css-directive.d.ts +2 -2
  27. package/dist/dts/styles/css.d.ts +0 -5
  28. package/dist/dts/styles/element-styles.d.ts +10 -17
  29. package/dist/dts/styles/host.d.ts +68 -0
  30. package/dist/dts/styles/style-strategy.d.ts +42 -0
  31. package/dist/dts/templating/binding-signal.d.ts +12 -27
  32. package/dist/dts/templating/binding-two-way.d.ts +22 -37
  33. package/dist/dts/templating/binding.d.ts +76 -208
  34. package/dist/dts/templating/children.d.ts +1 -1
  35. package/dist/dts/templating/compiler.d.ts +11 -13
  36. package/dist/dts/templating/html-directive.d.ts +91 -97
  37. package/dist/dts/templating/node-observation.d.ts +15 -6
  38. package/dist/dts/templating/ref.d.ts +7 -11
  39. package/dist/dts/templating/render.d.ts +296 -0
  40. package/dist/dts/templating/repeat.d.ts +23 -34
  41. package/dist/dts/templating/slotted.d.ts +1 -1
  42. package/dist/dts/templating/template.d.ts +92 -14
  43. package/dist/dts/templating/view.d.ts +81 -11
  44. package/dist/dts/templating/when.d.ts +3 -3
  45. package/dist/dts/testing/exports.d.ts +3 -0
  46. package/dist/dts/testing/fakes.d.ts +14 -0
  47. package/dist/dts/testing/fixture.d.ts +84 -0
  48. package/dist/dts/testing/timeout.d.ts +7 -0
  49. package/dist/dts/utilities.d.ts +53 -18
  50. package/dist/esm/components/attributes.js +28 -5
  51. package/dist/esm/components/{controller.js → element-controller.js} +239 -137
  52. package/dist/esm/components/fast-definitions.js +38 -30
  53. package/dist/esm/components/fast-element.js +27 -16
  54. package/dist/esm/components/hydration.js +35 -0
  55. package/dist/esm/components/install-hydration.js +2 -0
  56. package/dist/esm/context.js +5 -1
  57. package/dist/esm/debug.js +40 -5
  58. package/dist/esm/di/di.js +1430 -0
  59. package/dist/esm/dom-policy.js +337 -0
  60. package/dist/esm/dom.js +101 -0
  61. package/dist/esm/index.js +4 -2
  62. package/dist/esm/index.rollup.debug.js +3 -1
  63. package/dist/esm/index.rollup.js +3 -1
  64. package/dist/esm/interfaces.js +52 -0
  65. package/dist/esm/observation/arrays.js +303 -2
  66. package/dist/esm/observation/observable.js +88 -142
  67. package/dist/esm/observation/update-queue.js +2 -2
  68. package/dist/esm/pending-task.js +16 -0
  69. package/dist/esm/platform.js +27 -2
  70. package/dist/esm/polyfills.js +3 -61
  71. package/dist/esm/state/exports.js +3 -0
  72. package/dist/esm/state/reactive.js +34 -0
  73. package/dist/esm/state/state.js +148 -0
  74. package/dist/esm/state/visitor.js +28 -0
  75. package/dist/esm/state/watch.js +36 -0
  76. package/dist/esm/styles/css.js +4 -9
  77. package/dist/esm/styles/element-styles.js +14 -33
  78. package/dist/esm/styles/host.js +1 -0
  79. package/dist/esm/styles/style-strategy.js +1 -0
  80. package/dist/esm/templating/binding-signal.js +67 -62
  81. package/dist/esm/templating/binding-two-way.js +72 -39
  82. package/dist/esm/templating/binding.js +142 -286
  83. package/dist/esm/templating/children.js +8 -4
  84. package/dist/esm/templating/compiler.js +59 -43
  85. package/dist/esm/templating/html-directive.js +56 -75
  86. package/dist/esm/templating/node-observation.js +20 -13
  87. package/dist/esm/templating/ref.js +4 -12
  88. package/dist/esm/templating/render.js +402 -0
  89. package/dist/esm/templating/repeat.js +88 -75
  90. package/dist/esm/templating/template.js +132 -60
  91. package/dist/esm/templating/view.js +113 -29
  92. package/dist/esm/templating/when.js +5 -4
  93. package/dist/esm/testing/exports.js +3 -0
  94. package/dist/esm/testing/fakes.js +107 -0
  95. package/dist/esm/testing/fixture.js +86 -0
  96. package/dist/esm/testing/timeout.js +24 -0
  97. package/dist/esm/utilities.js +95 -95
  98. package/dist/fast-element.api.json +9487 -8326
  99. package/dist/fast-element.d.ts +847 -644
  100. package/dist/fast-element.debug.js +1993 -1166
  101. package/dist/fast-element.debug.min.js +1 -1
  102. package/dist/fast-element.js +1903 -1111
  103. package/dist/fast-element.min.js +1 -1
  104. package/dist/fast-element.untrimmed.d.ts +911 -701
  105. package/docs/api-report.md +329 -252
  106. package/package.json +38 -16
  107. package/dist/dts/hooks.d.ts +0 -20
  108. package/dist/dts/observation/behavior.d.ts +0 -19
  109. package/dist/dts/observation/splice-strategies.d.ts +0 -13
  110. package/dist/dts/templating/dom.d.ts +0 -41
  111. package/dist/esm/hooks.js +0 -32
  112. package/dist/esm/observation/splice-strategies.js +0 -400
  113. package/dist/esm/templating/dom.js +0 -49
@@ -1,10 +1,137 @@
1
+ if (globalThis.FAST === void 0) {
2
+ Reflect.defineProperty(globalThis, "FAST", {
3
+ value: Object.create(null),
4
+ configurable: false,
5
+ enumerable: false,
6
+ writable: false,
7
+ });
8
+ }
9
+ const FAST$1 = globalThis.FAST;
10
+ const debugMessages = {
11
+ [1101 /* needsArrayObservation */]: "Must call enableArrayObservation before observing arrays.",
12
+ [1201 /* onlySetDOMPolicyOnce */]: "The DOM Policy can only be set once.",
13
+ [1202 /* bindingInnerHTMLRequiresTrustedTypes */]: "To bind innerHTML, you must use a TrustedTypesPolicy.",
14
+ [1203 /* twoWayBindingRequiresObservables */]: "View=>Model update skipped. To use twoWay binding, the target property must be observable.",
15
+ [1204 /* hostBindingWithoutHost */]: "No host element is present. Cannot bind host with ${name}.",
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
+ [1401 /* missingElementDefinition */]: "Missing FASTElement definition.",
21
+ [1501 /* noRegistrationForContext */]: "No registration for Context/Interface '${name}'.",
22
+ [1502 /* noFactoryForResolver */]: "Dependency injection resolver for '${key}' returned a null factory.",
23
+ [1503 /* invalidResolverStrategy */]: "Invalid dependency injection resolver strategy specified '${strategy}'.",
24
+ [1504 /* cannotAutoregisterDependency */]: "Unable to autoregister dependency.",
25
+ [1505 /* cannotResolveKey */]: "Unable to resolve dependency injection key '${key}'.",
26
+ [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
+ [1507 /* cannotJITRegisterNonConstructor */]: "Attempted to jitRegister something that is not a constructor '${value}'. Did you forget to register this dependency?",
28
+ [1508 /* cannotJITRegisterIntrinsic */]: "Attempted to jitRegister an intrinsic type '${value}'. Did you forget to add @inject(Key)?",
29
+ [1509 /* cannotJITRegisterInterface */]: "Attempted to jitRegister an interface '${value}'.",
30
+ [1510 /* invalidResolver */]: "A valid resolver was not returned from the register method.",
31
+ [1511 /* invalidKey */]: "Key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?",
32
+ [1512 /* noDefaultResolver */]: "'${key}' not registered. Did you forget to add @singleton()?",
33
+ [1513 /* cyclicDependency */]: "Cyclic dependency found '${name}'.",
34
+ [1514 /* connectUpdateRequiresController */]: "Injected properties that are updated on changes to DOM connectivity require the target object to be an instance of FASTElement.",
35
+ };
36
+ const allPlaceholders = /(\$\{\w+?})/g;
37
+ const placeholder = /\$\{(\w+?)}/g;
38
+ const noValues = Object.freeze({});
39
+ function formatMessage(message, values) {
40
+ return message
41
+ .split(allPlaceholders)
42
+ .map(v => {
43
+ var _a;
44
+ const replaced = v.replace(placeholder, "$1");
45
+ return String((_a = values[replaced]) !== null && _a !== void 0 ? _a : v);
46
+ })
47
+ .join("");
48
+ }
49
+ Object.assign(FAST$1, {
50
+ addMessages(messages) {
51
+ Object.assign(debugMessages, messages);
52
+ },
53
+ warn(code, values = noValues) {
54
+ var _a;
55
+ const message = (_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Warning";
56
+ console.warn(formatMessage(message, values));
57
+ },
58
+ error(code, values = noValues) {
59
+ var _a;
60
+ const message = (_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Error";
61
+ return new Error(formatMessage(message, values));
62
+ },
63
+ });
64
+
65
+ let kernelMode;
66
+ const kernelAttr = "fast-kernel";
67
+ try {
68
+ if (document.currentScript) {
69
+ kernelMode = document.currentScript.getAttribute(kernelAttr);
70
+ }
71
+ else {
72
+ const scripts = document.getElementsByTagName("script");
73
+ const currentScript = scripts[scripts.length - 1];
74
+ kernelMode = currentScript.getAttribute(kernelAttr);
75
+ }
76
+ }
77
+ catch (e) {
78
+ kernelMode = "isolate";
79
+ }
80
+ let KernelServiceId;
81
+ switch (kernelMode) {
82
+ case "share": // share the kernel across major versions
83
+ KernelServiceId = Object.freeze({
84
+ updateQueue: 1,
85
+ observable: 2,
86
+ contextEvent: 3,
87
+ elementRegistry: 4,
88
+ });
89
+ break;
90
+ case "share-v2": // only share the kernel with other v2 instances
91
+ KernelServiceId = Object.freeze({
92
+ updateQueue: 1.2,
93
+ observable: 2.2,
94
+ contextEvent: 3.2,
95
+ elementRegistry: 4.2,
96
+ });
97
+ break;
98
+ default:
99
+ // fully isolate the kernel from all other FAST instances
100
+ const postfix = `-${Math.random().toString(36).substring(2, 8)}`;
101
+ KernelServiceId = Object.freeze({
102
+ updateQueue: `1.2${postfix}`,
103
+ observable: `2.2${postfix}`,
104
+ contextEvent: `3.2${postfix}`,
105
+ elementRegistry: `4.2${postfix}`,
106
+ });
107
+ break;
108
+ }
109
+ /**
110
+ * Determines whether or not an object is a function.
111
+ * @internal
112
+ */
113
+ const isFunction = (object) => typeof object === "function";
114
+ /**
115
+ * Determines whether or not an object is a string.
116
+ * @internal
117
+ */
118
+ const isString = (object) => typeof object === "string";
119
+ /**
120
+ * A function which does nothing.
121
+ * @internal
122
+ */
123
+ const noop = () => void 0;
124
+
125
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
1
126
  (function ensureGlobalThis() {
2
127
  if (typeof globalThis !== "undefined") {
3
128
  // We're running in a modern environment.
4
129
  return;
5
130
  }
131
+ // @ts-ignore
6
132
  if (typeof global !== "undefined") {
7
133
  // We're running in NodeJS
134
+ // @ts-ignore
8
135
  global.globalThis = global;
9
136
  }
10
137
  else if (typeof self !== "undefined") {
@@ -22,98 +149,8 @@
22
149
  result.globalThis = result;
23
150
  }
24
151
  })();
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
152
 
87
- if (globalThis.FAST === void 0) {
88
- Reflect.defineProperty(globalThis, "FAST", {
89
- value: Object.create(null),
90
- configurable: false,
91
- enumerable: false,
92
- writable: false,
93
- });
94
- }
95
- const FAST$1 = globalThis.FAST;
96
- const debugMessages = {
97
- [1101 /* needsArrayObservation */]: "Must call enableArrayObservation before observing arrays.",
98
- [1201 /* onlySetHTMLPolicyOnce */]: "The HTML policy can only be set once.",
99
- [1202 /* bindingInnerHTMLRequiresTrustedTypes */]: "To bind innerHTML, you must use a TrustedTypesPolicy.",
100
- [1401 /* missingElementDefinition */]: "Missing FASTElement definition.",
101
- };
102
- Object.assign(FAST$1, {
103
- addMessages(messages) {
104
- Object.assign(debugMessages, messages);
105
- },
106
- warn(code, ...args) {
107
- var _a;
108
- console.warn((_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Warning");
109
- },
110
- error(code, ...args) {
111
- var _a;
112
- return new Error((_a = debugMessages[code]) !== null && _a !== void 0 ? _a : "Unknown Error");
113
- },
114
- });
115
-
116
- // ensure FAST global - duplicated in polyfills.ts and debug.ts
153
+ // ensure FAST global - duplicated debug.ts
117
154
  const propConfig = {
118
155
  configurable: false,
119
156
  enumerable: false,
@@ -141,7 +178,7 @@ if (FAST.error === void 0) {
141
178
  Object.assign(FAST, {
142
179
  warn() { },
143
180
  error(code) {
144
- return new Error(`Code ${code}`);
181
+ return new Error(`Error ${code}`);
145
182
  },
146
183
  addMessages() { },
147
184
  });
@@ -172,25 +209,475 @@ function createTypeRegistry() {
172
209
  return typeToDefinition.get(key);
173
210
  },
174
211
  getForInstance(object) {
212
+ if (object === null || object === void 0) {
213
+ return void 0;
214
+ }
175
215
  return typeToDefinition.get(object.constructor);
176
216
  },
177
217
  });
178
218
  }
179
-
180
219
  /**
220
+ * Creates a function capable of locating metadata associated with a type.
221
+ * @returns A metadata locator function.
181
222
  * @internal
182
223
  */
183
- const isFunction = (object) => typeof object === "function";
224
+ function createMetadataLocator() {
225
+ const metadataLookup = new WeakMap();
226
+ return function (target) {
227
+ let metadata = metadataLookup.get(target);
228
+ if (metadata === void 0) {
229
+ let currentTarget = Reflect.getPrototypeOf(target);
230
+ while (metadata === void 0 && currentTarget !== null) {
231
+ metadata = metadataLookup.get(currentTarget);
232
+ currentTarget = Reflect.getPrototypeOf(currentTarget);
233
+ }
234
+ metadata = metadata === void 0 ? [] : metadata.slice(0);
235
+ metadataLookup.set(target, metadata);
236
+ }
237
+ return metadata;
238
+ };
239
+ }
240
+
184
241
  /**
185
- * @internal
242
+ * The type of HTML aspect to target.
243
+ * @public
186
244
  */
187
- const isString = (object) => typeof object === "string";
245
+ const DOMAspect = Object.freeze({
246
+ /**
247
+ * Not aspected.
248
+ */
249
+ none: 0,
250
+ /**
251
+ * An attribute.
252
+ */
253
+ attribute: 1,
254
+ /**
255
+ * A boolean attribute.
256
+ */
257
+ booleanAttribute: 2,
258
+ /**
259
+ * A property.
260
+ */
261
+ property: 3,
262
+ /**
263
+ * Content
264
+ */
265
+ content: 4,
266
+ /**
267
+ * A token list.
268
+ */
269
+ tokenList: 5,
270
+ /**
271
+ * An event.
272
+ */
273
+ event: 6,
274
+ });
275
+ const createHTML$1 = html => html;
276
+ const fastTrustedType = globalThis.trustedTypes
277
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML: createHTML$1 })
278
+ : { createHTML: createHTML$1 };
279
+ let defaultPolicy = Object.freeze({
280
+ createHTML(value) {
281
+ return fastTrustedType.createHTML(value);
282
+ },
283
+ protect(tagName, aspect, aspectName, sink) {
284
+ return sink;
285
+ },
286
+ });
287
+ const fastPolicy = defaultPolicy;
288
+ /**
289
+ * Common DOM APIs.
290
+ * @public
291
+ */
292
+ const DOM = Object.freeze({
293
+ /**
294
+ * Gets the dom policy used by the templating system.
295
+ */
296
+ get policy() {
297
+ return defaultPolicy;
298
+ },
299
+ /**
300
+ * Sets the dom policy used by the templating system.
301
+ * @param policy - The policy to set.
302
+ * @remarks
303
+ * This API can only be called once, for security reasons. It should be
304
+ * called by the application developer at the start of their program.
305
+ */
306
+ setPolicy(value) {
307
+ if (defaultPolicy !== fastPolicy) {
308
+ throw FAST.error(1201 /* Message.onlySetDOMPolicyOnce */);
309
+ }
310
+ defaultPolicy = value;
311
+ },
312
+ /**
313
+ * Sets an attribute value on an element.
314
+ * @param element - The element to set the attribute value on.
315
+ * @param attributeName - The attribute name to set.
316
+ * @param value - The value of the attribute to set.
317
+ * @remarks
318
+ * If the value is `null` or `undefined`, the attribute is removed, otherwise
319
+ * it is set to the provided value using the standard `setAttribute` API.
320
+ */
321
+ setAttribute(element, attributeName, value) {
322
+ value === null || value === undefined
323
+ ? element.removeAttribute(attributeName)
324
+ : element.setAttribute(attributeName, value);
325
+ },
326
+ /**
327
+ * Sets a boolean attribute value.
328
+ * @param element - The element to set the boolean attribute value on.
329
+ * @param attributeName - The attribute name to set.
330
+ * @param value - The value of the attribute to set.
331
+ * @remarks
332
+ * If the value is true, the attribute is added; otherwise it is removed.
333
+ */
334
+ setBooleanAttribute(element, attributeName, value) {
335
+ value
336
+ ? element.setAttribute(attributeName, "")
337
+ : element.removeAttribute(attributeName);
338
+ },
339
+ });
340
+
341
+ function safeURL(tagName, aspect, aspectName, sink) {
342
+ return (target, name, value, ...rest) => {
343
+ if (isString(value)) {
344
+ value = value.replace("javascript:", "");
345
+ }
346
+ sink(target, name, value, ...rest);
347
+ };
348
+ }
349
+ function block(tagName, aspect, aspectName, sink) {
350
+ throw new Error(`${aspectName} on ${tagName !== null && tagName !== void 0 ? tagName : "text"} is blocked by the current DOMPolicy.`);
351
+ }
352
+ const defaultDOMElementGuards = {
353
+ a: {
354
+ [DOMAspect.attribute]: {
355
+ href: safeURL,
356
+ },
357
+ [DOMAspect.property]: {
358
+ href: safeURL,
359
+ },
360
+ },
361
+ area: {
362
+ [DOMAspect.attribute]: {
363
+ href: safeURL,
364
+ },
365
+ [DOMAspect.property]: {
366
+ href: safeURL,
367
+ },
368
+ },
369
+ button: {
370
+ [DOMAspect.attribute]: {
371
+ formaction: safeURL,
372
+ },
373
+ [DOMAspect.property]: {
374
+ formAction: safeURL,
375
+ },
376
+ },
377
+ embed: {
378
+ [DOMAspect.attribute]: {
379
+ src: block,
380
+ },
381
+ [DOMAspect.property]: {
382
+ src: block,
383
+ },
384
+ },
385
+ form: {
386
+ [DOMAspect.attribute]: {
387
+ action: safeURL,
388
+ },
389
+ [DOMAspect.property]: {
390
+ action: safeURL,
391
+ },
392
+ },
393
+ frame: {
394
+ [DOMAspect.attribute]: {
395
+ src: safeURL,
396
+ },
397
+ [DOMAspect.property]: {
398
+ src: safeURL,
399
+ },
400
+ },
401
+ iframe: {
402
+ [DOMAspect.attribute]: {
403
+ src: safeURL,
404
+ },
405
+ [DOMAspect.property]: {
406
+ src: safeURL,
407
+ srcdoc: block,
408
+ },
409
+ },
410
+ input: {
411
+ [DOMAspect.attribute]: {
412
+ formaction: safeURL,
413
+ },
414
+ [DOMAspect.property]: {
415
+ formAction: safeURL,
416
+ },
417
+ },
418
+ link: {
419
+ [DOMAspect.attribute]: {
420
+ href: block,
421
+ },
422
+ [DOMAspect.property]: {
423
+ href: block,
424
+ },
425
+ },
426
+ object: {
427
+ [DOMAspect.attribute]: {
428
+ codebase: block,
429
+ data: block,
430
+ },
431
+ [DOMAspect.property]: {
432
+ codeBase: block,
433
+ data: block,
434
+ },
435
+ },
436
+ script: {
437
+ [DOMAspect.attribute]: {
438
+ src: block,
439
+ text: block,
440
+ },
441
+ [DOMAspect.property]: {
442
+ src: block,
443
+ text: block,
444
+ innerText: block,
445
+ textContent: block,
446
+ },
447
+ },
448
+ style: {
449
+ [DOMAspect.property]: {
450
+ innerText: block,
451
+ textContent: block,
452
+ },
453
+ },
454
+ };
455
+ const blockedEvents = {
456
+ onabort: block,
457
+ onauxclick: block,
458
+ onbeforeinput: block,
459
+ onbeforematch: block,
460
+ onblur: block,
461
+ oncancel: block,
462
+ oncanplay: block,
463
+ oncanplaythrough: block,
464
+ onchange: block,
465
+ onclick: block,
466
+ onclose: block,
467
+ oncontextlost: block,
468
+ oncontextmenu: block,
469
+ oncontextrestored: block,
470
+ oncopy: block,
471
+ oncuechange: block,
472
+ oncut: block,
473
+ ondblclick: block,
474
+ ondrag: block,
475
+ ondragend: block,
476
+ ondragenter: block,
477
+ ondragleave: block,
478
+ ondragover: block,
479
+ ondragstart: block,
480
+ ondrop: block,
481
+ ondurationchange: block,
482
+ onemptied: block,
483
+ onended: block,
484
+ onerror: block,
485
+ onfocus: block,
486
+ onformdata: block,
487
+ oninput: block,
488
+ oninvalid: block,
489
+ onkeydown: block,
490
+ onkeypress: block,
491
+ onkeyup: block,
492
+ onload: block,
493
+ onloadeddata: block,
494
+ onloadedmetadata: block,
495
+ onloadstart: block,
496
+ onmousedown: block,
497
+ onmouseenter: block,
498
+ onmouseleave: block,
499
+ onmousemove: block,
500
+ onmouseout: block,
501
+ onmouseover: block,
502
+ onmouseup: block,
503
+ onpaste: block,
504
+ onpause: block,
505
+ onplay: block,
506
+ onplaying: block,
507
+ onprogress: block,
508
+ onratechange: block,
509
+ onreset: block,
510
+ onresize: block,
511
+ onscroll: block,
512
+ onsecuritypolicyviolation: block,
513
+ onseeked: block,
514
+ onseeking: block,
515
+ onselect: block,
516
+ onslotchange: block,
517
+ onstalled: block,
518
+ onsubmit: block,
519
+ onsuspend: block,
520
+ ontimeupdate: block,
521
+ ontoggle: block,
522
+ onvolumechange: block,
523
+ onwaiting: block,
524
+ onwebkitanimationend: block,
525
+ onwebkitanimationiteration: block,
526
+ onwebkitanimationstart: block,
527
+ onwebkittransitionend: block,
528
+ onwheel: block,
529
+ };
530
+ const defaultDOMGuards = {
531
+ elements: defaultDOMElementGuards,
532
+ aspects: {
533
+ [DOMAspect.attribute]: Object.assign({}, blockedEvents),
534
+ [DOMAspect.property]: Object.assign({ innerHTML: block }, blockedEvents),
535
+ [DOMAspect.event]: Object.assign({}, blockedEvents),
536
+ },
537
+ };
538
+ function createDomSinkGuards(config, defaults) {
539
+ const result = {};
540
+ for (const name in defaults) {
541
+ const overrideValue = config[name];
542
+ const defaultValue = defaults[name];
543
+ switch (overrideValue) {
544
+ case null:
545
+ // remove the default
546
+ break;
547
+ case undefined:
548
+ // keep the default
549
+ result[name] = defaultValue;
550
+ break;
551
+ default:
552
+ // override the default
553
+ result[name] = overrideValue;
554
+ break;
555
+ }
556
+ }
557
+ // add any new sinks that were not overrides
558
+ for (const name in config) {
559
+ if (!(name in result)) {
560
+ result[name] = config[name];
561
+ }
562
+ }
563
+ return Object.freeze(result);
564
+ }
565
+ function createDOMAspectGuards(config, defaults) {
566
+ const result = {};
567
+ for (const aspect in defaults) {
568
+ const overrideValue = config[aspect];
569
+ const defaultValue = defaults[aspect];
570
+ switch (overrideValue) {
571
+ case null:
572
+ // remove the default
573
+ break;
574
+ case undefined:
575
+ // keep the default
576
+ result[aspect] = createDomSinkGuards(defaultValue, {});
577
+ break;
578
+ default:
579
+ // override the default
580
+ result[aspect] = createDomSinkGuards(overrideValue, defaultValue);
581
+ break;
582
+ }
583
+ }
584
+ // add any new aspect guards that were not overrides
585
+ for (const aspect in config) {
586
+ if (!(aspect in result)) {
587
+ result[aspect] = createDomSinkGuards(config[aspect], {});
588
+ }
589
+ }
590
+ return Object.freeze(result);
591
+ }
592
+ function createElementGuards(config, defaults) {
593
+ const result = {};
594
+ for (const tag in defaults) {
595
+ const overrideValue = config[tag];
596
+ const defaultValue = defaults[tag];
597
+ switch (overrideValue) {
598
+ case null:
599
+ // remove the default
600
+ break;
601
+ case undefined:
602
+ // keep the default
603
+ result[tag] = createDOMAspectGuards(overrideValue, {});
604
+ break;
605
+ default:
606
+ // override the default aspects
607
+ result[tag] = createDOMAspectGuards(overrideValue, defaultValue);
608
+ break;
609
+ }
610
+ }
611
+ // Add any new element guards that were not overrides
612
+ for (const tag in config) {
613
+ if (!(tag in result)) {
614
+ result[tag] = createDOMAspectGuards(config[tag], {});
615
+ }
616
+ }
617
+ return Object.freeze(result);
618
+ }
619
+ function createDOMGuards(config, defaults) {
620
+ return Object.freeze({
621
+ elements: config.elements
622
+ ? createElementGuards(config.elements, defaults.elements)
623
+ : defaults.elements,
624
+ aspects: config.aspects
625
+ ? createDOMAspectGuards(config.aspects, defaults.aspects)
626
+ : defaults.aspects,
627
+ });
628
+ }
629
+ function createTrustedType() {
630
+ const createHTML = html => html;
631
+ return globalThis.trustedTypes
632
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML })
633
+ : { createHTML };
634
+ }
635
+ function tryGuard(aspectGuards, tagName, aspect, aspectName, sink) {
636
+ const sinkGuards = aspectGuards[aspect];
637
+ if (sinkGuards) {
638
+ const guard = sinkGuards[aspectName];
639
+ if (guard) {
640
+ return guard(tagName, aspect, aspectName, sink);
641
+ }
642
+ }
643
+ }
644
+ const DOMPolicy = Object.freeze({
645
+ /**
646
+ * Creates a new DOM Policy object.
647
+ * @param options The options to use in creating the policy.
648
+ * @returns The newly created DOMPolicy.
649
+ */
650
+ create(options = {}) {
651
+ var _a, _b;
652
+ const trustedType = (_a = options.trustedType) !== null && _a !== void 0 ? _a : createTrustedType();
653
+ const guards = createDOMGuards((_b = options.guards) !== null && _b !== void 0 ? _b : {}, defaultDOMGuards);
654
+ return Object.freeze({
655
+ createHTML(value) {
656
+ return trustedType.createHTML(value);
657
+ },
658
+ protect(tagName, aspect, aspectName, sink) {
659
+ var _a;
660
+ // Check for element-specific guards.
661
+ const key = (tagName !== null && tagName !== void 0 ? tagName : "").toLowerCase();
662
+ const elementGuards = guards.elements[key];
663
+ if (elementGuards) {
664
+ const guard = tryGuard(elementGuards, tagName, aspect, aspectName, sink);
665
+ if (guard) {
666
+ return guard;
667
+ }
668
+ }
669
+ // Check for guards applicable to all nodes.
670
+ return ((_a = tryGuard(guards.aspects, tagName, aspect, aspectName, sink)) !== null && _a !== void 0 ? _a : sink);
671
+ },
672
+ });
673
+ },
674
+ });
188
675
 
189
676
  /**
190
677
  * The default UpdateQueue.
191
678
  * @public
192
679
  */
193
- const Updates = FAST.getById(1 /* KernelServiceId.updateQueue */, () => {
680
+ const Updates = FAST.getById(KernelServiceId.updateQueue, () => {
194
681
  const tasks = [];
195
682
  const pendingErrors = [];
196
683
  const rAF = globalThis.requestAnimationFrame;
@@ -417,15 +904,29 @@ class PropertyChangeNotifier {
417
904
  }
418
905
  }
419
906
 
907
+ /**
908
+ * Describes how the source's lifetime relates to its controller's lifetime.
909
+ * @public
910
+ */
911
+ const SourceLifetime = Object.freeze({
912
+ /**
913
+ * The source to controller lifetime relationship is unknown.
914
+ */
915
+ unknown: void 0,
916
+ /**
917
+ * The source and controller lifetimes are coupled to one another.
918
+ * They can/will be GC'd together.
919
+ */
920
+ coupled: 1,
921
+ });
420
922
  /**
421
923
  * Common Observable APIs.
422
924
  * @public
423
925
  */
424
- const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
926
+ const Observable = FAST.getById(KernelServiceId.observable, () => {
425
927
  const queueUpdate = Updates.enqueue;
426
928
  const volatileRegex = /(:|&&|\|\||if)/;
427
929
  const notifierLookup = new WeakMap();
428
- const accessorLookup = new WeakMap();
429
930
  let watcher = void 0;
430
931
  let createArrayObserver = (array) => {
431
932
  throw FAST.error(1101 /* Message.needsArrayObservation */);
@@ -440,19 +941,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
440
941
  }
441
942
  return found;
442
943
  }
443
- function getAccessors(target) {
444
- let accessors = accessorLookup.get(target);
445
- if (accessors === void 0) {
446
- let currentTarget = Reflect.getPrototypeOf(target);
447
- while (accessors === void 0 && currentTarget !== null) {
448
- accessors = accessorLookup.get(currentTarget);
449
- currentTarget = Reflect.getPrototypeOf(currentTarget);
450
- }
451
- accessors = accessors === void 0 ? [] : accessors.slice(0);
452
- accessorLookup.set(target, accessors);
453
- }
454
- return accessors;
455
- }
944
+ const getAccessors = createMetadataLocator();
456
945
  class DefaultObservableAccessor {
457
946
  constructor(name) {
458
947
  this.name = name;
@@ -478,10 +967,10 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
478
967
  }
479
968
  }
480
969
  }
481
- class BindingObserverImplementation extends SubscriberSet {
482
- constructor(binding, initialSubscriber, isVolatileBinding = false) {
483
- super(binding, initialSubscriber);
484
- this.binding = binding;
970
+ class ExpressionNotifierImplementation extends SubscriberSet {
971
+ constructor(expression, initialSubscriber, isVolatileBinding = false) {
972
+ super(expression, initialSubscriber);
973
+ this.expression = expression;
485
974
  this.isVolatileBinding = isVolatileBinding;
486
975
  this.needsRefresh = true;
487
976
  this.needsQueue = true;
@@ -492,10 +981,30 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
492
981
  this.propertyName = void 0;
493
982
  this.notifier = void 0;
494
983
  this.next = void 0;
984
+ /**
985
+ * Opts out of JSON stringification.
986
+ */
987
+ this.toJSON = noop;
495
988
  }
496
989
  setMode(isAsync) {
497
990
  this.isAsync = this.needsQueue = isAsync;
498
991
  }
992
+ bind(controller) {
993
+ this.controller = controller;
994
+ const value = this.observe(controller.source, controller.context);
995
+ if (!controller.isBound && this.requiresUnbind(controller)) {
996
+ controller.onUnbind(this);
997
+ }
998
+ return value;
999
+ }
1000
+ requiresUnbind(controller) {
1001
+ return (controller.sourceLifetime !== SourceLifetime.coupled ||
1002
+ this.first !== this.last ||
1003
+ this.first.propertySource !== controller.source);
1004
+ }
1005
+ unbind(controller) {
1006
+ this.dispose();
1007
+ }
499
1008
  observe(source, context) {
500
1009
  if (this.needsRefresh && this.last !== null) {
501
1010
  this.dispose();
@@ -503,10 +1012,19 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
503
1012
  const previousWatcher = watcher;
504
1013
  watcher = this.needsRefresh ? this : void 0;
505
1014
  this.needsRefresh = this.isVolatileBinding;
506
- const result = this.binding(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
507
- watcher = previousWatcher;
1015
+ let result;
1016
+ try {
1017
+ result = this.expression(source, context);
1018
+ }
1019
+ finally {
1020
+ watcher = previousWatcher;
1021
+ }
508
1022
  return result;
509
1023
  }
1024
+ // backwards compat with v1 kernel
1025
+ disconnect() {
1026
+ this.dispose();
1027
+ }
510
1028
  dispose() {
511
1029
  if (this.last !== null) {
512
1030
  let current = this.first;
@@ -633,22 +1151,22 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
633
1151
  */
634
1152
  getAccessors,
635
1153
  /**
636
- * Creates a {@link BindingObserver} that can watch the
637
- * provided {@link Binding} for changes.
638
- * @param binding - The binding to observe.
1154
+ * Creates a {@link ExpressionNotifier} that can watch the
1155
+ * provided {@link Expression} for changes.
1156
+ * @param expression - The binding to observe.
639
1157
  * @param initialSubscriber - An initial subscriber to changes in the binding value.
640
1158
  * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
641
1159
  */
642
- binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) {
643
- return new BindingObserverImplementation(binding, initialSubscriber, isVolatileBinding);
1160
+ binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
1161
+ return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
644
1162
  },
645
1163
  /**
646
1164
  * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
647
1165
  * on every evaluation of the value.
648
- * @param binding - The binding to inspect.
1166
+ * @param expression - The binding to inspect.
649
1167
  */
650
- isVolatileBinding(binding) {
651
- return volatileRegex.test(binding.toString());
1168
+ isVolatileBinding(expression) {
1169
+ return volatileRegex.test(expression.toString());
652
1170
  },
653
1171
  });
654
1172
  });
@@ -676,7 +1194,7 @@ function volatile(target, name, descriptor) {
676
1194
  },
677
1195
  });
678
1196
  }
679
- const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
1197
+ const contextEvent = FAST.getById(KernelServiceId.contextEvent, () => {
680
1198
  let current = null;
681
1199
  return {
682
1200
  get() {
@@ -691,123 +1209,38 @@ const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
691
1209
  * Provides additional contextual information available to behaviors and expressions.
692
1210
  * @public
693
1211
  */
694
- class ExecutionContext {
695
- constructor(parentSource = null, parentContext = null) {
696
- /**
697
- * The index of the current item within a repeat context.
698
- */
699
- this.index = 0;
700
- /**
701
- * The length of the current collection within a repeat context.
702
- */
703
- this.length = 0;
704
- this.parent = parentSource;
705
- this.parentContext = parentContext;
706
- }
707
- /**
708
- * The current event within an event handler.
709
- */
710
- get event() {
711
- return contextEvent.get();
712
- }
713
- /**
714
- * Indicates whether the current item within a repeat context
715
- * has an even index.
716
- */
717
- get isEven() {
718
- return this.index % 2 === 0;
719
- }
720
- /**
721
- * Indicates whether the current item within a repeat context
722
- * has an odd index.
723
- */
724
- get isOdd() {
725
- return this.index % 2 !== 0;
726
- }
727
- /**
728
- * Indicates whether the current item within a repeat context
729
- * is the first item in the collection.
730
- */
731
- get isFirst() {
732
- return this.index === 0;
733
- }
734
- /**
735
- * Indicates whether the current item within a repeat context
736
- * is somewhere in the middle of the collection.
737
- */
738
- get isInMiddle() {
739
- return !this.isFirst && !this.isLast;
740
- }
741
- /**
742
- * Indicates whether the current item within a repeat context
743
- * is the last item in the collection.
744
- */
745
- get isLast() {
746
- return this.index === this.length - 1;
747
- }
748
- /**
749
- * Returns the typed event detail of a custom event.
750
- */
751
- eventDetail() {
752
- return this.event.detail;
753
- }
754
- /**
755
- * Returns the typed event target of the event.
756
- */
757
- eventTarget() {
758
- return this.event.target;
759
- }
760
- /**
761
- * Updates the position/size on a context associated with a list item.
762
- * @param index - The new index of the item.
763
- * @param length - The new length of the list.
764
- */
765
- updatePosition(index, length) {
766
- this.index = index;
767
- this.length = length;
768
- }
1212
+ const ExecutionContext = Object.freeze({
769
1213
  /**
770
- * Creates a new execution context descendent from the current context.
771
- * @param source - The source for the context if different than the parent.
772
- * @returns A child execution context.
1214
+ * A default execution context.
773
1215
  */
774
- createChildContext(parentSource) {
775
- return new ExecutionContext(parentSource, this);
776
- }
1216
+ default: {
1217
+ index: 0,
1218
+ length: 0,
1219
+ get event() {
1220
+ return ExecutionContext.getEvent();
1221
+ },
1222
+ eventDetail() {
1223
+ return this.event.detail;
1224
+ },
1225
+ eventTarget() {
1226
+ return this.event.target;
1227
+ },
1228
+ },
777
1229
  /**
778
- * Creates a new execution context descent suitable for use in list rendering.
779
- * @param item - The list item to serve as the source.
780
- * @param index - The index of the item in the list.
781
- * @param length - The length of the list.
1230
+ * Gets the current event.
1231
+ * @returns An event object.
782
1232
  */
783
- createItemContext(index, length) {
784
- const childContext = Object.create(this);
785
- childContext.index = index;
786
- childContext.length = length;
787
- return childContext;
788
- }
1233
+ getEvent() {
1234
+ return contextEvent.get();
1235
+ },
789
1236
  /**
790
- * Sets the event for the current execution context.
791
- * @param event - The event to set.
792
- * @internal
1237
+ * Sets the current event.
1238
+ * @param event - An event object.
793
1239
  */
794
- static setEvent(event) {
1240
+ setEvent(event) {
795
1241
  contextEvent.set(event);
796
- }
797
- /**
798
- * Creates a new root execution context.
799
- * @returns A new execution context.
800
- */
801
- static create() {
802
- return new ExecutionContext();
803
- }
804
- }
805
- /**
806
- * The default execution context.
807
- */
808
- ExecutionContext.default = new ExecutionContext();
809
- Observable.defineProperty(ExecutionContext.prototype, "index");
810
- Observable.defineProperty(ExecutionContext.prototype, "length");
1242
+ },
1243
+ });
811
1244
 
812
1245
  /**
813
1246
  * A splice map is a representation of how a previous array of items
@@ -874,10 +1307,311 @@ const SpliceStrategySupport = Object.freeze({
874
1307
  const reset = new Splice(0, emptyArray, 0);
875
1308
  reset.reset = true;
876
1309
  const resetSplices = [reset];
1310
+ // Note: This function is *based* on the computation of the Levenshtein
1311
+ // "edit" distance. The one change is that "updates" are treated as two
1312
+ // edits - not one. With Array splices, an update is really a delete
1313
+ // followed by an add. By retaining this, we optimize for "keeping" the
1314
+ // maximum array items in the original array. For example:
1315
+ //
1316
+ // 'xxxx123' to '123yyyy'
1317
+ //
1318
+ // With 1-edit updates, the shortest path would be just to update all seven
1319
+ // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
1320
+ // leaves the substring '123' intact.
1321
+ function calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd) {
1322
+ // "Deletion" columns
1323
+ const rowCount = oldEnd - oldStart + 1;
1324
+ const columnCount = currentEnd - currentStart + 1;
1325
+ const distances = new Array(rowCount);
1326
+ let north;
1327
+ let west;
1328
+ // "Addition" rows. Initialize null column.
1329
+ for (let i = 0; i < rowCount; ++i) {
1330
+ distances[i] = new Array(columnCount);
1331
+ distances[i][0] = i;
1332
+ }
1333
+ // Initialize null row
1334
+ for (let j = 0; j < columnCount; ++j) {
1335
+ distances[0][j] = j;
1336
+ }
1337
+ for (let i = 1; i < rowCount; ++i) {
1338
+ for (let j = 1; j < columnCount; ++j) {
1339
+ if (current[currentStart + j - 1] === old[oldStart + i - 1]) {
1340
+ distances[i][j] = distances[i - 1][j - 1];
1341
+ }
1342
+ else {
1343
+ north = distances[i - 1][j] + 1;
1344
+ west = distances[i][j - 1] + 1;
1345
+ distances[i][j] = north < west ? north : west;
1346
+ }
1347
+ }
1348
+ }
1349
+ return distances;
1350
+ }
1351
+ // This starts at the final weight, and walks "backward" by finding
1352
+ // the minimum previous weight recursively until the origin of the weight
1353
+ // matrix.
1354
+ function spliceOperationsFromEditDistances(distances) {
1355
+ let i = distances.length - 1;
1356
+ let j = distances[0].length - 1;
1357
+ let current = distances[i][j];
1358
+ const edits = [];
1359
+ while (i > 0 || j > 0) {
1360
+ if (i === 0) {
1361
+ edits.push(2 /* Edit.add */);
1362
+ j--;
1363
+ continue;
1364
+ }
1365
+ if (j === 0) {
1366
+ edits.push(3 /* Edit.delete */);
1367
+ i--;
1368
+ continue;
1369
+ }
1370
+ const northWest = distances[i - 1][j - 1];
1371
+ const west = distances[i - 1][j];
1372
+ const north = distances[i][j - 1];
1373
+ let min;
1374
+ if (west < north) {
1375
+ min = west < northWest ? west : northWest;
1376
+ }
1377
+ else {
1378
+ min = north < northWest ? north : northWest;
1379
+ }
1380
+ if (min === northWest) {
1381
+ if (northWest === current) {
1382
+ edits.push(0 /* Edit.leave */);
1383
+ }
1384
+ else {
1385
+ edits.push(1 /* Edit.update */);
1386
+ current = northWest;
1387
+ }
1388
+ i--;
1389
+ j--;
1390
+ }
1391
+ else if (min === west) {
1392
+ edits.push(3 /* Edit.delete */);
1393
+ i--;
1394
+ current = west;
1395
+ }
1396
+ else {
1397
+ edits.push(2 /* Edit.add */);
1398
+ j--;
1399
+ current = north;
1400
+ }
1401
+ }
1402
+ return edits.reverse();
1403
+ }
1404
+ function sharedPrefix(current, old, searchLength) {
1405
+ for (let i = 0; i < searchLength; ++i) {
1406
+ if (current[i] !== old[i]) {
1407
+ return i;
1408
+ }
1409
+ }
1410
+ return searchLength;
1411
+ }
1412
+ function sharedSuffix(current, old, searchLength) {
1413
+ let index1 = current.length;
1414
+ let index2 = old.length;
1415
+ let count = 0;
1416
+ while (count < searchLength && current[--index1] === old[--index2]) {
1417
+ count++;
1418
+ }
1419
+ return count;
1420
+ }
1421
+ function intersect(start1, end1, start2, end2) {
1422
+ // Disjoint
1423
+ if (end1 < start2 || end2 < start1) {
1424
+ return -1;
1425
+ }
1426
+ // Adjacent
1427
+ if (end1 === start2 || end2 === start1) {
1428
+ return 0;
1429
+ }
1430
+ // Non-zero intersect, span1 first
1431
+ if (start1 < start2) {
1432
+ if (end1 < end2) {
1433
+ return end1 - start2; // Overlap
1434
+ }
1435
+ return end2 - start2; // Contained
1436
+ }
1437
+ // Non-zero intersect, span2 first
1438
+ if (end2 < end1) {
1439
+ return end2 - start1; // Overlap
1440
+ }
1441
+ return end1 - start1; // Contained
1442
+ }
1443
+ /**
1444
+ * @remarks
1445
+ * Lacking individual splice mutation information, the minimal set of
1446
+ * splices can be synthesized given the previous state and final state of an
1447
+ * array. The basic approach is to calculate the edit distance matrix and
1448
+ * choose the shortest path through it.
1449
+ *
1450
+ * Complexity: O(l * p)
1451
+ * l: The length of the current array
1452
+ * p: The length of the old array
1453
+ */
1454
+ function calc(current, currentStart, currentEnd, old, oldStart, oldEnd) {
1455
+ let prefixCount = 0;
1456
+ let suffixCount = 0;
1457
+ const minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
1458
+ if (currentStart === 0 && oldStart === 0) {
1459
+ prefixCount = sharedPrefix(current, old, minLength);
1460
+ }
1461
+ if (currentEnd === current.length && oldEnd === old.length) {
1462
+ suffixCount = sharedSuffix(current, old, minLength - prefixCount);
1463
+ }
1464
+ currentStart += prefixCount;
1465
+ oldStart += prefixCount;
1466
+ currentEnd -= suffixCount;
1467
+ oldEnd -= suffixCount;
1468
+ if (currentEnd - currentStart === 0 && oldEnd - oldStart === 0) {
1469
+ return emptyArray;
1470
+ }
1471
+ if (currentStart === currentEnd) {
1472
+ const splice = new Splice(currentStart, [], 0);
1473
+ while (oldStart < oldEnd) {
1474
+ splice.removed.push(old[oldStart++]);
1475
+ }
1476
+ return [splice];
1477
+ }
1478
+ else if (oldStart === oldEnd) {
1479
+ return [new Splice(currentStart, [], currentEnd - currentStart)];
1480
+ }
1481
+ const ops = spliceOperationsFromEditDistances(calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
1482
+ const splices = [];
1483
+ let splice = void 0;
1484
+ let index = currentStart;
1485
+ let oldIndex = oldStart;
1486
+ for (let i = 0; i < ops.length; ++i) {
1487
+ switch (ops[i]) {
1488
+ case 0 /* Edit.leave */:
1489
+ if (splice !== void 0) {
1490
+ splices.push(splice);
1491
+ splice = void 0;
1492
+ }
1493
+ index++;
1494
+ oldIndex++;
1495
+ break;
1496
+ case 1 /* Edit.update */:
1497
+ if (splice === void 0) {
1498
+ splice = new Splice(index, [], 0);
1499
+ }
1500
+ splice.addedCount++;
1501
+ index++;
1502
+ splice.removed.push(old[oldIndex]);
1503
+ oldIndex++;
1504
+ break;
1505
+ case 2 /* Edit.add */:
1506
+ if (splice === void 0) {
1507
+ splice = new Splice(index, [], 0);
1508
+ }
1509
+ splice.addedCount++;
1510
+ index++;
1511
+ break;
1512
+ case 3 /* Edit.delete */:
1513
+ if (splice === void 0) {
1514
+ splice = new Splice(index, [], 0);
1515
+ }
1516
+ splice.removed.push(old[oldIndex]);
1517
+ oldIndex++;
1518
+ break;
1519
+ // no default
1520
+ }
1521
+ }
1522
+ if (splice !== void 0) {
1523
+ splices.push(splice);
1524
+ }
1525
+ return splices;
1526
+ }
1527
+ function merge(splice, splices) {
1528
+ let inserted = false;
1529
+ let insertionOffset = 0;
1530
+ for (let i = 0; i < splices.length; i++) {
1531
+ const current = splices[i];
1532
+ current.index += insertionOffset;
1533
+ if (inserted) {
1534
+ continue;
1535
+ }
1536
+ const intersectCount = intersect(splice.index, splice.index + splice.removed.length, current.index, current.index + current.addedCount);
1537
+ if (intersectCount >= 0) {
1538
+ // Merge the two splices
1539
+ splices.splice(i, 1);
1540
+ i--;
1541
+ insertionOffset -= current.addedCount - current.removed.length;
1542
+ splice.addedCount += current.addedCount - intersectCount;
1543
+ const deleteCount = splice.removed.length + current.removed.length - intersectCount;
1544
+ if (!splice.addedCount && !deleteCount) {
1545
+ // merged splice is a noop. discard.
1546
+ inserted = true;
1547
+ }
1548
+ else {
1549
+ let currentRemoved = current.removed;
1550
+ if (splice.index < current.index) {
1551
+ // some prefix of splice.removed is prepended to current.removed.
1552
+ const prepend = splice.removed.slice(0, current.index - splice.index);
1553
+ prepend.push(...currentRemoved);
1554
+ currentRemoved = prepend;
1555
+ }
1556
+ if (splice.index + splice.removed.length >
1557
+ current.index + current.addedCount) {
1558
+ // some suffix of splice.removed is appended to current.removed.
1559
+ const append = splice.removed.slice(current.index + current.addedCount - splice.index);
1560
+ currentRemoved.push(...append);
1561
+ }
1562
+ splice.removed = currentRemoved;
1563
+ if (current.index < splice.index) {
1564
+ splice.index = current.index;
1565
+ }
1566
+ }
1567
+ }
1568
+ else if (splice.index < current.index) {
1569
+ // Insert splice here.
1570
+ inserted = true;
1571
+ splices.splice(i, 0, splice);
1572
+ i++;
1573
+ const offset = splice.addedCount - splice.removed.length;
1574
+ current.index += offset;
1575
+ insertionOffset += offset;
1576
+ }
1577
+ }
1578
+ if (!inserted) {
1579
+ splices.push(splice);
1580
+ }
1581
+ }
1582
+ function project(array, changes) {
1583
+ let splices = [];
1584
+ const initialSplices = [];
1585
+ for (let i = 0, ii = changes.length; i < ii; i++) {
1586
+ merge(changes[i], initialSplices);
1587
+ }
1588
+ for (let i = 0, ii = initialSplices.length; i < ii; ++i) {
1589
+ const splice = initialSplices[i];
1590
+ if (splice.addedCount === 1 && splice.removed.length === 1) {
1591
+ if (splice.removed[0] !== array[splice.index]) {
1592
+ splices.push(splice);
1593
+ }
1594
+ continue;
1595
+ }
1596
+ splices = splices.concat(calc(array, splice.index, splice.index + splice.addedCount, splice.removed, 0, splice.removed.length));
1597
+ }
1598
+ return splices;
1599
+ }
1600
+ /**
1601
+ * A SpliceStrategy that attempts to merge all splices into the minimal set of
1602
+ * splices needed to represent the change from the old array to the new array.
1603
+ * @public
1604
+ */
877
1605
  let defaultSpliceStrategy = Object.freeze({
878
- support: SpliceStrategySupport.splice,
1606
+ support: SpliceStrategySupport.optimized,
879
1607
  normalize(previous, current, changes) {
880
- return previous === void 0 ? changes !== null && changes !== void 0 ? changes : emptyArray : resetSplices;
1608
+ if (previous === void 0) {
1609
+ if (changes === void 0) {
1610
+ return emptyArray;
1611
+ }
1612
+ return changes.length > 1 ? project(current, changes) : changes;
1613
+ }
1614
+ return resetSplices;
881
1615
  },
882
1616
  pop(array, observer, pop, args) {
883
1617
  const notEmpty = array.length > 0;
@@ -1075,7 +1809,6 @@ function lengthOf(array) {
1075
1809
  return array.length;
1076
1810
  }
1077
1811
 
1078
- const styleSheetCache = new Map();
1079
1812
  let DefaultStyleStrategy;
1080
1813
  function reduceStyles(styles) {
1081
1814
  return styles
@@ -1146,42 +1879,26 @@ class ElementStyles {
1146
1879
  static setDefaultStrategy(Strategy) {
1147
1880
  DefaultStyleStrategy = Strategy;
1148
1881
  }
1882
+ /**
1883
+ * Normalizes a set of composable style options.
1884
+ * @param styles - The style options to normalize.
1885
+ * @returns A singular ElementStyles instance or undefined.
1886
+ */
1887
+ static normalize(styles) {
1888
+ return styles === void 0
1889
+ ? void 0
1890
+ : Array.isArray(styles)
1891
+ ? new ElementStyles(styles)
1892
+ : styles instanceof ElementStyles
1893
+ ? styles
1894
+ : new ElementStyles([styles]);
1895
+ }
1149
1896
  }
1150
1897
  /**
1151
1898
  * Indicates whether the DOM supports the adoptedStyleSheets feature.
1152
1899
  */
1153
1900
  ElementStyles.supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
1154
1901
  "replace" in CSSStyleSheet.prototype;
1155
- /**
1156
- * https://wicg.github.io/construct-stylesheets/
1157
- * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
1158
- *
1159
- * @internal
1160
- */
1161
- class AdoptedStyleSheetsStrategy {
1162
- constructor(styles) {
1163
- this.sheets = styles.map((x) => {
1164
- if (x instanceof CSSStyleSheet) {
1165
- return x;
1166
- }
1167
- let sheet = styleSheetCache.get(x);
1168
- if (sheet === void 0) {
1169
- sheet = new CSSStyleSheet();
1170
- sheet.replaceSync(x);
1171
- styleSheetCache.set(x, sheet);
1172
- }
1173
- return sheet;
1174
- });
1175
- }
1176
- addStylesTo(target) {
1177
- target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...this.sheets];
1178
- }
1179
- removeStylesFrom(target) {
1180
- const sheets = this.sheets;
1181
- target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
1182
- }
1183
- }
1184
- ElementStyles.setDefaultStrategy(FAST.getById(5 /* KernelServiceId.styleSheetStrategy */, () => AdoptedStyleSheetsStrategy));
1185
1902
 
1186
1903
  const registry$1 = createTypeRegistry();
1187
1904
  /**
@@ -1269,101 +1986,47 @@ const css = ((strings, ...values) => {
1269
1986
  class CSSPartial {
1270
1987
  constructor(styles, behaviors) {
1271
1988
  this.behaviors = behaviors;
1272
- this.css = "";
1273
- const stylesheets = styles.reduce((accumulated, current) => {
1274
- if (isString(current)) {
1275
- this.css += current;
1276
- }
1277
- else {
1278
- accumulated.push(current);
1279
- }
1280
- return accumulated;
1281
- }, []);
1282
- if (stylesheets.length) {
1283
- this.styles = new ElementStyles(stylesheets);
1284
- }
1285
- }
1286
- createCSS(add) {
1287
- this.behaviors.forEach(add);
1288
- if (this.styles) {
1289
- add(this);
1290
- }
1291
- return this.css;
1292
- }
1293
- bind(el) {
1294
- el.$fastController.addStyles(this.styles);
1295
- }
1296
- unbind(el) {
1297
- el.$fastController.removeStyles(this.styles);
1298
- }
1299
- }
1300
- CSSDirective.define(CSSPartial);
1301
- css.partial = (strings, ...values) => {
1302
- const { styles, behaviors } = collectStyles(strings, values);
1303
- return new CSSPartial(styles, behaviors);
1304
- };
1305
- /**
1306
- * @deprecated Use css.partial instead.
1307
- * @public
1308
- */
1309
- const cssPartial = css.partial;
1310
-
1311
- /**
1312
- * Common DOM APIs.
1313
- * @public
1314
- */
1315
- const DOM = Object.freeze({
1316
- /**
1317
- * @deprecated
1318
- * Use Updates.enqueue().
1319
- */
1320
- queueUpdate: Updates.enqueue,
1321
- /**
1322
- * @deprecated
1323
- * Use Updates.next()
1324
- */
1325
- nextUpdate: Updates.next,
1326
- /**
1327
- * @deprecated
1328
- * Use Updates.process()
1329
- */
1330
- processUpdates: Updates.process,
1331
- /**
1332
- * Sets an attribute value on an element.
1333
- * @param element - The element to set the attribute value on.
1334
- * @param attributeName - The attribute name to set.
1335
- * @param value - The value of the attribute to set.
1336
- * @remarks
1337
- * If the value is `null` or `undefined`, the attribute is removed, otherwise
1338
- * it is set to the provided value using the standard `setAttribute` API.
1339
- */
1340
- setAttribute(element, attributeName, value) {
1341
- value === null || value === undefined
1342
- ? element.removeAttribute(attributeName)
1343
- : element.setAttribute(attributeName, value);
1344
- },
1345
- /**
1346
- * Sets a boolean attribute value.
1347
- * @param element - The element to set the boolean attribute value on.
1348
- * @param attributeName - The attribute name to set.
1349
- * @param value - The value of the attribute to set.
1350
- * @remarks
1351
- * If the value is true, the attribute is added; otherwise it is removed.
1352
- */
1353
- setBooleanAttribute(element, attributeName, value) {
1354
- value
1355
- ? element.setAttribute(attributeName, "")
1356
- : element.removeAttribute(attributeName);
1357
- },
1358
- });
1989
+ this.css = "";
1990
+ const stylesheets = styles.reduce((accumulated, current) => {
1991
+ if (isString(current)) {
1992
+ this.css += current;
1993
+ }
1994
+ else {
1995
+ accumulated.push(current);
1996
+ }
1997
+ return accumulated;
1998
+ }, []);
1999
+ if (stylesheets.length) {
2000
+ this.styles = new ElementStyles(stylesheets);
2001
+ }
2002
+ }
2003
+ createCSS(add) {
2004
+ this.behaviors.forEach(add);
2005
+ if (this.styles) {
2006
+ add(this);
2007
+ }
2008
+ return this.css;
2009
+ }
2010
+ addedCallback(controller) {
2011
+ controller.addStyles(this.styles);
2012
+ }
2013
+ removedCallback(controller) {
2014
+ controller.removeStyles(this.styles);
2015
+ }
2016
+ }
2017
+ CSSDirective.define(CSSPartial);
2018
+ css.partial = (strings, ...values) => {
2019
+ const { styles, behaviors } = collectStyles(strings, values);
2020
+ return new CSSPartial(styles, behaviors);
2021
+ };
1359
2022
 
1360
2023
  const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
1361
2024
  const interpolationStart = `${marker}{`;
1362
2025
  const interpolationEnd = `}${marker}`;
1363
2026
  const interpolationEndLength = interpolationEnd.length;
1364
- let id = 0;
2027
+ let id$1 = 0;
1365
2028
  /** @internal */
1366
- const nextId = () => `${marker}-${++id}`;
2029
+ const nextId = () => `${marker}-${++id$1}`;
1367
2030
  /**
1368
2031
  * Common APIs related to markup generation.
1369
2032
  * @public
@@ -1460,97 +2123,71 @@ const HTMLDirective = Object.freeze({
1460
2123
  registry.register(options);
1461
2124
  return type;
1462
2125
  },
1463
- });
1464
- /**
1465
- * Decorator: Defines an HTMLDirective.
1466
- * @param options - Provides options that specify the directive's application.
1467
- * @public
1468
- */
1469
- function htmlDirective(options) {
1470
- /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
1471
- return function (type) {
1472
- HTMLDirective.define(type, options);
1473
- };
1474
- }
1475
- /**
1476
- * The type of HTML aspect to target.
1477
- * @public
1478
- */
1479
- const Aspect = Object.freeze({
1480
- /**
1481
- * Not aspected.
1482
- */
1483
- none: 0,
1484
- /**
1485
- * An attribute.
1486
- */
1487
- attribute: 1,
1488
- /**
1489
- * A boolean attribute.
1490
- */
1491
- booleanAttribute: 2,
1492
- /**
1493
- * A property.
1494
- */
1495
- property: 3,
1496
- /**
1497
- * Content
1498
- */
1499
- content: 4,
1500
- /**
1501
- * A token list.
1502
- */
1503
- tokenList: 5,
1504
- /**
1505
- * An event.
1506
- */
1507
- event: 6,
1508
2126
  /**
1509
2127
  *
1510
2128
  * @param directive - The directive to assign the aspect to.
1511
2129
  * @param value - The value to base the aspect determination on.
2130
+ * @remarks
2131
+ * If a falsy value is provided, then the content aspect will be assigned.
1512
2132
  */
1513
- assign(directive, value) {
1514
- directive.sourceAspect = value;
2133
+ assignAspect(directive, value) {
1515
2134
  if (!value) {
2135
+ directive.aspectType = DOMAspect.content;
1516
2136
  return;
1517
2137
  }
2138
+ directive.sourceAspect = value;
1518
2139
  switch (value[0]) {
1519
2140
  case ":":
1520
2141
  directive.targetAspect = value.substring(1);
1521
- switch (directive.targetAspect) {
1522
- case "innerHTML":
1523
- directive.aspectType = Aspect.property;
1524
- break;
1525
- case "classList":
1526
- directive.aspectType = Aspect.tokenList;
1527
- break;
1528
- default:
1529
- directive.aspectType = Aspect.property;
1530
- break;
1531
- }
2142
+ directive.aspectType =
2143
+ directive.targetAspect === "classList"
2144
+ ? DOMAspect.tokenList
2145
+ : DOMAspect.property;
1532
2146
  break;
1533
2147
  case "?":
1534
2148
  directive.targetAspect = value.substring(1);
1535
- directive.aspectType = Aspect.booleanAttribute;
2149
+ directive.aspectType = DOMAspect.booleanAttribute;
1536
2150
  break;
1537
2151
  case "@":
1538
2152
  directive.targetAspect = value.substring(1);
1539
- directive.aspectType = Aspect.event;
2153
+ directive.aspectType = DOMAspect.event;
1540
2154
  break;
1541
2155
  default:
1542
- if (value === "class") {
1543
- directive.targetAspect = "className";
1544
- directive.aspectType = Aspect.property;
1545
- }
1546
- else {
1547
- directive.targetAspect = value;
1548
- directive.aspectType = Aspect.attribute;
1549
- }
2156
+ directive.targetAspect = value;
2157
+ directive.aspectType = DOMAspect.attribute;
1550
2158
  break;
1551
2159
  }
1552
2160
  },
1553
2161
  });
2162
+ /**
2163
+ * Decorator: Defines an HTMLDirective.
2164
+ * @param options - Provides options that specify the directive's application.
2165
+ * @public
2166
+ */
2167
+ function htmlDirective(options) {
2168
+ /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
2169
+ return function (type) {
2170
+ HTMLDirective.define(type, options);
2171
+ };
2172
+ }
2173
+ /**
2174
+ * Captures a binding expression along with related information and capabilities.
2175
+ *
2176
+ * @public
2177
+ */
2178
+ class Binding {
2179
+ /**
2180
+ * Creates a binding.
2181
+ * @param evaluate - Evaluates the binding.
2182
+ * @param policy - The security policy to associate with this binding.
2183
+ * @param isVolatile - Indicates whether the binding is volatile.
2184
+ */
2185
+ constructor(evaluate, policy, isVolatile = false) {
2186
+ this.evaluate = evaluate;
2187
+ this.policy = policy;
2188
+ this.isVolatile = isVolatile;
2189
+ }
2190
+ }
1554
2191
  /**
1555
2192
  * A base class used for attribute directives that don't need internal state.
1556
2193
  * @public
@@ -1562,13 +2199,11 @@ class StatelessAttachedAttributeDirective {
1562
2199
  */
1563
2200
  constructor(options) {
1564
2201
  this.options = options;
1565
- }
1566
- /**
1567
- * Creates a behavior.
1568
- * @param targets - The targets available for behaviors to be attached to.
1569
- */
1570
- createBehavior(targets) {
1571
- return this;
2202
+ /**
2203
+ * Opts out of JSON stringification.
2204
+ * @internal
2205
+ */
2206
+ this.toJSON = noop;
1572
2207
  }
1573
2208
  /**
1574
2209
  * Creates a placeholder string based on the directive's index within the template.
@@ -1579,118 +2214,43 @@ class StatelessAttachedAttributeDirective {
1579
2214
  createHTML(add) {
1580
2215
  return Markup.attribute(add(this));
1581
2216
  }
1582
- }
1583
-
1584
- const createInnerHTMLBinding = globalThis.TrustedHTML
1585
- ? (binding) => (s, c) => {
1586
- const value = binding(s, c);
1587
- if (value instanceof TrustedHTML) {
1588
- return value;
1589
- }
1590
- throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
1591
- }
1592
- : (binding) => binding;
1593
- /**
1594
- * Describes how aspects of an HTML element will be affected by bindings.
1595
- * @public
1596
- */
1597
- const BindingMode = Object.freeze({
1598
- /**
1599
- * Creates a binding mode based on the supplied behavior types.
1600
- * @param UpdateType - The base behavior type used to update aspects.
1601
- * @param EventType - The base behavior type used to respond to events.
1602
- * @returns A new binding mode.
1603
- */
1604
- define(UpdateType, EventType = EventBinding) {
1605
- return Object.freeze({
1606
- [1]: d => new UpdateType(d, DOM.setAttribute),
1607
- [2]: d => new UpdateType(d, DOM.setBooleanAttribute),
1608
- [3]: d => new UpdateType(d, (t, a, v) => (t[a] = v)),
1609
- [4]: d => new (createContentBinding(UpdateType))(d, updateContentTarget),
1610
- [5]: d => new UpdateType(d, updateTokenListTarget),
1611
- [6]: d => new EventType(d),
1612
- });
1613
- },
1614
- });
1615
- /**
1616
- * Describes the configuration for a binding expression.
1617
- * @public
1618
- */
1619
- const BindingConfig = Object.freeze({
1620
- /**
1621
- * Creates a binding configuration based on the provided mode and options.
1622
- * @param mode - The mode to use for the configuration.
1623
- * @param defaultOptions - The default options to use for the configuration.
1624
- * @returns A new binding configuration.
1625
- */
1626
- define(mode, defaultOptions) {
1627
- const config = (options) => {
1628
- return {
1629
- mode: config.mode,
1630
- options: Object.assign({}, defaultOptions, options),
1631
- };
1632
- };
1633
- config.options = defaultOptions;
1634
- config.mode = mode;
1635
- return config;
1636
- },
1637
- });
1638
- /**
1639
- * A base binding behavior for DOM updates.
1640
- * @public
1641
- */
1642
- class UpdateBinding {
1643
- /**
1644
- * Creates an instance of UpdateBinding.
1645
- * @param directive - The directive that has the configuration for this behavior.
1646
- * @param updateTarget - The function used to update the target with the latest value.
1647
- */
1648
- constructor(directive, updateTarget) {
1649
- this.directive = directive;
1650
- this.updateTarget = updateTarget;
1651
- }
1652
- /**
1653
- * Bind this behavior to the source.
1654
- * @param source - The source to bind to.
1655
- * @param context - The execution context that the binding is operating within.
1656
- * @param targets - The targets that behaviors in a view can attach to.
1657
- */
1658
- bind(source, context, targets) { }
1659
- /**
1660
- * Unbinds this behavior from the source.
1661
- * @param source - The source to unbind from.
1662
- * @param context - The execution context that the binding is operating within.
1663
- * @param targets - The targets that behaviors in a view can attach to.
1664
- */
1665
- unbind(source, context, targets) { }
1666
2217
  /**
1667
2218
  * Creates a behavior.
1668
2219
  * @param targets - The targets available for behaviors to be attached to.
1669
2220
  */
1670
- createBehavior(targets) {
2221
+ createBehavior() {
1671
2222
  return this;
1672
2223
  }
1673
2224
  }
1674
- function createContentBinding(Type) {
1675
- return class extends Type {
1676
- unbind(source, context, targets) {
1677
- super.unbind(source, context, targets);
1678
- const target = targets[this.directive.nodeId];
1679
- const view = target.$fastView;
1680
- if (view !== void 0 && view.isComposed) {
1681
- view.unbind();
1682
- view.needsBindOnly = true;
1683
- }
1684
- }
1685
- };
2225
+
2226
+ class OnChangeBinding extends Binding {
2227
+ createObserver(_, subscriber) {
2228
+ return Observable.binding(this.evaluate, subscriber, this.isVolatile);
2229
+ }
2230
+ }
2231
+ class OneTimeBinding extends Binding {
2232
+ constructor() {
2233
+ super(...arguments);
2234
+ /**
2235
+ * Opts out of JSON stringification.
2236
+ * @internal
2237
+ */
2238
+ this.toJSON = noop;
2239
+ }
2240
+ createObserver() {
2241
+ return this;
2242
+ }
2243
+ bind(controller) {
2244
+ return this.evaluate(controller.source, controller.context);
2245
+ }
1686
2246
  }
1687
- function updateContentTarget(target, aspect, value, source, context) {
2247
+ function updateContent(target, aspect, value, controller) {
1688
2248
  // If there's no actual value, then this equates to the
1689
2249
  // empty string for the purposes of content bindings.
1690
2250
  if (value === null || value === undefined) {
1691
2251
  value = "";
1692
2252
  }
1693
- // If the value has a "create" method, then it's a template-like.
2253
+ // If the value has a "create" method, then it's a ContentTemplate.
1694
2254
  if (value.create) {
1695
2255
  target.textContent = "";
1696
2256
  let view = target.$fastView;
@@ -1716,14 +2276,14 @@ function updateContentTarget(target, aspect, value, source, context) {
1716
2276
  // and that there's actually no need to compose it.
1717
2277
  if (!view.isComposed) {
1718
2278
  view.isComposed = true;
1719
- view.bind(source, context);
2279
+ view.bind(controller.source, controller.context);
1720
2280
  view.insertBefore(target);
1721
2281
  target.$fastView = view;
1722
2282
  target.$fastTemplate = value;
1723
2283
  }
1724
2284
  else if (view.needsBindOnly) {
1725
2285
  view.needsBindOnly = false;
1726
- view.bind(source, context);
2286
+ view.bind(controller.source, controller.context);
1727
2287
  }
1728
2288
  }
1729
2289
  else {
@@ -1731,199 +2291,58 @@ function updateContentTarget(target, aspect, value, source, context) {
1731
2291
  // If there is a view and it's currently composed into
1732
2292
  // the DOM, then we need to remove it.
1733
2293
  if (view !== void 0 && view.isComposed) {
1734
- view.isComposed = false;
1735
- view.remove();
1736
- if (view.needsBindOnly) {
1737
- view.needsBindOnly = false;
1738
- }
1739
- else {
1740
- view.unbind();
1741
- }
1742
- }
1743
- target.textContent = value;
1744
- }
1745
- }
1746
- function updateTokenListTarget(target, aspect, value) {
1747
- var _a;
1748
- const directive = this.directive;
1749
- const lookup = `${directive.id}-t`;
1750
- const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
1751
- const versions = state.v;
1752
- let currentVersion = state.c;
1753
- const tokenList = target[aspect];
1754
- // Add the classes, tracking the version at which they were added.
1755
- if (value !== null && value !== undefined && value.length) {
1756
- const names = value.split(/\s+/);
1757
- for (let i = 0, ii = names.length; i < ii; ++i) {
1758
- const currentName = names[i];
1759
- if (currentName === "") {
1760
- continue;
1761
- }
1762
- versions[currentName] = currentVersion;
1763
- tokenList.add(currentName);
1764
- }
1765
- }
1766
- state.v = currentVersion + 1;
1767
- // If this is the first call to add classes, there's no need to remove old ones.
1768
- if (currentVersion === 0) {
1769
- return;
1770
- }
1771
- // Remove classes from the previous version.
1772
- currentVersion -= 1;
1773
- for (const name in versions) {
1774
- if (versions[name] === currentVersion) {
1775
- tokenList.remove(name);
1776
- }
1777
- }
1778
- }
1779
- /**
1780
- * A binding behavior for one-time bindings.
1781
- * @public
1782
- */
1783
- class OneTimeBinding extends UpdateBinding {
1784
- /**
1785
- * Bind this behavior to the source.
1786
- * @param source - The source to bind to.
1787
- * @param context - The execution context that the binding is operating within.
1788
- * @param targets - The targets that behaviors in a view can attach to.
1789
- */
1790
- bind(source, context, targets) {
1791
- const directive = this.directive;
1792
- this.updateTarget(targets[directive.nodeId], directive.targetAspect, directive.binding(source, context), source, context);
1793
- }
1794
- }
1795
- /**
1796
- * A binding behavior for bindings that change.
1797
- * @public
1798
- */
1799
- class ChangeBinding extends UpdateBinding {
1800
- /**
1801
- * Creates an instance of ChangeBinding.
1802
- * @param directive - The directive that has the configuration for this behavior.
1803
- * @param updateTarget - The function used to update the target with the latest value.
1804
- */
1805
- constructor(directive, updateTarget) {
1806
- super(directive, updateTarget);
1807
- this.isBindingVolatile = Observable.isVolatileBinding(directive.binding);
1808
- this.observerProperty = `${directive.id}-o`;
1809
- }
1810
- /**
1811
- * Returns the binding observer used to update the node.
1812
- * @param target - The target node.
1813
- * @returns A BindingObserver.
1814
- */
1815
- getObserver(target) {
1816
- var _a;
1817
- return ((_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = Observable.binding(this.directive.binding, this, this.isBindingVolatile)));
1818
- }
1819
- /**
1820
- * Bind this behavior to the source.
1821
- * @param source - The source to bind to.
1822
- * @param context - The execution context that the binding is operating within.
1823
- * @param targets - The targets that behaviors in a view can attach to.
1824
- */
1825
- bind(source, context, targets) {
1826
- const directive = this.directive;
1827
- const target = targets[directive.nodeId];
1828
- const observer = this.getObserver(target);
1829
- observer.target = target;
1830
- observer.source = source;
1831
- observer.context = context;
1832
- this.updateTarget(target, directive.targetAspect, observer.observe(source, context), source, context);
1833
- }
1834
- /**
1835
- * Unbinds this behavior from the source.
1836
- * @param source - The source to unbind from.
1837
- * @param context - The execution context that the binding is operating within.
1838
- * @param targets - The targets that behaviors in a view can attach to.
1839
- */
1840
- unbind(source, context, targets) {
1841
- const target = targets[this.directive.nodeId];
1842
- const observer = this.getObserver(target);
1843
- observer.dispose();
1844
- observer.target = null;
1845
- observer.source = null;
1846
- observer.context = null;
1847
- }
1848
- /** @internal */
1849
- handleChange(binding, observer) {
1850
- const target = observer.target;
1851
- const source = observer.source;
1852
- const context = observer.context;
1853
- this.updateTarget(target, this.directive.targetAspect, observer.observe(source, context), source, context);
1854
- }
1855
- }
1856
- /**
1857
- * A binding behavior for handling events.
1858
- * @public
1859
- */
1860
- class EventBinding {
1861
- /**
1862
- * Creates an instance of EventBinding.
1863
- * @param directive - The directive that has the configuration for this behavior.
1864
- */
1865
- constructor(directive) {
1866
- this.directive = directive;
1867
- this.sourceProperty = `${directive.id}-s`;
1868
- this.contextProperty = `${directive.id}-c`;
1869
- }
1870
- /**
1871
- * Bind this behavior to the source.
1872
- * @param source - The source to bind to.
1873
- * @param context - The execution context that the binding is operating within.
1874
- * @param targets - The targets that behaviors in a view can attach to.
1875
- */
1876
- bind(source, context, targets) {
1877
- const directive = this.directive;
1878
- const target = targets[directive.nodeId];
1879
- target[this.sourceProperty] = source;
1880
- target[this.contextProperty] = context;
1881
- target.addEventListener(directive.targetAspect, this, directive.options);
2294
+ view.isComposed = false;
2295
+ view.remove();
2296
+ if (view.needsBindOnly) {
2297
+ view.needsBindOnly = false;
2298
+ }
2299
+ else {
2300
+ view.unbind();
2301
+ }
2302
+ }
2303
+ target.textContent = value;
1882
2304
  }
1883
- /**
1884
- * Unbinds this behavior from the source.
1885
- * @param source - The source to unbind from.
1886
- * @param context - The execution context that the binding is operating within.
1887
- * @param targets - The targets that behaviors in a view can attach to.
1888
- */
1889
- unbind(source, context, targets) {
1890
- const directive = this.directive;
1891
- const target = targets[directive.nodeId];
1892
- target[this.sourceProperty] = target[this.contextProperty] = null;
1893
- target.removeEventListener(directive.targetAspect, this, directive.options);
2305
+ }
2306
+ function updateTokenList(target, aspect, value) {
2307
+ var _a;
2308
+ const lookup = `${this.id}-t`;
2309
+ const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
2310
+ const classVersions = state.cv;
2311
+ let version = state.v;
2312
+ const tokenList = target[aspect];
2313
+ // Add the classes, tracking the version at which they were added.
2314
+ if (value !== null && value !== undefined && value.length) {
2315
+ const names = value.split(/\s+/);
2316
+ for (let i = 0, ii = names.length; i < ii; ++i) {
2317
+ const currentName = names[i];
2318
+ if (currentName === "") {
2319
+ continue;
2320
+ }
2321
+ classVersions[currentName] = version;
2322
+ tokenList.add(currentName);
2323
+ }
1894
2324
  }
1895
- /**
1896
- * Creates a behavior.
1897
- * @param targets - The targets available for behaviors to be attached to.
1898
- */
1899
- createBehavior(targets) {
1900
- return this;
2325
+ state.v = version + 1;
2326
+ // If this is the first call to add classes, there's no need to remove old ones.
2327
+ if (version === 0) {
2328
+ return;
1901
2329
  }
1902
- /**
1903
- * @internal
1904
- */
1905
- handleEvent(event) {
1906
- const target = event.currentTarget;
1907
- ExecutionContext.setEvent(event);
1908
- const result = this.directive.binding(target[this.sourceProperty], target[this.contextProperty]);
1909
- ExecutionContext.setEvent(null);
1910
- if (result !== true) {
1911
- event.preventDefault();
2330
+ // Remove classes from the previous version.
2331
+ version -= 1;
2332
+ for (const name in classVersions) {
2333
+ if (classVersions[name] === version) {
2334
+ tokenList.remove(name);
1912
2335
  }
1913
2336
  }
1914
2337
  }
1915
- /**
1916
- * The default onChange binding configuration.
1917
- * @public
1918
- */
1919
- const onChange = BindingConfig.define(BindingMode.define(ChangeBinding), {});
1920
- /**
1921
- * The default onTime binding configuration.
1922
- * @public
1923
- */
1924
- const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
1925
- once: true,
1926
- });
2338
+ const sinkLookup = {
2339
+ [DOMAspect.attribute]: DOM.setAttribute,
2340
+ [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
2341
+ [DOMAspect.property]: (t, a, v) => (t[a] = v),
2342
+ [DOMAspect.content]: updateContent,
2343
+ [DOMAspect.tokenList]: updateTokenList,
2344
+ [DOMAspect.event]: () => void 0,
2345
+ };
1927
2346
  /**
1928
2347
  * A directive that applies bindings.
1929
2348
  * @public
@@ -1931,19 +2350,15 @@ const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
1931
2350
  class HTMLBindingDirective {
1932
2351
  /**
1933
2352
  * Creates an instance of HTMLBindingDirective.
1934
- * @param binding - The binding to apply.
1935
- * @param mode - The binding mode to use when applying the binding.
1936
- * @param options - The options to configure the binding with.
2353
+ * @param dataBinding - The binding configuration to apply.
1937
2354
  */
1938
- constructor(binding, mode, options) {
1939
- this.binding = binding;
1940
- this.mode = mode;
1941
- this.options = options;
1942
- this.factory = null;
2355
+ constructor(dataBinding) {
2356
+ this.dataBinding = dataBinding;
2357
+ this.updateTarget = null;
1943
2358
  /**
1944
2359
  * The type of aspect to target.
1945
2360
  */
1946
- this.aspectType = Aspect.content;
2361
+ this.aspectType = DOMAspect.content;
1947
2362
  }
1948
2363
  /**
1949
2364
  * Creates HTML to be used within a template.
@@ -1954,31 +2369,114 @@ class HTMLBindingDirective {
1954
2369
  }
1955
2370
  /**
1956
2371
  * Creates a behavior.
1957
- * @param targets - The targets available for behaviors to be attached to.
1958
2372
  */
1959
- createBehavior(targets) {
1960
- if (this.factory == null) {
1961
- if (this.targetAspect === "innerHTML") {
1962
- this.binding = createInnerHTMLBinding(this.binding);
2373
+ createBehavior() {
2374
+ var _a;
2375
+ if (this.updateTarget === null) {
2376
+ const sink = sinkLookup[this.aspectType];
2377
+ const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
2378
+ if (!sink) {
2379
+ throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
2380
+ }
2381
+ this.data = `${this.id}-d`;
2382
+ this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
2383
+ }
2384
+ return this;
2385
+ }
2386
+ /** @internal */
2387
+ bind(controller) {
2388
+ var _a;
2389
+ const target = controller.targets[this.targetNodeId];
2390
+ switch (this.aspectType) {
2391
+ case DOMAspect.event:
2392
+ target[this.data] = controller;
2393
+ target.addEventListener(this.targetAspect, this, this.dataBinding.options);
2394
+ break;
2395
+ case DOMAspect.content:
2396
+ controller.onUnbind(this);
2397
+ // intentional fall through
2398
+ default:
2399
+ const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
2400
+ observer.target = target;
2401
+ observer.controller = controller;
2402
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2403
+ break;
2404
+ }
2405
+ }
2406
+ /** @internal */
2407
+ unbind(controller) {
2408
+ const target = controller.targets[this.targetNodeId];
2409
+ const view = target.$fastView;
2410
+ if (view !== void 0 && view.isComposed) {
2411
+ view.unbind();
2412
+ view.needsBindOnly = true;
2413
+ }
2414
+ }
2415
+ /** @internal */
2416
+ handleEvent(event) {
2417
+ const controller = event.currentTarget[this.data];
2418
+ if (controller.isBound) {
2419
+ ExecutionContext.setEvent(event);
2420
+ const result = this.dataBinding.evaluate(controller.source, controller.context);
2421
+ ExecutionContext.setEvent(null);
2422
+ if (result !== true) {
2423
+ event.preventDefault();
1963
2424
  }
1964
- this.factory = this.mode[this.aspectType](this);
1965
2425
  }
1966
- return this.factory.createBehavior(targets);
2426
+ }
2427
+ /** @internal */
2428
+ handleChange(binding, observer) {
2429
+ const target = observer.target;
2430
+ const controller = observer.controller;
2431
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
1967
2432
  }
1968
2433
  }
1969
2434
  HTMLDirective.define(HTMLBindingDirective, { aspected: true });
1970
2435
  /**
1971
- * Creates a binding directive with the specified configuration.
1972
- * @param binding - The binding expression.
1973
- * @param config - The binding configuration.
1974
- * @returns A binding directive.
2436
+ * Creates an standard binding.
2437
+ * @param expression - The binding to refresh when changed.
2438
+ * @param policy - The security policy to associate with th binding.
2439
+ * @param isVolatile - Indicates whether the binding is volatile or not.
2440
+ * @returns A binding configuration.
1975
2441
  * @public
1976
2442
  */
1977
- function bind(binding, config = onChange) {
1978
- if (!("mode" in config)) {
1979
- config = onChange(config);
1980
- }
1981
- return new HTMLBindingDirective(binding, config.mode, config.options);
2443
+ function bind(expression, policy, isVolatile = Observable.isVolatileBinding(expression)) {
2444
+ return new OnChangeBinding(expression, policy, isVolatile);
2445
+ }
2446
+ /**
2447
+ * Creates a one time binding
2448
+ * @param expression - The binding to refresh when signaled.
2449
+ * @param policy - The security policy to associate with th binding.
2450
+ * @returns A binding configuration.
2451
+ * @public
2452
+ */
2453
+ function oneTime(expression, policy) {
2454
+ return new OneTimeBinding(expression, policy);
2455
+ }
2456
+ /**
2457
+ * Creates an event listener binding.
2458
+ * @param expression - The binding to invoke when the event is raised.
2459
+ * @param options - Event listener options.
2460
+ * @returns A binding configuration.
2461
+ * @public
2462
+ */
2463
+ function listener(expression, options) {
2464
+ const config = new OnChangeBinding(expression);
2465
+ config.options = options;
2466
+ return config;
2467
+ }
2468
+ /**
2469
+ * Normalizes the input value into a binding.
2470
+ * @param value - The value to create the default binding for.
2471
+ * @returns A binding configuration for the provided value.
2472
+ * @public
2473
+ */
2474
+ function normalizeBinding(value) {
2475
+ return isFunction(value)
2476
+ ? bind(value)
2477
+ : value instanceof Binding
2478
+ ? value
2479
+ : oneTime(() => value);
1982
2480
  }
1983
2481
 
1984
2482
  function removeNodeSequence(firstNode, lastNode) {
@@ -2007,17 +2505,92 @@ class HTMLView {
2007
2505
  this.factories = factories;
2008
2506
  this.targets = targets;
2009
2507
  this.behaviors = null;
2508
+ this.unbindables = [];
2010
2509
  /**
2011
2510
  * The data that the view is bound to.
2012
2511
  */
2013
2512
  this.source = null;
2513
+ /**
2514
+ * Indicates whether the controller is bound.
2515
+ */
2516
+ this.isBound = false;
2517
+ /**
2518
+ * Indicates how the source's lifetime relates to the controller's lifetime.
2519
+ */
2520
+ this.sourceLifetime = SourceLifetime.unknown;
2014
2521
  /**
2015
2522
  * The execution context the view is running within.
2016
2523
  */
2017
- this.context = null;
2524
+ this.context = this;
2525
+ /**
2526
+ * The index of the current item within a repeat context.
2527
+ */
2528
+ this.index = 0;
2529
+ /**
2530
+ * The length of the current collection within a repeat context.
2531
+ */
2532
+ this.length = 0;
2533
+ /**
2534
+ * Opts out of JSON stringification.
2535
+ * @internal
2536
+ */
2537
+ this.toJSON = noop;
2018
2538
  this.firstChild = fragment.firstChild;
2019
2539
  this.lastChild = fragment.lastChild;
2020
2540
  }
2541
+ /**
2542
+ * The current event within an event handler.
2543
+ */
2544
+ get event() {
2545
+ return ExecutionContext.getEvent();
2546
+ }
2547
+ /**
2548
+ * Indicates whether the current item within a repeat context
2549
+ * has an even index.
2550
+ */
2551
+ get isEven() {
2552
+ return this.index % 2 === 0;
2553
+ }
2554
+ /**
2555
+ * Indicates whether the current item within a repeat context
2556
+ * has an odd index.
2557
+ */
2558
+ get isOdd() {
2559
+ return this.index % 2 !== 0;
2560
+ }
2561
+ /**
2562
+ * Indicates whether the current item within a repeat context
2563
+ * is the first item in the collection.
2564
+ */
2565
+ get isFirst() {
2566
+ return this.index === 0;
2567
+ }
2568
+ /**
2569
+ * Indicates whether the current item within a repeat context
2570
+ * is somewhere in the middle of the collection.
2571
+ */
2572
+ get isInMiddle() {
2573
+ return !this.isFirst && !this.isLast;
2574
+ }
2575
+ /**
2576
+ * Indicates whether the current item within a repeat context
2577
+ * is the last item in the collection.
2578
+ */
2579
+ get isLast() {
2580
+ return this.index === this.length - 1;
2581
+ }
2582
+ /**
2583
+ * Returns the typed event detail of a custom event.
2584
+ */
2585
+ eventDetail() {
2586
+ return this.event.detail;
2587
+ }
2588
+ /**
2589
+ * Returns the typed event target of the event.
2590
+ */
2591
+ eventTarget() {
2592
+ return this.event.target;
2593
+ }
2021
2594
  /**
2022
2595
  * Appends the view's DOM nodes to the referenced node.
2023
2596
  * @param node - The parent node to append the view's DOM nodes to.
@@ -2034,8 +2607,10 @@ class HTMLView {
2034
2607
  node.parentNode.insertBefore(this.fragment, node);
2035
2608
  }
2036
2609
  else {
2037
- const parentNode = node.parentNode;
2038
2610
  const end = this.lastChild;
2611
+ if (node.previousSibling === end)
2612
+ return;
2613
+ const parentNode = node.parentNode;
2039
2614
  let current = this.firstChild;
2040
2615
  let next;
2041
2616
  while (current !== end) {
@@ -2070,58 +2645,61 @@ class HTMLView {
2070
2645
  removeNodeSequence(this.firstChild, this.lastChild);
2071
2646
  this.unbind();
2072
2647
  }
2648
+ onUnbind(behavior) {
2649
+ this.unbindables.push(behavior);
2650
+ }
2073
2651
  /**
2074
2652
  * Binds a view's behaviors to its binding source.
2075
2653
  * @param source - The binding source for the view's binding behaviors.
2076
2654
  * @param context - The execution context to run the behaviors within.
2077
2655
  */
2078
- bind(source, context) {
2079
- let behaviors = this.behaviors;
2080
- const oldSource = this.source;
2081
- if (oldSource === source) {
2656
+ bind(source, context = this) {
2657
+ if (this.source === source) {
2082
2658
  return;
2083
2659
  }
2084
- this.source = source;
2085
- this.context = context;
2086
- const targets = this.targets;
2087
- if (oldSource !== null) {
2088
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2089
- const current = behaviors[i];
2090
- current.unbind(oldSource, context, targets);
2091
- current.bind(source, context, targets);
2092
- }
2093
- }
2094
- else if (behaviors === null) {
2660
+ let behaviors = this.behaviors;
2661
+ if (behaviors === null) {
2662
+ this.source = source;
2663
+ this.context = context;
2095
2664
  this.behaviors = behaviors = new Array(this.factories.length);
2096
2665
  const factories = this.factories;
2097
2666
  for (let i = 0, ii = factories.length; i < ii; ++i) {
2098
- const behavior = factories[i].createBehavior(targets);
2099
- behavior.bind(source, context, targets);
2667
+ const behavior = factories[i].createBehavior();
2668
+ behavior.bind(this);
2100
2669
  behaviors[i] = behavior;
2101
2670
  }
2102
2671
  }
2103
2672
  else {
2673
+ if (this.source !== null) {
2674
+ this.evaluateUnbindables();
2675
+ }
2676
+ this.isBound = false;
2677
+ this.source = source;
2678
+ this.context = context;
2104
2679
  for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2105
- behaviors[i].bind(source, context, targets);
2680
+ behaviors[i].bind(this);
2106
2681
  }
2107
2682
  }
2683
+ this.isBound = true;
2108
2684
  }
2109
2685
  /**
2110
2686
  * Unbinds a view's behaviors from its binding source.
2111
2687
  */
2112
2688
  unbind() {
2113
- const oldSource = this.source;
2114
- if (oldSource === null) {
2689
+ if (!this.isBound || this.source === null) {
2115
2690
  return;
2116
2691
  }
2117
- const targets = this.targets;
2118
- const context = this.context;
2119
- const behaviors = this.behaviors;
2120
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2121
- behaviors[i].unbind(oldSource, context, targets);
2122
- }
2692
+ this.evaluateUnbindables();
2123
2693
  this.source = null;
2124
- this.context = null;
2694
+ this.context = this;
2695
+ this.isBound = false;
2696
+ }
2697
+ evaluateUnbindables() {
2698
+ const unbindables = this.unbindables;
2699
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
2700
+ unbindables[i].unbind(this);
2701
+ }
2702
+ unbindables.length = 0;
2125
2703
  }
2126
2704
  /**
2127
2705
  * Efficiently disposes of a contiguous range of synthetic view instances.
@@ -2137,6 +2715,8 @@ class HTMLView {
2137
2715
  }
2138
2716
  }
2139
2717
  }
2718
+ Observable.defineProperty(HTMLView.prototype, "index");
2719
+ Observable.defineProperty(HTMLView.prototype, "length");
2140
2720
 
2141
2721
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
2142
2722
  const descriptorCache = {};
@@ -2145,21 +2725,42 @@ const next = {
2145
2725
  index: 0,
2146
2726
  node: null,
2147
2727
  };
2728
+ function tryWarn(name) {
2729
+ if (!name.startsWith("fast-")) {
2730
+ FAST.warn(1204 /* Message.hostBindingWithoutHost */, { name });
2731
+ }
2732
+ }
2733
+ const warningHost = new Proxy(document.createElement("div"), {
2734
+ get(target, property) {
2735
+ tryWarn(property);
2736
+ const value = Reflect.get(target, property);
2737
+ return isFunction(value) ? value.bind(target) : value;
2738
+ },
2739
+ set(target, property, value) {
2740
+ tryWarn(property);
2741
+ return Reflect.set(target, property, value);
2742
+ },
2743
+ });
2148
2744
  class CompilationContext {
2149
- constructor(fragment, directives) {
2745
+ constructor(fragment, directives, policy) {
2150
2746
  this.fragment = fragment;
2151
2747
  this.directives = directives;
2748
+ this.policy = policy;
2152
2749
  this.proto = null;
2153
2750
  this.nodeIds = new Set();
2154
2751
  this.descriptors = {};
2155
2752
  this.factories = [];
2156
2753
  }
2157
- addFactory(factory, parentId, nodeId, targetIndex) {
2754
+ addFactory(factory, parentId, nodeId, targetIndex, tagName) {
2755
+ var _a, _b;
2158
2756
  if (!this.nodeIds.has(nodeId)) {
2159
2757
  this.nodeIds.add(nodeId);
2160
2758
  this.addTargetDescriptor(parentId, nodeId, targetIndex);
2161
2759
  }
2162
- factory.nodeId = nodeId;
2760
+ factory.id = (_a = factory.id) !== null && _a !== void 0 ? _a : nextId();
2761
+ factory.targetNodeId = nodeId;
2762
+ factory.targetTagName = tagName;
2763
+ factory.policy = (_b = factory.policy) !== null && _b !== void 0 ? _b : this.policy;
2163
2764
  this.factories.push(factory);
2164
2765
  }
2165
2766
  freeze() {
@@ -2195,7 +2796,7 @@ class CompilationContext {
2195
2796
  const fragment = this.fragment.cloneNode(true);
2196
2797
  const targets = Object.create(this.proto);
2197
2798
  targets.r = fragment;
2198
- targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : fragment;
2799
+ targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
2199
2800
  for (const id of this.nodeIds) {
2200
2801
  targets[id]; // trigger locator
2201
2802
  }
@@ -2212,19 +2813,19 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
2212
2813
  let result = null;
2213
2814
  if (parseResult === null) {
2214
2815
  if (includeBasicValues) {
2215
- result = bind(() => attrValue, oneTime);
2216
- Aspect.assign(result, attr.name);
2816
+ result = new HTMLBindingDirective(oneTime(() => attrValue, context.policy));
2817
+ HTMLDirective.assignAspect(result, attr.name);
2217
2818
  }
2218
2819
  }
2219
2820
  else {
2220
2821
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2221
- result = Compiler.aggregate(parseResult);
2822
+ result = Compiler.aggregate(parseResult, context.policy);
2222
2823
  }
2223
2824
  if (result !== null) {
2224
2825
  node.removeAttributeNode(attr);
2225
2826
  i--;
2226
2827
  ii--;
2227
- context.addFactory(result, parentId, nodeId, nodeIndex);
2828
+ context.addFactory(result, parentId, nodeId, nodeIndex, node.tagName);
2228
2829
  }
2229
2830
  }
2230
2831
  }
@@ -2249,7 +2850,8 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
2249
2850
  }
2250
2851
  else {
2251
2852
  currentNode.textContent = " ";
2252
- context.addFactory(currentPart, parentId, nodeId, nodeIndex);
2853
+ HTMLDirective.assignAspect(currentPart);
2854
+ context.addFactory(currentPart, parentId, nodeId, nodeIndex, null);
2253
2855
  }
2254
2856
  lastNode = currentNode;
2255
2857
  }
@@ -2281,7 +2883,7 @@ function compileNode(context, parentId, node, nodeIndex) {
2281
2883
  if (parts !== null) {
2282
2884
  context.addFactory(
2283
2885
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2284
- Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
2886
+ Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
2285
2887
  }
2286
2888
  break;
2287
2889
  }
@@ -2295,45 +2897,28 @@ function isMarker(node, directives) {
2295
2897
  Parser.parse(node.data, directives) !== null);
2296
2898
  }
2297
2899
  const templateTag = "TEMPLATE";
2298
- const policyOptions = { createHTML: html => html };
2299
- let htmlPolicy = globalThis.trustedTypes
2300
- ? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
2301
- : policyOptions;
2302
- const fastHTMLPolicy = htmlPolicy;
2303
2900
  /**
2304
2901
  * Common APIs related to compilation.
2305
2902
  * @public
2306
2903
  */
2307
2904
  const Compiler = {
2308
- /**
2309
- * Sets the HTML trusted types policy used by the compiler.
2310
- * @param policy - The policy to set for HTML.
2311
- * @remarks
2312
- * This API can only be called once, for security reasons. It should be
2313
- * called by the application developer at the start of their program.
2314
- */
2315
- setHTMLPolicy(policy) {
2316
- if (htmlPolicy !== fastHTMLPolicy) {
2317
- throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
2318
- }
2319
- htmlPolicy = policy;
2320
- },
2321
2905
  /**
2322
2906
  * Compiles a template and associated directives into a compilation
2323
2907
  * result which can be used to create views.
2324
2908
  * @param html - The html string or template element to compile.
2325
- * @param directives - The directives referenced by the template.
2909
+ * @param factories - The behavior factories referenced by the template.
2910
+ * @param policy - The security policy to compile the html with.
2326
2911
  * @remarks
2327
2912
  * The template that is provided for compilation is altered in-place
2328
2913
  * and cannot be compiled again. If the original template must be preserved,
2329
2914
  * it is recommended that you clone the original and pass the clone to this API.
2330
2915
  * @public
2331
2916
  */
2332
- compile(html, directives) {
2917
+ compile(html, factories, policy = DOM.policy) {
2333
2918
  let template;
2334
2919
  if (isString(html)) {
2335
2920
  template = document.createElement(templateTag);
2336
- template.innerHTML = htmlPolicy.createHTML(html);
2921
+ template.innerHTML = policy.createHTML(html);
2337
2922
  const fec = template.content.firstElementChild;
2338
2923
  if (fec !== null && fec.tagName === templateTag) {
2339
2924
  template = fec;
@@ -2344,18 +2929,18 @@ const Compiler = {
2344
2929
  }
2345
2930
  // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
2346
2931
  const fragment = document.adoptNode(template.content);
2347
- const context = new CompilationContext(fragment, directives);
2932
+ const context = new CompilationContext(fragment, factories, policy);
2348
2933
  compileAttributes(context, "", template, /* host */ "h", 0, true);
2349
2934
  if (
2350
2935
  // If the first node in a fragment is a marker, that means it's an unstable first node,
2351
2936
  // because something like a when, repeat, etc. could add nodes before the marker.
2352
2937
  // To mitigate this, we insert a stable first node. However, if we insert a node,
2353
2938
  // that will alter the result of the TreeWalker. So, we also need to offset the target index.
2354
- isMarker(fragment.firstChild, directives) ||
2939
+ isMarker(fragment.firstChild, factories) ||
2355
2940
  // Or if there is only one node and a directive, it means the template's content
2356
2941
  // is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
2357
2942
  // the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
2358
- (fragment.childNodes.length === 1 && Object.keys(directives).length > 0)) {
2943
+ (fragment.childNodes.length === 1 && Object.keys(factories).length > 0)) {
2359
2944
  fragment.insertBefore(document.createComment(""), fragment.firstChild);
2360
2945
  }
2361
2946
  compileChildren(context, fragment, /* root */ "r");
@@ -2374,34 +2959,88 @@ const Compiler = {
2374
2959
  * Aggregates an array of strings and directives into a single directive.
2375
2960
  * @param parts - A heterogeneous array of static strings interspersed with
2376
2961
  * directives.
2962
+ * @param policy - The security policy to use with the aggregated bindings.
2377
2963
  * @returns A single inline directive that aggregates the behavior of all the parts.
2378
2964
  */
2379
- aggregate(parts) {
2965
+ aggregate(parts, policy = DOM.policy) {
2380
2966
  if (parts.length === 1) {
2381
2967
  return parts[0];
2382
2968
  }
2383
2969
  let sourceAspect;
2970
+ let binding;
2971
+ let isVolatile = false;
2972
+ let bindingPolicy = void 0;
2384
2973
  const partCount = parts.length;
2385
2974
  const finalParts = parts.map((x) => {
2386
2975
  if (isString(x)) {
2387
2976
  return () => x;
2388
2977
  }
2389
2978
  sourceAspect = x.sourceAspect || sourceAspect;
2390
- return x.binding;
2979
+ binding = x.dataBinding || binding;
2980
+ isVolatile = isVolatile || x.dataBinding.isVolatile;
2981
+ bindingPolicy = bindingPolicy || x.dataBinding.policy;
2982
+ return x.dataBinding.evaluate;
2391
2983
  });
2392
- const binding = (scope, context) => {
2984
+ const expression = (scope, context) => {
2393
2985
  let output = "";
2394
2986
  for (let i = 0; i < partCount; ++i) {
2395
2987
  output += finalParts[i](scope, context);
2396
2988
  }
2397
2989
  return output;
2398
2990
  };
2399
- const directive = bind(binding);
2400
- Aspect.assign(directive, sourceAspect);
2991
+ binding.evaluate = expression;
2992
+ binding.isVolatile = isVolatile;
2993
+ binding.policy = bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy;
2994
+ const directive = new HTMLBindingDirective(binding);
2995
+ HTMLDirective.assignAspect(directive, sourceAspect);
2401
2996
  return directive;
2402
2997
  },
2403
2998
  };
2404
2999
 
3000
+ // Much thanks to LitHTML for working this out!
3001
+ const lastAttributeNameRegex =
3002
+ /* eslint-disable-next-line no-control-regex */
3003
+ /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
3004
+ const noFactories = Object.create(null);
3005
+ /**
3006
+ * Inlines a template into another template.
3007
+ * @public
3008
+ */
3009
+ class InlineTemplateDirective {
3010
+ /**
3011
+ * Creates an instance of InlineTemplateDirective.
3012
+ * @param template - The template to inline.
3013
+ */
3014
+ constructor(html, factories = noFactories) {
3015
+ this.html = html;
3016
+ this.factories = factories;
3017
+ }
3018
+ /**
3019
+ * Creates HTML to be used within a template.
3020
+ * @param add - Can be used to add behavior factories to a template.
3021
+ */
3022
+ createHTML(add) {
3023
+ const factories = this.factories;
3024
+ for (const key in factories) {
3025
+ add(factories[key]);
3026
+ }
3027
+ return this.html;
3028
+ }
3029
+ }
3030
+ /**
3031
+ * An empty template partial.
3032
+ */
3033
+ InlineTemplateDirective.empty = new InlineTemplateDirective("");
3034
+ HTMLDirective.define(InlineTemplateDirective);
3035
+ function createHTML(value, prevString, add, definition = HTMLDirective.getForInstance(value)) {
3036
+ if (definition.aspected) {
3037
+ const match = lastAttributeNameRegex.exec(prevString);
3038
+ if (match !== null) {
3039
+ HTMLDirective.assignAspect(value, match[2]);
3040
+ }
3041
+ }
3042
+ return value.createHTML(add);
3043
+ }
2405
3044
  /**
2406
3045
  * A template capable of creating HTMLView instances or rendering directly to DOM.
2407
3046
  * @public
@@ -2411,9 +3050,16 @@ class ViewTemplate {
2411
3050
  * Creates an instance of ViewTemplate.
2412
3051
  * @param html - The html representing what this template will instantiate, including placeholders for directives.
2413
3052
  * @param factories - The directives that will be connected to placeholders in the html.
3053
+ * @param policy - The security policy to use when compiling this template.
2414
3054
  */
2415
- constructor(html, factories) {
3055
+ constructor(html, factories = {}, policy) {
3056
+ this.policy = policy;
2416
3057
  this.result = null;
3058
+ /**
3059
+ * Opts out of JSON stringification.
3060
+ * @internal
3061
+ */
3062
+ this.toJSON = noop;
2417
3063
  this.html = html;
2418
3064
  this.factories = factories;
2419
3065
  }
@@ -2423,10 +3069,34 @@ class ViewTemplate {
2423
3069
  */
2424
3070
  create(hostBindingTarget) {
2425
3071
  if (this.result === null) {
2426
- this.result = Compiler.compile(this.html, this.factories);
3072
+ this.result = Compiler.compile(this.html, this.factories, this.policy);
2427
3073
  }
2428
3074
  return this.result.createView(hostBindingTarget);
2429
3075
  }
3076
+ /**
3077
+ * Returns a directive that can inline the template.
3078
+ */
3079
+ inline() {
3080
+ return new InlineTemplateDirective(isString(this.html) ? this.html : this.html.innerHTML, this.factories);
3081
+ }
3082
+ /**
3083
+ * Sets the DOMPolicy for this template.
3084
+ * @param policy - The policy to associated with this template.
3085
+ * @returns The modified template instance.
3086
+ * @remarks
3087
+ * The DOMPolicy can only be set once for a template and cannot be
3088
+ * set after the template is compiled.
3089
+ */
3090
+ withPolicy(policy) {
3091
+ if (this.result) {
3092
+ throw FAST.error(1208 /* Message.cannotSetTemplatePolicyAfterCompilation */);
3093
+ }
3094
+ if (this.policy) {
3095
+ throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
3096
+ }
3097
+ this.policy = policy;
3098
+ return this;
3099
+ }
2430
3100
  /**
2431
3101
  * Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
2432
3102
  * @param source - The data source to bind the template to.
@@ -2434,75 +3104,72 @@ class ViewTemplate {
2434
3104
  * @param hostBindingTarget - An HTML element to target the host bindings at if different from the
2435
3105
  * host that the template is being attached to.
2436
3106
  */
2437
- render(source, host, hostBindingTarget, context) {
2438
- const view = this.create(hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : host);
2439
- view.bind(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
3107
+ render(source, host, hostBindingTarget) {
3108
+ const view = this.create(hostBindingTarget);
3109
+ view.bind(source);
2440
3110
  view.appendTo(host);
2441
3111
  return view;
2442
3112
  }
2443
- }
2444
- // Much thanks to LitHTML for working this out!
2445
- const lastAttributeNameRegex =
2446
- /* eslint-disable-next-line no-control-regex */
2447
- /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
2448
- function createAspectedHTML(value, prevString, add) {
2449
- const match = lastAttributeNameRegex.exec(prevString);
2450
- if (match !== null) {
2451
- Aspect.assign(value, match[2]);
2452
- }
2453
- return value.createHTML(add);
2454
- }
2455
- /**
2456
- * Transforms a template literal string into a ViewTemplate.
2457
- * @param strings - The string fragments that are interpolated with the values.
2458
- * @param values - The values that are interpolated with the string fragments.
2459
- * @remarks
2460
- * The html helper supports interpolation of strings, numbers, binding expressions,
2461
- * other template instances, and Directive instances.
2462
- * @public
2463
- */
2464
- function html(strings, ...values) {
2465
- let html = "";
2466
- const factories = Object.create(null);
2467
- const add = (factory) => {
2468
- var _a;
2469
- const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
2470
- factories[id] = factory;
2471
- return id;
2472
- };
2473
- for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
2474
- const currentString = strings[i];
2475
- const currentValue = values[i];
2476
- let definition;
2477
- html += currentString;
2478
- if (isFunction(currentValue)) {
2479
- html += createAspectedHTML(bind(currentValue), currentString, add);
2480
- }
2481
- else if (isString(currentValue)) {
2482
- const match = lastAttributeNameRegex.exec(currentString);
2483
- if (match !== null) {
2484
- const directive = bind(() => currentValue, oneTime);
2485
- Aspect.assign(directive, match[2]);
2486
- html += directive.createHTML(add);
2487
- }
2488
- else {
2489
- html += currentValue;
3113
+ /**
3114
+ * Creates a template based on a set of static strings and dynamic values.
3115
+ * @param strings - The static strings to create the template with.
3116
+ * @param values - The dynamic values to create the template with.
3117
+ * @param policy - The DOMPolicy to associated with the template.
3118
+ * @returns A ViewTemplate.
3119
+ * @remarks
3120
+ * This API should not be used directly under normal circumstances because constructing
3121
+ * a template in this way, if not done properly, can open up the application to XSS
3122
+ * attacks. When using this API, provide a strong DOMPolicy that can properly sanitize
3123
+ * and also be sure to manually sanitize all static strings particularly if they can
3124
+ * come from user input.
3125
+ */
3126
+ static create(strings, values, policy) {
3127
+ let html = "";
3128
+ const factories = Object.create(null);
3129
+ const add = (factory) => {
3130
+ var _a;
3131
+ const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
3132
+ factories[id] = factory;
3133
+ return id;
3134
+ };
3135
+ for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
3136
+ const currentString = strings[i];
3137
+ let currentValue = values[i];
3138
+ let definition;
3139
+ html += currentString;
3140
+ if (isFunction(currentValue)) {
3141
+ currentValue = new HTMLBindingDirective(bind(currentValue));
2490
3142
  }
2491
- }
2492
- else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
2493
- html += createAspectedHTML(bind(() => currentValue, oneTime), currentString, add);
2494
- }
2495
- else {
2496
- if (definition.aspected) {
2497
- html += createAspectedHTML(currentValue, currentString, add);
3143
+ else if (currentValue instanceof Binding) {
3144
+ currentValue = new HTMLBindingDirective(currentValue);
2498
3145
  }
2499
- else {
2500
- html += currentValue.createHTML(add);
3146
+ else if (!(definition = HTMLDirective.getForInstance(currentValue))) {
3147
+ const staticValue = currentValue;
3148
+ currentValue = new HTMLBindingDirective(oneTime(() => staticValue));
2501
3149
  }
3150
+ html += createHTML(currentValue, currentString, add, definition);
2502
3151
  }
3152
+ return new ViewTemplate(html + strings[strings.length - 1], factories, policy);
3153
+ }
3154
+ }
3155
+ /**
3156
+ * Transforms a template literal string into a ViewTemplate.
3157
+ * @param strings - The string fragments that are interpolated with the values.
3158
+ * @param values - The values that are interpolated with the string fragments.
3159
+ * @remarks
3160
+ * The html helper supports interpolation of strings, numbers, binding expressions,
3161
+ * other template instances, and Directive instances.
3162
+ * @public
3163
+ */
3164
+ const html = ((strings, ...values) => {
3165
+ if (Array.isArray(strings) && Array.isArray(strings.raw)) {
3166
+ return ViewTemplate.create(strings, values);
2503
3167
  }
2504
- return new ViewTemplate(html + strings[strings.length - 1], factories);
2505
- }
3168
+ throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
3169
+ });
3170
+ html.partial = (html) => {
3171
+ return new InlineTemplateDirective(html);
3172
+ };
2506
3173
 
2507
3174
  /**
2508
3175
  * The runtime behavior for template references.
@@ -2510,20 +3177,12 @@ function html(strings, ...values) {
2510
3177
  */
2511
3178
  class RefDirective extends StatelessAttachedAttributeDirective {
2512
3179
  /**
2513
- * Bind this behavior to the source.
2514
- * @param source - The source to bind to.
2515
- * @param context - The execution context that the binding is operating within.
2516
- * @param targets - The targets that behaviors in a view can attach to.
3180
+ * Bind this behavior.
3181
+ * @param controller - The view controller that manages the lifecycle of this behavior.
2517
3182
  */
2518
- bind(source, context, targets) {
2519
- source[this.options] = targets[this.nodeId];
3183
+ bind(controller) {
3184
+ controller.source[this.options] = controller.targets[this.targetNodeId];
2520
3185
  }
2521
- /**
2522
- * Unbinds this behavior from the source.
2523
- * @param source - The source to unbind from.
2524
- */
2525
- /* eslint-disable-next-line @typescript-eslint/no-empty-function */
2526
- unbind() { }
2527
3186
  }
2528
3187
  HTMLDirective.define(RefDirective);
2529
3188
  /**
@@ -2535,27 +3194,34 @@ const ref = (propertyName) => new RefDirective(propertyName);
2535
3194
 
2536
3195
  /**
2537
3196
  * A directive that enables basic conditional rendering in a template.
2538
- * @param binding - The condition to test for rendering.
3197
+ * @param condition - The condition to test for rendering.
2539
3198
  * @param templateOrTemplateBinding - The template or a binding that gets
2540
3199
  * the template to render when the condition is true.
2541
3200
  * @public
2542
3201
  */
2543
- function when(binding, templateOrTemplateBinding) {
2544
- const getTemplate = isFunction(templateOrTemplateBinding)
3202
+ function when(condition, templateOrTemplateBinding) {
3203
+ const dataBinding = isFunction(condition) ? condition : () => condition;
3204
+ const templateBinding = isFunction(templateOrTemplateBinding)
2545
3205
  ? templateOrTemplateBinding
2546
3206
  : () => templateOrTemplateBinding;
2547
- return (source, context) => binding(source, context) ? getTemplate(source, context) : null;
3207
+ return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
2548
3208
  }
2549
3209
 
2550
3210
  const defaultRepeatOptions = Object.freeze({
2551
3211
  positioning: false,
2552
3212
  recycle: true,
2553
3213
  });
2554
- function bindWithoutPositioning(view, items, index, context) {
2555
- view.bind(items[index], context);
3214
+ function bindWithoutPositioning(view, items, index, controller) {
3215
+ view.context.parent = controller.source;
3216
+ view.context.parentContext = controller.context;
3217
+ view.bind(items[index]);
2556
3218
  }
2557
- function bindWithPositioning(view, items, index, context) {
2558
- view.bind(items[index], context.createItemContext(index, items.length));
3219
+ function bindWithPositioning(view, items, index, controller) {
3220
+ view.context.parent = controller.source;
3221
+ view.context.parentContext = controller.context;
3222
+ view.context.length = items.length;
3223
+ view.context.index = index;
3224
+ view.bind(items[index]);
2559
3225
  }
2560
3226
  /**
2561
3227
  * A behavior that renders a template for each item in an array.
@@ -2565,57 +3231,46 @@ class RepeatBehavior {
2565
3231
  /**
2566
3232
  * Creates an instance of RepeatBehavior.
2567
3233
  * @param location - The location in the DOM to render the repeat.
2568
- * @param itemsBinding - The array to render.
3234
+ * @param dataBinding - The array to render.
2569
3235
  * @param isItemsBindingVolatile - Indicates whether the items binding has volatile dependencies.
2570
3236
  * @param templateBinding - The template to render for each item.
2571
3237
  * @param isTemplateBindingVolatile - Indicates whether the template binding has volatile dependencies.
2572
3238
  * @param options - Options used to turn on special repeat features.
2573
3239
  */
2574
- constructor(location, itemsBinding, isItemsBindingVolatile, templateBinding, isTemplateBindingVolatile, options) {
2575
- this.location = location;
2576
- this.itemsBinding = itemsBinding;
2577
- this.templateBinding = templateBinding;
2578
- this.options = options;
2579
- this.source = null;
2580
- this.views = [];
3240
+ constructor(directive) {
3241
+ this.directive = directive;
2581
3242
  this.items = null;
2582
3243
  this.itemsObserver = null;
2583
- this.context = void 0;
2584
- this.childContext = void 0;
2585
3244
  this.bindView = bindWithoutPositioning;
2586
- this.itemsBindingObserver = Observable.binding(itemsBinding, this, isItemsBindingVolatile);
2587
- this.templateBindingObserver = Observable.binding(templateBinding, this, isTemplateBindingVolatile);
2588
- if (options.positioning) {
3245
+ /** @internal */
3246
+ this.views = [];
3247
+ this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
3248
+ this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
3249
+ if (directive.options.positioning) {
2589
3250
  this.bindView = bindWithPositioning;
2590
3251
  }
2591
3252
  }
2592
3253
  /**
2593
- * Bind this behavior to the source.
2594
- * @param source - The source to bind to.
2595
- * @param context - The execution context that the binding is operating within.
3254
+ * Bind this behavior.
3255
+ * @param controller - The view controller that manages the lifecycle of this behavior.
2596
3256
  */
2597
- bind(source, context) {
2598
- this.source = source;
2599
- this.context = context;
2600
- this.childContext = context.createChildContext(source);
2601
- this.items = this.itemsBindingObserver.observe(source, this.context);
2602
- this.template = this.templateBindingObserver.observe(source, this.context);
3257
+ bind(controller) {
3258
+ this.location = controller.targets[this.directive.targetNodeId];
3259
+ this.controller = controller;
3260
+ this.items = this.itemsBindingObserver.bind(controller);
3261
+ this.template = this.templateBindingObserver.bind(controller);
2603
3262
  this.observeItems(true);
2604
3263
  this.refreshAllViews();
3264
+ controller.onUnbind(this);
2605
3265
  }
2606
3266
  /**
2607
- * Unbinds this behavior from the source.
2608
- * @param source - The source to unbind from.
3267
+ * Unbinds this behavior.
2609
3268
  */
2610
3269
  unbind() {
2611
- this.source = null;
2612
- this.items = null;
2613
3270
  if (this.itemsObserver !== null) {
2614
3271
  this.itemsObserver.unsubscribe(this);
2615
3272
  }
2616
3273
  this.unbindAllViews();
2617
- this.itemsBindingObserver.dispose();
2618
- this.templateBindingObserver.dispose();
2619
3274
  }
2620
3275
  /**
2621
3276
  * Handles changes in the array, its items, and the repeat template.
@@ -2623,15 +3278,18 @@ class RepeatBehavior {
2623
3278
  * @param args - The details about what was changed.
2624
3279
  */
2625
3280
  handleChange(source, args) {
2626
- if (source === this.itemsBinding) {
2627
- this.items = this.itemsBindingObserver.observe(this.source, this.context);
3281
+ if (args === this.itemsBindingObserver) {
3282
+ this.items = this.itemsBindingObserver.bind(this.controller);
2628
3283
  this.observeItems();
2629
3284
  this.refreshAllViews();
2630
3285
  }
2631
- else if (source === this.templateBinding) {
2632
- this.template = this.templateBindingObserver.observe(this.source, this.context);
3286
+ else if (args === this.templateBindingObserver) {
3287
+ this.template = this.templateBindingObserver.bind(this.controller);
2633
3288
  this.refreshAllViews(true);
2634
3289
  }
3290
+ else if (!args[0]) {
3291
+ return;
3292
+ }
2635
3293
  else if (args[0].reset) {
2636
3294
  this.refreshAllViews();
2637
3295
  }
@@ -2656,39 +3314,57 @@ class RepeatBehavior {
2656
3314
  }
2657
3315
  updateViews(splices) {
2658
3316
  const views = this.views;
2659
- const childContext = this.childContext;
2660
- const totalRemoved = [];
2661
3317
  const bindView = this.bindView;
2662
- let removeDelta = 0;
2663
- for (let i = 0, ii = splices.length; i < ii; ++i) {
2664
- const splice = splices[i];
2665
- const removed = splice.removed;
2666
- totalRemoved.push(...views.splice(splice.index + removeDelta, removed.length));
2667
- removeDelta -= splice.addedCount;
2668
- }
2669
3318
  const items = this.items;
2670
3319
  const template = this.template;
3320
+ const controller = this.controller;
3321
+ const recycle = this.directive.options.recycle;
3322
+ const leftoverViews = [];
3323
+ let leftoverIndex = 0;
3324
+ let availableViews = 0;
2671
3325
  for (let i = 0, ii = splices.length; i < ii; ++i) {
2672
3326
  const splice = splices[i];
3327
+ const removed = splice.removed;
3328
+ let removeIndex = 0;
2673
3329
  let addIndex = splice.index;
2674
3330
  const end = addIndex + splice.addedCount;
3331
+ const removedViews = views.splice(splice.index, removed.length);
3332
+ const totalAvailableViews = (availableViews =
3333
+ leftoverViews.length + removedViews.length);
2675
3334
  for (; addIndex < end; ++addIndex) {
2676
3335
  const neighbor = views[addIndex];
2677
3336
  const location = neighbor ? neighbor.firstChild : this.location;
2678
- const view = this.options.recycle && totalRemoved.length > 0
2679
- ? totalRemoved.shift()
2680
- : template.create();
3337
+ let view;
3338
+ if (recycle && availableViews > 0) {
3339
+ if (removeIndex <= totalAvailableViews && removedViews.length > 0) {
3340
+ view = removedViews[removeIndex];
3341
+ removeIndex++;
3342
+ }
3343
+ else {
3344
+ view = leftoverViews[leftoverIndex];
3345
+ leftoverIndex++;
3346
+ }
3347
+ availableViews--;
3348
+ }
3349
+ else {
3350
+ view = template.create();
3351
+ }
2681
3352
  views.splice(addIndex, 0, view);
2682
- bindView(view, items, addIndex, childContext);
3353
+ bindView(view, items, addIndex, controller);
2683
3354
  view.insertBefore(location);
2684
3355
  }
3356
+ if (removedViews[removeIndex]) {
3357
+ leftoverViews.push(...removedViews.slice(removeIndex));
3358
+ }
2685
3359
  }
2686
- for (let i = 0, ii = totalRemoved.length; i < ii; ++i) {
2687
- totalRemoved[i].dispose();
3360
+ for (let i = leftoverIndex, ii = leftoverViews.length; i < ii; ++i) {
3361
+ leftoverViews[i].dispose();
2688
3362
  }
2689
- if (this.options.positioning) {
2690
- for (let i = 0, ii = views.length; i < ii; ++i) {
2691
- views[i].context.updatePosition(i, ii);
3363
+ if (this.directive.options.positioning) {
3364
+ for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
3365
+ const context = views[i].context;
3366
+ context.length = viewsLength;
3367
+ context.index = i;
2692
3368
  }
2693
3369
  }
2694
3370
  }
@@ -2697,11 +3373,11 @@ class RepeatBehavior {
2697
3373
  const template = this.template;
2698
3374
  const location = this.location;
2699
3375
  const bindView = this.bindView;
2700
- const childContext = this.childContext;
3376
+ const controller = this.controller;
2701
3377
  let itemsLength = items.length;
2702
3378
  let views = this.views;
2703
3379
  let viewsLength = views.length;
2704
- if (itemsLength === 0 || templateChanged || !this.options.recycle) {
3380
+ if (itemsLength === 0 || templateChanged || !this.directive.options.recycle) {
2705
3381
  // all views need to be removed
2706
3382
  HTMLView.disposeContiguousBatch(views);
2707
3383
  viewsLength = 0;
@@ -2711,7 +3387,7 @@ class RepeatBehavior {
2711
3387
  this.views = views = new Array(itemsLength);
2712
3388
  for (let i = 0; i < itemsLength; ++i) {
2713
3389
  const view = template.create();
2714
- bindView(view, items, i, childContext);
3390
+ bindView(view, items, i, controller);
2715
3391
  views[i] = view;
2716
3392
  view.insertBefore(location);
2717
3393
  }
@@ -2722,11 +3398,11 @@ class RepeatBehavior {
2722
3398
  for (; i < itemsLength; ++i) {
2723
3399
  if (i < viewsLength) {
2724
3400
  const view = views[i];
2725
- bindView(view, items, i, childContext);
3401
+ bindView(view, items, i, controller);
2726
3402
  }
2727
3403
  else {
2728
3404
  const view = template.create();
2729
- bindView(view, items, i, childContext);
3405
+ bindView(view, items, i, controller);
2730
3406
  views.push(view);
2731
3407
  view.insertBefore(location);
2732
3408
  }
@@ -2751,17 +3427,15 @@ class RepeatBehavior {
2751
3427
  class RepeatDirective {
2752
3428
  /**
2753
3429
  * Creates an instance of RepeatDirective.
2754
- * @param itemsBinding - The binding that provides the array to render.
3430
+ * @param dataBinding - The binding that provides the array to render.
2755
3431
  * @param templateBinding - The template binding used to obtain a template to render for each item in the array.
2756
3432
  * @param options - Options used to turn on special repeat features.
2757
3433
  */
2758
- constructor(itemsBinding, templateBinding, options) {
2759
- this.itemsBinding = itemsBinding;
3434
+ constructor(dataBinding, templateBinding, options) {
3435
+ this.dataBinding = dataBinding;
2760
3436
  this.templateBinding = templateBinding;
2761
3437
  this.options = options;
2762
3438
  ArrayObserver.enable();
2763
- this.isItemsBindingVolatile = Observable.isVolatileBinding(itemsBinding);
2764
- this.isTemplateBindingVolatile = Observable.isVolatileBinding(templateBinding);
2765
3439
  }
2766
3440
  /**
2767
3441
  * Creates a placeholder string based on the directive's index within the template.
@@ -2774,24 +3448,23 @@ class RepeatDirective {
2774
3448
  * Creates a behavior for the provided target node.
2775
3449
  * @param target - The node instance to create the behavior for.
2776
3450
  */
2777
- createBehavior(targets) {
2778
- return new RepeatBehavior(targets[this.nodeId], this.itemsBinding, this.isItemsBindingVolatile, this.templateBinding, this.isTemplateBindingVolatile, this.options);
3451
+ createBehavior() {
3452
+ return new RepeatBehavior(this);
2779
3453
  }
2780
3454
  }
2781
3455
  HTMLDirective.define(RepeatDirective);
2782
3456
  /**
2783
3457
  * A directive that enables list rendering.
2784
- * @param itemsBinding - The array to render.
2785
- * @param templateOrTemplateBinding - The template or a template binding used obtain a template
3458
+ * @param items - The array to render.
3459
+ * @param template - The template or a template binding used obtain a template
2786
3460
  * to render for each item in the array.
2787
3461
  * @param options - Options used to turn on special repeat features.
2788
3462
  * @public
2789
3463
  */
2790
- function repeat(itemsBinding, templateOrTemplateBinding, options = defaultRepeatOptions) {
2791
- const templateBinding = isFunction(templateOrTemplateBinding)
2792
- ? templateOrTemplateBinding
2793
- : () => templateOrTemplateBinding;
2794
- return new RepeatDirective(itemsBinding, templateBinding, options);
3464
+ function repeat(items, template, options = defaultRepeatOptions) {
3465
+ const dataBinding = normalizeBinding(items);
3466
+ const templateBinding = normalizeBinding(template);
3467
+ return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
2795
3468
  }
2796
3469
 
2797
3470
  const selectElements = (value) => value.nodeType === 1;
@@ -2810,9 +3483,15 @@ const elements = (selector) => selector
2810
3483
  * Internally used by the SlottedDirective and the ChildrenDirective.
2811
3484
  */
2812
3485
  class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2813
- constructor() {
2814
- super(...arguments);
2815
- this.sourceProperty = `${this.id}-s`;
3486
+ /**
3487
+ * The unique id of the factory.
3488
+ */
3489
+ get id() {
3490
+ return this._id;
3491
+ }
3492
+ set id(value) {
3493
+ this._id = value;
3494
+ this._controllerProperty = `${value}-c`;
2816
3495
  }
2817
3496
  /**
2818
3497
  * Bind this behavior to the source.
@@ -2820,11 +3499,12 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2820
3499
  * @param context - The execution context that the binding is operating within.
2821
3500
  * @param targets - The targets that behaviors in a view can attach to.
2822
3501
  */
2823
- bind(source, context, targets) {
2824
- const target = targets[this.nodeId];
2825
- target[this.sourceProperty] = source;
2826
- this.updateTarget(source, this.computeNodes(target));
3502
+ bind(controller) {
3503
+ const target = controller.targets[this.targetNodeId];
3504
+ target[this._controllerProperty] = controller;
3505
+ this.updateTarget(controller.source, this.computeNodes(target));
2827
3506
  this.observe(target);
3507
+ controller.onUnbind(this);
2828
3508
  }
2829
3509
  /**
2830
3510
  * Unbinds this behavior from the source.
@@ -2832,11 +3512,11 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2832
3512
  * @param context - The execution context that the binding is operating within.
2833
3513
  * @param targets - The targets that behaviors in a view can attach to.
2834
3514
  */
2835
- unbind(source, context, targets) {
2836
- const target = targets[this.nodeId];
2837
- this.updateTarget(source, emptyArray);
3515
+ unbind(controller) {
3516
+ const target = controller.targets[this.targetNodeId];
3517
+ this.updateTarget(controller.source, emptyArray);
2838
3518
  this.disconnect(target);
2839
- target[this.sourceProperty] = null;
3519
+ target[this._controllerProperty] = null;
2840
3520
  }
2841
3521
  /**
2842
3522
  * Gets the data source for the target.
@@ -2844,7 +3524,7 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2844
3524
  * @returns The source.
2845
3525
  */
2846
3526
  getSource(target) {
2847
- return target[this.sourceProperty];
3527
+ return target[this._controllerProperty].source;
2848
3528
  }
2849
3529
  /**
2850
3530
  * Updates the source property with the computed nodes.
@@ -2940,9 +3620,13 @@ class ChildrenDirective extends NodeObservationDirective {
2940
3620
  * @param target - The target to observe.
2941
3621
  */
2942
3622
  observe(target) {
2943
- var _a;
2944
- const observer = (_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = new MutationObserver(this.handleEvent));
2945
- observer.target = target;
3623
+ let observer = target[this.observerProperty];
3624
+ if (!observer) {
3625
+ observer = new MutationObserver(this.handleEvent);
3626
+ observer.toJSON = noop;
3627
+ observer.target = target;
3628
+ target[this.observerProperty] = observer;
3629
+ }
2946
3630
  observer.observe(target, this.options);
2947
3631
  }
2948
3632
  /**
@@ -2983,6 +3667,16 @@ function children(propertyOrOptions) {
2983
3667
 
2984
3668
  const booleanMode = "boolean";
2985
3669
  const reflectMode = "reflect";
3670
+ /**
3671
+ * Metadata used to configure a custom attribute's behavior.
3672
+ * @public
3673
+ */
3674
+ const AttributeConfiguration = Object.freeze({
3675
+ /**
3676
+ * Locates all attribute configurations associated with a type.
3677
+ */
3678
+ locate: createMetadataLocator(),
3679
+ });
2986
3680
  /**
2987
3681
  * A {@link ValueConverter} that converts to and from `boolean` values.
2988
3682
  * @remarks
@@ -3003,6 +3697,20 @@ const booleanConverter = {
3003
3697
  : true;
3004
3698
  },
3005
3699
  };
3700
+ /**
3701
+ * A {@link ValueConverter} that converts to and from `boolean` values. `null`, `undefined`, `""`, and `void` values are converted to `null`.
3702
+ * @public
3703
+ */
3704
+ const nullableBooleanConverter = {
3705
+ toView(value) {
3706
+ return typeof value === "boolean" ? value.toString() : "";
3707
+ },
3708
+ fromView(value) {
3709
+ return [null, undefined, void 0].includes(value)
3710
+ ? null
3711
+ : booleanConverter.fromView(value);
3712
+ },
3713
+ };
3006
3714
  function toNumber(value) {
3007
3715
  if (value === null || value === undefined) {
3008
3716
  return null;
@@ -3120,7 +3828,7 @@ class AttributeDefinition {
3120
3828
  */
3121
3829
  static collect(Owner, ...attributeLists) {
3122
3830
  const attributes = [];
3123
- attributeLists.push(Owner.attributes);
3831
+ attributeLists.push(AttributeConfiguration.locate(Owner));
3124
3832
  for (let i = 0, ii = attributeLists.length; i < ii; ++i) {
3125
3833
  const list = attributeLists[i];
3126
3834
  if (list === void 0) {
@@ -3150,9 +3858,7 @@ function attr(configOrTarget, prop) {
3150
3858
  // - @attr({...opts})
3151
3859
  config.property = $prop;
3152
3860
  }
3153
- const attributes = $target.constructor.attributes ||
3154
- ($target.constructor.attributes = []);
3155
- attributes.push(config);
3861
+ AttributeConfiguration.locate($target.constructor).push(config);
3156
3862
  }
3157
3863
  if (arguments.length > 1) {
3158
3864
  // Non invocation:
@@ -3170,25 +3876,24 @@ function attr(configOrTarget, prop) {
3170
3876
 
3171
3877
  const defaultShadowOptions = { mode: "open" };
3172
3878
  const defaultElementOptions = {};
3173
- const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */, () => createTypeRegistry());
3879
+ const fastElementBaseTypes = new Set();
3880
+ const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
3174
3881
  /**
3175
3882
  * Defines metadata for a FASTElement.
3176
3883
  * @public
3177
3884
  */
3178
3885
  class FASTElementDefinition {
3179
- /**
3180
- * Creates an instance of FASTElementDefinition.
3181
- * @param type - The type this definition is being created for.
3182
- * @param nameOrConfig - The name of the element to define or a config object
3183
- * that describes the element to define.
3184
- */
3185
3886
  constructor(type, nameOrConfig = type.definition) {
3887
+ var _a;
3888
+ this.platformDefined = false;
3186
3889
  if (isString(nameOrConfig)) {
3187
3890
  nameOrConfig = { name: nameOrConfig };
3188
3891
  }
3189
3892
  this.type = type;
3190
3893
  this.name = nameOrConfig.name;
3191
3894
  this.template = nameOrConfig.template;
3895
+ this.registry = (_a = nameOrConfig.registry) !== null && _a !== void 0 ? _a : customElements;
3896
+ const proto = type.prototype;
3192
3897
  const attributes = AttributeDefinition.collect(type, nameOrConfig.attributes);
3193
3898
  const observedAttributes = new Array(attributes.length);
3194
3899
  const propertyLookup = {};
@@ -3198,9 +3903,13 @@ class FASTElementDefinition {
3198
3903
  observedAttributes[i] = current.attribute;
3199
3904
  propertyLookup[current.name] = current;
3200
3905
  attributeLookup[current.attribute] = current;
3906
+ Observable.defineProperty(proto, current);
3201
3907
  }
3908
+ Reflect.defineProperty(type, "observedAttributes", {
3909
+ value: observedAttributes,
3910
+ enumerable: true,
3911
+ });
3202
3912
  this.attributes = attributes;
3203
- this.observedAttributes = observedAttributes;
3204
3913
  this.propertyLookup = propertyLookup;
3205
3914
  this.attributeLookup = attributeLookup;
3206
3915
  this.shadowOptions =
@@ -3213,20 +3922,14 @@ class FASTElementDefinition {
3213
3922
  nameOrConfig.elementOptions === void 0
3214
3923
  ? defaultElementOptions
3215
3924
  : Object.assign(Object.assign({}, defaultElementOptions), nameOrConfig.elementOptions);
3216
- this.styles =
3217
- nameOrConfig.styles === void 0
3218
- ? void 0
3219
- : Array.isArray(nameOrConfig.styles)
3220
- ? new ElementStyles(nameOrConfig.styles)
3221
- : nameOrConfig.styles instanceof ElementStyles
3222
- ? nameOrConfig.styles
3223
- : new ElementStyles([nameOrConfig.styles]);
3925
+ this.styles = ElementStyles.normalize(nameOrConfig.styles);
3926
+ fastElementRegistry.register(this);
3224
3927
  }
3225
3928
  /**
3226
3929
  * Indicates if this element has been defined in at least one registry.
3227
3930
  */
3228
3931
  get isDefined() {
3229
- return !!fastElementRegistry.getByType(this.type);
3932
+ return this.platformDefined;
3230
3933
  }
3231
3934
  /**
3232
3935
  * Defines a custom element based on this definition.
@@ -3234,24 +3937,35 @@ class FASTElementDefinition {
3234
3937
  * @remarks
3235
3938
  * This operation is idempotent per registry.
3236
3939
  */
3237
- define(registry = customElements) {
3940
+ define(registry = this.registry) {
3238
3941
  const type = this.type;
3239
- if (fastElementRegistry.register(this)) {
3240
- const attributes = this.attributes;
3241
- const proto = type.prototype;
3242
- for (let i = 0, ii = attributes.length; i < ii; ++i) {
3243
- Observable.defineProperty(proto, attributes[i]);
3244
- }
3245
- Reflect.defineProperty(type, "observedAttributes", {
3246
- value: this.observedAttributes,
3247
- enumerable: true,
3248
- });
3249
- }
3250
3942
  if (!registry.get(this.name)) {
3943
+ this.platformDefined = true;
3251
3944
  registry.define(this.name, type, this.elementOptions);
3252
3945
  }
3253
3946
  return this;
3254
3947
  }
3948
+ /**
3949
+ * Creates an instance of FASTElementDefinition.
3950
+ * @param type - The type this definition is being created for.
3951
+ * @param nameOrDef - The name of the element to define or a config object
3952
+ * that describes the element to define.
3953
+ */
3954
+ static compose(type, nameOrDef) {
3955
+ if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
3956
+ return new FASTElementDefinition(class extends type {
3957
+ }, nameOrDef);
3958
+ }
3959
+ return new FASTElementDefinition(type, nameOrDef);
3960
+ }
3961
+ /**
3962
+ * Registers a FASTElement base type.
3963
+ * @param type - The type to register as a base type.
3964
+ * @internal
3965
+ */
3966
+ static registerBaseType(type) {
3967
+ fastElementBaseTypes.add(type);
3968
+ }
3255
3969
  }
3256
3970
  /**
3257
3971
  * Gets the element definition associated with the specified type.
@@ -3264,22 +3978,23 @@ FASTElementDefinition.getByType = fastElementRegistry.getByType;
3264
3978
  */
3265
3979
  FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
3266
3980
 
3267
- const shadowRoots = new WeakMap();
3268
3981
  const defaultEventOptions = {
3269
3982
  bubbles: true,
3270
3983
  composed: true,
3271
3984
  cancelable: true,
3272
3985
  };
3986
+ const isConnectedPropertyName = "isConnected";
3987
+ const shadowRoots = new WeakMap();
3273
3988
  function getShadowRoot(element) {
3274
3989
  var _a, _b;
3275
3990
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
3276
3991
  }
3277
- const isConnectedPropertyName = "isConnected";
3992
+ let elementControllerStrategy;
3278
3993
  /**
3279
3994
  * Controls the lifecycle and rendering of a `FASTElement`.
3280
3995
  * @public
3281
3996
  */
3282
- class Controller extends PropertyChangeNotifier {
3997
+ class ElementController extends PropertyChangeNotifier {
3283
3998
  /**
3284
3999
  * Creates a Controller to control the specified element.
3285
4000
  * @param element - The element to be controlled by this controller.
@@ -3290,12 +4005,18 @@ class Controller extends PropertyChangeNotifier {
3290
4005
  constructor(element, definition) {
3291
4006
  super(element);
3292
4007
  this.boundObservables = null;
3293
- this.behaviors = null;
3294
4008
  this.needsInitialization = true;
3295
4009
  this.hasExistingShadowRoot = false;
3296
4010
  this._template = null;
3297
- this._styles = null;
3298
- this._isConnected = false;
4011
+ this.stage = 3 /* Stages.disconnected */;
4012
+ /**
4013
+ * A guard against connecting behaviors multiple times
4014
+ * during connect in scenarios where a behavior adds
4015
+ * another behavior during it's connectedCallback
4016
+ */
4017
+ this.guardBehaviorConnection = false;
4018
+ this.behaviors = null;
4019
+ this._mainStyles = null;
3299
4020
  /**
3300
4021
  * This allows Observable.getNotifier(...) to return the Controller
3301
4022
  * when the notifier for the Controller itself is being requested. The
@@ -3311,7 +4032,12 @@ class Controller extends PropertyChangeNotifier {
3311
4032
  * If `null` then the element is managing its own rendering.
3312
4033
  */
3313
4034
  this.view = null;
3314
- this.element = element;
4035
+ /**
4036
+ * Opts out of JSON stringification.
4037
+ * @internal
4038
+ */
4039
+ this.toJSON = noop;
4040
+ this.source = element;
3315
4041
  this.definition = definition;
3316
4042
  const shadowOptions = definition.shadowOptions;
3317
4043
  if (shadowOptions !== void 0) {
@@ -3349,11 +4075,7 @@ class Controller extends PropertyChangeNotifier {
3349
4075
  */
3350
4076
  get isConnected() {
3351
4077
  Observable.track(this, isConnectedPropertyName);
3352
- return this._isConnected;
3353
- }
3354
- setIsConnected(value) {
3355
- this._isConnected = value;
3356
- Observable.notify(this, isConnectedPropertyName);
4078
+ return this.stage === 1 /* Stages.connected */;
3357
4079
  }
3358
4080
  /**
3359
4081
  * Gets/sets the template used to render the component.
@@ -3365,9 +4087,9 @@ class Controller extends PropertyChangeNotifier {
3365
4087
  // 1. Template overrides take top precedence.
3366
4088
  if (this._template === null) {
3367
4089
  const definition = this.definition;
3368
- if (this.element.resolveTemplate) {
4090
+ if (this.source.resolveTemplate) {
3369
4091
  // 2. Allow for element instance overrides next.
3370
- this._template = this.element.resolveTemplate();
4092
+ this._template = this.source.resolveTemplate();
3371
4093
  }
3372
4094
  else if (definition.template) {
3373
4095
  // 3. Default to the static definition.
@@ -3386,56 +4108,104 @@ class Controller extends PropertyChangeNotifier {
3386
4108
  }
3387
4109
  }
3388
4110
  /**
3389
- * Gets/sets the primary styles used for the component.
3390
- * @remarks
3391
- * This value can only be accurately read after connect but can be set at any time.
4111
+ * The main set of styles used for the component, independent
4112
+ * of any dynamically added styles.
3392
4113
  */
3393
- get styles() {
4114
+ get mainStyles() {
3394
4115
  var _a;
3395
4116
  // 1. Styles overrides take top precedence.
3396
- if (this._styles === null) {
4117
+ if (this._mainStyles === null) {
3397
4118
  const definition = this.definition;
3398
- if (this.element.resolveStyles) {
4119
+ if (this.source.resolveStyles) {
3399
4120
  // 2. Allow for element instance overrides next.
3400
- this._styles = this.element.resolveStyles();
4121
+ this._mainStyles = this.source.resolveStyles();
3401
4122
  }
3402
4123
  else if (definition.styles) {
3403
4124
  // 3. Default to the static definition.
3404
- this._styles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
4125
+ this._mainStyles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
3405
4126
  }
3406
4127
  }
3407
- return this._styles;
4128
+ return this._mainStyles;
3408
4129
  }
3409
- set styles(value) {
3410
- if (this._styles === value) {
4130
+ set mainStyles(value) {
4131
+ if (this._mainStyles === value) {
3411
4132
  return;
3412
4133
  }
3413
- if (this._styles !== null) {
3414
- this.removeStyles(this._styles);
4134
+ if (this._mainStyles !== null) {
4135
+ this.removeStyles(this._mainStyles);
3415
4136
  }
3416
- this._styles = value;
4137
+ this._mainStyles = value;
3417
4138
  if (!this.needsInitialization) {
3418
4139
  this.addStyles(value);
3419
4140
  }
3420
4141
  }
4142
+ /**
4143
+ * Adds the behavior to the component.
4144
+ * @param behavior - The behavior to add.
4145
+ */
4146
+ addBehavior(behavior) {
4147
+ var _a, _b;
4148
+ const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
4149
+ const count = (_b = targetBehaviors.get(behavior)) !== null && _b !== void 0 ? _b : 0;
4150
+ if (count === 0) {
4151
+ targetBehaviors.set(behavior, 1);
4152
+ behavior.addedCallback && behavior.addedCallback(this);
4153
+ if (behavior.connectedCallback &&
4154
+ !this.guardBehaviorConnection &&
4155
+ (this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
4156
+ behavior.connectedCallback(this);
4157
+ }
4158
+ }
4159
+ else {
4160
+ targetBehaviors.set(behavior, count + 1);
4161
+ }
4162
+ }
4163
+ /**
4164
+ * Removes the behavior from the component.
4165
+ * @param behavior - The behavior to remove.
4166
+ * @param force - Forces removal even if this behavior was added more than once.
4167
+ */
4168
+ removeBehavior(behavior, force = false) {
4169
+ const targetBehaviors = this.behaviors;
4170
+ if (targetBehaviors === null) {
4171
+ return;
4172
+ }
4173
+ const count = targetBehaviors.get(behavior);
4174
+ if (count === void 0) {
4175
+ return;
4176
+ }
4177
+ if (count === 1 || force) {
4178
+ targetBehaviors.delete(behavior);
4179
+ if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
4180
+ behavior.disconnectedCallback(this);
4181
+ }
4182
+ behavior.removedCallback && behavior.removedCallback(this);
4183
+ }
4184
+ else {
4185
+ targetBehaviors.set(behavior, count - 1);
4186
+ }
4187
+ }
3421
4188
  /**
3422
4189
  * Adds styles to this element. Providing an HTMLStyleElement will attach the element instance to the shadowRoot.
3423
4190
  * @param styles - The styles to add.
3424
4191
  */
3425
4192
  addStyles(styles) {
4193
+ var _a;
3426
4194
  if (!styles) {
3427
4195
  return;
3428
4196
  }
3429
- const target = getShadowRoot(this.element) ||
3430
- this.element.getRootNode();
4197
+ const source = this.source;
3431
4198
  if (styles instanceof HTMLElement) {
4199
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
3432
4200
  target.append(styles);
3433
4201
  }
3434
- else if (!styles.isAttachedTo(target)) {
4202
+ else if (!styles.isAttachedTo(source)) {
3435
4203
  const sourceBehaviors = styles.behaviors;
3436
- styles.addStylesTo(target);
4204
+ styles.addStylesTo(source);
3437
4205
  if (sourceBehaviors !== null) {
3438
- this.addBehaviors(sourceBehaviors);
4206
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
4207
+ this.addBehavior(sourceBehaviors[i]);
4208
+ }
3439
4209
  }
3440
4210
  }
3441
4211
  }
@@ -3444,121 +4214,82 @@ class Controller extends PropertyChangeNotifier {
3444
4214
  * @param styles - the styles to remove.
3445
4215
  */
3446
4216
  removeStyles(styles) {
4217
+ var _a;
3447
4218
  if (!styles) {
3448
4219
  return;
3449
4220
  }
3450
- const target = getShadowRoot(this.element) ||
3451
- this.element.getRootNode();
4221
+ const source = this.source;
3452
4222
  if (styles instanceof HTMLElement) {
4223
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
3453
4224
  target.removeChild(styles);
3454
4225
  }
3455
- else if (styles.isAttachedTo(target)) {
4226
+ else if (styles.isAttachedTo(source)) {
3456
4227
  const sourceBehaviors = styles.behaviors;
3457
- styles.removeStylesFrom(target);
4228
+ styles.removeStylesFrom(source);
3458
4229
  if (sourceBehaviors !== null) {
3459
- this.removeBehaviors(sourceBehaviors);
3460
- }
3461
- }
3462
- }
3463
- /**
3464
- * Adds behaviors to this element.
3465
- * @param behaviors - The behaviors to add.
3466
- */
3467
- addBehaviors(behaviors) {
3468
- var _a;
3469
- const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
3470
- const length = behaviors.length;
3471
- const behaviorsToBind = [];
3472
- for (let i = 0; i < length; ++i) {
3473
- const behavior = behaviors[i];
3474
- if (targetBehaviors.has(behavior)) {
3475
- targetBehaviors.set(behavior, targetBehaviors.get(behavior) + 1);
3476
- }
3477
- else {
3478
- targetBehaviors.set(behavior, 1);
3479
- behaviorsToBind.push(behavior);
3480
- }
3481
- }
3482
- if (this._isConnected) {
3483
- const element = this.element;
3484
- const context = ExecutionContext.default;
3485
- for (let i = 0; i < behaviorsToBind.length; ++i) {
3486
- behaviorsToBind[i].bind(element, context);
4230
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
4231
+ this.addBehavior(sourceBehaviors[i]);
4232
+ }
3487
4233
  }
3488
4234
  }
3489
4235
  }
3490
4236
  /**
3491
- * Removes behaviors from this element.
3492
- * @param behaviors - The behaviors to remove.
3493
- * @param force - Forces unbinding of behaviors.
4237
+ * Runs connected lifecycle behavior on the associated element.
3494
4238
  */
3495
- removeBehaviors(behaviors, force = false) {
3496
- const targetBehaviors = this.behaviors;
3497
- if (targetBehaviors === null) {
4239
+ connect() {
4240
+ if (this.stage !== 3 /* Stages.disconnected */) {
3498
4241
  return;
3499
4242
  }
3500
- const length = behaviors.length;
3501
- const behaviorsToUnbind = [];
3502
- for (let i = 0; i < length; ++i) {
3503
- const behavior = behaviors[i];
3504
- if (targetBehaviors.has(behavior)) {
3505
- const count = targetBehaviors.get(behavior) - 1;
3506
- count === 0 || force
3507
- ? targetBehaviors.delete(behavior) && behaviorsToUnbind.push(behavior)
3508
- : targetBehaviors.set(behavior, count);
4243
+ this.stage = 0 /* Stages.connecting */;
4244
+ // If we have any observables that were bound, re-apply their values.
4245
+ if (this.boundObservables !== null) {
4246
+ const element = this.source;
4247
+ const boundObservables = this.boundObservables;
4248
+ const propertyNames = Object.keys(boundObservables);
4249
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
4250
+ const propertyName = propertyNames[i];
4251
+ element[propertyName] = boundObservables[propertyName];
3509
4252
  }
4253
+ this.boundObservables = null;
3510
4254
  }
3511
- if (this._isConnected) {
3512
- const element = this.element;
3513
- const context = ExecutionContext.default;
3514
- for (let i = 0; i < behaviorsToUnbind.length; ++i) {
3515
- behaviorsToUnbind[i].unbind(element, context);
4255
+ const behaviors = this.behaviors;
4256
+ if (behaviors !== null) {
4257
+ this.guardBehaviorConnection = true;
4258
+ for (const key of behaviors.keys()) {
4259
+ key.connectedCallback && key.connectedCallback(this);
3516
4260
  }
4261
+ this.guardBehaviorConnection = false;
3517
4262
  }
3518
- }
3519
- /**
3520
- * Runs connected lifecycle behavior on the associated element.
3521
- */
3522
- onConnectedCallback() {
3523
- if (this._isConnected) {
3524
- return;
3525
- }
3526
- const element = this.element;
3527
- const context = ExecutionContext.default;
3528
4263
  if (this.needsInitialization) {
3529
- this.finishInitialization();
4264
+ this.renderTemplate(this.template);
4265
+ this.addStyles(this.mainStyles);
4266
+ this.needsInitialization = false;
3530
4267
  }
3531
4268
  else if (this.view !== null) {
3532
- this.view.bind(element, context);
3533
- }
3534
- const behaviors = this.behaviors;
3535
- if (behaviors !== null) {
3536
- for (const behavior of behaviors.keys()) {
3537
- behavior.bind(element, context);
3538
- }
4269
+ this.view.bind(this.source);
3539
4270
  }
3540
- this.setIsConnected(true);
4271
+ this.stage = 1 /* Stages.connected */;
4272
+ Observable.notify(this, isConnectedPropertyName);
3541
4273
  }
3542
4274
  /**
3543
4275
  * Runs disconnected lifecycle behavior on the associated element.
3544
4276
  */
3545
- onDisconnectedCallback() {
3546
- if (!this._isConnected) {
4277
+ disconnect() {
4278
+ if (this.stage !== 1 /* Stages.connected */) {
3547
4279
  return;
3548
4280
  }
3549
- this.setIsConnected(false);
3550
- const view = this.view;
3551
- if (view !== null) {
3552
- view.unbind();
4281
+ this.stage = 2 /* Stages.disconnecting */;
4282
+ Observable.notify(this, isConnectedPropertyName);
4283
+ if (this.view !== null) {
4284
+ this.view.unbind();
3553
4285
  }
3554
4286
  const behaviors = this.behaviors;
3555
4287
  if (behaviors !== null) {
3556
- const element = this.element;
3557
- const context = ExecutionContext.default;
3558
- for (const behavior of behaviors.keys()) {
3559
- behavior.unbind(element, context);
4288
+ for (const key of behaviors.keys()) {
4289
+ key.disconnectedCallback && key.disconnectedCallback(this);
3560
4290
  }
3561
4291
  }
4292
+ this.stage = 3 /* Stages.disconnected */;
3562
4293
  }
3563
4294
  /**
3564
4295
  * Runs the attribute changed callback for the associated element.
@@ -3569,7 +4300,7 @@ class Controller extends PropertyChangeNotifier {
3569
4300
  onAttributeChangedCallback(name, oldValue, newValue) {
3570
4301
  const attrDef = this.definition.attributeLookup[name];
3571
4302
  if (attrDef !== void 0) {
3572
- attrDef.onAttributeChangedCallback(this.element, newValue);
4303
+ attrDef.onAttributeChangedCallback(this.source, newValue);
3573
4304
  }
3574
4305
  }
3575
4306
  /**
@@ -3581,33 +4312,17 @@ class Controller extends PropertyChangeNotifier {
3581
4312
  * Only emits events if connected.
3582
4313
  */
3583
4314
  emit(type, detail, options) {
3584
- if (this._isConnected) {
3585
- return this.element.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
4315
+ if (this.stage === 1 /* Stages.connected */) {
4316
+ return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
3586
4317
  }
3587
4318
  return false;
3588
4319
  }
3589
- finishInitialization() {
3590
- const element = this.element;
3591
- const boundObservables = this.boundObservables;
3592
- // If we have any observables that were bound, re-apply their values.
3593
- if (boundObservables !== null) {
3594
- const propertyNames = Object.keys(boundObservables);
3595
- for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
3596
- const propertyName = propertyNames[i];
3597
- element[propertyName] = boundObservables[propertyName];
3598
- }
3599
- this.boundObservables = null;
3600
- }
3601
- this.renderTemplate(this.template);
3602
- this.addStyles(this.styles);
3603
- this.needsInitialization = false;
3604
- }
3605
4320
  renderTemplate(template) {
3606
4321
  var _a;
3607
- const element = this.element;
3608
4322
  // When getting the host to render to, we start by looking
3609
4323
  // up the shadow root. If there isn't one, then that means
3610
4324
  // we're doing a Light DOM render to the element's direct children.
4325
+ const element = this.source;
3611
4326
  const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
3612
4327
  if (this.view !== null) {
3613
4328
  // If there's already a view, we need to unbind and remove through dispose.
@@ -3624,6 +4339,8 @@ class Controller extends PropertyChangeNotifier {
3624
4339
  if (template) {
3625
4340
  // If a new template was provided, render it.
3626
4341
  this.view = template.render(element, host, element);
4342
+ this.view.sourceLifetime =
4343
+ SourceLifetime.coupled;
3627
4344
  }
3628
4345
  }
3629
4346
  /**
@@ -3643,31 +4360,146 @@ class Controller extends PropertyChangeNotifier {
3643
4360
  if (definition === void 0) {
3644
4361
  throw FAST.error(1401 /* Message.missingElementDefinition */);
3645
4362
  }
3646
- return (element.$fastController = new Controller(element, definition));
4363
+ return (element.$fastController = new elementControllerStrategy(element, definition));
4364
+ }
4365
+ /**
4366
+ * Sets the strategy that ElementController.forCustomElement uses to construct
4367
+ * ElementController instances for an element.
4368
+ * @param strategy - The strategy to use.
4369
+ */
4370
+ static setStrategy(strategy) {
4371
+ elementControllerStrategy = strategy;
4372
+ }
4373
+ }
4374
+ // Set default strategy for ElementController
4375
+ ElementController.setStrategy(ElementController);
4376
+ /**
4377
+ * Converts a styleTarget into the operative target. When the provided target is an Element
4378
+ * that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
4379
+ * it will return the root node for the element.
4380
+ * @param target
4381
+ * @returns
4382
+ */
4383
+ function normalizeStyleTarget(target) {
4384
+ var _a;
4385
+ if ("adoptedStyleSheets" in target) {
4386
+ return target;
4387
+ }
4388
+ else {
4389
+ return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
4390
+ }
4391
+ }
4392
+ // Default StyleStrategy implementations are defined in this module because they
4393
+ // require access to element shadowRoots, and we don't want to leak shadowRoot
4394
+ // objects out of this module.
4395
+ /**
4396
+ * https://wicg.github.io/construct-stylesheets/
4397
+ * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
4398
+ *
4399
+ * @internal
4400
+ */
4401
+ class AdoptedStyleSheetsStrategy {
4402
+ constructor(styles) {
4403
+ const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
4404
+ this.sheets = styles.map((x) => {
4405
+ if (x instanceof CSSStyleSheet) {
4406
+ return x;
4407
+ }
4408
+ let sheet = styleSheetCache.get(x);
4409
+ if (sheet === void 0) {
4410
+ sheet = new CSSStyleSheet();
4411
+ sheet.replaceSync(x);
4412
+ styleSheetCache.set(x, sheet);
4413
+ }
4414
+ return sheet;
4415
+ });
4416
+ }
4417
+ addStylesTo(target) {
4418
+ const t = normalizeStyleTarget(target);
4419
+ t.adoptedStyleSheets = [...t.adoptedStyleSheets, ...this.sheets];
4420
+ }
4421
+ removeStylesFrom(target) {
4422
+ const t = normalizeStyleTarget(target);
4423
+ const sheets = this.sheets;
4424
+ t.adoptedStyleSheets = t.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
4425
+ }
4426
+ }
4427
+ AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
4428
+ let id = 0;
4429
+ const nextStyleId = () => `fast-${++id}`;
4430
+ function usableStyleTarget(target) {
4431
+ return target === document ? document.body : target;
4432
+ }
4433
+ /**
4434
+ * @internal
4435
+ */
4436
+ class StyleElementStrategy {
4437
+ constructor(styles) {
4438
+ this.styles = styles;
4439
+ this.styleClass = nextStyleId();
4440
+ }
4441
+ addStylesTo(target) {
4442
+ target = usableStyleTarget(normalizeStyleTarget(target));
4443
+ const styles = this.styles;
4444
+ const styleClass = this.styleClass;
4445
+ for (let i = 0; i < styles.length; i++) {
4446
+ const element = document.createElement("style");
4447
+ element.innerHTML = styles[i];
4448
+ element.className = styleClass;
4449
+ target.append(element);
4450
+ }
4451
+ }
4452
+ removeStylesFrom(target) {
4453
+ target = usableStyleTarget(normalizeStyleTarget(target));
4454
+ const styles = target.querySelectorAll(`.${this.styleClass}`);
4455
+ styles[0].parentNode;
4456
+ for (let i = 0, ii = styles.length; i < ii; ++i) {
4457
+ target.removeChild(styles[i]);
4458
+ }
3647
4459
  }
3648
4460
  }
4461
+ ElementStyles.setDefaultStrategy(ElementStyles.supportsAdoptedStyleSheets
4462
+ ? AdoptedStyleSheetsStrategy
4463
+ : StyleElementStrategy);
3649
4464
 
3650
4465
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3651
4466
  function createFASTElement(BaseType) {
3652
- return class extends BaseType {
4467
+ const type = class extends BaseType {
3653
4468
  constructor() {
3654
4469
  /* eslint-disable-next-line */
3655
4470
  super();
3656
- Controller.forCustomElement(this);
4471
+ ElementController.forCustomElement(this);
3657
4472
  }
3658
4473
  $emit(type, detail, options) {
3659
4474
  return this.$fastController.emit(type, detail, options);
3660
4475
  }
3661
4476
  connectedCallback() {
3662
- this.$fastController.onConnectedCallback();
4477
+ this.$fastController.connect();
3663
4478
  }
3664
4479
  disconnectedCallback() {
3665
- this.$fastController.onDisconnectedCallback();
4480
+ this.$fastController.disconnect();
3666
4481
  }
3667
4482
  attributeChangedCallback(name, oldValue, newValue) {
3668
4483
  this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
3669
4484
  }
3670
4485
  };
4486
+ FASTElementDefinition.registerBaseType(type);
4487
+ return type;
4488
+ }
4489
+ function compose(type, nameOrDef) {
4490
+ if (isFunction(type)) {
4491
+ return FASTElementDefinition.compose(type, nameOrDef);
4492
+ }
4493
+ return FASTElementDefinition.compose(this, type);
4494
+ }
4495
+ function define(type, nameOrDef) {
4496
+ if (isFunction(type)) {
4497
+ return FASTElementDefinition.compose(type, nameOrDef).define().type;
4498
+ }
4499
+ return FASTElementDefinition.compose(this, type).define().type;
4500
+ }
4501
+ function from(BaseType) {
4502
+ return createFASTElement(BaseType);
3671
4503
  }
3672
4504
  /**
3673
4505
  * A minimal base class for FASTElements that also provides
@@ -3680,26 +4512,19 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3680
4512
  * provided base type.
3681
4513
  * @param BaseType - The base element type to inherit from.
3682
4514
  */
3683
- from(BaseType) {
3684
- return createFASTElement(BaseType);
3685
- },
4515
+ from,
3686
4516
  /**
3687
4517
  * Defines a platform custom element based on the provided type and definition.
3688
4518
  * @param type - The custom element type to define.
3689
4519
  * @param nameOrDef - The name of the element to define or a definition object
3690
4520
  * that describes the element to define.
3691
4521
  */
3692
- define(type, nameOrDef) {
3693
- return this.metadata(type, nameOrDef).define().type;
3694
- },
4522
+ define,
3695
4523
  /**
3696
4524
  * Defines metadata for a FASTElement which can be used to later define the element.
3697
- * IMPORTANT: This API will be renamed to "compose" in a future beta.
3698
4525
  * @public
3699
4526
  */
3700
- metadata(type, nameOrDef) {
3701
- return new FASTElementDefinition(type, nameOrDef);
3702
- },
4527
+ compose,
3703
4528
  });
3704
4529
  /**
3705
4530
  * Decorator: Defines a platform custom element based on `FASTElement`.
@@ -3710,8 +4535,10 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3710
4535
  function customElement(nameOrDef) {
3711
4536
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3712
4537
  return function (type) {
3713
- FASTElement.define(type, nameOrDef);
4538
+ define(type, nameOrDef);
3714
4539
  };
3715
4540
  }
3716
4541
 
3717
- export { AdoptedStyleSheetsStrategy, ArrayObserver, Aspect, AttributeDefinition, BindingConfig, BindingMode, CSSDirective, ChangeBinding, ChildrenDirective, Compiler, Controller, DOM, ElementStyles, EventBinding, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, Markup, NodeObservationDirective, Observable, OneTimeBinding, Parser, PropertyChangeNotifier, RefDirective, RepeatBehavior, RepeatDirective, SlottedDirective, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, UpdateBinding, Updates, ViewTemplate, attr, bind, booleanConverter, children, createTypeRegistry, css, cssDirective, cssPartial, customElement, elements, emptyArray, html, htmlDirective, lengthOf, nullableNumberConverter, observable, onChange, oneTime, ref, repeat, slotted, volatile, when };
4542
+ DOM.setPolicy(DOMPolicy.create());
4543
+
4544
+ export { ArrayObserver, AttributeConfiguration, AttributeDefinition, Binding, CSSDirective, ChildrenDirective, Compiler, DOM, DOMAspect, ElementController, ElementStyles, ExecutionContext, FAST, FASTElement, FASTElementDefinition, HTMLBindingDirective, HTMLDirective, HTMLView, InlineTemplateDirective, Markup, NodeObservationDirective, Observable, Parser, PropertyChangeNotifier, RefDirective, RepeatBehavior, RepeatDirective, SlottedDirective, SourceLifetime, Splice, SpliceStrategy, SpliceStrategySupport, StatelessAttachedAttributeDirective, SubscriberSet, Updates, ViewTemplate, attr, bind, booleanConverter, children, createMetadataLocator, createTypeRegistry, css, cssDirective, customElement, elements, emptyArray, html, htmlDirective, lengthOf, listener, normalizeBinding, nullableBooleanConverter, nullableNumberConverter, observable, oneTime, ref, repeat, slotted, volatile, when };