@microsoft/fast-element 2.0.0-beta.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.eslintrc.json +1 -1
  2. package/CHANGELOG.json +512 -0
  3. package/CHANGELOG.md +180 -1
  4. package/README.md +1 -9
  5. package/api-extractor.context.json +14 -0
  6. package/api-extractor.di.json +14 -0
  7. package/dist/context/context.api.json +1068 -0
  8. package/dist/di/di.api.json +4929 -0
  9. package/dist/dts/binding/binding.d.ts +49 -0
  10. package/dist/dts/binding/normalize.d.ts +9 -0
  11. package/dist/dts/binding/one-time.d.ts +11 -0
  12. package/dist/dts/binding/one-way.d.ts +20 -0
  13. package/dist/dts/{templating/binding-signal.d.ts → binding/signal.d.ts} +19 -4
  14. package/dist/dts/{templating/binding-two-way.d.ts → binding/two-way.d.ts} +9 -5
  15. package/dist/dts/components/attributes.d.ts +6 -0
  16. package/dist/dts/components/element-controller.d.ts +104 -8
  17. package/dist/dts/components/element-hydration.d.ts +2 -0
  18. package/dist/dts/components/fast-definitions.d.ts +6 -0
  19. package/dist/dts/components/hydration.d.ts +56 -0
  20. package/dist/dts/components/install-hydration.d.ts +1 -0
  21. package/dist/dts/context.d.ts +29 -15
  22. package/dist/dts/di/di.d.ts +0 -5
  23. package/dist/dts/dom-policy.d.ts +83 -0
  24. package/dist/dts/dom.d.ts +100 -0
  25. package/dist/dts/hydration/target-builder.d.ts +63 -0
  26. package/dist/dts/index.d.ts +33 -26
  27. package/dist/dts/index.rollup.d.ts +0 -1
  28. package/dist/dts/index.rollup.debug.d.ts +0 -1
  29. package/dist/dts/interfaces.d.ts +32 -82
  30. package/dist/dts/metadata.d.ts +6 -5
  31. package/dist/dts/observation/arrays.d.ts +1 -1
  32. package/dist/dts/observation/observable.bench.d.ts +18 -0
  33. package/dist/dts/observation/observable.d.ts +5 -5
  34. package/dist/dts/pending-task.d.ts +19 -7
  35. package/dist/dts/platform.d.ts +11 -2
  36. package/dist/dts/polyfills.d.ts +0 -8
  37. package/dist/dts/styles/css-binding-directive.d.ts +60 -0
  38. package/dist/dts/styles/css.d.ts +9 -7
  39. package/dist/dts/styles/element-styles.d.ts +1 -14
  40. package/dist/dts/styles/host.d.ts +2 -5
  41. package/dist/dts/styles/style-strategy.d.ts +42 -0
  42. package/dist/dts/templating/compiler.d.ts +11 -13
  43. package/dist/dts/templating/{binding.d.ts → html-binding-directive.d.ts} +22 -42
  44. package/dist/dts/templating/html-directive.d.ts +44 -140
  45. package/dist/dts/templating/install-hydratable-view-templates.d.ts +1 -0
  46. package/dist/dts/templating/node-observation.d.ts +11 -1
  47. package/dist/dts/templating/ref.d.ts +4 -0
  48. package/dist/dts/templating/render.bench.d.ts +3 -0
  49. package/dist/dts/templating/render.d.ts +49 -9
  50. package/dist/dts/templating/repeat-basic-reverse.bench.d.ts +3 -0
  51. package/dist/dts/templating/repeat-basic-shift.bench.d.ts +3 -0
  52. package/dist/dts/templating/repeat.d.ts +31 -9
  53. package/dist/dts/templating/template.d.ts +97 -12
  54. package/dist/dts/templating/view.d.ts +149 -26
  55. package/dist/dts/templating/when-basic.bench.d.ts +3 -0
  56. package/dist/dts/templating/when-conditional.bench.d.ts +3 -0
  57. package/dist/dts/templating/when-switch.bench.d.ts +3 -0
  58. package/dist/dts/templating/when.d.ts +3 -1
  59. package/dist/dts/testing/fakes.d.ts +12 -1
  60. package/dist/dts/tsdoc-metadata.json +1 -1
  61. package/dist/dts/utilities.d.ts +55 -1
  62. package/dist/esm/binding/binding.js +18 -0
  63. package/dist/esm/binding/normalize.js +17 -0
  64. package/dist/esm/binding/one-time.js +21 -0
  65. package/dist/esm/binding/one-way.js +30 -0
  66. package/dist/esm/{templating/binding-signal.js → binding/signal.js} +22 -6
  67. package/dist/esm/{templating/binding-two-way.js → binding/two-way.js} +18 -12
  68. package/dist/esm/components/attributes.js +16 -1
  69. package/dist/esm/components/element-controller.js +319 -49
  70. package/dist/esm/components/element-hydration.js +2 -0
  71. package/dist/esm/components/fast-definitions.js +12 -4
  72. package/dist/esm/components/fast-element.js +3 -1
  73. package/dist/esm/components/hydration.js +104 -0
  74. package/dist/esm/components/install-hydration.js +3 -0
  75. package/dist/esm/context.js +26 -4
  76. package/dist/esm/debug.js +8 -2
  77. package/dist/esm/di/di.js +9 -12
  78. package/dist/esm/dom-policy.js +345 -0
  79. package/dist/esm/dom.js +101 -0
  80. package/dist/esm/hydration/target-builder.js +175 -0
  81. package/dist/esm/index.js +34 -25
  82. package/dist/esm/index.rollup.debug.js +3 -1
  83. package/dist/esm/index.rollup.js +3 -1
  84. package/dist/esm/interfaces.js +51 -3
  85. package/dist/esm/metadata.js +11 -8
  86. package/dist/esm/observation/arrays.js +1 -1
  87. package/dist/esm/observation/observable.bench.js +79 -0
  88. package/dist/esm/observation/observable.js +20 -15
  89. package/dist/esm/observation/update-queue.js +2 -2
  90. package/dist/esm/pending-task.js +13 -1
  91. package/dist/esm/platform.js +12 -2
  92. package/dist/esm/polyfills.js +3 -61
  93. package/dist/esm/styles/css-binding-directive.js +76 -0
  94. package/dist/esm/styles/css.js +14 -7
  95. package/dist/esm/styles/element-styles.js +0 -33
  96. package/dist/esm/styles/style-strategy.js +1 -0
  97. package/dist/esm/templating/children.js +8 -4
  98. package/dist/esm/templating/compiler.js +37 -44
  99. package/dist/esm/templating/html-binding-directive.js +218 -0
  100. package/dist/esm/templating/html-directive.js +25 -152
  101. package/dist/esm/templating/install-hydratable-view-templates.js +17 -0
  102. package/dist/esm/templating/node-observation.js +14 -8
  103. package/dist/esm/templating/ref.js +1 -1
  104. package/dist/esm/templating/render.bench.js +56 -0
  105. package/dist/esm/templating/render.js +74 -30
  106. package/dist/esm/templating/repeat-basic-reverse.bench.js +43 -0
  107. package/dist/esm/templating/repeat-basic-shift.bench.js +43 -0
  108. package/dist/esm/templating/repeat.js +116 -17
  109. package/dist/esm/templating/template.js +135 -60
  110. package/dist/esm/templating/view.js +259 -32
  111. package/dist/esm/templating/when-basic.bench.js +36 -0
  112. package/dist/esm/templating/when-conditional.bench.js +39 -0
  113. package/dist/esm/templating/when-switch.bench.js +68 -0
  114. package/dist/esm/templating/when.js +12 -5
  115. package/dist/esm/testing/fakes.js +32 -1
  116. package/dist/esm/testing/fixture.js +1 -1
  117. package/dist/esm/utilities.js +97 -1
  118. package/dist/fast-element.api.json +9804 -5622
  119. package/dist/fast-element.d.ts +813 -2386
  120. package/dist/fast-element.debug.js +2797 -974
  121. package/dist/fast-element.debug.min.js +3 -1
  122. package/dist/fast-element.js +2642 -825
  123. package/dist/fast-element.min.js +3 -1
  124. package/dist/fast-element.untrimmed.d.ts +669 -315
  125. package/docs/{api-report.md → api-report.api.md} +243 -158
  126. package/docs/context/api-report.api.md +69 -0
  127. package/docs/di/api-report.api.md +315 -0
  128. package/karma.conf.cjs +2 -1
  129. package/package.json +59 -47
  130. package/scripts/run-api-extractor.js +51 -0
  131. package/scripts/run-benchmarks.js +46 -0
  132. package/tensile.config.js +12 -0
  133. package/dist/dts/templating/dom.d.ts +0 -41
  134. package/dist/esm/templating/binding.js +0 -282
  135. package/dist/esm/templating/dom.js +0 -49
  136. package/docs/guide/declaring-templates.md +0 -230
  137. package/docs/guide/defining-elements.md +0 -214
  138. package/docs/guide/leveraging-css.md +0 -253
  139. package/docs/guide/next-steps.md +0 -13
  140. package/docs/guide/observables-and-state.md +0 -213
  141. package/docs/guide/using-directives.md +0 -576
  142. package/docs/guide/working-with-shadow-dom.md +0 -296
@@ -1,8 +1,11 @@
1
1
  import "../interfaces.js";
2
2
  import { PropertyChangeNotifier } from "../observation/notifier.js";
3
- import { Observable, SourceLifetime } from "../observation/observable.js";
4
- import { FAST } from "../platform.js";
3
+ import { ExecutionContext, Observable, SourceLifetime, } from "../observation/observable.js";
4
+ import { FAST, makeSerializationNoop } from "../platform.js";
5
+ import { ElementStyles } from "../styles/element-styles.js";
6
+ import { UnobservableMutationObserver } from "../utilities.js";
5
7
  import { FASTElementDefinition } from "./fast-definitions.js";
8
+ import { HydrationMarkup, isHydratable } from "./hydration.js";
6
9
  const defaultEventOptions = {
7
10
  bubbles: true,
8
11
  composed: true,
@@ -14,6 +17,7 @@ function getShadowRoot(element) {
14
17
  var _a, _b;
15
18
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
16
19
  }
20
+ let elementControllerStrategy;
17
21
  /**
18
22
  * Controls the lifecycle and rendering of a `FASTElement`.
19
23
  * @public
@@ -32,8 +36,19 @@ export class ElementController extends PropertyChangeNotifier {
32
36
  this.needsInitialization = true;
33
37
  this.hasExistingShadowRoot = false;
34
38
  this._template = null;
35
- this._isConnected = false;
39
+ this.stage = 3 /* Stages.disconnected */;
40
+ /**
41
+ * A guard against connecting behaviors multiple times
42
+ * during connect in scenarios where a behavior adds
43
+ * another behavior during it's connectedCallback
44
+ */
45
+ this.guardBehaviorConnection = false;
36
46
  this.behaviors = null;
47
+ /**
48
+ * Tracks whether behaviors are connected so that
49
+ * behaviors cant be connected multiple times
50
+ */
51
+ this.behaviorsConnected = false;
37
52
  this._mainStyles = null;
38
53
  /**
39
54
  * This allows Observable.getNotifier(...) to return the Controller
@@ -88,11 +103,28 @@ export class ElementController extends PropertyChangeNotifier {
88
103
  */
89
104
  get isConnected() {
90
105
  Observable.track(this, isConnectedPropertyName);
91
- return this._isConnected;
106
+ return this.stage === 1 /* Stages.connected */;
92
107
  }
93
- setIsConnected(value) {
94
- this._isConnected = value;
95
- Observable.notify(this, isConnectedPropertyName);
108
+ /**
109
+ * The context the expression is evaluated against.
110
+ */
111
+ get context() {
112
+ var _a, _b;
113
+ return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.context) !== null && _b !== void 0 ? _b : ExecutionContext.default;
114
+ }
115
+ /**
116
+ * Indicates whether the controller is bound.
117
+ */
118
+ get isBound() {
119
+ var _a, _b;
120
+ return (_b = (_a = this.view) === null || _a === void 0 ? void 0 : _a.isBound) !== null && _b !== void 0 ? _b : false;
121
+ }
122
+ /**
123
+ * Indicates how the source's lifetime relates to the controller's lifetime.
124
+ */
125
+ get sourceLifetime() {
126
+ var _a;
127
+ return (_a = this.view) === null || _a === void 0 ? void 0 : _a.sourceLifetime;
96
128
  }
97
129
  /**
98
130
  * Gets/sets the template used to render the component.
@@ -156,6 +188,14 @@ export class ElementController extends PropertyChangeNotifier {
156
188
  this.addStyles(value);
157
189
  }
158
190
  }
191
+ /**
192
+ * Registers an unbind handler with the controller.
193
+ * @param behavior - An object to call when the controller unbinds.
194
+ */
195
+ onUnbind(behavior) {
196
+ var _a;
197
+ (_a = this.view) === null || _a === void 0 ? void 0 : _a.onUnbind(behavior);
198
+ }
159
199
  /**
160
200
  * Adds the behavior to the component.
161
201
  * @param behavior - The behavior to add.
@@ -167,7 +207,9 @@ export class ElementController extends PropertyChangeNotifier {
167
207
  if (count === 0) {
168
208
  targetBehaviors.set(behavior, 1);
169
209
  behavior.addedCallback && behavior.addedCallback(this);
170
- if (behavior.connectedCallback && this.isConnected) {
210
+ if (behavior.connectedCallback &&
211
+ !this.guardBehaviorConnection &&
212
+ (this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
171
213
  behavior.connectedCallback(this);
172
214
  }
173
215
  }
@@ -191,7 +233,7 @@ export class ElementController extends PropertyChangeNotifier {
191
233
  }
192
234
  if (count === 1 || force) {
193
235
  targetBehaviors.delete(behavior);
194
- if (behavior.disconnectedCallback && this.isConnected) {
236
+ if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
195
237
  behavior.disconnectedCallback(this);
196
238
  }
197
239
  behavior.removedCallback && behavior.removedCallback(this);
@@ -210,13 +252,13 @@ export class ElementController extends PropertyChangeNotifier {
210
252
  return;
211
253
  }
212
254
  const source = this.source;
213
- const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
214
255
  if (styles instanceof HTMLElement) {
256
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
215
257
  target.append(styles);
216
258
  }
217
- else if (!styles.isAttachedTo(target)) {
259
+ else if (!styles.isAttachedTo(source)) {
218
260
  const sourceBehaviors = styles.behaviors;
219
- styles.addStylesTo(target);
261
+ styles.addStylesTo(source);
220
262
  if (sourceBehaviors !== null) {
221
263
  for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
222
264
  this.addBehavior(sourceBehaviors[i]);
@@ -234,16 +276,16 @@ export class ElementController extends PropertyChangeNotifier {
234
276
  return;
235
277
  }
236
278
  const source = this.source;
237
- const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source.getRootNode();
238
279
  if (styles instanceof HTMLElement) {
280
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
239
281
  target.removeChild(styles);
240
282
  }
241
- else if (styles.isAttachedTo(target)) {
283
+ else if (styles.isAttachedTo(source)) {
242
284
  const sourceBehaviors = styles.behaviors;
243
- styles.removeStylesFrom(target);
285
+ styles.removeStylesFrom(source);
244
286
  if (sourceBehaviors !== null) {
245
287
  for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
246
- this.addBehavior(sourceBehaviors[i]);
288
+ this.removeBehavior(sourceBehaviors[i]);
247
289
  }
248
290
  }
249
291
  }
@@ -252,40 +294,73 @@ export class ElementController extends PropertyChangeNotifier {
252
294
  * Runs connected lifecycle behavior on the associated element.
253
295
  */
254
296
  connect() {
255
- if (this._isConnected) {
297
+ if (this.stage !== 3 /* Stages.disconnected */) {
256
298
  return;
257
299
  }
300
+ this.stage = 0 /* Stages.connecting */;
301
+ this.bindObservables();
302
+ this.connectBehaviors();
258
303
  if (this.needsInitialization) {
259
- this.finishInitialization();
304
+ this.renderTemplate(this.template);
305
+ this.addStyles(this.mainStyles);
306
+ this.needsInitialization = false;
260
307
  }
261
308
  else if (this.view !== null) {
262
309
  this.view.bind(this.source);
263
310
  }
264
- const behaviors = this.behaviors;
265
- if (behaviors !== null) {
266
- for (const key of behaviors.keys()) {
267
- key.connectedCallback && key.connectedCallback(this);
311
+ this.stage = 1 /* Stages.connected */;
312
+ Observable.notify(this, isConnectedPropertyName);
313
+ }
314
+ bindObservables() {
315
+ if (this.boundObservables !== null) {
316
+ const element = this.source;
317
+ const boundObservables = this.boundObservables;
318
+ const propertyNames = Object.keys(boundObservables);
319
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
320
+ const propertyName = propertyNames[i];
321
+ element[propertyName] = boundObservables[propertyName];
322
+ }
323
+ this.boundObservables = null;
324
+ }
325
+ }
326
+ connectBehaviors() {
327
+ if (this.behaviorsConnected === false) {
328
+ const behaviors = this.behaviors;
329
+ if (behaviors !== null) {
330
+ this.guardBehaviorConnection = true;
331
+ for (const key of behaviors.keys()) {
332
+ key.connectedCallback && key.connectedCallback(this);
333
+ }
334
+ this.guardBehaviorConnection = false;
335
+ }
336
+ this.behaviorsConnected = true;
337
+ }
338
+ }
339
+ disconnectBehaviors() {
340
+ if (this.behaviorsConnected === true) {
341
+ const behaviors = this.behaviors;
342
+ if (behaviors !== null) {
343
+ for (const key of behaviors.keys()) {
344
+ key.disconnectedCallback && key.disconnectedCallback(this);
345
+ }
268
346
  }
347
+ this.behaviorsConnected = false;
269
348
  }
270
- this.setIsConnected(true);
271
349
  }
272
350
  /**
273
351
  * Runs disconnected lifecycle behavior on the associated element.
274
352
  */
275
353
  disconnect() {
276
- if (!this._isConnected) {
354
+ if (this.stage !== 1 /* Stages.connected */) {
277
355
  return;
278
356
  }
279
- this.setIsConnected(false);
357
+ this.stage = 2 /* Stages.disconnecting */;
358
+ Observable.notify(this, isConnectedPropertyName);
280
359
  if (this.view !== null) {
281
360
  this.view.unbind();
282
361
  }
283
- const behaviors = this.behaviors;
284
- if (behaviors !== null) {
285
- for (const key of behaviors.keys()) {
286
- key.disconnectedCallback && key.disconnectedCallback(this);
287
- }
288
- }
362
+ this.disconnectBehaviors();
363
+ this.stage = 3 /* Stages.disconnected */;
289
364
  }
290
365
  /**
291
366
  * Runs the attribute changed callback for the associated element.
@@ -308,27 +383,11 @@ export class ElementController extends PropertyChangeNotifier {
308
383
  * Only emits events if connected.
309
384
  */
310
385
  emit(type, detail, options) {
311
- if (this._isConnected) {
386
+ if (this.stage === 1 /* Stages.connected */) {
312
387
  return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
313
388
  }
314
389
  return false;
315
390
  }
316
- finishInitialization() {
317
- const element = this.source;
318
- const boundObservables = this.boundObservables;
319
- // If we have any observables that were bound, re-apply their values.
320
- if (boundObservables !== null) {
321
- const propertyNames = Object.keys(boundObservables);
322
- for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
323
- const propertyName = propertyNames[i];
324
- element[propertyName] = boundObservables[propertyName];
325
- }
326
- this.boundObservables = null;
327
- }
328
- this.renderTemplate(this.template);
329
- this.addStyles(this.mainStyles);
330
- this.needsInitialization = false;
331
- }
332
391
  renderTemplate(template) {
333
392
  var _a;
334
393
  // When getting the host to render to, we start by looking
@@ -372,6 +431,217 @@ export class ElementController extends PropertyChangeNotifier {
372
431
  if (definition === void 0) {
373
432
  throw FAST.error(1401 /* Message.missingElementDefinition */);
374
433
  }
375
- return (element.$fastController = new ElementController(element, definition));
434
+ return (element.$fastController = new elementControllerStrategy(element, definition));
435
+ }
436
+ /**
437
+ * Sets the strategy that ElementController.forCustomElement uses to construct
438
+ * ElementController instances for an element.
439
+ * @param strategy - The strategy to use.
440
+ */
441
+ static setStrategy(strategy) {
442
+ elementControllerStrategy = strategy;
443
+ }
444
+ }
445
+ makeSerializationNoop(ElementController);
446
+ // Set default strategy for ElementController
447
+ ElementController.setStrategy(ElementController);
448
+ /**
449
+ * Converts a styleTarget into the operative target. When the provided target is an Element
450
+ * that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
451
+ * it will return the root node for the element.
452
+ * @param target
453
+ * @returns
454
+ */
455
+ function normalizeStyleTarget(target) {
456
+ var _a;
457
+ if ("adoptedStyleSheets" in target) {
458
+ return target;
459
+ }
460
+ else {
461
+ return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
462
+ }
463
+ }
464
+ // Default StyleStrategy implementations are defined in this module because they
465
+ // require access to element shadowRoots, and we don't want to leak shadowRoot
466
+ // objects out of this module.
467
+ /**
468
+ * https://wicg.github.io/construct-stylesheets/
469
+ * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
470
+ *
471
+ * @internal
472
+ */
473
+ export class AdoptedStyleSheetsStrategy {
474
+ constructor(styles) {
475
+ const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
476
+ this.sheets = styles.map((x) => {
477
+ if (x instanceof CSSStyleSheet) {
478
+ return x;
479
+ }
480
+ let sheet = styleSheetCache.get(x);
481
+ if (sheet === void 0) {
482
+ sheet = new CSSStyleSheet();
483
+ sheet.replaceSync(x);
484
+ styleSheetCache.set(x, sheet);
485
+ }
486
+ return sheet;
487
+ });
488
+ }
489
+ addStylesTo(target) {
490
+ addAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
491
+ }
492
+ removeStylesFrom(target) {
493
+ removeAdoptedStyleSheets(normalizeStyleTarget(target), this.sheets);
494
+ }
495
+ }
496
+ AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
497
+ let id = 0;
498
+ const nextStyleId = () => `fast-${++id}`;
499
+ function usableStyleTarget(target) {
500
+ return target === document ? document.body : target;
501
+ }
502
+ /**
503
+ * @internal
504
+ */
505
+ export class StyleElementStrategy {
506
+ constructor(styles) {
507
+ this.styles = styles;
508
+ this.styleClass = nextStyleId();
509
+ }
510
+ addStylesTo(target) {
511
+ target = usableStyleTarget(normalizeStyleTarget(target));
512
+ const styles = this.styles;
513
+ const styleClass = this.styleClass;
514
+ for (let i = 0; i < styles.length; i++) {
515
+ const element = document.createElement("style");
516
+ element.innerHTML = styles[i];
517
+ element.className = styleClass;
518
+ target.append(element);
519
+ }
520
+ }
521
+ removeStylesFrom(target) {
522
+ target = usableStyleTarget(normalizeStyleTarget(target));
523
+ const styles = target.querySelectorAll(`.${this.styleClass}`);
524
+ for (let i = 0, ii = styles.length; i < ii; ++i) {
525
+ target.removeChild(styles[i]);
526
+ }
527
+ }
528
+ }
529
+ let addAdoptedStyleSheets = (target, sheets) => {
530
+ target.adoptedStyleSheets = [...target.adoptedStyleSheets, ...sheets];
531
+ };
532
+ let removeAdoptedStyleSheets = (target, sheets) => {
533
+ target.adoptedStyleSheets = target.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
534
+ };
535
+ if (ElementStyles.supportsAdoptedStyleSheets) {
536
+ try {
537
+ // Test if browser implementation uses FrozenArray.
538
+ // If not, use push / splice to alter the stylesheets
539
+ // in place. This circumvents a bug in Safari 16.4 where
540
+ // periodically, assigning the array would previously
541
+ // cause sheets to be removed.
542
+ document.adoptedStyleSheets.push();
543
+ document.adoptedStyleSheets.splice();
544
+ addAdoptedStyleSheets = (target, sheets) => {
545
+ target.adoptedStyleSheets.push(...sheets);
546
+ };
547
+ removeAdoptedStyleSheets = (target, sheets) => {
548
+ for (const sheet of sheets) {
549
+ const index = target.adoptedStyleSheets.indexOf(sheet);
550
+ if (index !== -1) {
551
+ target.adoptedStyleSheets.splice(index, 1);
552
+ }
553
+ }
554
+ };
555
+ }
556
+ catch (e) {
557
+ // Do nothing if an error is thrown, the default
558
+ // case handles FrozenArray.
559
+ }
560
+ ElementStyles.setDefaultStrategy(AdoptedStyleSheetsStrategy);
561
+ }
562
+ else {
563
+ ElementStyles.setDefaultStrategy(StyleElementStrategy);
564
+ }
565
+ const deferHydrationAttribute = "defer-hydration";
566
+ const needsHydrationAttribute = "needs-hydration";
567
+ /**
568
+ * An ElementController capable of hydrating FAST elements from
569
+ * Declarative Shadow DOM.
570
+ *
571
+ * @beta
572
+ */
573
+ export class HydratableElementController extends ElementController {
574
+ static hydrationObserverHandler(records) {
575
+ for (const record of records) {
576
+ HydratableElementController.hydrationObserver.unobserve(record.target);
577
+ record.target.$fastController.connect();
578
+ }
579
+ }
580
+ connect() {
581
+ var _a, _b;
582
+ // Initialize needsHydration on first connect
583
+ if (this.needsHydration === undefined) {
584
+ this.needsHydration =
585
+ this.source.getAttribute(needsHydrationAttribute) !== null;
586
+ }
587
+ // If the `defer-hydration` attribute exists on the source,
588
+ // wait for it to be removed before continuing connection behavior.
589
+ if (this.source.hasAttribute(deferHydrationAttribute)) {
590
+ HydratableElementController.hydrationObserver.observe(this.source, {
591
+ attributeFilter: [deferHydrationAttribute],
592
+ });
593
+ return;
594
+ }
595
+ // If the controller does not need to be hydrated, defer connection behavior
596
+ // to the base-class. This case handles element re-connection and initial connection
597
+ // of elements that did not get declarative shadow-dom emitted, as well as if an extending
598
+ // class
599
+ if (!this.needsHydration) {
600
+ super.connect();
601
+ return;
602
+ }
603
+ if (this.stage !== 3 /* Stages.disconnected */) {
604
+ return;
605
+ }
606
+ this.stage = 0 /* Stages.connecting */;
607
+ this.bindObservables();
608
+ this.connectBehaviors();
609
+ const element = this.source;
610
+ const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
611
+ if (this.template) {
612
+ if (isHydratable(this.template)) {
613
+ let firstChild = host.firstChild;
614
+ let lastChild = host.lastChild;
615
+ if (element.shadowRoot === null) {
616
+ // handle element boundary markers when shadowRoot is not present
617
+ if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
618
+ firstChild.data = "";
619
+ firstChild = firstChild.nextSibling;
620
+ }
621
+ if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
622
+ lastChild.data = "";
623
+ lastChild = lastChild.previousSibling;
624
+ }
625
+ }
626
+ this.view = this.template.hydrate(firstChild, lastChild, element);
627
+ (_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
628
+ }
629
+ else {
630
+ this.renderTemplate(this.template);
631
+ }
632
+ }
633
+ this.addStyles(this.mainStyles);
634
+ this.stage = 1 /* Stages.connected */;
635
+ this.source.removeAttribute(needsHydrationAttribute);
636
+ this.needsInitialization = this.needsHydration = false;
637
+ Observable.notify(this, isConnectedPropertyName);
638
+ }
639
+ disconnect() {
640
+ super.disconnect();
641
+ HydratableElementController.hydrationObserver.unobserve(this.source);
642
+ }
643
+ static install() {
644
+ ElementController.setStrategy(HydratableElementController);
376
645
  }
377
646
  }
647
+ HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
@@ -0,0 +1,2 @@
1
+ export { HydratableElementController } from "./element-controller.js";
2
+ export * from "./hydration.js";
@@ -1,11 +1,12 @@
1
- import { isString } from "../interfaces.js";
1
+ import { isString, KernelServiceId } from "../interfaces.js";
2
2
  import { Observable } from "../observation/observable.js";
3
3
  import { createTypeRegistry, FAST } from "../platform.js";
4
4
  import { ElementStyles } from "../styles/element-styles.js";
5
5
  import { AttributeDefinition } from "./attributes.js";
6
6
  const defaultShadowOptions = { mode: "open" };
7
7
  const defaultElementOptions = {};
8
- const fastElementRegistry = FAST.getById(4 /* KernelServiceId.elementRegistry */, () => createTypeRegistry());
8
+ const fastElementBaseTypes = new Set();
9
+ const fastElementRegistry = FAST.getById(KernelServiceId.elementRegistry, () => createTypeRegistry());
9
10
  /**
10
11
  * Defines metadata for a FASTElement.
11
12
  * @public
@@ -80,13 +81,20 @@ export class FASTElementDefinition {
80
81
  * that describes the element to define.
81
82
  */
82
83
  static compose(type, nameOrDef) {
83
- const found = fastElementRegistry.getByType(type);
84
- if (found) {
84
+ if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
85
85
  return new FASTElementDefinition(class extends type {
86
86
  }, nameOrDef);
87
87
  }
88
88
  return new FASTElementDefinition(type, nameOrDef);
89
89
  }
90
+ /**
91
+ * Registers a FASTElement base type.
92
+ * @param type - The type to register as a base type.
93
+ * @internal
94
+ */
95
+ static registerBaseType(type) {
96
+ fastElementBaseTypes.add(type);
97
+ }
90
98
  }
91
99
  /**
92
100
  * Gets the element definition associated with the specified type.
@@ -3,7 +3,7 @@ import { ElementController } from "./element-controller.js";
3
3
  import { FASTElementDefinition, } from "./fast-definitions.js";
4
4
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
5
5
  function createFASTElement(BaseType) {
6
- return class extends BaseType {
6
+ const type = class extends BaseType {
7
7
  constructor() {
8
8
  /* eslint-disable-next-line */
9
9
  super();
@@ -22,6 +22,8 @@ function createFASTElement(BaseType) {
22
22
  this.$fastController.onAttributeChangedCallback(name, oldValue, newValue);
23
23
  }
24
24
  };
25
+ FASTElementDefinition.registerBaseType(type);
26
+ return type;
25
27
  }
26
28
  function compose(type, nameOrDef) {
27
29
  if (isFunction(type)) {
@@ -0,0 +1,104 @@
1
+ const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
2
+ const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
3
+ const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
4
+ const repeatViewEndMarker = /fe-repeat\$\$end\$\$(\d+)\$\$fe-repeat/;
5
+ const elementBoundaryStartMarker = /^(?:.{0,1000})fe-eb\$\$start\$\$(.+?)\$\$fe-eb/;
6
+ const elementBoundaryEndMarker = /fe-eb\$\$end\$\$(.{0,1000})\$\$fe-eb(?:.{0,1000})$/;
7
+ function isComment(node) {
8
+ return node && node.nodeType === Node.COMMENT_NODE;
9
+ }
10
+ /**
11
+ * Markup utilities to aid in template hydration.
12
+ * @internal
13
+ */
14
+ export const HydrationMarkup = Object.freeze({
15
+ attributeMarkerName: "data-fe-b",
16
+ attributeBindingSeparator: " ",
17
+ contentBindingStartMarker(index, uniqueId) {
18
+ return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
19
+ },
20
+ contentBindingEndMarker(index, uniqueId) {
21
+ return `fe-b$$end$$${index}$$${uniqueId}$$fe-b`;
22
+ },
23
+ repeatStartMarker(index) {
24
+ return `fe-repeat$$start$$${index}$$fe-repeat`;
25
+ },
26
+ repeatEndMarker(index) {
27
+ return `fe-repeat$$end$$${index}$$fe-repeat`;
28
+ },
29
+ isContentBindingStartMarker(content) {
30
+ return bindingStartMarker.test(content);
31
+ },
32
+ isContentBindingEndMarker(content) {
33
+ return bindingEndMarker.test(content);
34
+ },
35
+ isRepeatViewStartMarker(content) {
36
+ return repeatViewStartMarker.test(content);
37
+ },
38
+ isRepeatViewEndMarker(content) {
39
+ return repeatViewEndMarker.test(content);
40
+ },
41
+ isElementBoundaryStartMarker(node) {
42
+ return isComment(node) && elementBoundaryStartMarker.test(node.data.trim());
43
+ },
44
+ isElementBoundaryEndMarker(node) {
45
+ return isComment(node) && elementBoundaryEndMarker.test(node.data);
46
+ },
47
+ /**
48
+ * Returns the indexes of the ViewBehaviorFactories affecting
49
+ * attributes for the element, or null if no factories were found.
50
+ */
51
+ parseAttributeBinding(node) {
52
+ const attr = node.getAttribute(this.attributeMarkerName);
53
+ return attr === null
54
+ ? attr
55
+ : attr.split(this.attributeBindingSeparator).map(i => parseInt(i));
56
+ },
57
+ /**
58
+ * Parses the ViewBehaviorFactory index from string data. Returns
59
+ * the binding index or null if the index cannot be retrieved.
60
+ */
61
+ parseContentBindingStartMarker(content) {
62
+ return parseIndexAndIdMarker(bindingStartMarker, content);
63
+ },
64
+ parseContentBindingEndMarker(content) {
65
+ return parseIndexAndIdMarker(bindingEndMarker, content);
66
+ },
67
+ /**
68
+ * Parses the index of a repeat directive from a content string.
69
+ */
70
+ parseRepeatStartMarker(content) {
71
+ return parseIntMarker(repeatViewStartMarker, content);
72
+ },
73
+ parseRepeatEndMarker(content) {
74
+ return parseIntMarker(repeatViewEndMarker, content);
75
+ },
76
+ /**
77
+ * Parses element Id from element boundary markers
78
+ */
79
+ parseElementBoundaryStartMarker(content) {
80
+ return parseStringMarker(elementBoundaryStartMarker, content.trim());
81
+ },
82
+ parseElementBoundaryEndMarker(content) {
83
+ return parseStringMarker(elementBoundaryEndMarker, content);
84
+ },
85
+ });
86
+ function parseIntMarker(regex, content) {
87
+ const match = regex.exec(content);
88
+ return match === null ? match : parseInt(match[1]);
89
+ }
90
+ function parseStringMarker(regex, content) {
91
+ const match = regex.exec(content);
92
+ return match === null ? match : match[1];
93
+ }
94
+ function parseIndexAndIdMarker(regex, content) {
95
+ const match = regex.exec(content);
96
+ return match === null ? match : [parseInt(match[1]), match[2]];
97
+ }
98
+ /**
99
+ * @internal
100
+ */
101
+ export const Hydratable = Symbol.for("fe-hydration");
102
+ export function isHydratable(value) {
103
+ return value[Hydratable] === Hydratable;
104
+ }
@@ -0,0 +1,3 @@
1
+ import "../templating/install-hydratable-view-templates.js";
2
+ import { HydratableElementController } from "./element-controller.js";
3
+ HydratableElementController.install();