@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,73 @@
1
+ let kernelMode;
2
+ const kernelAttr = "fast-kernel";
3
+ try {
4
+ if (document.currentScript) {
5
+ kernelMode = document.currentScript.getAttribute(kernelAttr);
6
+ }
7
+ else {
8
+ const scripts = document.getElementsByTagName("script");
9
+ const currentScript = scripts[scripts.length - 1];
10
+ kernelMode = currentScript.getAttribute(kernelAttr);
11
+ }
12
+ }
13
+ catch (e) {
14
+ kernelMode = "isolate";
15
+ }
16
+ let KernelServiceId;
17
+ switch (kernelMode) {
18
+ case "share": // share the kernel across major versions
19
+ KernelServiceId = Object.freeze({
20
+ updateQueue: 1,
21
+ observable: 2,
22
+ contextEvent: 3,
23
+ elementRegistry: 4,
24
+ });
25
+ break;
26
+ case "share-v2": // only share the kernel with other v2 instances
27
+ KernelServiceId = Object.freeze({
28
+ updateQueue: 1.2,
29
+ observable: 2.2,
30
+ contextEvent: 3.2,
31
+ elementRegistry: 4.2,
32
+ });
33
+ break;
34
+ default:
35
+ // fully isolate the kernel from all other FAST instances
36
+ const postfix = `-${Math.random().toString(36).substring(2, 8)}`;
37
+ KernelServiceId = Object.freeze({
38
+ updateQueue: `1.2${postfix}`,
39
+ observable: `2.2${postfix}`,
40
+ contextEvent: `3.2${postfix}`,
41
+ elementRegistry: `4.2${postfix}`,
42
+ });
43
+ break;
44
+ }
45
+ /**
46
+ * Determines whether or not an object is a function.
47
+ * @internal
48
+ */
49
+ const isFunction = (object) => typeof object === "function";
50
+ /**
51
+ * Determines whether or not an object is a string.
52
+ * @internal
53
+ */
54
+ const isString = (object) => typeof object === "string";
55
+ /**
56
+ * A function which does nothing.
57
+ * @internal
58
+ */
59
+ const noop = () => void 0;
60
+
61
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
1
62
  (function ensureGlobalThis() {
2
63
  if (typeof globalThis !== "undefined") {
3
64
  // We're running in a modern environment.
4
65
  return;
5
66
  }
67
+ // @ts-ignore
6
68
  if (typeof global !== "undefined") {
7
69
  // We're running in NodeJS
70
+ // @ts-ignore
8
71
  global.globalThis = global;
9
72
  }
10
73
  else if (typeof self !== "undefined") {
@@ -22,69 +85,8 @@
22
85
  result.globalThis = result;
23
86
  }
24
87
  })();
25
- // API-only Polyfill for trustedTypes
26
- if (!globalThis.trustedTypes) {
27
- globalThis.trustedTypes = {
28
- createPolicy: (n, r) => r,
29
- };
30
- }
31
- // ensure FAST global - duplicated in platform.ts
32
- const propConfig$1 = {
33
- configurable: false,
34
- enumerable: false,
35
- writable: false,
36
- };
37
- if (globalThis.FAST === void 0) {
38
- Reflect.defineProperty(globalThis, "FAST", Object.assign({ value: Object.create(null) }, propConfig$1));
39
- }
40
- const FAST$1 = globalThis.FAST;
41
- if (FAST$1.getById === void 0) {
42
- const storage = Object.create(null);
43
- Reflect.defineProperty(FAST$1, "getById", Object.assign({ value(id, initialize) {
44
- let found = storage[id];
45
- if (found === void 0) {
46
- found = initialize ? (storage[id] = initialize()) : null;
47
- }
48
- return found;
49
- } }, propConfig$1));
50
- }
51
- // duplicated from DOM
52
- const supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
53
- "replace" in CSSStyleSheet.prototype;
54
- function usableStyleTarget(target) {
55
- return target === document ? document.body : target;
56
- }
57
- let id$1 = 0;
58
- const nextStyleId = () => `fast-${++id$1}`;
59
- class StyleElementStrategy {
60
- constructor(styles) {
61
- this.styles = styles;
62
- this.styleClass = nextStyleId();
63
- }
64
- addStylesTo(target) {
65
- target = usableStyleTarget(target);
66
- const styles = this.styles;
67
- const styleClass = this.styleClass;
68
- for (let i = 0; i < styles.length; i++) {
69
- const element = document.createElement("style");
70
- element.innerHTML = styles[i];
71
- element.className = styleClass;
72
- target.append(element);
73
- }
74
- }
75
- removeStylesFrom(target) {
76
- const styles = target.querySelectorAll(`.${this.styleClass}`);
77
- target = usableStyleTarget(target);
78
- for (let i = 0, ii = styles.length; i < ii; ++i) {
79
- target.removeChild(styles[i]);
80
- }
81
- }
82
- }
83
- if (!supportsAdoptedStyleSheets) {
84
- FAST$1.getById(/* KernelServiceId.styleSheetStrategy */ 5, () => StyleElementStrategy);
85
- }
86
88
 
87
- // ensure FAST global - duplicated in polyfills.ts and debug.ts
89
+ // ensure FAST global - duplicated debug.ts
88
90
  const propConfig = {
89
91
  configurable: false,
90
92
  enumerable: false,
@@ -112,7 +114,7 @@ if (FAST.error === void 0) {
112
114
  Object.assign(FAST, {
113
115
  warn() { },
114
116
  error(code) {
115
- return new Error(`Code ${code}`);
117
+ return new Error(`Error ${code}`);
116
118
  },
117
119
  addMessages() { },
118
120
  });
@@ -143,25 +145,475 @@ function createTypeRegistry() {
143
145
  return typeToDefinition.get(key);
144
146
  },
145
147
  getForInstance(object) {
148
+ if (object === null || object === void 0) {
149
+ return void 0;
150
+ }
146
151
  return typeToDefinition.get(object.constructor);
147
152
  },
148
153
  });
149
154
  }
150
-
151
155
  /**
156
+ * Creates a function capable of locating metadata associated with a type.
157
+ * @returns A metadata locator function.
152
158
  * @internal
153
159
  */
154
- const isFunction = (object) => typeof object === "function";
160
+ function createMetadataLocator() {
161
+ const metadataLookup = new WeakMap();
162
+ return function (target) {
163
+ let metadata = metadataLookup.get(target);
164
+ if (metadata === void 0) {
165
+ let currentTarget = Reflect.getPrototypeOf(target);
166
+ while (metadata === void 0 && currentTarget !== null) {
167
+ metadata = metadataLookup.get(currentTarget);
168
+ currentTarget = Reflect.getPrototypeOf(currentTarget);
169
+ }
170
+ metadata = metadata === void 0 ? [] : metadata.slice(0);
171
+ metadataLookup.set(target, metadata);
172
+ }
173
+ return metadata;
174
+ };
175
+ }
176
+
155
177
  /**
156
- * @internal
178
+ * The type of HTML aspect to target.
179
+ * @public
157
180
  */
158
- const isString = (object) => typeof object === "string";
181
+ const DOMAspect = Object.freeze({
182
+ /**
183
+ * Not aspected.
184
+ */
185
+ none: 0,
186
+ /**
187
+ * An attribute.
188
+ */
189
+ attribute: 1,
190
+ /**
191
+ * A boolean attribute.
192
+ */
193
+ booleanAttribute: 2,
194
+ /**
195
+ * A property.
196
+ */
197
+ property: 3,
198
+ /**
199
+ * Content
200
+ */
201
+ content: 4,
202
+ /**
203
+ * A token list.
204
+ */
205
+ tokenList: 5,
206
+ /**
207
+ * An event.
208
+ */
209
+ event: 6,
210
+ });
211
+ const createHTML$1 = html => html;
212
+ const fastTrustedType = globalThis.trustedTypes
213
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML: createHTML$1 })
214
+ : { createHTML: createHTML$1 };
215
+ let defaultPolicy = Object.freeze({
216
+ createHTML(value) {
217
+ return fastTrustedType.createHTML(value);
218
+ },
219
+ protect(tagName, aspect, aspectName, sink) {
220
+ return sink;
221
+ },
222
+ });
223
+ const fastPolicy = defaultPolicy;
224
+ /**
225
+ * Common DOM APIs.
226
+ * @public
227
+ */
228
+ const DOM = Object.freeze({
229
+ /**
230
+ * Gets the dom policy used by the templating system.
231
+ */
232
+ get policy() {
233
+ return defaultPolicy;
234
+ },
235
+ /**
236
+ * Sets the dom policy used by the templating system.
237
+ * @param policy - The policy to set.
238
+ * @remarks
239
+ * This API can only be called once, for security reasons. It should be
240
+ * called by the application developer at the start of their program.
241
+ */
242
+ setPolicy(value) {
243
+ if (defaultPolicy !== fastPolicy) {
244
+ throw FAST.error(1201 /* Message.onlySetDOMPolicyOnce */);
245
+ }
246
+ defaultPolicy = value;
247
+ },
248
+ /**
249
+ * Sets an attribute value on an element.
250
+ * @param element - The element to set the attribute value on.
251
+ * @param attributeName - The attribute name to set.
252
+ * @param value - The value of the attribute to set.
253
+ * @remarks
254
+ * If the value is `null` or `undefined`, the attribute is removed, otherwise
255
+ * it is set to the provided value using the standard `setAttribute` API.
256
+ */
257
+ setAttribute(element, attributeName, value) {
258
+ value === null || value === undefined
259
+ ? element.removeAttribute(attributeName)
260
+ : element.setAttribute(attributeName, value);
261
+ },
262
+ /**
263
+ * Sets a boolean attribute value.
264
+ * @param element - The element to set the boolean attribute value on.
265
+ * @param attributeName - The attribute name to set.
266
+ * @param value - The value of the attribute to set.
267
+ * @remarks
268
+ * If the value is true, the attribute is added; otherwise it is removed.
269
+ */
270
+ setBooleanAttribute(element, attributeName, value) {
271
+ value
272
+ ? element.setAttribute(attributeName, "")
273
+ : element.removeAttribute(attributeName);
274
+ },
275
+ });
276
+
277
+ function safeURL(tagName, aspect, aspectName, sink) {
278
+ return (target, name, value, ...rest) => {
279
+ if (isString(value)) {
280
+ value = value.replace("javascript:", "");
281
+ }
282
+ sink(target, name, value, ...rest);
283
+ };
284
+ }
285
+ function block(tagName, aspect, aspectName, sink) {
286
+ throw new Error(`${aspectName} on ${tagName !== null && tagName !== void 0 ? tagName : "text"} is blocked by the current DOMPolicy.`);
287
+ }
288
+ const defaultDOMElementGuards = {
289
+ a: {
290
+ [DOMAspect.attribute]: {
291
+ href: safeURL,
292
+ },
293
+ [DOMAspect.property]: {
294
+ href: safeURL,
295
+ },
296
+ },
297
+ area: {
298
+ [DOMAspect.attribute]: {
299
+ href: safeURL,
300
+ },
301
+ [DOMAspect.property]: {
302
+ href: safeURL,
303
+ },
304
+ },
305
+ button: {
306
+ [DOMAspect.attribute]: {
307
+ formaction: safeURL,
308
+ },
309
+ [DOMAspect.property]: {
310
+ formAction: safeURL,
311
+ },
312
+ },
313
+ embed: {
314
+ [DOMAspect.attribute]: {
315
+ src: block,
316
+ },
317
+ [DOMAspect.property]: {
318
+ src: block,
319
+ },
320
+ },
321
+ form: {
322
+ [DOMAspect.attribute]: {
323
+ action: safeURL,
324
+ },
325
+ [DOMAspect.property]: {
326
+ action: safeURL,
327
+ },
328
+ },
329
+ frame: {
330
+ [DOMAspect.attribute]: {
331
+ src: safeURL,
332
+ },
333
+ [DOMAspect.property]: {
334
+ src: safeURL,
335
+ },
336
+ },
337
+ iframe: {
338
+ [DOMAspect.attribute]: {
339
+ src: safeURL,
340
+ },
341
+ [DOMAspect.property]: {
342
+ src: safeURL,
343
+ srcdoc: block,
344
+ },
345
+ },
346
+ input: {
347
+ [DOMAspect.attribute]: {
348
+ formaction: safeURL,
349
+ },
350
+ [DOMAspect.property]: {
351
+ formAction: safeURL,
352
+ },
353
+ },
354
+ link: {
355
+ [DOMAspect.attribute]: {
356
+ href: block,
357
+ },
358
+ [DOMAspect.property]: {
359
+ href: block,
360
+ },
361
+ },
362
+ object: {
363
+ [DOMAspect.attribute]: {
364
+ codebase: block,
365
+ data: block,
366
+ },
367
+ [DOMAspect.property]: {
368
+ codeBase: block,
369
+ data: block,
370
+ },
371
+ },
372
+ script: {
373
+ [DOMAspect.attribute]: {
374
+ src: block,
375
+ text: block,
376
+ },
377
+ [DOMAspect.property]: {
378
+ src: block,
379
+ text: block,
380
+ innerText: block,
381
+ textContent: block,
382
+ },
383
+ },
384
+ style: {
385
+ [DOMAspect.property]: {
386
+ innerText: block,
387
+ textContent: block,
388
+ },
389
+ },
390
+ };
391
+ const blockedEvents = {
392
+ onabort: block,
393
+ onauxclick: block,
394
+ onbeforeinput: block,
395
+ onbeforematch: block,
396
+ onblur: block,
397
+ oncancel: block,
398
+ oncanplay: block,
399
+ oncanplaythrough: block,
400
+ onchange: block,
401
+ onclick: block,
402
+ onclose: block,
403
+ oncontextlost: block,
404
+ oncontextmenu: block,
405
+ oncontextrestored: block,
406
+ oncopy: block,
407
+ oncuechange: block,
408
+ oncut: block,
409
+ ondblclick: block,
410
+ ondrag: block,
411
+ ondragend: block,
412
+ ondragenter: block,
413
+ ondragleave: block,
414
+ ondragover: block,
415
+ ondragstart: block,
416
+ ondrop: block,
417
+ ondurationchange: block,
418
+ onemptied: block,
419
+ onended: block,
420
+ onerror: block,
421
+ onfocus: block,
422
+ onformdata: block,
423
+ oninput: block,
424
+ oninvalid: block,
425
+ onkeydown: block,
426
+ onkeypress: block,
427
+ onkeyup: block,
428
+ onload: block,
429
+ onloadeddata: block,
430
+ onloadedmetadata: block,
431
+ onloadstart: block,
432
+ onmousedown: block,
433
+ onmouseenter: block,
434
+ onmouseleave: block,
435
+ onmousemove: block,
436
+ onmouseout: block,
437
+ onmouseover: block,
438
+ onmouseup: block,
439
+ onpaste: block,
440
+ onpause: block,
441
+ onplay: block,
442
+ onplaying: block,
443
+ onprogress: block,
444
+ onratechange: block,
445
+ onreset: block,
446
+ onresize: block,
447
+ onscroll: block,
448
+ onsecuritypolicyviolation: block,
449
+ onseeked: block,
450
+ onseeking: block,
451
+ onselect: block,
452
+ onslotchange: block,
453
+ onstalled: block,
454
+ onsubmit: block,
455
+ onsuspend: block,
456
+ ontimeupdate: block,
457
+ ontoggle: block,
458
+ onvolumechange: block,
459
+ onwaiting: block,
460
+ onwebkitanimationend: block,
461
+ onwebkitanimationiteration: block,
462
+ onwebkitanimationstart: block,
463
+ onwebkittransitionend: block,
464
+ onwheel: block,
465
+ };
466
+ const defaultDOMGuards = {
467
+ elements: defaultDOMElementGuards,
468
+ aspects: {
469
+ [DOMAspect.attribute]: Object.assign({}, blockedEvents),
470
+ [DOMAspect.property]: Object.assign({ innerHTML: block }, blockedEvents),
471
+ [DOMAspect.event]: Object.assign({}, blockedEvents),
472
+ },
473
+ };
474
+ function createDomSinkGuards(config, defaults) {
475
+ const result = {};
476
+ for (const name in defaults) {
477
+ const overrideValue = config[name];
478
+ const defaultValue = defaults[name];
479
+ switch (overrideValue) {
480
+ case null:
481
+ // remove the default
482
+ break;
483
+ case undefined:
484
+ // keep the default
485
+ result[name] = defaultValue;
486
+ break;
487
+ default:
488
+ // override the default
489
+ result[name] = overrideValue;
490
+ break;
491
+ }
492
+ }
493
+ // add any new sinks that were not overrides
494
+ for (const name in config) {
495
+ if (!(name in result)) {
496
+ result[name] = config[name];
497
+ }
498
+ }
499
+ return Object.freeze(result);
500
+ }
501
+ function createDOMAspectGuards(config, defaults) {
502
+ const result = {};
503
+ for (const aspect in defaults) {
504
+ const overrideValue = config[aspect];
505
+ const defaultValue = defaults[aspect];
506
+ switch (overrideValue) {
507
+ case null:
508
+ // remove the default
509
+ break;
510
+ case undefined:
511
+ // keep the default
512
+ result[aspect] = createDomSinkGuards(defaultValue, {});
513
+ break;
514
+ default:
515
+ // override the default
516
+ result[aspect] = createDomSinkGuards(overrideValue, defaultValue);
517
+ break;
518
+ }
519
+ }
520
+ // add any new aspect guards that were not overrides
521
+ for (const aspect in config) {
522
+ if (!(aspect in result)) {
523
+ result[aspect] = createDomSinkGuards(config[aspect], {});
524
+ }
525
+ }
526
+ return Object.freeze(result);
527
+ }
528
+ function createElementGuards(config, defaults) {
529
+ const result = {};
530
+ for (const tag in defaults) {
531
+ const overrideValue = config[tag];
532
+ const defaultValue = defaults[tag];
533
+ switch (overrideValue) {
534
+ case null:
535
+ // remove the default
536
+ break;
537
+ case undefined:
538
+ // keep the default
539
+ result[tag] = createDOMAspectGuards(overrideValue, {});
540
+ break;
541
+ default:
542
+ // override the default aspects
543
+ result[tag] = createDOMAspectGuards(overrideValue, defaultValue);
544
+ break;
545
+ }
546
+ }
547
+ // Add any new element guards that were not overrides
548
+ for (const tag in config) {
549
+ if (!(tag in result)) {
550
+ result[tag] = createDOMAspectGuards(config[tag], {});
551
+ }
552
+ }
553
+ return Object.freeze(result);
554
+ }
555
+ function createDOMGuards(config, defaults) {
556
+ return Object.freeze({
557
+ elements: config.elements
558
+ ? createElementGuards(config.elements, defaults.elements)
559
+ : defaults.elements,
560
+ aspects: config.aspects
561
+ ? createDOMAspectGuards(config.aspects, defaults.aspects)
562
+ : defaults.aspects,
563
+ });
564
+ }
565
+ function createTrustedType() {
566
+ const createHTML = html => html;
567
+ return globalThis.trustedTypes
568
+ ? globalThis.trustedTypes.createPolicy("fast-html", { createHTML })
569
+ : { createHTML };
570
+ }
571
+ function tryGuard(aspectGuards, tagName, aspect, aspectName, sink) {
572
+ const sinkGuards = aspectGuards[aspect];
573
+ if (sinkGuards) {
574
+ const guard = sinkGuards[aspectName];
575
+ if (guard) {
576
+ return guard(tagName, aspect, aspectName, sink);
577
+ }
578
+ }
579
+ }
580
+ const DOMPolicy = Object.freeze({
581
+ /**
582
+ * Creates a new DOM Policy object.
583
+ * @param options The options to use in creating the policy.
584
+ * @returns The newly created DOMPolicy.
585
+ */
586
+ create(options = {}) {
587
+ var _a, _b;
588
+ const trustedType = (_a = options.trustedType) !== null && _a !== void 0 ? _a : createTrustedType();
589
+ const guards = createDOMGuards((_b = options.guards) !== null && _b !== void 0 ? _b : {}, defaultDOMGuards);
590
+ return Object.freeze({
591
+ createHTML(value) {
592
+ return trustedType.createHTML(value);
593
+ },
594
+ protect(tagName, aspect, aspectName, sink) {
595
+ var _a;
596
+ // Check for element-specific guards.
597
+ const key = (tagName !== null && tagName !== void 0 ? tagName : "").toLowerCase();
598
+ const elementGuards = guards.elements[key];
599
+ if (elementGuards) {
600
+ const guard = tryGuard(elementGuards, tagName, aspect, aspectName, sink);
601
+ if (guard) {
602
+ return guard;
603
+ }
604
+ }
605
+ // Check for guards applicable to all nodes.
606
+ return ((_a = tryGuard(guards.aspects, tagName, aspect, aspectName, sink)) !== null && _a !== void 0 ? _a : sink);
607
+ },
608
+ });
609
+ },
610
+ });
159
611
 
160
612
  /**
161
613
  * The default UpdateQueue.
162
614
  * @public
163
615
  */
164
- const Updates = FAST.getById(1 /* KernelServiceId.updateQueue */, () => {
616
+ const Updates = FAST.getById(KernelServiceId.updateQueue, () => {
165
617
  const tasks = [];
166
618
  const pendingErrors = [];
167
619
  const rAF = globalThis.requestAnimationFrame;
@@ -388,15 +840,29 @@ class PropertyChangeNotifier {
388
840
  }
389
841
  }
390
842
 
843
+ /**
844
+ * Describes how the source's lifetime relates to its controller's lifetime.
845
+ * @public
846
+ */
847
+ const SourceLifetime = Object.freeze({
848
+ /**
849
+ * The source to controller lifetime relationship is unknown.
850
+ */
851
+ unknown: void 0,
852
+ /**
853
+ * The source and controller lifetimes are coupled to one another.
854
+ * They can/will be GC'd together.
855
+ */
856
+ coupled: 1,
857
+ });
391
858
  /**
392
859
  * Common Observable APIs.
393
860
  * @public
394
861
  */
395
- const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
862
+ const Observable = FAST.getById(KernelServiceId.observable, () => {
396
863
  const queueUpdate = Updates.enqueue;
397
864
  const volatileRegex = /(:|&&|\|\||if)/;
398
865
  const notifierLookup = new WeakMap();
399
- const accessorLookup = new WeakMap();
400
866
  let watcher = void 0;
401
867
  let createArrayObserver = (array) => {
402
868
  throw FAST.error(1101 /* Message.needsArrayObservation */);
@@ -411,19 +877,7 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
411
877
  }
412
878
  return found;
413
879
  }
414
- function getAccessors(target) {
415
- let accessors = accessorLookup.get(target);
416
- if (accessors === void 0) {
417
- let currentTarget = Reflect.getPrototypeOf(target);
418
- while (accessors === void 0 && currentTarget !== null) {
419
- accessors = accessorLookup.get(currentTarget);
420
- currentTarget = Reflect.getPrototypeOf(currentTarget);
421
- }
422
- accessors = accessors === void 0 ? [] : accessors.slice(0);
423
- accessorLookup.set(target, accessors);
424
- }
425
- return accessors;
426
- }
880
+ const getAccessors = createMetadataLocator();
427
881
  class DefaultObservableAccessor {
428
882
  constructor(name) {
429
883
  this.name = name;
@@ -449,10 +903,10 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
449
903
  }
450
904
  }
451
905
  }
452
- class BindingObserverImplementation extends SubscriberSet {
453
- constructor(binding, initialSubscriber, isVolatileBinding = false) {
454
- super(binding, initialSubscriber);
455
- this.binding = binding;
906
+ class ExpressionNotifierImplementation extends SubscriberSet {
907
+ constructor(expression, initialSubscriber, isVolatileBinding = false) {
908
+ super(expression, initialSubscriber);
909
+ this.expression = expression;
456
910
  this.isVolatileBinding = isVolatileBinding;
457
911
  this.needsRefresh = true;
458
912
  this.needsQueue = true;
@@ -463,21 +917,50 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
463
917
  this.propertyName = void 0;
464
918
  this.notifier = void 0;
465
919
  this.next = void 0;
920
+ /**
921
+ * Opts out of JSON stringification.
922
+ */
923
+ this.toJSON = noop;
466
924
  }
467
925
  setMode(isAsync) {
468
926
  this.isAsync = this.needsQueue = isAsync;
469
927
  }
470
- observe(source, context) {
471
- if (this.needsRefresh && this.last !== null) {
472
- this.dispose();
928
+ bind(controller) {
929
+ this.controller = controller;
930
+ const value = this.observe(controller.source, controller.context);
931
+ if (!controller.isBound && this.requiresUnbind(controller)) {
932
+ controller.onUnbind(this);
933
+ }
934
+ return value;
935
+ }
936
+ requiresUnbind(controller) {
937
+ return (controller.sourceLifetime !== SourceLifetime.coupled ||
938
+ this.first !== this.last ||
939
+ this.first.propertySource !== controller.source);
940
+ }
941
+ unbind(controller) {
942
+ this.dispose();
943
+ }
944
+ observe(source, context) {
945
+ if (this.needsRefresh && this.last !== null) {
946
+ this.dispose();
473
947
  }
474
948
  const previousWatcher = watcher;
475
949
  watcher = this.needsRefresh ? this : void 0;
476
950
  this.needsRefresh = this.isVolatileBinding;
477
- const result = this.binding(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
478
- watcher = previousWatcher;
951
+ let result;
952
+ try {
953
+ result = this.expression(source, context);
954
+ }
955
+ finally {
956
+ watcher = previousWatcher;
957
+ }
479
958
  return result;
480
959
  }
960
+ // backwards compat with v1 kernel
961
+ disconnect() {
962
+ this.dispose();
963
+ }
481
964
  dispose() {
482
965
  if (this.last !== null) {
483
966
  let current = this.first;
@@ -604,22 +1087,22 @@ const Observable = FAST.getById(2 /* KernelServiceId.observable */, () => {
604
1087
  */
605
1088
  getAccessors,
606
1089
  /**
607
- * Creates a {@link BindingObserver} that can watch the
608
- * provided {@link Binding} for changes.
609
- * @param binding - The binding to observe.
1090
+ * Creates a {@link ExpressionNotifier} that can watch the
1091
+ * provided {@link Expression} for changes.
1092
+ * @param expression - The binding to observe.
610
1093
  * @param initialSubscriber - An initial subscriber to changes in the binding value.
611
1094
  * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
612
1095
  */
613
- binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) {
614
- return new BindingObserverImplementation(binding, initialSubscriber, isVolatileBinding);
1096
+ binding(expression, initialSubscriber, isVolatileBinding = this.isVolatileBinding(expression)) {
1097
+ return new ExpressionNotifierImplementation(expression, initialSubscriber, isVolatileBinding);
615
1098
  },
616
1099
  /**
617
1100
  * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
618
1101
  * on every evaluation of the value.
619
- * @param binding - The binding to inspect.
1102
+ * @param expression - The binding to inspect.
620
1103
  */
621
- isVolatileBinding(binding) {
622
- return volatileRegex.test(binding.toString());
1104
+ isVolatileBinding(expression) {
1105
+ return volatileRegex.test(expression.toString());
623
1106
  },
624
1107
  });
625
1108
  });
@@ -647,7 +1130,7 @@ function volatile(target, name, descriptor) {
647
1130
  },
648
1131
  });
649
1132
  }
650
- const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
1133
+ const contextEvent = FAST.getById(KernelServiceId.contextEvent, () => {
651
1134
  let current = null;
652
1135
  return {
653
1136
  get() {
@@ -662,123 +1145,38 @@ const contextEvent = FAST.getById(3 /* KernelServiceId.contextEvent */, () => {
662
1145
  * Provides additional contextual information available to behaviors and expressions.
663
1146
  * @public
664
1147
  */
665
- class ExecutionContext {
666
- constructor(parentSource = null, parentContext = null) {
667
- /**
668
- * The index of the current item within a repeat context.
669
- */
670
- this.index = 0;
671
- /**
672
- * The length of the current collection within a repeat context.
673
- */
674
- this.length = 0;
675
- this.parent = parentSource;
676
- this.parentContext = parentContext;
677
- }
1148
+ const ExecutionContext = Object.freeze({
678
1149
  /**
679
- * The current event within an event handler.
1150
+ * A default execution context.
680
1151
  */
681
- get event() {
682
- return contextEvent.get();
683
- }
684
- /**
685
- * Indicates whether the current item within a repeat context
686
- * has an even index.
687
- */
688
- get isEven() {
689
- return this.index % 2 === 0;
690
- }
691
- /**
692
- * Indicates whether the current item within a repeat context
693
- * has an odd index.
694
- */
695
- get isOdd() {
696
- return this.index % 2 !== 0;
697
- }
698
- /**
699
- * Indicates whether the current item within a repeat context
700
- * is the first item in the collection.
701
- */
702
- get isFirst() {
703
- return this.index === 0;
704
- }
705
- /**
706
- * Indicates whether the current item within a repeat context
707
- * is somewhere in the middle of the collection.
708
- */
709
- get isInMiddle() {
710
- return !this.isFirst && !this.isLast;
711
- }
712
- /**
713
- * Indicates whether the current item within a repeat context
714
- * is the last item in the collection.
715
- */
716
- get isLast() {
717
- return this.index === this.length - 1;
718
- }
719
- /**
720
- * Returns the typed event detail of a custom event.
721
- */
722
- eventDetail() {
723
- return this.event.detail;
724
- }
725
- /**
726
- * Returns the typed event target of the event.
727
- */
728
- eventTarget() {
729
- return this.event.target;
730
- }
731
- /**
732
- * Updates the position/size on a context associated with a list item.
733
- * @param index - The new index of the item.
734
- * @param length - The new length of the list.
735
- */
736
- updatePosition(index, length) {
737
- this.index = index;
738
- this.length = length;
739
- }
740
- /**
741
- * Creates a new execution context descendent from the current context.
742
- * @param source - The source for the context if different than the parent.
743
- * @returns A child execution context.
744
- */
745
- createChildContext(parentSource) {
746
- return new ExecutionContext(parentSource, this);
747
- }
1152
+ default: {
1153
+ index: 0,
1154
+ length: 0,
1155
+ get event() {
1156
+ return ExecutionContext.getEvent();
1157
+ },
1158
+ eventDetail() {
1159
+ return this.event.detail;
1160
+ },
1161
+ eventTarget() {
1162
+ return this.event.target;
1163
+ },
1164
+ },
748
1165
  /**
749
- * Creates a new execution context descent suitable for use in list rendering.
750
- * @param item - The list item to serve as the source.
751
- * @param index - The index of the item in the list.
752
- * @param length - The length of the list.
1166
+ * Gets the current event.
1167
+ * @returns An event object.
753
1168
  */
754
- createItemContext(index, length) {
755
- const childContext = Object.create(this);
756
- childContext.index = index;
757
- childContext.length = length;
758
- return childContext;
759
- }
1169
+ getEvent() {
1170
+ return contextEvent.get();
1171
+ },
760
1172
  /**
761
- * Sets the event for the current execution context.
762
- * @param event - The event to set.
763
- * @internal
1173
+ * Sets the current event.
1174
+ * @param event - An event object.
764
1175
  */
765
- static setEvent(event) {
1176
+ setEvent(event) {
766
1177
  contextEvent.set(event);
767
- }
768
- /**
769
- * Creates a new root execution context.
770
- * @returns A new execution context.
771
- */
772
- static create() {
773
- return new ExecutionContext();
774
- }
775
- }
776
- /**
777
- * The default execution context.
778
- */
779
- ExecutionContext.default = new ExecutionContext();
780
- Observable.defineProperty(ExecutionContext.prototype, "index");
781
- Observable.defineProperty(ExecutionContext.prototype, "length");
1178
+ },
1179
+ });
782
1180
 
783
1181
  /**
784
1182
  * A splice map is a representation of how a previous array of items
@@ -845,10 +1243,311 @@ const SpliceStrategySupport = Object.freeze({
845
1243
  const reset = new Splice(0, emptyArray, 0);
846
1244
  reset.reset = true;
847
1245
  const resetSplices = [reset];
1246
+ // Note: This function is *based* on the computation of the Levenshtein
1247
+ // "edit" distance. The one change is that "updates" are treated as two
1248
+ // edits - not one. With Array splices, an update is really a delete
1249
+ // followed by an add. By retaining this, we optimize for "keeping" the
1250
+ // maximum array items in the original array. For example:
1251
+ //
1252
+ // 'xxxx123' to '123yyyy'
1253
+ //
1254
+ // With 1-edit updates, the shortest path would be just to update all seven
1255
+ // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
1256
+ // leaves the substring '123' intact.
1257
+ function calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd) {
1258
+ // "Deletion" columns
1259
+ const rowCount = oldEnd - oldStart + 1;
1260
+ const columnCount = currentEnd - currentStart + 1;
1261
+ const distances = new Array(rowCount);
1262
+ let north;
1263
+ let west;
1264
+ // "Addition" rows. Initialize null column.
1265
+ for (let i = 0; i < rowCount; ++i) {
1266
+ distances[i] = new Array(columnCount);
1267
+ distances[i][0] = i;
1268
+ }
1269
+ // Initialize null row
1270
+ for (let j = 0; j < columnCount; ++j) {
1271
+ distances[0][j] = j;
1272
+ }
1273
+ for (let i = 1; i < rowCount; ++i) {
1274
+ for (let j = 1; j < columnCount; ++j) {
1275
+ if (current[currentStart + j - 1] === old[oldStart + i - 1]) {
1276
+ distances[i][j] = distances[i - 1][j - 1];
1277
+ }
1278
+ else {
1279
+ north = distances[i - 1][j] + 1;
1280
+ west = distances[i][j - 1] + 1;
1281
+ distances[i][j] = north < west ? north : west;
1282
+ }
1283
+ }
1284
+ }
1285
+ return distances;
1286
+ }
1287
+ // This starts at the final weight, and walks "backward" by finding
1288
+ // the minimum previous weight recursively until the origin of the weight
1289
+ // matrix.
1290
+ function spliceOperationsFromEditDistances(distances) {
1291
+ let i = distances.length - 1;
1292
+ let j = distances[0].length - 1;
1293
+ let current = distances[i][j];
1294
+ const edits = [];
1295
+ while (i > 0 || j > 0) {
1296
+ if (i === 0) {
1297
+ edits.push(2 /* Edit.add */);
1298
+ j--;
1299
+ continue;
1300
+ }
1301
+ if (j === 0) {
1302
+ edits.push(3 /* Edit.delete */);
1303
+ i--;
1304
+ continue;
1305
+ }
1306
+ const northWest = distances[i - 1][j - 1];
1307
+ const west = distances[i - 1][j];
1308
+ const north = distances[i][j - 1];
1309
+ let min;
1310
+ if (west < north) {
1311
+ min = west < northWest ? west : northWest;
1312
+ }
1313
+ else {
1314
+ min = north < northWest ? north : northWest;
1315
+ }
1316
+ if (min === northWest) {
1317
+ if (northWest === current) {
1318
+ edits.push(0 /* Edit.leave */);
1319
+ }
1320
+ else {
1321
+ edits.push(1 /* Edit.update */);
1322
+ current = northWest;
1323
+ }
1324
+ i--;
1325
+ j--;
1326
+ }
1327
+ else if (min === west) {
1328
+ edits.push(3 /* Edit.delete */);
1329
+ i--;
1330
+ current = west;
1331
+ }
1332
+ else {
1333
+ edits.push(2 /* Edit.add */);
1334
+ j--;
1335
+ current = north;
1336
+ }
1337
+ }
1338
+ return edits.reverse();
1339
+ }
1340
+ function sharedPrefix(current, old, searchLength) {
1341
+ for (let i = 0; i < searchLength; ++i) {
1342
+ if (current[i] !== old[i]) {
1343
+ return i;
1344
+ }
1345
+ }
1346
+ return searchLength;
1347
+ }
1348
+ function sharedSuffix(current, old, searchLength) {
1349
+ let index1 = current.length;
1350
+ let index2 = old.length;
1351
+ let count = 0;
1352
+ while (count < searchLength && current[--index1] === old[--index2]) {
1353
+ count++;
1354
+ }
1355
+ return count;
1356
+ }
1357
+ function intersect(start1, end1, start2, end2) {
1358
+ // Disjoint
1359
+ if (end1 < start2 || end2 < start1) {
1360
+ return -1;
1361
+ }
1362
+ // Adjacent
1363
+ if (end1 === start2 || end2 === start1) {
1364
+ return 0;
1365
+ }
1366
+ // Non-zero intersect, span1 first
1367
+ if (start1 < start2) {
1368
+ if (end1 < end2) {
1369
+ return end1 - start2; // Overlap
1370
+ }
1371
+ return end2 - start2; // Contained
1372
+ }
1373
+ // Non-zero intersect, span2 first
1374
+ if (end2 < end1) {
1375
+ return end2 - start1; // Overlap
1376
+ }
1377
+ return end1 - start1; // Contained
1378
+ }
1379
+ /**
1380
+ * @remarks
1381
+ * Lacking individual splice mutation information, the minimal set of
1382
+ * splices can be synthesized given the previous state and final state of an
1383
+ * array. The basic approach is to calculate the edit distance matrix and
1384
+ * choose the shortest path through it.
1385
+ *
1386
+ * Complexity: O(l * p)
1387
+ * l: The length of the current array
1388
+ * p: The length of the old array
1389
+ */
1390
+ function calc(current, currentStart, currentEnd, old, oldStart, oldEnd) {
1391
+ let prefixCount = 0;
1392
+ let suffixCount = 0;
1393
+ const minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
1394
+ if (currentStart === 0 && oldStart === 0) {
1395
+ prefixCount = sharedPrefix(current, old, minLength);
1396
+ }
1397
+ if (currentEnd === current.length && oldEnd === old.length) {
1398
+ suffixCount = sharedSuffix(current, old, minLength - prefixCount);
1399
+ }
1400
+ currentStart += prefixCount;
1401
+ oldStart += prefixCount;
1402
+ currentEnd -= suffixCount;
1403
+ oldEnd -= suffixCount;
1404
+ if (currentEnd - currentStart === 0 && oldEnd - oldStart === 0) {
1405
+ return emptyArray;
1406
+ }
1407
+ if (currentStart === currentEnd) {
1408
+ const splice = new Splice(currentStart, [], 0);
1409
+ while (oldStart < oldEnd) {
1410
+ splice.removed.push(old[oldStart++]);
1411
+ }
1412
+ return [splice];
1413
+ }
1414
+ else if (oldStart === oldEnd) {
1415
+ return [new Splice(currentStart, [], currentEnd - currentStart)];
1416
+ }
1417
+ const ops = spliceOperationsFromEditDistances(calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
1418
+ const splices = [];
1419
+ let splice = void 0;
1420
+ let index = currentStart;
1421
+ let oldIndex = oldStart;
1422
+ for (let i = 0; i < ops.length; ++i) {
1423
+ switch (ops[i]) {
1424
+ case 0 /* Edit.leave */:
1425
+ if (splice !== void 0) {
1426
+ splices.push(splice);
1427
+ splice = void 0;
1428
+ }
1429
+ index++;
1430
+ oldIndex++;
1431
+ break;
1432
+ case 1 /* Edit.update */:
1433
+ if (splice === void 0) {
1434
+ splice = new Splice(index, [], 0);
1435
+ }
1436
+ splice.addedCount++;
1437
+ index++;
1438
+ splice.removed.push(old[oldIndex]);
1439
+ oldIndex++;
1440
+ break;
1441
+ case 2 /* Edit.add */:
1442
+ if (splice === void 0) {
1443
+ splice = new Splice(index, [], 0);
1444
+ }
1445
+ splice.addedCount++;
1446
+ index++;
1447
+ break;
1448
+ case 3 /* Edit.delete */:
1449
+ if (splice === void 0) {
1450
+ splice = new Splice(index, [], 0);
1451
+ }
1452
+ splice.removed.push(old[oldIndex]);
1453
+ oldIndex++;
1454
+ break;
1455
+ // no default
1456
+ }
1457
+ }
1458
+ if (splice !== void 0) {
1459
+ splices.push(splice);
1460
+ }
1461
+ return splices;
1462
+ }
1463
+ function merge(splice, splices) {
1464
+ let inserted = false;
1465
+ let insertionOffset = 0;
1466
+ for (let i = 0; i < splices.length; i++) {
1467
+ const current = splices[i];
1468
+ current.index += insertionOffset;
1469
+ if (inserted) {
1470
+ continue;
1471
+ }
1472
+ const intersectCount = intersect(splice.index, splice.index + splice.removed.length, current.index, current.index + current.addedCount);
1473
+ if (intersectCount >= 0) {
1474
+ // Merge the two splices
1475
+ splices.splice(i, 1);
1476
+ i--;
1477
+ insertionOffset -= current.addedCount - current.removed.length;
1478
+ splice.addedCount += current.addedCount - intersectCount;
1479
+ const deleteCount = splice.removed.length + current.removed.length - intersectCount;
1480
+ if (!splice.addedCount && !deleteCount) {
1481
+ // merged splice is a noop. discard.
1482
+ inserted = true;
1483
+ }
1484
+ else {
1485
+ let currentRemoved = current.removed;
1486
+ if (splice.index < current.index) {
1487
+ // some prefix of splice.removed is prepended to current.removed.
1488
+ const prepend = splice.removed.slice(0, current.index - splice.index);
1489
+ prepend.push(...currentRemoved);
1490
+ currentRemoved = prepend;
1491
+ }
1492
+ if (splice.index + splice.removed.length >
1493
+ current.index + current.addedCount) {
1494
+ // some suffix of splice.removed is appended to current.removed.
1495
+ const append = splice.removed.slice(current.index + current.addedCount - splice.index);
1496
+ currentRemoved.push(...append);
1497
+ }
1498
+ splice.removed = currentRemoved;
1499
+ if (current.index < splice.index) {
1500
+ splice.index = current.index;
1501
+ }
1502
+ }
1503
+ }
1504
+ else if (splice.index < current.index) {
1505
+ // Insert splice here.
1506
+ inserted = true;
1507
+ splices.splice(i, 0, splice);
1508
+ i++;
1509
+ const offset = splice.addedCount - splice.removed.length;
1510
+ current.index += offset;
1511
+ insertionOffset += offset;
1512
+ }
1513
+ }
1514
+ if (!inserted) {
1515
+ splices.push(splice);
1516
+ }
1517
+ }
1518
+ function project(array, changes) {
1519
+ let splices = [];
1520
+ const initialSplices = [];
1521
+ for (let i = 0, ii = changes.length; i < ii; i++) {
1522
+ merge(changes[i], initialSplices);
1523
+ }
1524
+ for (let i = 0, ii = initialSplices.length; i < ii; ++i) {
1525
+ const splice = initialSplices[i];
1526
+ if (splice.addedCount === 1 && splice.removed.length === 1) {
1527
+ if (splice.removed[0] !== array[splice.index]) {
1528
+ splices.push(splice);
1529
+ }
1530
+ continue;
1531
+ }
1532
+ splices = splices.concat(calc(array, splice.index, splice.index + splice.addedCount, splice.removed, 0, splice.removed.length));
1533
+ }
1534
+ return splices;
1535
+ }
1536
+ /**
1537
+ * A SpliceStrategy that attempts to merge all splices into the minimal set of
1538
+ * splices needed to represent the change from the old array to the new array.
1539
+ * @public
1540
+ */
848
1541
  let defaultSpliceStrategy = Object.freeze({
849
- support: SpliceStrategySupport.splice,
1542
+ support: SpliceStrategySupport.optimized,
850
1543
  normalize(previous, current, changes) {
851
- return previous === void 0 ? changes !== null && changes !== void 0 ? changes : emptyArray : resetSplices;
1544
+ if (previous === void 0) {
1545
+ if (changes === void 0) {
1546
+ return emptyArray;
1547
+ }
1548
+ return changes.length > 1 ? project(current, changes) : changes;
1549
+ }
1550
+ return resetSplices;
852
1551
  },
853
1552
  pop(array, observer, pop, args) {
854
1553
  const notEmpty = array.length > 0;
@@ -1046,7 +1745,6 @@ function lengthOf(array) {
1046
1745
  return array.length;
1047
1746
  }
1048
1747
 
1049
- const styleSheetCache = new Map();
1050
1748
  let DefaultStyleStrategy;
1051
1749
  function reduceStyles(styles) {
1052
1750
  return styles
@@ -1117,42 +1815,26 @@ class ElementStyles {
1117
1815
  static setDefaultStrategy(Strategy) {
1118
1816
  DefaultStyleStrategy = Strategy;
1119
1817
  }
1818
+ /**
1819
+ * Normalizes a set of composable style options.
1820
+ * @param styles - The style options to normalize.
1821
+ * @returns A singular ElementStyles instance or undefined.
1822
+ */
1823
+ static normalize(styles) {
1824
+ return styles === void 0
1825
+ ? void 0
1826
+ : Array.isArray(styles)
1827
+ ? new ElementStyles(styles)
1828
+ : styles instanceof ElementStyles
1829
+ ? styles
1830
+ : new ElementStyles([styles]);
1831
+ }
1120
1832
  }
1121
1833
  /**
1122
1834
  * Indicates whether the DOM supports the adoptedStyleSheets feature.
1123
1835
  */
1124
1836
  ElementStyles.supportsAdoptedStyleSheets = Array.isArray(document.adoptedStyleSheets) &&
1125
1837
  "replace" in CSSStyleSheet.prototype;
1126
- /**
1127
- * https://wicg.github.io/construct-stylesheets/
1128
- * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
1129
- *
1130
- * @internal
1131
- */
1132
- class AdoptedStyleSheetsStrategy {
1133
- constructor(styles) {
1134
- this.sheets = styles.map((x) => {
1135
- if (x instanceof CSSStyleSheet) {
1136
- return x;
1137
- }
1138
- let sheet = styleSheetCache.get(x);
1139
- if (sheet === void 0) {
1140
- sheet = new CSSStyleSheet();
1141
- sheet.replaceSync(x);
1142
- styleSheetCache.set(x, sheet);
1143
- }
1144
- return sheet;
1145
- });
1146
- }
1147
- addStylesTo(target) {
1148
- target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...this.sheets];
1149
- }
1150
- removeStylesFrom(target) {
1151
- const sheets = this.sheets;
1152
- target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
1153
- }
1154
- }
1155
- ElementStyles.setDefaultStrategy(FAST.getById(5 /* KernelServiceId.styleSheetStrategy */, () => AdoptedStyleSheetsStrategy));
1156
1838
 
1157
1839
  const registry$1 = createTypeRegistry();
1158
1840
  /**
@@ -1261,11 +1943,11 @@ class CSSPartial {
1261
1943
  }
1262
1944
  return this.css;
1263
1945
  }
1264
- bind(el) {
1265
- el.$fastController.addStyles(this.styles);
1946
+ addedCallback(controller) {
1947
+ controller.addStyles(this.styles);
1266
1948
  }
1267
- unbind(el) {
1268
- el.$fastController.removeStyles(this.styles);
1949
+ removedCallback(controller) {
1950
+ controller.removeStyles(this.styles);
1269
1951
  }
1270
1952
  }
1271
1953
  CSSDirective.define(CSSPartial);
@@ -1273,68 +1955,14 @@ css.partial = (strings, ...values) => {
1273
1955
  const { styles, behaviors } = collectStyles(strings, values);
1274
1956
  return new CSSPartial(styles, behaviors);
1275
1957
  };
1276
- /**
1277
- * @deprecated Use css.partial instead.
1278
- * @public
1279
- */
1280
- const cssPartial = css.partial;
1281
-
1282
- /**
1283
- * Common DOM APIs.
1284
- * @public
1285
- */
1286
- const DOM = Object.freeze({
1287
- /**
1288
- * @deprecated
1289
- * Use Updates.enqueue().
1290
- */
1291
- queueUpdate: Updates.enqueue,
1292
- /**
1293
- * @deprecated
1294
- * Use Updates.next()
1295
- */
1296
- nextUpdate: Updates.next,
1297
- /**
1298
- * @deprecated
1299
- * Use Updates.process()
1300
- */
1301
- processUpdates: Updates.process,
1302
- /**
1303
- * Sets an attribute value on an element.
1304
- * @param element - The element to set the attribute value on.
1305
- * @param attributeName - The attribute name to set.
1306
- * @param value - The value of the attribute to set.
1307
- * @remarks
1308
- * If the value is `null` or `undefined`, the attribute is removed, otherwise
1309
- * it is set to the provided value using the standard `setAttribute` API.
1310
- */
1311
- setAttribute(element, attributeName, value) {
1312
- value === null || value === undefined
1313
- ? element.removeAttribute(attributeName)
1314
- : element.setAttribute(attributeName, value);
1315
- },
1316
- /**
1317
- * Sets a boolean attribute value.
1318
- * @param element - The element to set the boolean attribute value on.
1319
- * @param attributeName - The attribute name to set.
1320
- * @param value - The value of the attribute to set.
1321
- * @remarks
1322
- * If the value is true, the attribute is added; otherwise it is removed.
1323
- */
1324
- setBooleanAttribute(element, attributeName, value) {
1325
- value
1326
- ? element.setAttribute(attributeName, "")
1327
- : element.removeAttribute(attributeName);
1328
- },
1329
- });
1330
1958
 
1331
1959
  const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
1332
1960
  const interpolationStart = `${marker}{`;
1333
1961
  const interpolationEnd = `}${marker}`;
1334
1962
  const interpolationEndLength = interpolationEnd.length;
1335
- let id = 0;
1963
+ let id$1 = 0;
1336
1964
  /** @internal */
1337
- const nextId = () => `${marker}-${++id}`;
1965
+ const nextId = () => `${marker}-${++id$1}`;
1338
1966
  /**
1339
1967
  * Common APIs related to markup generation.
1340
1968
  * @public
@@ -1431,97 +2059,71 @@ const HTMLDirective = Object.freeze({
1431
2059
  registry.register(options);
1432
2060
  return type;
1433
2061
  },
1434
- });
1435
- /**
1436
- * Decorator: Defines an HTMLDirective.
1437
- * @param options - Provides options that specify the directive's application.
1438
- * @public
1439
- */
1440
- function htmlDirective(options) {
1441
- /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
1442
- return function (type) {
1443
- HTMLDirective.define(type, options);
1444
- };
1445
- }
1446
- /**
1447
- * The type of HTML aspect to target.
1448
- * @public
1449
- */
1450
- const Aspect = Object.freeze({
1451
- /**
1452
- * Not aspected.
1453
- */
1454
- none: 0,
1455
- /**
1456
- * An attribute.
1457
- */
1458
- attribute: 1,
1459
- /**
1460
- * A boolean attribute.
1461
- */
1462
- booleanAttribute: 2,
1463
- /**
1464
- * A property.
1465
- */
1466
- property: 3,
1467
- /**
1468
- * Content
1469
- */
1470
- content: 4,
1471
- /**
1472
- * A token list.
1473
- */
1474
- tokenList: 5,
1475
- /**
1476
- * An event.
1477
- */
1478
- event: 6,
1479
2062
  /**
1480
2063
  *
1481
2064
  * @param directive - The directive to assign the aspect to.
1482
2065
  * @param value - The value to base the aspect determination on.
2066
+ * @remarks
2067
+ * If a falsy value is provided, then the content aspect will be assigned.
1483
2068
  */
1484
- assign(directive, value) {
1485
- directive.sourceAspect = value;
2069
+ assignAspect(directive, value) {
1486
2070
  if (!value) {
2071
+ directive.aspectType = DOMAspect.content;
1487
2072
  return;
1488
2073
  }
2074
+ directive.sourceAspect = value;
1489
2075
  switch (value[0]) {
1490
2076
  case ":":
1491
2077
  directive.targetAspect = value.substring(1);
1492
- switch (directive.targetAspect) {
1493
- case "innerHTML":
1494
- directive.aspectType = Aspect.property;
1495
- break;
1496
- case "classList":
1497
- directive.aspectType = Aspect.tokenList;
1498
- break;
1499
- default:
1500
- directive.aspectType = Aspect.property;
1501
- break;
1502
- }
2078
+ directive.aspectType =
2079
+ directive.targetAspect === "classList"
2080
+ ? DOMAspect.tokenList
2081
+ : DOMAspect.property;
1503
2082
  break;
1504
2083
  case "?":
1505
2084
  directive.targetAspect = value.substring(1);
1506
- directive.aspectType = Aspect.booleanAttribute;
2085
+ directive.aspectType = DOMAspect.booleanAttribute;
1507
2086
  break;
1508
2087
  case "@":
1509
2088
  directive.targetAspect = value.substring(1);
1510
- directive.aspectType = Aspect.event;
2089
+ directive.aspectType = DOMAspect.event;
1511
2090
  break;
1512
2091
  default:
1513
- if (value === "class") {
1514
- directive.targetAspect = "className";
1515
- directive.aspectType = Aspect.property;
1516
- }
1517
- else {
1518
- directive.targetAspect = value;
1519
- directive.aspectType = Aspect.attribute;
1520
- }
2092
+ directive.targetAspect = value;
2093
+ directive.aspectType = DOMAspect.attribute;
1521
2094
  break;
1522
2095
  }
1523
2096
  },
1524
2097
  });
2098
+ /**
2099
+ * Decorator: Defines an HTMLDirective.
2100
+ * @param options - Provides options that specify the directive's application.
2101
+ * @public
2102
+ */
2103
+ function htmlDirective(options) {
2104
+ /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
2105
+ return function (type) {
2106
+ HTMLDirective.define(type, options);
2107
+ };
2108
+ }
2109
+ /**
2110
+ * Captures a binding expression along with related information and capabilities.
2111
+ *
2112
+ * @public
2113
+ */
2114
+ class Binding {
2115
+ /**
2116
+ * Creates a binding.
2117
+ * @param evaluate - Evaluates the binding.
2118
+ * @param policy - The security policy to associate with this binding.
2119
+ * @param isVolatile - Indicates whether the binding is volatile.
2120
+ */
2121
+ constructor(evaluate, policy, isVolatile = false) {
2122
+ this.evaluate = evaluate;
2123
+ this.policy = policy;
2124
+ this.isVolatile = isVolatile;
2125
+ }
2126
+ }
1525
2127
  /**
1526
2128
  * A base class used for attribute directives that don't need internal state.
1527
2129
  * @public
@@ -1533,13 +2135,11 @@ class StatelessAttachedAttributeDirective {
1533
2135
  */
1534
2136
  constructor(options) {
1535
2137
  this.options = options;
1536
- }
1537
- /**
1538
- * Creates a behavior.
1539
- * @param targets - The targets available for behaviors to be attached to.
1540
- */
1541
- createBehavior(targets) {
1542
- return this;
2138
+ /**
2139
+ * Opts out of JSON stringification.
2140
+ * @internal
2141
+ */
2142
+ this.toJSON = noop;
1543
2143
  }
1544
2144
  /**
1545
2145
  * Creates a placeholder string based on the directive's index within the template.
@@ -1550,118 +2150,43 @@ class StatelessAttachedAttributeDirective {
1550
2150
  createHTML(add) {
1551
2151
  return Markup.attribute(add(this));
1552
2152
  }
1553
- }
1554
-
1555
- const createInnerHTMLBinding = globalThis.TrustedHTML
1556
- ? (binding) => (s, c) => {
1557
- const value = binding(s, c);
1558
- if (value instanceof TrustedHTML) {
1559
- return value;
1560
- }
1561
- throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
1562
- }
1563
- : (binding) => binding;
1564
- /**
1565
- * Describes how aspects of an HTML element will be affected by bindings.
1566
- * @public
1567
- */
1568
- const BindingMode = Object.freeze({
1569
- /**
1570
- * Creates a binding mode based on the supplied behavior types.
1571
- * @param UpdateType - The base behavior type used to update aspects.
1572
- * @param EventType - The base behavior type used to respond to events.
1573
- * @returns A new binding mode.
1574
- */
1575
- define(UpdateType, EventType = EventBinding) {
1576
- return Object.freeze({
1577
- [1]: d => new UpdateType(d, DOM.setAttribute),
1578
- [2]: d => new UpdateType(d, DOM.setBooleanAttribute),
1579
- [3]: d => new UpdateType(d, (t, a, v) => (t[a] = v)),
1580
- [4]: d => new (createContentBinding(UpdateType))(d, updateContentTarget),
1581
- [5]: d => new UpdateType(d, updateTokenListTarget),
1582
- [6]: d => new EventType(d),
1583
- });
1584
- },
1585
- });
1586
- /**
1587
- * Describes the configuration for a binding expression.
1588
- * @public
1589
- */
1590
- const BindingConfig = Object.freeze({
1591
- /**
1592
- * Creates a binding configuration based on the provided mode and options.
1593
- * @param mode - The mode to use for the configuration.
1594
- * @param defaultOptions - The default options to use for the configuration.
1595
- * @returns A new binding configuration.
1596
- */
1597
- define(mode, defaultOptions) {
1598
- const config = (options) => {
1599
- return {
1600
- mode: config.mode,
1601
- options: Object.assign({}, defaultOptions, options),
1602
- };
1603
- };
1604
- config.options = defaultOptions;
1605
- config.mode = mode;
1606
- return config;
1607
- },
1608
- });
1609
- /**
1610
- * A base binding behavior for DOM updates.
1611
- * @public
1612
- */
1613
- class UpdateBinding {
1614
- /**
1615
- * Creates an instance of UpdateBinding.
1616
- * @param directive - The directive that has the configuration for this behavior.
1617
- * @param updateTarget - The function used to update the target with the latest value.
1618
- */
1619
- constructor(directive, updateTarget) {
1620
- this.directive = directive;
1621
- this.updateTarget = updateTarget;
1622
- }
1623
- /**
1624
- * Bind this behavior to the source.
1625
- * @param source - The source to bind to.
1626
- * @param context - The execution context that the binding is operating within.
1627
- * @param targets - The targets that behaviors in a view can attach to.
1628
- */
1629
- bind(source, context, targets) { }
1630
- /**
1631
- * Unbinds this behavior from the source.
1632
- * @param source - The source to unbind from.
1633
- * @param context - The execution context that the binding is operating within.
1634
- * @param targets - The targets that behaviors in a view can attach to.
1635
- */
1636
- unbind(source, context, targets) { }
1637
2153
  /**
1638
2154
  * Creates a behavior.
1639
2155
  * @param targets - The targets available for behaviors to be attached to.
1640
2156
  */
1641
- createBehavior(targets) {
2157
+ createBehavior() {
1642
2158
  return this;
1643
2159
  }
1644
2160
  }
1645
- function createContentBinding(Type) {
1646
- return class extends Type {
1647
- unbind(source, context, targets) {
1648
- super.unbind(source, context, targets);
1649
- const target = targets[this.directive.nodeId];
1650
- const view = target.$fastView;
1651
- if (view !== void 0 && view.isComposed) {
1652
- view.unbind();
1653
- view.needsBindOnly = true;
1654
- }
1655
- }
1656
- };
2161
+
2162
+ class OnChangeBinding extends Binding {
2163
+ createObserver(_, subscriber) {
2164
+ return Observable.binding(this.evaluate, subscriber, this.isVolatile);
2165
+ }
2166
+ }
2167
+ class OneTimeBinding extends Binding {
2168
+ constructor() {
2169
+ super(...arguments);
2170
+ /**
2171
+ * Opts out of JSON stringification.
2172
+ * @internal
2173
+ */
2174
+ this.toJSON = noop;
2175
+ }
2176
+ createObserver() {
2177
+ return this;
2178
+ }
2179
+ bind(controller) {
2180
+ return this.evaluate(controller.source, controller.context);
2181
+ }
1657
2182
  }
1658
- function updateContentTarget(target, aspect, value, source, context) {
2183
+ function updateContent(target, aspect, value, controller) {
1659
2184
  // If there's no actual value, then this equates to the
1660
2185
  // empty string for the purposes of content bindings.
1661
2186
  if (value === null || value === undefined) {
1662
2187
  value = "";
1663
2188
  }
1664
- // If the value has a "create" method, then it's a template-like.
2189
+ // If the value has a "create" method, then it's a ContentTemplate.
1665
2190
  if (value.create) {
1666
2191
  target.textContent = "";
1667
2192
  let view = target.$fastView;
@@ -1687,14 +2212,14 @@ function updateContentTarget(target, aspect, value, source, context) {
1687
2212
  // and that there's actually no need to compose it.
1688
2213
  if (!view.isComposed) {
1689
2214
  view.isComposed = true;
1690
- view.bind(source, context);
2215
+ view.bind(controller.source, controller.context);
1691
2216
  view.insertBefore(target);
1692
2217
  target.$fastView = view;
1693
2218
  target.$fastTemplate = value;
1694
2219
  }
1695
2220
  else if (view.needsBindOnly) {
1696
2221
  view.needsBindOnly = false;
1697
- view.bind(source, context);
2222
+ view.bind(controller.source, controller.context);
1698
2223
  }
1699
2224
  }
1700
2225
  else {
@@ -1702,199 +2227,58 @@ function updateContentTarget(target, aspect, value, source, context) {
1702
2227
  // If there is a view and it's currently composed into
1703
2228
  // the DOM, then we need to remove it.
1704
2229
  if (view !== void 0 && view.isComposed) {
1705
- view.isComposed = false;
1706
- view.remove();
1707
- if (view.needsBindOnly) {
1708
- view.needsBindOnly = false;
1709
- }
1710
- else {
1711
- view.unbind();
1712
- }
1713
- }
1714
- target.textContent = value;
1715
- }
1716
- }
1717
- function updateTokenListTarget(target, aspect, value) {
1718
- var _a;
1719
- const directive = this.directive;
1720
- const lookup = `${directive.id}-t`;
1721
- const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
1722
- const versions = state.v;
1723
- let currentVersion = state.c;
1724
- const tokenList = target[aspect];
1725
- // Add the classes, tracking the version at which they were added.
1726
- if (value !== null && value !== undefined && value.length) {
1727
- const names = value.split(/\s+/);
1728
- for (let i = 0, ii = names.length; i < ii; ++i) {
1729
- const currentName = names[i];
1730
- if (currentName === "") {
1731
- continue;
1732
- }
1733
- versions[currentName] = currentVersion;
1734
- tokenList.add(currentName);
1735
- }
1736
- }
1737
- state.v = currentVersion + 1;
1738
- // If this is the first call to add classes, there's no need to remove old ones.
1739
- if (currentVersion === 0) {
1740
- return;
1741
- }
1742
- // Remove classes from the previous version.
1743
- currentVersion -= 1;
1744
- for (const name in versions) {
1745
- if (versions[name] === currentVersion) {
1746
- tokenList.remove(name);
1747
- }
1748
- }
1749
- }
1750
- /**
1751
- * A binding behavior for one-time bindings.
1752
- * @public
1753
- */
1754
- class OneTimeBinding extends UpdateBinding {
1755
- /**
1756
- * Bind this behavior to the source.
1757
- * @param source - The source to bind to.
1758
- * @param context - The execution context that the binding is operating within.
1759
- * @param targets - The targets that behaviors in a view can attach to.
1760
- */
1761
- bind(source, context, targets) {
1762
- const directive = this.directive;
1763
- this.updateTarget(targets[directive.nodeId], directive.targetAspect, directive.binding(source, context), source, context);
1764
- }
1765
- }
1766
- /**
1767
- * A binding behavior for bindings that change.
1768
- * @public
1769
- */
1770
- class ChangeBinding extends UpdateBinding {
1771
- /**
1772
- * Creates an instance of ChangeBinding.
1773
- * @param directive - The directive that has the configuration for this behavior.
1774
- * @param updateTarget - The function used to update the target with the latest value.
1775
- */
1776
- constructor(directive, updateTarget) {
1777
- super(directive, updateTarget);
1778
- this.isBindingVolatile = Observable.isVolatileBinding(directive.binding);
1779
- this.observerProperty = `${directive.id}-o`;
1780
- }
1781
- /**
1782
- * Returns the binding observer used to update the node.
1783
- * @param target - The target node.
1784
- * @returns A BindingObserver.
1785
- */
1786
- getObserver(target) {
1787
- var _a;
1788
- return ((_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = Observable.binding(this.directive.binding, this, this.isBindingVolatile)));
1789
- }
1790
- /**
1791
- * Bind this behavior to the source.
1792
- * @param source - The source to bind to.
1793
- * @param context - The execution context that the binding is operating within.
1794
- * @param targets - The targets that behaviors in a view can attach to.
1795
- */
1796
- bind(source, context, targets) {
1797
- const directive = this.directive;
1798
- const target = targets[directive.nodeId];
1799
- const observer = this.getObserver(target);
1800
- observer.target = target;
1801
- observer.source = source;
1802
- observer.context = context;
1803
- this.updateTarget(target, directive.targetAspect, observer.observe(source, context), source, context);
1804
- }
1805
- /**
1806
- * Unbinds this behavior from the source.
1807
- * @param source - The source to unbind from.
1808
- * @param context - The execution context that the binding is operating within.
1809
- * @param targets - The targets that behaviors in a view can attach to.
1810
- */
1811
- unbind(source, context, targets) {
1812
- const target = targets[this.directive.nodeId];
1813
- const observer = this.getObserver(target);
1814
- observer.dispose();
1815
- observer.target = null;
1816
- observer.source = null;
1817
- observer.context = null;
1818
- }
1819
- /** @internal */
1820
- handleChange(binding, observer) {
1821
- const target = observer.target;
1822
- const source = observer.source;
1823
- const context = observer.context;
1824
- this.updateTarget(target, this.directive.targetAspect, observer.observe(source, context), source, context);
1825
- }
1826
- }
1827
- /**
1828
- * A binding behavior for handling events.
1829
- * @public
1830
- */
1831
- class EventBinding {
1832
- /**
1833
- * Creates an instance of EventBinding.
1834
- * @param directive - The directive that has the configuration for this behavior.
1835
- */
1836
- constructor(directive) {
1837
- this.directive = directive;
1838
- this.sourceProperty = `${directive.id}-s`;
1839
- this.contextProperty = `${directive.id}-c`;
1840
- }
1841
- /**
1842
- * Bind this behavior to the source.
1843
- * @param source - The source to bind to.
1844
- * @param context - The execution context that the binding is operating within.
1845
- * @param targets - The targets that behaviors in a view can attach to.
1846
- */
1847
- bind(source, context, targets) {
1848
- const directive = this.directive;
1849
- const target = targets[directive.nodeId];
1850
- target[this.sourceProperty] = source;
1851
- target[this.contextProperty] = context;
1852
- target.addEventListener(directive.targetAspect, this, directive.options);
2230
+ view.isComposed = false;
2231
+ view.remove();
2232
+ if (view.needsBindOnly) {
2233
+ view.needsBindOnly = false;
2234
+ }
2235
+ else {
2236
+ view.unbind();
2237
+ }
2238
+ }
2239
+ target.textContent = value;
1853
2240
  }
1854
- /**
1855
- * Unbinds this behavior from the source.
1856
- * @param source - The source to unbind from.
1857
- * @param context - The execution context that the binding is operating within.
1858
- * @param targets - The targets that behaviors in a view can attach to.
1859
- */
1860
- unbind(source, context, targets) {
1861
- const directive = this.directive;
1862
- const target = targets[directive.nodeId];
1863
- target[this.sourceProperty] = target[this.contextProperty] = null;
1864
- target.removeEventListener(directive.targetAspect, this, directive.options);
2241
+ }
2242
+ function updateTokenList(target, aspect, value) {
2243
+ var _a;
2244
+ const lookup = `${this.id}-t`;
2245
+ const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
2246
+ const classVersions = state.cv;
2247
+ let version = state.v;
2248
+ const tokenList = target[aspect];
2249
+ // Add the classes, tracking the version at which they were added.
2250
+ if (value !== null && value !== undefined && value.length) {
2251
+ const names = value.split(/\s+/);
2252
+ for (let i = 0, ii = names.length; i < ii; ++i) {
2253
+ const currentName = names[i];
2254
+ if (currentName === "") {
2255
+ continue;
2256
+ }
2257
+ classVersions[currentName] = version;
2258
+ tokenList.add(currentName);
2259
+ }
1865
2260
  }
1866
- /**
1867
- * Creates a behavior.
1868
- * @param targets - The targets available for behaviors to be attached to.
1869
- */
1870
- createBehavior(targets) {
1871
- return this;
2261
+ state.v = version + 1;
2262
+ // If this is the first call to add classes, there's no need to remove old ones.
2263
+ if (version === 0) {
2264
+ return;
1872
2265
  }
1873
- /**
1874
- * @internal
1875
- */
1876
- handleEvent(event) {
1877
- const target = event.currentTarget;
1878
- ExecutionContext.setEvent(event);
1879
- const result = this.directive.binding(target[this.sourceProperty], target[this.contextProperty]);
1880
- ExecutionContext.setEvent(null);
1881
- if (result !== true) {
1882
- event.preventDefault();
2266
+ // Remove classes from the previous version.
2267
+ version -= 1;
2268
+ for (const name in classVersions) {
2269
+ if (classVersions[name] === version) {
2270
+ tokenList.remove(name);
1883
2271
  }
1884
2272
  }
1885
2273
  }
1886
- /**
1887
- * The default onChange binding configuration.
1888
- * @public
1889
- */
1890
- const onChange = BindingConfig.define(BindingMode.define(ChangeBinding), {});
1891
- /**
1892
- * The default onTime binding configuration.
1893
- * @public
1894
- */
1895
- const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
1896
- once: true,
1897
- });
2274
+ const sinkLookup = {
2275
+ [DOMAspect.attribute]: DOM.setAttribute,
2276
+ [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
2277
+ [DOMAspect.property]: (t, a, v) => (t[a] = v),
2278
+ [DOMAspect.content]: updateContent,
2279
+ [DOMAspect.tokenList]: updateTokenList,
2280
+ [DOMAspect.event]: () => void 0,
2281
+ };
1898
2282
  /**
1899
2283
  * A directive that applies bindings.
1900
2284
  * @public
@@ -1902,19 +2286,15 @@ const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
1902
2286
  class HTMLBindingDirective {
1903
2287
  /**
1904
2288
  * Creates an instance of HTMLBindingDirective.
1905
- * @param binding - The binding to apply.
1906
- * @param mode - The binding mode to use when applying the binding.
1907
- * @param options - The options to configure the binding with.
2289
+ * @param dataBinding - The binding configuration to apply.
1908
2290
  */
1909
- constructor(binding, mode, options) {
1910
- this.binding = binding;
1911
- this.mode = mode;
1912
- this.options = options;
1913
- this.factory = null;
2291
+ constructor(dataBinding) {
2292
+ this.dataBinding = dataBinding;
2293
+ this.updateTarget = null;
1914
2294
  /**
1915
2295
  * The type of aspect to target.
1916
2296
  */
1917
- this.aspectType = Aspect.content;
2297
+ this.aspectType = DOMAspect.content;
1918
2298
  }
1919
2299
  /**
1920
2300
  * Creates HTML to be used within a template.
@@ -1925,31 +2305,114 @@ class HTMLBindingDirective {
1925
2305
  }
1926
2306
  /**
1927
2307
  * Creates a behavior.
1928
- * @param targets - The targets available for behaviors to be attached to.
1929
2308
  */
1930
- createBehavior(targets) {
1931
- if (this.factory == null) {
1932
- if (this.targetAspect === "innerHTML") {
1933
- this.binding = createInnerHTMLBinding(this.binding);
2309
+ createBehavior() {
2310
+ var _a;
2311
+ if (this.updateTarget === null) {
2312
+ const sink = sinkLookup[this.aspectType];
2313
+ const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
2314
+ if (!sink) {
2315
+ throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
2316
+ }
2317
+ this.data = `${this.id}-d`;
2318
+ this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
2319
+ }
2320
+ return this;
2321
+ }
2322
+ /** @internal */
2323
+ bind(controller) {
2324
+ var _a;
2325
+ const target = controller.targets[this.targetNodeId];
2326
+ switch (this.aspectType) {
2327
+ case DOMAspect.event:
2328
+ target[this.data] = controller;
2329
+ target.addEventListener(this.targetAspect, this, this.dataBinding.options);
2330
+ break;
2331
+ case DOMAspect.content:
2332
+ controller.onUnbind(this);
2333
+ // intentional fall through
2334
+ default:
2335
+ const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
2336
+ observer.target = target;
2337
+ observer.controller = controller;
2338
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2339
+ break;
2340
+ }
2341
+ }
2342
+ /** @internal */
2343
+ unbind(controller) {
2344
+ const target = controller.targets[this.targetNodeId];
2345
+ const view = target.$fastView;
2346
+ if (view !== void 0 && view.isComposed) {
2347
+ view.unbind();
2348
+ view.needsBindOnly = true;
2349
+ }
2350
+ }
2351
+ /** @internal */
2352
+ handleEvent(event) {
2353
+ const controller = event.currentTarget[this.data];
2354
+ if (controller.isBound) {
2355
+ ExecutionContext.setEvent(event);
2356
+ const result = this.dataBinding.evaluate(controller.source, controller.context);
2357
+ ExecutionContext.setEvent(null);
2358
+ if (result !== true) {
2359
+ event.preventDefault();
1934
2360
  }
1935
- this.factory = this.mode[this.aspectType](this);
1936
2361
  }
1937
- return this.factory.createBehavior(targets);
2362
+ }
2363
+ /** @internal */
2364
+ handleChange(binding, observer) {
2365
+ const target = observer.target;
2366
+ const controller = observer.controller;
2367
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
1938
2368
  }
1939
2369
  }
1940
2370
  HTMLDirective.define(HTMLBindingDirective, { aspected: true });
1941
2371
  /**
1942
- * Creates a binding directive with the specified configuration.
1943
- * @param binding - The binding expression.
1944
- * @param config - The binding configuration.
1945
- * @returns A binding directive.
2372
+ * Creates an standard binding.
2373
+ * @param expression - The binding to refresh when changed.
2374
+ * @param policy - The security policy to associate with th binding.
2375
+ * @param isVolatile - Indicates whether the binding is volatile or not.
2376
+ * @returns A binding configuration.
1946
2377
  * @public
1947
2378
  */
1948
- function bind(binding, config = onChange) {
1949
- if (!("mode" in config)) {
1950
- config = onChange(config);
1951
- }
1952
- return new HTMLBindingDirective(binding, config.mode, config.options);
2379
+ function bind(expression, policy, isVolatile = Observable.isVolatileBinding(expression)) {
2380
+ return new OnChangeBinding(expression, policy, isVolatile);
2381
+ }
2382
+ /**
2383
+ * Creates a one time binding
2384
+ * @param expression - The binding to refresh when signaled.
2385
+ * @param policy - The security policy to associate with th binding.
2386
+ * @returns A binding configuration.
2387
+ * @public
2388
+ */
2389
+ function oneTime(expression, policy) {
2390
+ return new OneTimeBinding(expression, policy);
2391
+ }
2392
+ /**
2393
+ * Creates an event listener binding.
2394
+ * @param expression - The binding to invoke when the event is raised.
2395
+ * @param options - Event listener options.
2396
+ * @returns A binding configuration.
2397
+ * @public
2398
+ */
2399
+ function listener(expression, options) {
2400
+ const config = new OnChangeBinding(expression);
2401
+ config.options = options;
2402
+ return config;
2403
+ }
2404
+ /**
2405
+ * Normalizes the input value into a binding.
2406
+ * @param value - The value to create the default binding for.
2407
+ * @returns A binding configuration for the provided value.
2408
+ * @public
2409
+ */
2410
+ function normalizeBinding(value) {
2411
+ return isFunction(value)
2412
+ ? bind(value)
2413
+ : value instanceof Binding
2414
+ ? value
2415
+ : oneTime(() => value);
1953
2416
  }
1954
2417
 
1955
2418
  function removeNodeSequence(firstNode, lastNode) {
@@ -1978,17 +2441,92 @@ class HTMLView {
1978
2441
  this.factories = factories;
1979
2442
  this.targets = targets;
1980
2443
  this.behaviors = null;
2444
+ this.unbindables = [];
1981
2445
  /**
1982
2446
  * The data that the view is bound to.
1983
2447
  */
1984
2448
  this.source = null;
2449
+ /**
2450
+ * Indicates whether the controller is bound.
2451
+ */
2452
+ this.isBound = false;
2453
+ /**
2454
+ * Indicates how the source's lifetime relates to the controller's lifetime.
2455
+ */
2456
+ this.sourceLifetime = SourceLifetime.unknown;
1985
2457
  /**
1986
2458
  * The execution context the view is running within.
1987
2459
  */
1988
- this.context = null;
2460
+ this.context = this;
2461
+ /**
2462
+ * The index of the current item within a repeat context.
2463
+ */
2464
+ this.index = 0;
2465
+ /**
2466
+ * The length of the current collection within a repeat context.
2467
+ */
2468
+ this.length = 0;
2469
+ /**
2470
+ * Opts out of JSON stringification.
2471
+ * @internal
2472
+ */
2473
+ this.toJSON = noop;
1989
2474
  this.firstChild = fragment.firstChild;
1990
2475
  this.lastChild = fragment.lastChild;
1991
2476
  }
2477
+ /**
2478
+ * The current event within an event handler.
2479
+ */
2480
+ get event() {
2481
+ return ExecutionContext.getEvent();
2482
+ }
2483
+ /**
2484
+ * Indicates whether the current item within a repeat context
2485
+ * has an even index.
2486
+ */
2487
+ get isEven() {
2488
+ return this.index % 2 === 0;
2489
+ }
2490
+ /**
2491
+ * Indicates whether the current item within a repeat context
2492
+ * has an odd index.
2493
+ */
2494
+ get isOdd() {
2495
+ return this.index % 2 !== 0;
2496
+ }
2497
+ /**
2498
+ * Indicates whether the current item within a repeat context
2499
+ * is the first item in the collection.
2500
+ */
2501
+ get isFirst() {
2502
+ return this.index === 0;
2503
+ }
2504
+ /**
2505
+ * Indicates whether the current item within a repeat context
2506
+ * is somewhere in the middle of the collection.
2507
+ */
2508
+ get isInMiddle() {
2509
+ return !this.isFirst && !this.isLast;
2510
+ }
2511
+ /**
2512
+ * Indicates whether the current item within a repeat context
2513
+ * is the last item in the collection.
2514
+ */
2515
+ get isLast() {
2516
+ return this.index === this.length - 1;
2517
+ }
2518
+ /**
2519
+ * Returns the typed event detail of a custom event.
2520
+ */
2521
+ eventDetail() {
2522
+ return this.event.detail;
2523
+ }
2524
+ /**
2525
+ * Returns the typed event target of the event.
2526
+ */
2527
+ eventTarget() {
2528
+ return this.event.target;
2529
+ }
1992
2530
  /**
1993
2531
  * Appends the view's DOM nodes to the referenced node.
1994
2532
  * @param node - The parent node to append the view's DOM nodes to.
@@ -2005,8 +2543,10 @@ class HTMLView {
2005
2543
  node.parentNode.insertBefore(this.fragment, node);
2006
2544
  }
2007
2545
  else {
2008
- const parentNode = node.parentNode;
2009
2546
  const end = this.lastChild;
2547
+ if (node.previousSibling === end)
2548
+ return;
2549
+ const parentNode = node.parentNode;
2010
2550
  let current = this.firstChild;
2011
2551
  let next;
2012
2552
  while (current !== end) {
@@ -2041,58 +2581,61 @@ class HTMLView {
2041
2581
  removeNodeSequence(this.firstChild, this.lastChild);
2042
2582
  this.unbind();
2043
2583
  }
2584
+ onUnbind(behavior) {
2585
+ this.unbindables.push(behavior);
2586
+ }
2044
2587
  /**
2045
2588
  * Binds a view's behaviors to its binding source.
2046
2589
  * @param source - The binding source for the view's binding behaviors.
2047
2590
  * @param context - The execution context to run the behaviors within.
2048
2591
  */
2049
- bind(source, context) {
2050
- let behaviors = this.behaviors;
2051
- const oldSource = this.source;
2052
- if (oldSource === source) {
2592
+ bind(source, context = this) {
2593
+ if (this.source === source) {
2053
2594
  return;
2054
2595
  }
2055
- this.source = source;
2056
- this.context = context;
2057
- const targets = this.targets;
2058
- if (oldSource !== null) {
2059
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2060
- const current = behaviors[i];
2061
- current.unbind(oldSource, context, targets);
2062
- current.bind(source, context, targets);
2063
- }
2064
- }
2065
- else if (behaviors === null) {
2596
+ let behaviors = this.behaviors;
2597
+ if (behaviors === null) {
2598
+ this.source = source;
2599
+ this.context = context;
2066
2600
  this.behaviors = behaviors = new Array(this.factories.length);
2067
2601
  const factories = this.factories;
2068
2602
  for (let i = 0, ii = factories.length; i < ii; ++i) {
2069
- const behavior = factories[i].createBehavior(targets);
2070
- behavior.bind(source, context, targets);
2603
+ const behavior = factories[i].createBehavior();
2604
+ behavior.bind(this);
2071
2605
  behaviors[i] = behavior;
2072
2606
  }
2073
2607
  }
2074
2608
  else {
2609
+ if (this.source !== null) {
2610
+ this.evaluateUnbindables();
2611
+ }
2612
+ this.isBound = false;
2613
+ this.source = source;
2614
+ this.context = context;
2075
2615
  for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2076
- behaviors[i].bind(source, context, targets);
2616
+ behaviors[i].bind(this);
2077
2617
  }
2078
2618
  }
2619
+ this.isBound = true;
2079
2620
  }
2080
2621
  /**
2081
2622
  * Unbinds a view's behaviors from its binding source.
2082
2623
  */
2083
2624
  unbind() {
2084
- const oldSource = this.source;
2085
- if (oldSource === null) {
2625
+ if (!this.isBound || this.source === null) {
2086
2626
  return;
2087
2627
  }
2088
- const targets = this.targets;
2089
- const context = this.context;
2090
- const behaviors = this.behaviors;
2091
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2092
- behaviors[i].unbind(oldSource, context, targets);
2093
- }
2628
+ this.evaluateUnbindables();
2094
2629
  this.source = null;
2095
- this.context = null;
2630
+ this.context = this;
2631
+ this.isBound = false;
2632
+ }
2633
+ evaluateUnbindables() {
2634
+ const unbindables = this.unbindables;
2635
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
2636
+ unbindables[i].unbind(this);
2637
+ }
2638
+ unbindables.length = 0;
2096
2639
  }
2097
2640
  /**
2098
2641
  * Efficiently disposes of a contiguous range of synthetic view instances.
@@ -2108,6 +2651,8 @@ class HTMLView {
2108
2651
  }
2109
2652
  }
2110
2653
  }
2654
+ Observable.defineProperty(HTMLView.prototype, "index");
2655
+ Observable.defineProperty(HTMLView.prototype, "length");
2111
2656
 
2112
2657
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
2113
2658
  const descriptorCache = {};
@@ -2116,21 +2661,42 @@ const next = {
2116
2661
  index: 0,
2117
2662
  node: null,
2118
2663
  };
2664
+ function tryWarn(name) {
2665
+ if (!name.startsWith("fast-")) {
2666
+ FAST.warn(1204 /* Message.hostBindingWithoutHost */, { name });
2667
+ }
2668
+ }
2669
+ const warningHost = new Proxy(document.createElement("div"), {
2670
+ get(target, property) {
2671
+ tryWarn(property);
2672
+ const value = Reflect.get(target, property);
2673
+ return isFunction(value) ? value.bind(target) : value;
2674
+ },
2675
+ set(target, property, value) {
2676
+ tryWarn(property);
2677
+ return Reflect.set(target, property, value);
2678
+ },
2679
+ });
2119
2680
  class CompilationContext {
2120
- constructor(fragment, directives) {
2681
+ constructor(fragment, directives, policy) {
2121
2682
  this.fragment = fragment;
2122
2683
  this.directives = directives;
2684
+ this.policy = policy;
2123
2685
  this.proto = null;
2124
2686
  this.nodeIds = new Set();
2125
2687
  this.descriptors = {};
2126
2688
  this.factories = [];
2127
2689
  }
2128
- addFactory(factory, parentId, nodeId, targetIndex) {
2690
+ addFactory(factory, parentId, nodeId, targetIndex, tagName) {
2691
+ var _a, _b;
2129
2692
  if (!this.nodeIds.has(nodeId)) {
2130
2693
  this.nodeIds.add(nodeId);
2131
2694
  this.addTargetDescriptor(parentId, nodeId, targetIndex);
2132
2695
  }
2133
- factory.nodeId = nodeId;
2696
+ factory.id = (_a = factory.id) !== null && _a !== void 0 ? _a : nextId();
2697
+ factory.targetNodeId = nodeId;
2698
+ factory.targetTagName = tagName;
2699
+ factory.policy = (_b = factory.policy) !== null && _b !== void 0 ? _b : this.policy;
2134
2700
  this.factories.push(factory);
2135
2701
  }
2136
2702
  freeze() {
@@ -2166,7 +2732,7 @@ class CompilationContext {
2166
2732
  const fragment = this.fragment.cloneNode(true);
2167
2733
  const targets = Object.create(this.proto);
2168
2734
  targets.r = fragment;
2169
- targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : fragment;
2735
+ targets.h = hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : warningHost;
2170
2736
  for (const id of this.nodeIds) {
2171
2737
  targets[id]; // trigger locator
2172
2738
  }
@@ -2183,19 +2749,19 @@ function compileAttributes(context, parentId, node, nodeId, nodeIndex, includeBa
2183
2749
  let result = null;
2184
2750
  if (parseResult === null) {
2185
2751
  if (includeBasicValues) {
2186
- result = bind(() => attrValue, oneTime);
2187
- Aspect.assign(result, attr.name);
2752
+ result = new HTMLBindingDirective(oneTime(() => attrValue, context.policy));
2753
+ HTMLDirective.assignAspect(result, attr.name);
2188
2754
  }
2189
2755
  }
2190
2756
  else {
2191
2757
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2192
- result = Compiler.aggregate(parseResult);
2758
+ result = Compiler.aggregate(parseResult, context.policy);
2193
2759
  }
2194
2760
  if (result !== null) {
2195
2761
  node.removeAttributeNode(attr);
2196
2762
  i--;
2197
2763
  ii--;
2198
- context.addFactory(result, parentId, nodeId, nodeIndex);
2764
+ context.addFactory(result, parentId, nodeId, nodeIndex, node.tagName);
2199
2765
  }
2200
2766
  }
2201
2767
  }
@@ -2220,7 +2786,8 @@ function compileContent(context, node, parentId, nodeId, nodeIndex) {
2220
2786
  }
2221
2787
  else {
2222
2788
  currentNode.textContent = " ";
2223
- context.addFactory(currentPart, parentId, nodeId, nodeIndex);
2789
+ HTMLDirective.assignAspect(currentPart);
2790
+ context.addFactory(currentPart, parentId, nodeId, nodeIndex, null);
2224
2791
  }
2225
2792
  lastNode = currentNode;
2226
2793
  }
@@ -2252,7 +2819,7 @@ function compileNode(context, parentId, node, nodeIndex) {
2252
2819
  if (parts !== null) {
2253
2820
  context.addFactory(
2254
2821
  /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
2255
- Compiler.aggregate(parts), parentId, nodeId, nodeIndex);
2822
+ Compiler.aggregate(parts), parentId, nodeId, nodeIndex, null);
2256
2823
  }
2257
2824
  break;
2258
2825
  }
@@ -2266,45 +2833,28 @@ function isMarker(node, directives) {
2266
2833
  Parser.parse(node.data, directives) !== null);
2267
2834
  }
2268
2835
  const templateTag = "TEMPLATE";
2269
- const policyOptions = { createHTML: html => html };
2270
- let htmlPolicy = globalThis.trustedTypes
2271
- ? globalThis.trustedTypes.createPolicy("fast-html", policyOptions)
2272
- : policyOptions;
2273
- const fastHTMLPolicy = htmlPolicy;
2274
2836
  /**
2275
2837
  * Common APIs related to compilation.
2276
2838
  * @public
2277
2839
  */
2278
2840
  const Compiler = {
2279
- /**
2280
- * Sets the HTML trusted types policy used by the compiler.
2281
- * @param policy - The policy to set for HTML.
2282
- * @remarks
2283
- * This API can only be called once, for security reasons. It should be
2284
- * called by the application developer at the start of their program.
2285
- */
2286
- setHTMLPolicy(policy) {
2287
- if (htmlPolicy !== fastHTMLPolicy) {
2288
- throw FAST.error(1201 /* Message.onlySetHTMLPolicyOnce */);
2289
- }
2290
- htmlPolicy = policy;
2291
- },
2292
2841
  /**
2293
2842
  * Compiles a template and associated directives into a compilation
2294
2843
  * result which can be used to create views.
2295
2844
  * @param html - The html string or template element to compile.
2296
- * @param directives - The directives referenced by the template.
2845
+ * @param factories - The behavior factories referenced by the template.
2846
+ * @param policy - The security policy to compile the html with.
2297
2847
  * @remarks
2298
2848
  * The template that is provided for compilation is altered in-place
2299
2849
  * and cannot be compiled again. If the original template must be preserved,
2300
2850
  * it is recommended that you clone the original and pass the clone to this API.
2301
2851
  * @public
2302
2852
  */
2303
- compile(html, directives) {
2853
+ compile(html, factories, policy = DOM.policy) {
2304
2854
  let template;
2305
2855
  if (isString(html)) {
2306
2856
  template = document.createElement(templateTag);
2307
- template.innerHTML = htmlPolicy.createHTML(html);
2857
+ template.innerHTML = policy.createHTML(html);
2308
2858
  const fec = template.content.firstElementChild;
2309
2859
  if (fec !== null && fec.tagName === templateTag) {
2310
2860
  template = fec;
@@ -2315,18 +2865,18 @@ const Compiler = {
2315
2865
  }
2316
2866
  // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864
2317
2867
  const fragment = document.adoptNode(template.content);
2318
- const context = new CompilationContext(fragment, directives);
2868
+ const context = new CompilationContext(fragment, factories, policy);
2319
2869
  compileAttributes(context, "", template, /* host */ "h", 0, true);
2320
2870
  if (
2321
2871
  // If the first node in a fragment is a marker, that means it's an unstable first node,
2322
2872
  // because something like a when, repeat, etc. could add nodes before the marker.
2323
2873
  // To mitigate this, we insert a stable first node. However, if we insert a node,
2324
2874
  // that will alter the result of the TreeWalker. So, we also need to offset the target index.
2325
- isMarker(fragment.firstChild, directives) ||
2875
+ isMarker(fragment.firstChild, factories) ||
2326
2876
  // Or if there is only one node and a directive, it means the template's content
2327
2877
  // is *only* the directive. In that case, HTMLView.dispose() misses any nodes inserted by
2328
2878
  // the directive. Inserting a new node ensures proper disposal of nodes added by the directive.
2329
- (fragment.childNodes.length === 1 && Object.keys(directives).length > 0)) {
2879
+ (fragment.childNodes.length === 1 && Object.keys(factories).length > 0)) {
2330
2880
  fragment.insertBefore(document.createComment(""), fragment.firstChild);
2331
2881
  }
2332
2882
  compileChildren(context, fragment, /* root */ "r");
@@ -2345,34 +2895,88 @@ const Compiler = {
2345
2895
  * Aggregates an array of strings and directives into a single directive.
2346
2896
  * @param parts - A heterogeneous array of static strings interspersed with
2347
2897
  * directives.
2898
+ * @param policy - The security policy to use with the aggregated bindings.
2348
2899
  * @returns A single inline directive that aggregates the behavior of all the parts.
2349
2900
  */
2350
- aggregate(parts) {
2901
+ aggregate(parts, policy = DOM.policy) {
2351
2902
  if (parts.length === 1) {
2352
2903
  return parts[0];
2353
2904
  }
2354
2905
  let sourceAspect;
2906
+ let binding;
2907
+ let isVolatile = false;
2908
+ let bindingPolicy = void 0;
2355
2909
  const partCount = parts.length;
2356
2910
  const finalParts = parts.map((x) => {
2357
2911
  if (isString(x)) {
2358
2912
  return () => x;
2359
2913
  }
2360
2914
  sourceAspect = x.sourceAspect || sourceAspect;
2361
- return x.binding;
2915
+ binding = x.dataBinding || binding;
2916
+ isVolatile = isVolatile || x.dataBinding.isVolatile;
2917
+ bindingPolicy = bindingPolicy || x.dataBinding.policy;
2918
+ return x.dataBinding.evaluate;
2362
2919
  });
2363
- const binding = (scope, context) => {
2920
+ const expression = (scope, context) => {
2364
2921
  let output = "";
2365
2922
  for (let i = 0; i < partCount; ++i) {
2366
2923
  output += finalParts[i](scope, context);
2367
2924
  }
2368
2925
  return output;
2369
2926
  };
2370
- const directive = bind(binding);
2371
- Aspect.assign(directive, sourceAspect);
2927
+ binding.evaluate = expression;
2928
+ binding.isVolatile = isVolatile;
2929
+ binding.policy = bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy;
2930
+ const directive = new HTMLBindingDirective(binding);
2931
+ HTMLDirective.assignAspect(directive, sourceAspect);
2372
2932
  return directive;
2373
2933
  },
2374
2934
  };
2375
2935
 
2936
+ // Much thanks to LitHTML for working this out!
2937
+ const lastAttributeNameRegex =
2938
+ /* eslint-disable-next-line no-control-regex */
2939
+ /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
2940
+ const noFactories = Object.create(null);
2941
+ /**
2942
+ * Inlines a template into another template.
2943
+ * @public
2944
+ */
2945
+ class InlineTemplateDirective {
2946
+ /**
2947
+ * Creates an instance of InlineTemplateDirective.
2948
+ * @param template - The template to inline.
2949
+ */
2950
+ constructor(html, factories = noFactories) {
2951
+ this.html = html;
2952
+ this.factories = factories;
2953
+ }
2954
+ /**
2955
+ * Creates HTML to be used within a template.
2956
+ * @param add - Can be used to add behavior factories to a template.
2957
+ */
2958
+ createHTML(add) {
2959
+ const factories = this.factories;
2960
+ for (const key in factories) {
2961
+ add(factories[key]);
2962
+ }
2963
+ return this.html;
2964
+ }
2965
+ }
2966
+ /**
2967
+ * An empty template partial.
2968
+ */
2969
+ InlineTemplateDirective.empty = new InlineTemplateDirective("");
2970
+ HTMLDirective.define(InlineTemplateDirective);
2971
+ function createHTML(value, prevString, add, definition = HTMLDirective.getForInstance(value)) {
2972
+ if (definition.aspected) {
2973
+ const match = lastAttributeNameRegex.exec(prevString);
2974
+ if (match !== null) {
2975
+ HTMLDirective.assignAspect(value, match[2]);
2976
+ }
2977
+ }
2978
+ return value.createHTML(add);
2979
+ }
2376
2980
  /**
2377
2981
  * A template capable of creating HTMLView instances or rendering directly to DOM.
2378
2982
  * @public
@@ -2382,9 +2986,16 @@ class ViewTemplate {
2382
2986
  * Creates an instance of ViewTemplate.
2383
2987
  * @param html - The html representing what this template will instantiate, including placeholders for directives.
2384
2988
  * @param factories - The directives that will be connected to placeholders in the html.
2989
+ * @param policy - The security policy to use when compiling this template.
2385
2990
  */
2386
- constructor(html, factories) {
2991
+ constructor(html, factories = {}, policy) {
2992
+ this.policy = policy;
2387
2993
  this.result = null;
2994
+ /**
2995
+ * Opts out of JSON stringification.
2996
+ * @internal
2997
+ */
2998
+ this.toJSON = noop;
2388
2999
  this.html = html;
2389
3000
  this.factories = factories;
2390
3001
  }
@@ -2394,10 +3005,34 @@ class ViewTemplate {
2394
3005
  */
2395
3006
  create(hostBindingTarget) {
2396
3007
  if (this.result === null) {
2397
- this.result = Compiler.compile(this.html, this.factories);
3008
+ this.result = Compiler.compile(this.html, this.factories, this.policy);
2398
3009
  }
2399
3010
  return this.result.createView(hostBindingTarget);
2400
3011
  }
3012
+ /**
3013
+ * Returns a directive that can inline the template.
3014
+ */
3015
+ inline() {
3016
+ return new InlineTemplateDirective(isString(this.html) ? this.html : this.html.innerHTML, this.factories);
3017
+ }
3018
+ /**
3019
+ * Sets the DOMPolicy for this template.
3020
+ * @param policy - The policy to associated with this template.
3021
+ * @returns The modified template instance.
3022
+ * @remarks
3023
+ * The DOMPolicy can only be set once for a template and cannot be
3024
+ * set after the template is compiled.
3025
+ */
3026
+ withPolicy(policy) {
3027
+ if (this.result) {
3028
+ throw FAST.error(1208 /* Message.cannotSetTemplatePolicyAfterCompilation */);
3029
+ }
3030
+ if (this.policy) {
3031
+ throw FAST.error(1207 /* Message.onlySetTemplatePolicyOnce */);
3032
+ }
3033
+ this.policy = policy;
3034
+ return this;
3035
+ }
2401
3036
  /**
2402
3037
  * Creates an HTMLView from this template, binds it to the source, and then appends it to the host.
2403
3038
  * @param source - The data source to bind the template to.
@@ -2405,75 +3040,72 @@ class ViewTemplate {
2405
3040
  * @param hostBindingTarget - An HTML element to target the host bindings at if different from the
2406
3041
  * host that the template is being attached to.
2407
3042
  */
2408
- render(source, host, hostBindingTarget, context) {
2409
- const view = this.create(hostBindingTarget !== null && hostBindingTarget !== void 0 ? hostBindingTarget : host);
2410
- view.bind(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
3043
+ render(source, host, hostBindingTarget) {
3044
+ const view = this.create(hostBindingTarget);
3045
+ view.bind(source);
2411
3046
  view.appendTo(host);
2412
3047
  return view;
2413
3048
  }
2414
- }
2415
- // Much thanks to LitHTML for working this out!
2416
- const lastAttributeNameRegex =
2417
- /* eslint-disable-next-line no-control-regex */
2418
- /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
2419
- function createAspectedHTML(value, prevString, add) {
2420
- const match = lastAttributeNameRegex.exec(prevString);
2421
- if (match !== null) {
2422
- Aspect.assign(value, match[2]);
2423
- }
2424
- return value.createHTML(add);
2425
- }
2426
- /**
2427
- * Transforms a template literal string into a ViewTemplate.
2428
- * @param strings - The string fragments that are interpolated with the values.
2429
- * @param values - The values that are interpolated with the string fragments.
2430
- * @remarks
2431
- * The html helper supports interpolation of strings, numbers, binding expressions,
2432
- * other template instances, and Directive instances.
2433
- * @public
2434
- */
2435
- function html(strings, ...values) {
2436
- let html = "";
2437
- const factories = Object.create(null);
2438
- const add = (factory) => {
2439
- var _a;
2440
- const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
2441
- factories[id] = factory;
2442
- return id;
2443
- };
2444
- for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
2445
- const currentString = strings[i];
2446
- const currentValue = values[i];
2447
- let definition;
2448
- html += currentString;
2449
- if (isFunction(currentValue)) {
2450
- html += createAspectedHTML(bind(currentValue), currentString, add);
2451
- }
2452
- else if (isString(currentValue)) {
2453
- const match = lastAttributeNameRegex.exec(currentString);
2454
- if (match !== null) {
2455
- const directive = bind(() => currentValue, oneTime);
2456
- Aspect.assign(directive, match[2]);
2457
- html += directive.createHTML(add);
2458
- }
2459
- else {
2460
- html += currentValue;
3049
+ /**
3050
+ * Creates a template based on a set of static strings and dynamic values.
3051
+ * @param strings - The static strings to create the template with.
3052
+ * @param values - The dynamic values to create the template with.
3053
+ * @param policy - The DOMPolicy to associated with the template.
3054
+ * @returns A ViewTemplate.
3055
+ * @remarks
3056
+ * This API should not be used directly under normal circumstances because constructing
3057
+ * a template in this way, if not done properly, can open up the application to XSS
3058
+ * attacks. When using this API, provide a strong DOMPolicy that can properly sanitize
3059
+ * and also be sure to manually sanitize all static strings particularly if they can
3060
+ * come from user input.
3061
+ */
3062
+ static create(strings, values, policy) {
3063
+ let html = "";
3064
+ const factories = Object.create(null);
3065
+ const add = (factory) => {
3066
+ var _a;
3067
+ const id = (_a = factory.id) !== null && _a !== void 0 ? _a : (factory.id = nextId());
3068
+ factories[id] = factory;
3069
+ return id;
3070
+ };
3071
+ for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
3072
+ const currentString = strings[i];
3073
+ let currentValue = values[i];
3074
+ let definition;
3075
+ html += currentString;
3076
+ if (isFunction(currentValue)) {
3077
+ currentValue = new HTMLBindingDirective(bind(currentValue));
2461
3078
  }
2462
- }
2463
- else if ((definition = HTMLDirective.getForInstance(currentValue)) === void 0) {
2464
- html += createAspectedHTML(bind(() => currentValue, oneTime), currentString, add);
2465
- }
2466
- else {
2467
- if (definition.aspected) {
2468
- html += createAspectedHTML(currentValue, currentString, add);
3079
+ else if (currentValue instanceof Binding) {
3080
+ currentValue = new HTMLBindingDirective(currentValue);
2469
3081
  }
2470
- else {
2471
- html += currentValue.createHTML(add);
3082
+ else if (!(definition = HTMLDirective.getForInstance(currentValue))) {
3083
+ const staticValue = currentValue;
3084
+ currentValue = new HTMLBindingDirective(oneTime(() => staticValue));
2472
3085
  }
3086
+ html += createHTML(currentValue, currentString, add, definition);
2473
3087
  }
3088
+ return new ViewTemplate(html + strings[strings.length - 1], factories, policy);
3089
+ }
3090
+ }
3091
+ /**
3092
+ * Transforms a template literal string into a ViewTemplate.
3093
+ * @param strings - The string fragments that are interpolated with the values.
3094
+ * @param values - The values that are interpolated with the string fragments.
3095
+ * @remarks
3096
+ * The html helper supports interpolation of strings, numbers, binding expressions,
3097
+ * other template instances, and Directive instances.
3098
+ * @public
3099
+ */
3100
+ const html = ((strings, ...values) => {
3101
+ if (Array.isArray(strings) && Array.isArray(strings.raw)) {
3102
+ return ViewTemplate.create(strings, values);
2474
3103
  }
2475
- return new ViewTemplate(html + strings[strings.length - 1], factories);
2476
- }
3104
+ throw FAST.error(1206 /* Message.directCallToHTMLTagNotAllowed */);
3105
+ });
3106
+ html.partial = (html) => {
3107
+ return new InlineTemplateDirective(html);
3108
+ };
2477
3109
 
2478
3110
  /**
2479
3111
  * The runtime behavior for template references.
@@ -2481,20 +3113,12 @@ function html(strings, ...values) {
2481
3113
  */
2482
3114
  class RefDirective extends StatelessAttachedAttributeDirective {
2483
3115
  /**
2484
- * Bind this behavior to the source.
2485
- * @param source - The source to bind to.
2486
- * @param context - The execution context that the binding is operating within.
2487
- * @param targets - The targets that behaviors in a view can attach to.
3116
+ * Bind this behavior.
3117
+ * @param controller - The view controller that manages the lifecycle of this behavior.
2488
3118
  */
2489
- bind(source, context, targets) {
2490
- source[this.options] = targets[this.nodeId];
3119
+ bind(controller) {
3120
+ controller.source[this.options] = controller.targets[this.targetNodeId];
2491
3121
  }
2492
- /**
2493
- * Unbinds this behavior from the source.
2494
- * @param source - The source to unbind from.
2495
- */
2496
- /* eslint-disable-next-line @typescript-eslint/no-empty-function */
2497
- unbind() { }
2498
3122
  }
2499
3123
  HTMLDirective.define(RefDirective);
2500
3124
  /**
@@ -2506,27 +3130,34 @@ const ref = (propertyName) => new RefDirective(propertyName);
2506
3130
 
2507
3131
  /**
2508
3132
  * A directive that enables basic conditional rendering in a template.
2509
- * @param binding - The condition to test for rendering.
3133
+ * @param condition - The condition to test for rendering.
2510
3134
  * @param templateOrTemplateBinding - The template or a binding that gets
2511
3135
  * the template to render when the condition is true.
2512
3136
  * @public
2513
3137
  */
2514
- function when(binding, templateOrTemplateBinding) {
2515
- const getTemplate = isFunction(templateOrTemplateBinding)
3138
+ function when(condition, templateOrTemplateBinding) {
3139
+ const dataBinding = isFunction(condition) ? condition : () => condition;
3140
+ const templateBinding = isFunction(templateOrTemplateBinding)
2516
3141
  ? templateOrTemplateBinding
2517
3142
  : () => templateOrTemplateBinding;
2518
- return (source, context) => binding(source, context) ? getTemplate(source, context) : null;
3143
+ return (source, context) => dataBinding(source, context) ? templateBinding(source, context) : null;
2519
3144
  }
2520
3145
 
2521
3146
  const defaultRepeatOptions = Object.freeze({
2522
3147
  positioning: false,
2523
3148
  recycle: true,
2524
3149
  });
2525
- function bindWithoutPositioning(view, items, index, context) {
2526
- view.bind(items[index], context);
3150
+ function bindWithoutPositioning(view, items, index, controller) {
3151
+ view.context.parent = controller.source;
3152
+ view.context.parentContext = controller.context;
3153
+ view.bind(items[index]);
2527
3154
  }
2528
- function bindWithPositioning(view, items, index, context) {
2529
- view.bind(items[index], context.createItemContext(index, items.length));
3155
+ function bindWithPositioning(view, items, index, controller) {
3156
+ view.context.parent = controller.source;
3157
+ view.context.parentContext = controller.context;
3158
+ view.context.length = items.length;
3159
+ view.context.index = index;
3160
+ view.bind(items[index]);
2530
3161
  }
2531
3162
  /**
2532
3163
  * A behavior that renders a template for each item in an array.
@@ -2536,57 +3167,46 @@ class RepeatBehavior {
2536
3167
  /**
2537
3168
  * Creates an instance of RepeatBehavior.
2538
3169
  * @param location - The location in the DOM to render the repeat.
2539
- * @param itemsBinding - The array to render.
3170
+ * @param dataBinding - The array to render.
2540
3171
  * @param isItemsBindingVolatile - Indicates whether the items binding has volatile dependencies.
2541
3172
  * @param templateBinding - The template to render for each item.
2542
3173
  * @param isTemplateBindingVolatile - Indicates whether the template binding has volatile dependencies.
2543
3174
  * @param options - Options used to turn on special repeat features.
2544
3175
  */
2545
- constructor(location, itemsBinding, isItemsBindingVolatile, templateBinding, isTemplateBindingVolatile, options) {
2546
- this.location = location;
2547
- this.itemsBinding = itemsBinding;
2548
- this.templateBinding = templateBinding;
2549
- this.options = options;
2550
- this.source = null;
2551
- this.views = [];
3176
+ constructor(directive) {
3177
+ this.directive = directive;
2552
3178
  this.items = null;
2553
3179
  this.itemsObserver = null;
2554
- this.context = void 0;
2555
- this.childContext = void 0;
2556
3180
  this.bindView = bindWithoutPositioning;
2557
- this.itemsBindingObserver = Observable.binding(itemsBinding, this, isItemsBindingVolatile);
2558
- this.templateBindingObserver = Observable.binding(templateBinding, this, isTemplateBindingVolatile);
2559
- if (options.positioning) {
3181
+ /** @internal */
3182
+ this.views = [];
3183
+ this.itemsBindingObserver = directive.dataBinding.createObserver(directive, this);
3184
+ this.templateBindingObserver = directive.templateBinding.createObserver(directive, this);
3185
+ if (directive.options.positioning) {
2560
3186
  this.bindView = bindWithPositioning;
2561
3187
  }
2562
3188
  }
2563
3189
  /**
2564
- * Bind this behavior to the source.
2565
- * @param source - The source to bind to.
2566
- * @param context - The execution context that the binding is operating within.
3190
+ * Bind this behavior.
3191
+ * @param controller - The view controller that manages the lifecycle of this behavior.
2567
3192
  */
2568
- bind(source, context) {
2569
- this.source = source;
2570
- this.context = context;
2571
- this.childContext = context.createChildContext(source);
2572
- this.items = this.itemsBindingObserver.observe(source, this.context);
2573
- this.template = this.templateBindingObserver.observe(source, this.context);
3193
+ bind(controller) {
3194
+ this.location = controller.targets[this.directive.targetNodeId];
3195
+ this.controller = controller;
3196
+ this.items = this.itemsBindingObserver.bind(controller);
3197
+ this.template = this.templateBindingObserver.bind(controller);
2574
3198
  this.observeItems(true);
2575
3199
  this.refreshAllViews();
3200
+ controller.onUnbind(this);
2576
3201
  }
2577
3202
  /**
2578
- * Unbinds this behavior from the source.
2579
- * @param source - The source to unbind from.
3203
+ * Unbinds this behavior.
2580
3204
  */
2581
3205
  unbind() {
2582
- this.source = null;
2583
- this.items = null;
2584
3206
  if (this.itemsObserver !== null) {
2585
3207
  this.itemsObserver.unsubscribe(this);
2586
3208
  }
2587
3209
  this.unbindAllViews();
2588
- this.itemsBindingObserver.dispose();
2589
- this.templateBindingObserver.dispose();
2590
3210
  }
2591
3211
  /**
2592
3212
  * Handles changes in the array, its items, and the repeat template.
@@ -2594,15 +3214,18 @@ class RepeatBehavior {
2594
3214
  * @param args - The details about what was changed.
2595
3215
  */
2596
3216
  handleChange(source, args) {
2597
- if (source === this.itemsBinding) {
2598
- this.items = this.itemsBindingObserver.observe(this.source, this.context);
3217
+ if (args === this.itemsBindingObserver) {
3218
+ this.items = this.itemsBindingObserver.bind(this.controller);
2599
3219
  this.observeItems();
2600
3220
  this.refreshAllViews();
2601
3221
  }
2602
- else if (source === this.templateBinding) {
2603
- this.template = this.templateBindingObserver.observe(this.source, this.context);
3222
+ else if (args === this.templateBindingObserver) {
3223
+ this.template = this.templateBindingObserver.bind(this.controller);
2604
3224
  this.refreshAllViews(true);
2605
3225
  }
3226
+ else if (!args[0]) {
3227
+ return;
3228
+ }
2606
3229
  else if (args[0].reset) {
2607
3230
  this.refreshAllViews();
2608
3231
  }
@@ -2627,39 +3250,57 @@ class RepeatBehavior {
2627
3250
  }
2628
3251
  updateViews(splices) {
2629
3252
  const views = this.views;
2630
- const childContext = this.childContext;
2631
- const totalRemoved = [];
2632
3253
  const bindView = this.bindView;
2633
- let removeDelta = 0;
2634
- for (let i = 0, ii = splices.length; i < ii; ++i) {
2635
- const splice = splices[i];
2636
- const removed = splice.removed;
2637
- totalRemoved.push(...views.splice(splice.index + removeDelta, removed.length));
2638
- removeDelta -= splice.addedCount;
2639
- }
2640
3254
  const items = this.items;
2641
3255
  const template = this.template;
3256
+ const controller = this.controller;
3257
+ const recycle = this.directive.options.recycle;
3258
+ const leftoverViews = [];
3259
+ let leftoverIndex = 0;
3260
+ let availableViews = 0;
2642
3261
  for (let i = 0, ii = splices.length; i < ii; ++i) {
2643
3262
  const splice = splices[i];
3263
+ const removed = splice.removed;
3264
+ let removeIndex = 0;
2644
3265
  let addIndex = splice.index;
2645
3266
  const end = addIndex + splice.addedCount;
3267
+ const removedViews = views.splice(splice.index, removed.length);
3268
+ const totalAvailableViews = (availableViews =
3269
+ leftoverViews.length + removedViews.length);
2646
3270
  for (; addIndex < end; ++addIndex) {
2647
3271
  const neighbor = views[addIndex];
2648
3272
  const location = neighbor ? neighbor.firstChild : this.location;
2649
- const view = this.options.recycle && totalRemoved.length > 0
2650
- ? totalRemoved.shift()
2651
- : template.create();
3273
+ let view;
3274
+ if (recycle && availableViews > 0) {
3275
+ if (removeIndex <= totalAvailableViews && removedViews.length > 0) {
3276
+ view = removedViews[removeIndex];
3277
+ removeIndex++;
3278
+ }
3279
+ else {
3280
+ view = leftoverViews[leftoverIndex];
3281
+ leftoverIndex++;
3282
+ }
3283
+ availableViews--;
3284
+ }
3285
+ else {
3286
+ view = template.create();
3287
+ }
2652
3288
  views.splice(addIndex, 0, view);
2653
- bindView(view, items, addIndex, childContext);
3289
+ bindView(view, items, addIndex, controller);
2654
3290
  view.insertBefore(location);
2655
3291
  }
3292
+ if (removedViews[removeIndex]) {
3293
+ leftoverViews.push(...removedViews.slice(removeIndex));
3294
+ }
2656
3295
  }
2657
- for (let i = 0, ii = totalRemoved.length; i < ii; ++i) {
2658
- totalRemoved[i].dispose();
3296
+ for (let i = leftoverIndex, ii = leftoverViews.length; i < ii; ++i) {
3297
+ leftoverViews[i].dispose();
2659
3298
  }
2660
- if (this.options.positioning) {
2661
- for (let i = 0, ii = views.length; i < ii; ++i) {
2662
- views[i].context.updatePosition(i, ii);
3299
+ if (this.directive.options.positioning) {
3300
+ for (let i = 0, viewsLength = views.length; i < viewsLength; ++i) {
3301
+ const context = views[i].context;
3302
+ context.length = viewsLength;
3303
+ context.index = i;
2663
3304
  }
2664
3305
  }
2665
3306
  }
@@ -2668,11 +3309,11 @@ class RepeatBehavior {
2668
3309
  const template = this.template;
2669
3310
  const location = this.location;
2670
3311
  const bindView = this.bindView;
2671
- const childContext = this.childContext;
3312
+ const controller = this.controller;
2672
3313
  let itemsLength = items.length;
2673
3314
  let views = this.views;
2674
3315
  let viewsLength = views.length;
2675
- if (itemsLength === 0 || templateChanged || !this.options.recycle) {
3316
+ if (itemsLength === 0 || templateChanged || !this.directive.options.recycle) {
2676
3317
  // all views need to be removed
2677
3318
  HTMLView.disposeContiguousBatch(views);
2678
3319
  viewsLength = 0;
@@ -2682,7 +3323,7 @@ class RepeatBehavior {
2682
3323
  this.views = views = new Array(itemsLength);
2683
3324
  for (let i = 0; i < itemsLength; ++i) {
2684
3325
  const view = template.create();
2685
- bindView(view, items, i, childContext);
3326
+ bindView(view, items, i, controller);
2686
3327
  views[i] = view;
2687
3328
  view.insertBefore(location);
2688
3329
  }
@@ -2693,11 +3334,11 @@ class RepeatBehavior {
2693
3334
  for (; i < itemsLength; ++i) {
2694
3335
  if (i < viewsLength) {
2695
3336
  const view = views[i];
2696
- bindView(view, items, i, childContext);
3337
+ bindView(view, items, i, controller);
2697
3338
  }
2698
3339
  else {
2699
3340
  const view = template.create();
2700
- bindView(view, items, i, childContext);
3341
+ bindView(view, items, i, controller);
2701
3342
  views.push(view);
2702
3343
  view.insertBefore(location);
2703
3344
  }
@@ -2722,17 +3363,15 @@ class RepeatBehavior {
2722
3363
  class RepeatDirective {
2723
3364
  /**
2724
3365
  * Creates an instance of RepeatDirective.
2725
- * @param itemsBinding - The binding that provides the array to render.
3366
+ * @param dataBinding - The binding that provides the array to render.
2726
3367
  * @param templateBinding - The template binding used to obtain a template to render for each item in the array.
2727
3368
  * @param options - Options used to turn on special repeat features.
2728
3369
  */
2729
- constructor(itemsBinding, templateBinding, options) {
2730
- this.itemsBinding = itemsBinding;
3370
+ constructor(dataBinding, templateBinding, options) {
3371
+ this.dataBinding = dataBinding;
2731
3372
  this.templateBinding = templateBinding;
2732
3373
  this.options = options;
2733
3374
  ArrayObserver.enable();
2734
- this.isItemsBindingVolatile = Observable.isVolatileBinding(itemsBinding);
2735
- this.isTemplateBindingVolatile = Observable.isVolatileBinding(templateBinding);
2736
3375
  }
2737
3376
  /**
2738
3377
  * Creates a placeholder string based on the directive's index within the template.
@@ -2745,24 +3384,23 @@ class RepeatDirective {
2745
3384
  * Creates a behavior for the provided target node.
2746
3385
  * @param target - The node instance to create the behavior for.
2747
3386
  */
2748
- createBehavior(targets) {
2749
- return new RepeatBehavior(targets[this.nodeId], this.itemsBinding, this.isItemsBindingVolatile, this.templateBinding, this.isTemplateBindingVolatile, this.options);
3387
+ createBehavior() {
3388
+ return new RepeatBehavior(this);
2750
3389
  }
2751
3390
  }
2752
3391
  HTMLDirective.define(RepeatDirective);
2753
3392
  /**
2754
3393
  * A directive that enables list rendering.
2755
- * @param itemsBinding - The array to render.
2756
- * @param templateOrTemplateBinding - The template or a template binding used obtain a template
3394
+ * @param items - The array to render.
3395
+ * @param template - The template or a template binding used obtain a template
2757
3396
  * to render for each item in the array.
2758
3397
  * @param options - Options used to turn on special repeat features.
2759
3398
  * @public
2760
3399
  */
2761
- function repeat(itemsBinding, templateOrTemplateBinding, options = defaultRepeatOptions) {
2762
- const templateBinding = isFunction(templateOrTemplateBinding)
2763
- ? templateOrTemplateBinding
2764
- : () => templateOrTemplateBinding;
2765
- return new RepeatDirective(itemsBinding, templateBinding, options);
3400
+ function repeat(items, template, options = defaultRepeatOptions) {
3401
+ const dataBinding = normalizeBinding(items);
3402
+ const templateBinding = normalizeBinding(template);
3403
+ return new RepeatDirective(dataBinding, templateBinding, Object.assign(Object.assign({}, defaultRepeatOptions), options));
2766
3404
  }
2767
3405
 
2768
3406
  const selectElements = (value) => value.nodeType === 1;
@@ -2781,9 +3419,15 @@ const elements = (selector) => selector
2781
3419
  * Internally used by the SlottedDirective and the ChildrenDirective.
2782
3420
  */
2783
3421
  class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2784
- constructor() {
2785
- super(...arguments);
2786
- this.sourceProperty = `${this.id}-s`;
3422
+ /**
3423
+ * The unique id of the factory.
3424
+ */
3425
+ get id() {
3426
+ return this._id;
3427
+ }
3428
+ set id(value) {
3429
+ this._id = value;
3430
+ this._controllerProperty = `${value}-c`;
2787
3431
  }
2788
3432
  /**
2789
3433
  * Bind this behavior to the source.
@@ -2791,11 +3435,12 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2791
3435
  * @param context - The execution context that the binding is operating within.
2792
3436
  * @param targets - The targets that behaviors in a view can attach to.
2793
3437
  */
2794
- bind(source, context, targets) {
2795
- const target = targets[this.nodeId];
2796
- target[this.sourceProperty] = source;
2797
- this.updateTarget(source, this.computeNodes(target));
3438
+ bind(controller) {
3439
+ const target = controller.targets[this.targetNodeId];
3440
+ target[this._controllerProperty] = controller;
3441
+ this.updateTarget(controller.source, this.computeNodes(target));
2798
3442
  this.observe(target);
3443
+ controller.onUnbind(this);
2799
3444
  }
2800
3445
  /**
2801
3446
  * Unbinds this behavior from the source.
@@ -2803,11 +3448,11 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2803
3448
  * @param context - The execution context that the binding is operating within.
2804
3449
  * @param targets - The targets that behaviors in a view can attach to.
2805
3450
  */
2806
- unbind(source, context, targets) {
2807
- const target = targets[this.nodeId];
2808
- this.updateTarget(source, emptyArray);
3451
+ unbind(controller) {
3452
+ const target = controller.targets[this.targetNodeId];
3453
+ this.updateTarget(controller.source, emptyArray);
2809
3454
  this.disconnect(target);
2810
- target[this.sourceProperty] = null;
3455
+ target[this._controllerProperty] = null;
2811
3456
  }
2812
3457
  /**
2813
3458
  * Gets the data source for the target.
@@ -2815,7 +3460,7 @@ class NodeObservationDirective extends StatelessAttachedAttributeDirective {
2815
3460
  * @returns The source.
2816
3461
  */
2817
3462
  getSource(target) {
2818
- return target[this.sourceProperty];
3463
+ return target[this._controllerProperty].source;
2819
3464
  }
2820
3465
  /**
2821
3466
  * Updates the source property with the computed nodes.
@@ -2911,9 +3556,13 @@ class ChildrenDirective extends NodeObservationDirective {
2911
3556
  * @param target - The target to observe.
2912
3557
  */
2913
3558
  observe(target) {
2914
- var _a;
2915
- const observer = (_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = new MutationObserver(this.handleEvent));
2916
- observer.target = target;
3559
+ let observer = target[this.observerProperty];
3560
+ if (!observer) {
3561
+ observer = new MutationObserver(this.handleEvent);
3562
+ observer.toJSON = noop;
3563
+ observer.target = target;
3564
+ target[this.observerProperty] = observer;
3565
+ }
2917
3566
  observer.observe(target, this.options);
2918
3567
  }
2919
3568
  /**
@@ -2954,6 +3603,16 @@ function children(propertyOrOptions) {
2954
3603
 
2955
3604
  const booleanMode = "boolean";
2956
3605
  const reflectMode = "reflect";
3606
+ /**
3607
+ * Metadata used to configure a custom attribute's behavior.
3608
+ * @public
3609
+ */
3610
+ const AttributeConfiguration = Object.freeze({
3611
+ /**
3612
+ * Locates all attribute configurations associated with a type.
3613
+ */
3614
+ locate: createMetadataLocator(),
3615
+ });
2957
3616
  /**
2958
3617
  * A {@link ValueConverter} that converts to and from `boolean` values.
2959
3618
  * @remarks
@@ -2974,6 +3633,20 @@ const booleanConverter = {
2974
3633
  : true;
2975
3634
  },
2976
3635
  };
3636
+ /**
3637
+ * A {@link ValueConverter} that converts to and from `boolean` values. `null`, `undefined`, `""`, and `void` values are converted to `null`.
3638
+ * @public
3639
+ */
3640
+ const nullableBooleanConverter = {
3641
+ toView(value) {
3642
+ return typeof value === "boolean" ? value.toString() : "";
3643
+ },
3644
+ fromView(value) {
3645
+ return [null, undefined, void 0].includes(value)
3646
+ ? null
3647
+ : booleanConverter.fromView(value);
3648
+ },
3649
+ };
2977
3650
  function toNumber(value) {
2978
3651
  if (value === null || value === undefined) {
2979
3652
  return null;
@@ -3091,7 +3764,7 @@ class AttributeDefinition {
3091
3764
  */
3092
3765
  static collect(Owner, ...attributeLists) {
3093
3766
  const attributes = [];
3094
- attributeLists.push(Owner.attributes);
3767
+ attributeLists.push(AttributeConfiguration.locate(Owner));
3095
3768
  for (let i = 0, ii = attributeLists.length; i < ii; ++i) {
3096
3769
  const list = attributeLists[i];
3097
3770
  if (list === void 0) {
@@ -3121,9 +3794,7 @@ function attr(configOrTarget, prop) {
3121
3794
  // - @attr({...opts})
3122
3795
  config.property = $prop;
3123
3796
  }
3124
- const attributes = $target.constructor.attributes ||
3125
- ($target.constructor.attributes = []);
3126
- attributes.push(config);
3797
+ AttributeConfiguration.locate($target.constructor).push(config);
3127
3798
  }
3128
3799
  if (arguments.length > 1) {
3129
3800
  // Non invocation:
@@ -3141,25 +3812,24 @@ function attr(configOrTarget, prop) {
3141
3812
 
3142
3813
  const defaultShadowOptions = { mode: "open" };
3143
3814
  const defaultElementOptions = {};
3144
- const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */, () => createTypeRegistry());
3815
+ const fastElementBaseTypes = new Set();
3816
+ const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
3145
3817
  /**
3146
3818
  * Defines metadata for a FASTElement.
3147
3819
  * @public
3148
3820
  */
3149
3821
  class FASTElementDefinition {
3150
- /**
3151
- * Creates an instance of FASTElementDefinition.
3152
- * @param type - The type this definition is being created for.
3153
- * @param nameOrConfig - The name of the element to define or a config object
3154
- * that describes the element to define.
3155
- */
3156
3822
  constructor(type, nameOrConfig = type.definition) {
3823
+ var _a;
3824
+ this.platformDefined = false;
3157
3825
  if (isString(nameOrConfig)) {
3158
3826
  nameOrConfig = { name: nameOrConfig };
3159
3827
  }
3160
3828
  this.type = type;
3161
3829
  this.name = nameOrConfig.name;
3162
3830
  this.template = nameOrConfig.template;
3831
+ this.registry = (_a = nameOrConfig.registry) !== null && _a !== void 0 ? _a : customElements;
3832
+ const proto = type.prototype;
3163
3833
  const attributes = AttributeDefinition.collect(type, nameOrConfig.attributes);
3164
3834
  const observedAttributes = new Array(attributes.length);
3165
3835
  const propertyLookup = {};
@@ -3169,9 +3839,13 @@ class FASTElementDefinition {
3169
3839
  observedAttributes[i] = current.attribute;
3170
3840
  propertyLookup[current.name] = current;
3171
3841
  attributeLookup[current.attribute] = current;
3842
+ Observable.defineProperty(proto, current);
3172
3843
  }
3844
+ Reflect.defineProperty(type, "observedAttributes", {
3845
+ value: observedAttributes,
3846
+ enumerable: true,
3847
+ });
3173
3848
  this.attributes = attributes;
3174
- this.observedAttributes = observedAttributes;
3175
3849
  this.propertyLookup = propertyLookup;
3176
3850
  this.attributeLookup = attributeLookup;
3177
3851
  this.shadowOptions =
@@ -3184,20 +3858,14 @@ class FASTElementDefinition {
3184
3858
  nameOrConfig.elementOptions === void 0
3185
3859
  ? defaultElementOptions
3186
3860
  : Object.assign(Object.assign({}, defaultElementOptions), nameOrConfig.elementOptions);
3187
- this.styles =
3188
- nameOrConfig.styles === void 0
3189
- ? void 0
3190
- : Array.isArray(nameOrConfig.styles)
3191
- ? new ElementStyles(nameOrConfig.styles)
3192
- : nameOrConfig.styles instanceof ElementStyles
3193
- ? nameOrConfig.styles
3194
- : new ElementStyles([nameOrConfig.styles]);
3861
+ this.styles = ElementStyles.normalize(nameOrConfig.styles);
3862
+ fastElementRegistry.register(this);
3195
3863
  }
3196
3864
  /**
3197
3865
  * Indicates if this element has been defined in at least one registry.
3198
3866
  */
3199
3867
  get isDefined() {
3200
- return !!fastElementRegistry.getByType(this.type);
3868
+ return this.platformDefined;
3201
3869
  }
3202
3870
  /**
3203
3871
  * Defines a custom element based on this definition.
@@ -3205,24 +3873,35 @@ class FASTElementDefinition {
3205
3873
  * @remarks
3206
3874
  * This operation is idempotent per registry.
3207
3875
  */
3208
- define(registry = customElements) {
3876
+ define(registry = this.registry) {
3209
3877
  const type = this.type;
3210
- if (fastElementRegistry.register(this)) {
3211
- const attributes = this.attributes;
3212
- const proto = type.prototype;
3213
- for (let i = 0, ii = attributes.length; i < ii; ++i) {
3214
- Observable.defineProperty(proto, attributes[i]);
3215
- }
3216
- Reflect.defineProperty(type, "observedAttributes", {
3217
- value: this.observedAttributes,
3218
- enumerable: true,
3219
- });
3220
- }
3221
3878
  if (!registry.get(this.name)) {
3879
+ this.platformDefined = true;
3222
3880
  registry.define(this.name, type, this.elementOptions);
3223
3881
  }
3224
3882
  return this;
3225
3883
  }
3884
+ /**
3885
+ * Creates an instance of FASTElementDefinition.
3886
+ * @param type - The type this definition is being created for.
3887
+ * @param nameOrDef - The name of the element to define or a config object
3888
+ * that describes the element to define.
3889
+ */
3890
+ static compose(type, nameOrDef) {
3891
+ if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
3892
+ return new FASTElementDefinition(class extends type {
3893
+ }, nameOrDef);
3894
+ }
3895
+ return new FASTElementDefinition(type, nameOrDef);
3896
+ }
3897
+ /**
3898
+ * Registers a FASTElement base type.
3899
+ * @param type - The type to register as a base type.
3900
+ * @internal
3901
+ */
3902
+ static registerBaseType(type) {
3903
+ fastElementBaseTypes.add(type);
3904
+ }
3226
3905
  }
3227
3906
  /**
3228
3907
  * Gets the element definition associated with the specified type.
@@ -3235,22 +3914,23 @@ FASTElementDefinition.getByType = fastElementRegistry.getByType;
3235
3914
  */
3236
3915
  FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
3237
3916
 
3238
- const shadowRoots = new WeakMap();
3239
3917
  const defaultEventOptions = {
3240
3918
  bubbles: true,
3241
3919
  composed: true,
3242
3920
  cancelable: true,
3243
3921
  };
3922
+ const isConnectedPropertyName = "isConnected";
3923
+ const shadowRoots = new WeakMap();
3244
3924
  function getShadowRoot(element) {
3245
3925
  var _a, _b;
3246
3926
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
3247
3927
  }
3248
- const isConnectedPropertyName = "isConnected";
3928
+ let elementControllerStrategy;
3249
3929
  /**
3250
3930
  * Controls the lifecycle and rendering of a `FASTElement`.
3251
3931
  * @public
3252
3932
  */
3253
- class Controller extends PropertyChangeNotifier {
3933
+ class ElementController extends PropertyChangeNotifier {
3254
3934
  /**
3255
3935
  * Creates a Controller to control the specified element.
3256
3936
  * @param element - The element to be controlled by this controller.
@@ -3261,12 +3941,18 @@ class Controller extends PropertyChangeNotifier {
3261
3941
  constructor(element, definition) {
3262
3942
  super(element);
3263
3943
  this.boundObservables = null;
3264
- this.behaviors = null;
3265
3944
  this.needsInitialization = true;
3266
3945
  this.hasExistingShadowRoot = false;
3267
3946
  this._template = null;
3268
- this._styles = null;
3269
- this._isConnected = false;
3947
+ this.stage = 3 /* Stages.disconnected */;
3948
+ /**
3949
+ * A guard against connecting behaviors multiple times
3950
+ * during connect in scenarios where a behavior adds
3951
+ * another behavior during it's connectedCallback
3952
+ */
3953
+ this.guardBehaviorConnection = false;
3954
+ this.behaviors = null;
3955
+ this._mainStyles = null;
3270
3956
  /**
3271
3957
  * This allows Observable.getNotifier(...) to return the Controller
3272
3958
  * when the notifier for the Controller itself is being requested. The
@@ -3282,7 +3968,12 @@ class Controller extends PropertyChangeNotifier {
3282
3968
  * If `null` then the element is managing its own rendering.
3283
3969
  */
3284
3970
  this.view = null;
3285
- this.element = element;
3971
+ /**
3972
+ * Opts out of JSON stringification.
3973
+ * @internal
3974
+ */
3975
+ this.toJSON = noop;
3976
+ this.source = element;
3286
3977
  this.definition = definition;
3287
3978
  const shadowOptions = definition.shadowOptions;
3288
3979
  if (shadowOptions !== void 0) {
@@ -3320,11 +4011,7 @@ class Controller extends PropertyChangeNotifier {
3320
4011
  */
3321
4012
  get isConnected() {
3322
4013
  Observable.track(this, isConnectedPropertyName);
3323
- return this._isConnected;
3324
- }
3325
- setIsConnected(value) {
3326
- this._isConnected = value;
3327
- Observable.notify(this, isConnectedPropertyName);
4014
+ return this.stage === 1 /* Stages.connected */;
3328
4015
  }
3329
4016
  /**
3330
4017
  * Gets/sets the template used to render the component.
@@ -3336,9 +4023,9 @@ class Controller extends PropertyChangeNotifier {
3336
4023
  // 1. Template overrides take top precedence.
3337
4024
  if (this._template === null) {
3338
4025
  const definition = this.definition;
3339
- if (this.element.resolveTemplate) {
4026
+ if (this.source.resolveTemplate) {
3340
4027
  // 2. Allow for element instance overrides next.
3341
- this._template = this.element.resolveTemplate();
4028
+ this._template = this.source.resolveTemplate();
3342
4029
  }
3343
4030
  else if (definition.template) {
3344
4031
  // 3. Default to the static definition.
@@ -3357,56 +4044,104 @@ class Controller extends PropertyChangeNotifier {
3357
4044
  }
3358
4045
  }
3359
4046
  /**
3360
- * Gets/sets the primary styles used for the component.
3361
- * @remarks
3362
- * This value can only be accurately read after connect but can be set at any time.
4047
+ * The main set of styles used for the component, independent
4048
+ * of any dynamically added styles.
3363
4049
  */
3364
- get styles() {
4050
+ get mainStyles() {
3365
4051
  var _a;
3366
4052
  // 1. Styles overrides take top precedence.
3367
- if (this._styles === null) {
4053
+ if (this._mainStyles === null) {
3368
4054
  const definition = this.definition;
3369
- if (this.element.resolveStyles) {
4055
+ if (this.source.resolveStyles) {
3370
4056
  // 2. Allow for element instance overrides next.
3371
- this._styles = this.element.resolveStyles();
4057
+ this._mainStyles = this.source.resolveStyles();
3372
4058
  }
3373
4059
  else if (definition.styles) {
3374
4060
  // 3. Default to the static definition.
3375
- this._styles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
4061
+ this._mainStyles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
3376
4062
  }
3377
4063
  }
3378
- return this._styles;
4064
+ return this._mainStyles;
3379
4065
  }
3380
- set styles(value) {
3381
- if (this._styles === value) {
4066
+ set mainStyles(value) {
4067
+ if (this._mainStyles === value) {
3382
4068
  return;
3383
4069
  }
3384
- if (this._styles !== null) {
3385
- this.removeStyles(this._styles);
4070
+ if (this._mainStyles !== null) {
4071
+ this.removeStyles(this._mainStyles);
3386
4072
  }
3387
- this._styles = value;
4073
+ this._mainStyles = value;
3388
4074
  if (!this.needsInitialization) {
3389
4075
  this.addStyles(value);
3390
4076
  }
3391
4077
  }
4078
+ /**
4079
+ * Adds the behavior to the component.
4080
+ * @param behavior - The behavior to add.
4081
+ */
4082
+ addBehavior(behavior) {
4083
+ var _a, _b;
4084
+ const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
4085
+ const count = (_b = targetBehaviors.get(behavior)) !== null && _b !== void 0 ? _b : 0;
4086
+ if (count === 0) {
4087
+ targetBehaviors.set(behavior, 1);
4088
+ behavior.addedCallback && behavior.addedCallback(this);
4089
+ if (behavior.connectedCallback &&
4090
+ !this.guardBehaviorConnection &&
4091
+ (this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
4092
+ behavior.connectedCallback(this);
4093
+ }
4094
+ }
4095
+ else {
4096
+ targetBehaviors.set(behavior, count + 1);
4097
+ }
4098
+ }
4099
+ /**
4100
+ * Removes the behavior from the component.
4101
+ * @param behavior - The behavior to remove.
4102
+ * @param force - Forces removal even if this behavior was added more than once.
4103
+ */
4104
+ removeBehavior(behavior, force = false) {
4105
+ const targetBehaviors = this.behaviors;
4106
+ if (targetBehaviors === null) {
4107
+ return;
4108
+ }
4109
+ const count = targetBehaviors.get(behavior);
4110
+ if (count === void 0) {
4111
+ return;
4112
+ }
4113
+ if (count === 1 || force) {
4114
+ targetBehaviors.delete(behavior);
4115
+ if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
4116
+ behavior.disconnectedCallback(this);
4117
+ }
4118
+ behavior.removedCallback && behavior.removedCallback(this);
4119
+ }
4120
+ else {
4121
+ targetBehaviors.set(behavior, count - 1);
4122
+ }
4123
+ }
3392
4124
  /**
3393
4125
  * Adds styles to this element. Providing an HTMLStyleElement will attach the element instance to the shadowRoot.
3394
4126
  * @param styles - The styles to add.
3395
4127
  */
3396
4128
  addStyles(styles) {
4129
+ var _a;
3397
4130
  if (!styles) {
3398
4131
  return;
3399
4132
  }
3400
- const target = getShadowRoot(this.element) ||
3401
- this.element.getRootNode();
4133
+ const source = this.source;
3402
4134
  if (styles instanceof HTMLElement) {
4135
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
3403
4136
  target.append(styles);
3404
4137
  }
3405
- else if (!styles.isAttachedTo(target)) {
4138
+ else if (!styles.isAttachedTo(source)) {
3406
4139
  const sourceBehaviors = styles.behaviors;
3407
- styles.addStylesTo(target);
4140
+ styles.addStylesTo(source);
3408
4141
  if (sourceBehaviors !== null) {
3409
- this.addBehaviors(sourceBehaviors);
4142
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
4143
+ this.addBehavior(sourceBehaviors[i]);
4144
+ }
3410
4145
  }
3411
4146
  }
3412
4147
  }
@@ -3415,121 +4150,82 @@ class Controller extends PropertyChangeNotifier {
3415
4150
  * @param styles - the styles to remove.
3416
4151
  */
3417
4152
  removeStyles(styles) {
4153
+ var _a;
3418
4154
  if (!styles) {
3419
4155
  return;
3420
4156
  }
3421
- const target = getShadowRoot(this.element) ||
3422
- this.element.getRootNode();
4157
+ const source = this.source;
3423
4158
  if (styles instanceof HTMLElement) {
4159
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
3424
4160
  target.removeChild(styles);
3425
4161
  }
3426
- else if (styles.isAttachedTo(target)) {
4162
+ else if (styles.isAttachedTo(source)) {
3427
4163
  const sourceBehaviors = styles.behaviors;
3428
- styles.removeStylesFrom(target);
4164
+ styles.removeStylesFrom(source);
3429
4165
  if (sourceBehaviors !== null) {
3430
- this.removeBehaviors(sourceBehaviors);
3431
- }
3432
- }
3433
- }
3434
- /**
3435
- * Adds behaviors to this element.
3436
- * @param behaviors - The behaviors to add.
3437
- */
3438
- addBehaviors(behaviors) {
3439
- var _a;
3440
- const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
3441
- const length = behaviors.length;
3442
- const behaviorsToBind = [];
3443
- for (let i = 0; i < length; ++i) {
3444
- const behavior = behaviors[i];
3445
- if (targetBehaviors.has(behavior)) {
3446
- targetBehaviors.set(behavior, targetBehaviors.get(behavior) + 1);
3447
- }
3448
- else {
3449
- targetBehaviors.set(behavior, 1);
3450
- behaviorsToBind.push(behavior);
3451
- }
3452
- }
3453
- if (this._isConnected) {
3454
- const element = this.element;
3455
- const context = ExecutionContext.default;
3456
- for (let i = 0; i < behaviorsToBind.length; ++i) {
3457
- behaviorsToBind[i].bind(element, context);
4166
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
4167
+ this.addBehavior(sourceBehaviors[i]);
4168
+ }
3458
4169
  }
3459
4170
  }
3460
4171
  }
3461
4172
  /**
3462
- * Removes behaviors from this element.
3463
- * @param behaviors - The behaviors to remove.
3464
- * @param force - Forces unbinding of behaviors.
4173
+ * Runs connected lifecycle behavior on the associated element.
3465
4174
  */
3466
- removeBehaviors(behaviors, force = false) {
3467
- const targetBehaviors = this.behaviors;
3468
- if (targetBehaviors === null) {
4175
+ connect() {
4176
+ if (this.stage !== 3 /* Stages.disconnected */) {
3469
4177
  return;
3470
4178
  }
3471
- const length = behaviors.length;
3472
- const behaviorsToUnbind = [];
3473
- for (let i = 0; i < length; ++i) {
3474
- const behavior = behaviors[i];
3475
- if (targetBehaviors.has(behavior)) {
3476
- const count = targetBehaviors.get(behavior) - 1;
3477
- count === 0 || force
3478
- ? targetBehaviors.delete(behavior) && behaviorsToUnbind.push(behavior)
3479
- : targetBehaviors.set(behavior, count);
4179
+ this.stage = 0 /* Stages.connecting */;
4180
+ // If we have any observables that were bound, re-apply their values.
4181
+ if (this.boundObservables !== null) {
4182
+ const element = this.source;
4183
+ const boundObservables = this.boundObservables;
4184
+ const propertyNames = Object.keys(boundObservables);
4185
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
4186
+ const propertyName = propertyNames[i];
4187
+ element[propertyName] = boundObservables[propertyName];
3480
4188
  }
4189
+ this.boundObservables = null;
3481
4190
  }
3482
- if (this._isConnected) {
3483
- const element = this.element;
3484
- const context = ExecutionContext.default;
3485
- for (let i = 0; i < behaviorsToUnbind.length; ++i) {
3486
- behaviorsToUnbind[i].unbind(element, context);
4191
+ const behaviors = this.behaviors;
4192
+ if (behaviors !== null) {
4193
+ this.guardBehaviorConnection = true;
4194
+ for (const key of behaviors.keys()) {
4195
+ key.connectedCallback && key.connectedCallback(this);
3487
4196
  }
4197
+ this.guardBehaviorConnection = false;
3488
4198
  }
3489
- }
3490
- /**
3491
- * Runs connected lifecycle behavior on the associated element.
3492
- */
3493
- onConnectedCallback() {
3494
- if (this._isConnected) {
3495
- return;
3496
- }
3497
- const element = this.element;
3498
- const context = ExecutionContext.default;
3499
4199
  if (this.needsInitialization) {
3500
- this.finishInitialization();
4200
+ this.renderTemplate(this.template);
4201
+ this.addStyles(this.mainStyles);
4202
+ this.needsInitialization = false;
3501
4203
  }
3502
4204
  else if (this.view !== null) {
3503
- this.view.bind(element, context);
3504
- }
3505
- const behaviors = this.behaviors;
3506
- if (behaviors !== null) {
3507
- for (const behavior of behaviors.keys()) {
3508
- behavior.bind(element, context);
3509
- }
4205
+ this.view.bind(this.source);
3510
4206
  }
3511
- this.setIsConnected(true);
4207
+ this.stage = 1 /* Stages.connected */;
4208
+ Observable.notify(this, isConnectedPropertyName);
3512
4209
  }
3513
4210
  /**
3514
4211
  * Runs disconnected lifecycle behavior on the associated element.
3515
4212
  */
3516
- onDisconnectedCallback() {
3517
- if (!this._isConnected) {
4213
+ disconnect() {
4214
+ if (this.stage !== 1 /* Stages.connected */) {
3518
4215
  return;
3519
4216
  }
3520
- this.setIsConnected(false);
3521
- const view = this.view;
3522
- if (view !== null) {
3523
- view.unbind();
4217
+ this.stage = 2 /* Stages.disconnecting */;
4218
+ Observable.notify(this, isConnectedPropertyName);
4219
+ if (this.view !== null) {
4220
+ this.view.unbind();
3524
4221
  }
3525
4222
  const behaviors = this.behaviors;
3526
4223
  if (behaviors !== null) {
3527
- const element = this.element;
3528
- const context = ExecutionContext.default;
3529
- for (const behavior of behaviors.keys()) {
3530
- behavior.unbind(element, context);
4224
+ for (const key of behaviors.keys()) {
4225
+ key.disconnectedCallback && key.disconnectedCallback(this);
3531
4226
  }
3532
4227
  }
4228
+ this.stage = 3 /* Stages.disconnected */;
3533
4229
  }
3534
4230
  /**
3535
4231
  * Runs the attribute changed callback for the associated element.
@@ -3540,7 +4236,7 @@ class Controller extends PropertyChangeNotifier {
3540
4236
  onAttributeChangedCallback(name, oldValue, newValue) {
3541
4237
  const attrDef = this.definition.attributeLookup[name];
3542
4238
  if (attrDef !== void 0) {
3543
- attrDef.onAttributeChangedCallback(this.element, newValue);
4239
+ attrDef.onAttributeChangedCallback(this.source, newValue);
3544
4240
  }
3545
4241
  }
3546
4242
  /**
@@ -3552,33 +4248,17 @@ class Controller extends PropertyChangeNotifier {
3552
4248
  * Only emits events if connected.
3553
4249
  */
3554
4250
  emit(type, detail, options) {
3555
- if (this._isConnected) {
3556
- return this.element.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
4251
+ if (this.stage === 1 /* Stages.connected */) {
4252
+ return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
3557
4253
  }
3558
4254
  return false;
3559
4255
  }
3560
- finishInitialization() {
3561
- const element = this.element;
3562
- const boundObservables = this.boundObservables;
3563
- // If we have any observables that were bound, re-apply their values.
3564
- if (boundObservables !== null) {
3565
- const propertyNames = Object.keys(boundObservables);
3566
- for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
3567
- const propertyName = propertyNames[i];
3568
- element[propertyName] = boundObservables[propertyName];
3569
- }
3570
- this.boundObservables = null;
3571
- }
3572
- this.renderTemplate(this.template);
3573
- this.addStyles(this.styles);
3574
- this.needsInitialization = false;
3575
- }
3576
4256
  renderTemplate(template) {
3577
4257
  var _a;
3578
- const element = this.element;
3579
4258
  // When getting the host to render to, we start by looking
3580
4259
  // up the shadow root. If there isn't one, then that means
3581
4260
  // we're doing a Light DOM render to the element's direct children.
4261
+ const element = this.source;
3582
4262
  const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
3583
4263
  if (this.view !== null) {
3584
4264
  // If there's already a view, we need to unbind and remove through dispose.
@@ -3595,6 +4275,8 @@ class Controller extends PropertyChangeNotifier {
3595
4275
  if (template) {
3596
4276
  // If a new template was provided, render it.
3597
4277
  this.view = template.render(element, host, element);
4278
+ this.view.sourceLifetime =
4279
+ SourceLifetime.coupled;
3598
4280
  }
3599
4281
  }
3600
4282
  /**
@@ -3614,31 +4296,146 @@ class Controller extends PropertyChangeNotifier {
3614
4296
  if (definition === void 0) {
3615
4297
  throw FAST.error(1401 /* Message.missingElementDefinition */);
3616
4298
  }
3617
- return (element.$fastController = new Controller(element, definition));
4299
+ return (element.$fastController = new elementControllerStrategy(element, definition));
4300
+ }
4301
+ /**
4302
+ * Sets the strategy that ElementController.forCustomElement uses to construct
4303
+ * ElementController instances for an element.
4304
+ * @param strategy - The strategy to use.
4305
+ */
4306
+ static setStrategy(strategy) {
4307
+ elementControllerStrategy = strategy;
4308
+ }
4309
+ }
4310
+ // Set default strategy for ElementController
4311
+ ElementController.setStrategy(ElementController);
4312
+ /**
4313
+ * Converts a styleTarget into the operative target. When the provided target is an Element
4314
+ * that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
4315
+ * it will return the root node for the element.
4316
+ * @param target
4317
+ * @returns
4318
+ */
4319
+ function normalizeStyleTarget(target) {
4320
+ var _a;
4321
+ if ("adoptedStyleSheets" in target) {
4322
+ return target;
4323
+ }
4324
+ else {
4325
+ return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
4326
+ }
4327
+ }
4328
+ // Default StyleStrategy implementations are defined in this module because they
4329
+ // require access to element shadowRoots, and we don't want to leak shadowRoot
4330
+ // objects out of this module.
4331
+ /**
4332
+ * https://wicg.github.io/construct-stylesheets/
4333
+ * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
4334
+ *
4335
+ * @internal
4336
+ */
4337
+ class AdoptedStyleSheetsStrategy {
4338
+ constructor(styles) {
4339
+ const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
4340
+ this.sheets = styles.map((x) => {
4341
+ if (x instanceof CSSStyleSheet) {
4342
+ return x;
4343
+ }
4344
+ let sheet = styleSheetCache.get(x);
4345
+ if (sheet === void 0) {
4346
+ sheet = new CSSStyleSheet();
4347
+ sheet.replaceSync(x);
4348
+ styleSheetCache.set(x, sheet);
4349
+ }
4350
+ return sheet;
4351
+ });
4352
+ }
4353
+ addStylesTo(target) {
4354
+ const t = normalizeStyleTarget(target);
4355
+ t.adoptedStyleSheets = [...t.adoptedStyleSheets, ...this.sheets];
4356
+ }
4357
+ removeStylesFrom(target) {
4358
+ const t = normalizeStyleTarget(target);
4359
+ const sheets = this.sheets;
4360
+ t.adoptedStyleSheets = t.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
4361
+ }
4362
+ }
4363
+ AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
4364
+ let id = 0;
4365
+ const nextStyleId = () => `fast-${++id}`;
4366
+ function usableStyleTarget(target) {
4367
+ return target === document ? document.body : target;
4368
+ }
4369
+ /**
4370
+ * @internal
4371
+ */
4372
+ class StyleElementStrategy {
4373
+ constructor(styles) {
4374
+ this.styles = styles;
4375
+ this.styleClass = nextStyleId();
4376
+ }
4377
+ addStylesTo(target) {
4378
+ target = usableStyleTarget(normalizeStyleTarget(target));
4379
+ const styles = this.styles;
4380
+ const styleClass = this.styleClass;
4381
+ for (let i = 0; i < styles.length; i++) {
4382
+ const element = document.createElement("style");
4383
+ element.innerHTML = styles[i];
4384
+ element.className = styleClass;
4385
+ target.append(element);
4386
+ }
4387
+ }
4388
+ removeStylesFrom(target) {
4389
+ target = usableStyleTarget(normalizeStyleTarget(target));
4390
+ const styles = target.querySelectorAll(`.${this.styleClass}`);
4391
+ styles[0].parentNode;
4392
+ for (let i = 0, ii = styles.length; i < ii; ++i) {
4393
+ target.removeChild(styles[i]);
4394
+ }
3618
4395
  }
3619
4396
  }
4397
+ ElementStyles.setDefaultStrategy(ElementStyles.supportsAdoptedStyleSheets
4398
+ ? AdoptedStyleSheetsStrategy
4399
+ : StyleElementStrategy);
3620
4400
 
3621
4401
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3622
4402
  function createFASTElement(BaseType) {
3623
- return class extends BaseType {
4403
+ const type = class extends BaseType {
3624
4404
  constructor() {
3625
4405
  /* eslint-disable-next-line */
3626
4406
  super();
3627
- Controller.forCustomElement(this);
4407
+ ElementController.forCustomElement(this);
3628
4408
  }
3629
4409
  $emit(type, detail, options) {
3630
4410
  return this.$fastController.emit(type, detail, options);
3631
4411
  }
3632
4412
  connectedCallback() {
3633
- this.$fastController.onConnectedCallback();
4413
+ this.$fastController.connect();
3634
4414
  }
3635
4415
  disconnectedCallback() {
3636
- this.$fastController.onDisconnectedCallback();
4416
+ this.$fastController.disconnect();
3637
4417
  }
3638
4418
  attributeChangedCallback(name, oldValue, newValue) {
3639
4419
  this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
3640
4420
  }
3641
4421
  };
4422
+ FASTElementDefinition.registerBaseType(type);
4423
+ return type;
4424
+ }
4425
+ function compose(type, nameOrDef) {
4426
+ if (isFunction(type)) {
4427
+ return FASTElementDefinition.compose(type, nameOrDef);
4428
+ }
4429
+ return FASTElementDefinition.compose(this, type);
4430
+ }
4431
+ function define(type, nameOrDef) {
4432
+ if (isFunction(type)) {
4433
+ return FASTElementDefinition.compose(type, nameOrDef).define().type;
4434
+ }
4435
+ return FASTElementDefinition.compose(this, type).define().type;
4436
+ }
4437
+ function from(BaseType) {
4438
+ return createFASTElement(BaseType);
3642
4439
  }
3643
4440
  /**
3644
4441
  * A minimal base class for FASTElements that also provides
@@ -3651,26 +4448,19 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3651
4448
  * provided base type.
3652
4449
  * @param BaseType - The base element type to inherit from.
3653
4450
  */
3654
- from(BaseType) {
3655
- return createFASTElement(BaseType);
3656
- },
4451
+ from,
3657
4452
  /**
3658
4453
  * Defines a platform custom element based on the provided type and definition.
3659
4454
  * @param type - The custom element type to define.
3660
4455
  * @param nameOrDef - The name of the element to define or a definition object
3661
4456
  * that describes the element to define.
3662
4457
  */
3663
- define(type, nameOrDef) {
3664
- return this.metadata(type, nameOrDef).define().type;
3665
- },
4458
+ define,
3666
4459
  /**
3667
4460
  * Defines metadata for a FASTElement which can be used to later define the element.
3668
- * IMPORTANT: This API will be renamed to "compose" in a future beta.
3669
4461
  * @public
3670
4462
  */
3671
- metadata(type, nameOrDef) {
3672
- return new FASTElementDefinition(type, nameOrDef);
3673
- },
4463
+ compose,
3674
4464
  });
3675
4465
  /**
3676
4466
  * Decorator: Defines a platform custom element based on `FASTElement`.
@@ -3681,8 +4471,10 @@ const FASTElement = Object.assign(createFASTElement(HTMLElement), {
3681
4471
  function customElement(nameOrDef) {
3682
4472
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3683
4473
  return function (type) {
3684
- FASTElement.define(type, nameOrDef);
4474
+ define(type, nameOrDef);
3685
4475
  };
3686
4476
  }
3687
4477
 
3688
- 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 };
4478
+ DOM.setPolicy(DOMPolicy.create());
4479
+
4480
+ 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 };