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

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 (115) hide show
  1. package/CHANGELOG.json +509 -0
  2. package/CHANGELOG.md +189 -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 +7 -7
  10. package/dist/dts/di/di.d.ts +894 -0
  11. package/dist/dts/dom-policy.d.ts +83 -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 +62 -80
  17. package/dist/dts/metadata.d.ts +5 -5
  18. package/dist/dts/observation/observable.d.ts +99 -54
  19. package/dist/dts/pending-task.d.ts +32 -0
  20. package/dist/dts/platform.d.ts +8 -1
  21. package/dist/dts/polyfills.d.ts +0 -8
  22. package/dist/dts/state/exports.d.ts +3 -0
  23. package/dist/dts/state/reactive.d.ts +8 -0
  24. package/dist/dts/state/state.d.ts +141 -0
  25. package/dist/dts/state/visitor.d.ts +6 -0
  26. package/dist/dts/state/watch.d.ts +10 -0
  27. package/dist/dts/styles/css-directive.d.ts +2 -2
  28. package/dist/dts/styles/css.d.ts +0 -5
  29. package/dist/dts/styles/element-styles.d.ts +10 -17
  30. package/dist/dts/styles/host.d.ts +68 -0
  31. package/dist/dts/styles/style-strategy.d.ts +42 -0
  32. package/dist/dts/templating/binding-signal.d.ts +12 -27
  33. package/dist/dts/templating/binding-two-way.d.ts +22 -37
  34. package/dist/dts/templating/binding.d.ts +76 -208
  35. package/dist/dts/templating/children.d.ts +1 -1
  36. package/dist/dts/templating/compiler.d.ts +11 -13
  37. package/dist/dts/templating/html-directive.d.ts +91 -97
  38. package/dist/dts/templating/node-observation.d.ts +15 -6
  39. package/dist/dts/templating/ref.d.ts +7 -11
  40. package/dist/dts/templating/render.d.ts +296 -0
  41. package/dist/dts/templating/repeat.d.ts +23 -34
  42. package/dist/dts/templating/slotted.d.ts +1 -1
  43. package/dist/dts/templating/template.d.ts +92 -14
  44. package/dist/dts/templating/view.d.ts +81 -11
  45. package/dist/dts/templating/when.d.ts +3 -3
  46. package/dist/dts/testing/exports.d.ts +3 -0
  47. package/dist/dts/testing/fakes.d.ts +14 -0
  48. package/dist/dts/testing/fixture.d.ts +84 -0
  49. package/dist/dts/testing/timeout.d.ts +7 -0
  50. package/dist/dts/utilities.d.ts +55 -19
  51. package/dist/esm/components/attributes.js +28 -5
  52. package/dist/esm/components/{controller.js → element-controller.js} +238 -137
  53. package/dist/esm/components/fast-definitions.js +38 -30
  54. package/dist/esm/components/fast-element.js +27 -16
  55. package/dist/esm/components/hydration.js +35 -0
  56. package/dist/esm/components/install-hydration.js +2 -0
  57. package/dist/esm/context.js +7 -3
  58. package/dist/esm/debug.js +41 -5
  59. package/dist/esm/di/di.js +1430 -0
  60. package/dist/esm/dom-policy.js +345 -0
  61. package/dist/esm/dom.js +101 -0
  62. package/dist/esm/index.js +4 -2
  63. package/dist/esm/index.rollup.debug.js +3 -1
  64. package/dist/esm/index.rollup.js +3 -1
  65. package/dist/esm/interfaces.js +52 -0
  66. package/dist/esm/metadata.js +9 -8
  67. package/dist/esm/observation/arrays.js +303 -2
  68. package/dist/esm/observation/observable.js +88 -142
  69. package/dist/esm/observation/update-queue.js +2 -2
  70. package/dist/esm/pending-task.js +28 -0
  71. package/dist/esm/platform.js +28 -3
  72. package/dist/esm/polyfills.js +3 -61
  73. package/dist/esm/state/exports.js +3 -0
  74. package/dist/esm/state/reactive.js +34 -0
  75. package/dist/esm/state/state.js +148 -0
  76. package/dist/esm/state/visitor.js +28 -0
  77. package/dist/esm/state/watch.js +36 -0
  78. package/dist/esm/styles/css.js +4 -9
  79. package/dist/esm/styles/element-styles.js +14 -33
  80. package/dist/esm/styles/host.js +1 -0
  81. package/dist/esm/styles/style-strategy.js +1 -0
  82. package/dist/esm/templating/binding-signal.js +67 -62
  83. package/dist/esm/templating/binding-two-way.js +72 -39
  84. package/dist/esm/templating/binding.js +142 -286
  85. package/dist/esm/templating/children.js +8 -4
  86. package/dist/esm/templating/compiler.js +59 -43
  87. package/dist/esm/templating/html-directive.js +56 -75
  88. package/dist/esm/templating/node-observation.js +20 -13
  89. package/dist/esm/templating/ref.js +4 -12
  90. package/dist/esm/templating/render.js +402 -0
  91. package/dist/esm/templating/repeat.js +88 -75
  92. package/dist/esm/templating/template.js +132 -60
  93. package/dist/esm/templating/view.js +113 -29
  94. package/dist/esm/templating/when.js +5 -4
  95. package/dist/esm/testing/exports.js +3 -0
  96. package/dist/esm/testing/fakes.js +107 -0
  97. package/dist/esm/testing/fixture.js +86 -0
  98. package/dist/esm/testing/timeout.js +24 -0
  99. package/dist/esm/utilities.js +97 -96
  100. package/dist/fast-element.api.json +9741 -8201
  101. package/dist/fast-element.d.ts +889 -646
  102. package/dist/fast-element.debug.js +2001 -1167
  103. package/dist/fast-element.debug.min.js +1 -1
  104. package/dist/fast-element.js +1907 -1109
  105. package/dist/fast-element.min.js +1 -1
  106. package/dist/fast-element.untrimmed.d.ts +913 -703
  107. package/docs/api-report.md +331 -258
  108. package/package.json +38 -16
  109. package/dist/dts/hooks.d.ts +0 -20
  110. package/dist/dts/observation/behavior.d.ts +0 -19
  111. package/dist/dts/observation/splice-strategies.d.ts +0 -13
  112. package/dist/dts/templating/dom.d.ts +0 -41
  113. package/dist/esm/hooks.js +0 -32
  114. package/dist/esm/observation/splice-strategies.js +0 -400
  115. package/dist/esm/templating/dom.js +0 -49
@@ -1,24 +1,26 @@
1
- import "../interfaces.js";
1
+ import { noop } from "../interfaces.js";
2
2
  import { PropertyChangeNotifier } from "../observation/notifier.js";
3
- import { ExecutionContext, Observable } from "../observation/observable.js";
3
+ import { Observable, SourceLifetime } from "../observation/observable.js";
4
4
  import { FAST } from "../platform.js";
5
+ import { ElementStyles } from "../styles/element-styles.js";
5
6
  import { FASTElementDefinition } from "./fast-definitions.js";
6
- const shadowRoots = new WeakMap();
7
7
  const defaultEventOptions = {
8
8
  bubbles: true,
9
9
  composed: true,
10
10
  cancelable: true,
11
11
  };
12
+ const isConnectedPropertyName = "isConnected";
13
+ const shadowRoots = new WeakMap();
12
14
  function getShadowRoot(element) {
13
15
  var _a, _b;
14
16
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
15
17
  }
16
- const isConnectedPropertyName = "isConnected";
18
+ let elementControllerStrategy;
17
19
  /**
18
20
  * Controls the lifecycle and rendering of a `FASTElement`.
19
21
  * @public
20
22
  */
21
- export class Controller extends PropertyChangeNotifier {
23
+ export class ElementController extends PropertyChangeNotifier {
22
24
  /**
23
25
  * Creates a Controller to control the specified element.
24
26
  * @param element - The element to be controlled by this controller.
@@ -29,12 +31,18 @@ export class Controller extends PropertyChangeNotifier {
29
31
  constructor(element, definition) {
30
32
  super(element);
31
33
  this.boundObservables = null;
32
- this.behaviors = null;
33
34
  this.needsInitialization = true;
34
35
  this.hasExistingShadowRoot = false;
35
36
  this._template = null;
36
- this._styles = null;
37
- this._isConnected = false;
37
+ this.stage = 3 /* Stages.disconnected */;
38
+ /**
39
+ * A guard against connecting behaviors multiple times
40
+ * during connect in scenarios where a behavior adds
41
+ * another behavior during it's connectedCallback
42
+ */
43
+ this.guardBehaviorConnection = false;
44
+ this.behaviors = null;
45
+ this._mainStyles = null;
38
46
  /**
39
47
  * This allows Observable.getNotifier(...) to return the Controller
40
48
  * when the notifier for the Controller itself is being requested. The
@@ -50,7 +58,12 @@ export class Controller extends PropertyChangeNotifier {
50
58
  * If `null` then the element is managing its own rendering.
51
59
  */
52
60
  this.view = null;
53
- this.element = element;
61
+ /**
62
+ * Opts out of JSON stringification.
63
+ * @internal
64
+ */
65
+ this.toJSON = noop;
66
+ this.source = element;
54
67
  this.definition = definition;
55
68
  const shadowOptions = definition.shadowOptions;
56
69
  if (shadowOptions !== void 0) {
@@ -88,11 +101,7 @@ export class Controller extends PropertyChangeNotifier {
88
101
  */
89
102
  get isConnected() {
90
103
  Observable.track(this, isConnectedPropertyName);
91
- return this._isConnected;
92
- }
93
- setIsConnected(value) {
94
- this._isConnected = value;
95
- Observable.notify(this, isConnectedPropertyName);
104
+ return this.stage === 1 /* Stages.connected */;
96
105
  }
97
106
  /**
98
107
  * Gets/sets the template used to render the component.
@@ -104,9 +113,9 @@ export class Controller extends PropertyChangeNotifier {
104
113
  // 1. Template overrides take top precedence.
105
114
  if (this._template === null) {
106
115
  const definition = this.definition;
107
- if (this.element.resolveTemplate) {
116
+ if (this.source.resolveTemplate) {
108
117
  // 2. Allow for element instance overrides next.
109
- this._template = this.element.resolveTemplate();
118
+ this._template = this.source.resolveTemplate();
110
119
  }
111
120
  else if (definition.template) {
112
121
  // 3. Default to the static definition.
@@ -125,56 +134,104 @@ export class Controller extends PropertyChangeNotifier {
125
134
  }
126
135
  }
127
136
  /**
128
- * Gets/sets the primary styles used for the component.
129
- * @remarks
130
- * This value can only be accurately read after connect but can be set at any time.
137
+ * The main set of styles used for the component, independent
138
+ * of any dynamically added styles.
131
139
  */
132
- get styles() {
140
+ get mainStyles() {
133
141
  var _a;
134
142
  // 1. Styles overrides take top precedence.
135
- if (this._styles === null) {
143
+ if (this._mainStyles === null) {
136
144
  const definition = this.definition;
137
- if (this.element.resolveStyles) {
145
+ if (this.source.resolveStyles) {
138
146
  // 2. Allow for element instance overrides next.
139
- this._styles = this.element.resolveStyles();
147
+ this._mainStyles = this.source.resolveStyles();
140
148
  }
141
149
  else if (definition.styles) {
142
150
  // 3. Default to the static definition.
143
- this._styles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
151
+ this._mainStyles = (_a = definition.styles) !== null && _a !== void 0 ? _a : null;
144
152
  }
145
153
  }
146
- return this._styles;
154
+ return this._mainStyles;
147
155
  }
148
- set styles(value) {
149
- if (this._styles === value) {
156
+ set mainStyles(value) {
157
+ if (this._mainStyles === value) {
150
158
  return;
151
159
  }
152
- if (this._styles !== null) {
153
- this.removeStyles(this._styles);
160
+ if (this._mainStyles !== null) {
161
+ this.removeStyles(this._mainStyles);
154
162
  }
155
- this._styles = value;
163
+ this._mainStyles = value;
156
164
  if (!this.needsInitialization) {
157
165
  this.addStyles(value);
158
166
  }
159
167
  }
168
+ /**
169
+ * Adds the behavior to the component.
170
+ * @param behavior - The behavior to add.
171
+ */
172
+ addBehavior(behavior) {
173
+ var _a, _b;
174
+ const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
175
+ const count = (_b = targetBehaviors.get(behavior)) !== null && _b !== void 0 ? _b : 0;
176
+ if (count === 0) {
177
+ targetBehaviors.set(behavior, 1);
178
+ behavior.addedCallback && behavior.addedCallback(this);
179
+ if (behavior.connectedCallback &&
180
+ !this.guardBehaviorConnection &&
181
+ (this.stage === 1 /* Stages.connected */ || this.stage === 0 /* Stages.connecting */)) {
182
+ behavior.connectedCallback(this);
183
+ }
184
+ }
185
+ else {
186
+ targetBehaviors.set(behavior, count + 1);
187
+ }
188
+ }
189
+ /**
190
+ * Removes the behavior from the component.
191
+ * @param behavior - The behavior to remove.
192
+ * @param force - Forces removal even if this behavior was added more than once.
193
+ */
194
+ removeBehavior(behavior, force = false) {
195
+ const targetBehaviors = this.behaviors;
196
+ if (targetBehaviors === null) {
197
+ return;
198
+ }
199
+ const count = targetBehaviors.get(behavior);
200
+ if (count === void 0) {
201
+ return;
202
+ }
203
+ if (count === 1 || force) {
204
+ targetBehaviors.delete(behavior);
205
+ if (behavior.disconnectedCallback && this.stage !== 3 /* Stages.disconnected */) {
206
+ behavior.disconnectedCallback(this);
207
+ }
208
+ behavior.removedCallback && behavior.removedCallback(this);
209
+ }
210
+ else {
211
+ targetBehaviors.set(behavior, count - 1);
212
+ }
213
+ }
160
214
  /**
161
215
  * Adds styles to this element. Providing an HTMLStyleElement will attach the element instance to the shadowRoot.
162
216
  * @param styles - The styles to add.
163
217
  */
164
218
  addStyles(styles) {
219
+ var _a;
165
220
  if (!styles) {
166
221
  return;
167
222
  }
168
- const target = getShadowRoot(this.element) ||
169
- this.element.getRootNode();
223
+ const source = this.source;
170
224
  if (styles instanceof HTMLElement) {
225
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : this.source;
171
226
  target.append(styles);
172
227
  }
173
- else if (!styles.isAttachedTo(target)) {
228
+ else if (!styles.isAttachedTo(source)) {
174
229
  const sourceBehaviors = styles.behaviors;
175
- styles.addStylesTo(target);
230
+ styles.addStylesTo(source);
176
231
  if (sourceBehaviors !== null) {
177
- this.addBehaviors(sourceBehaviors);
232
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
233
+ this.addBehavior(sourceBehaviors[i]);
234
+ }
178
235
  }
179
236
  }
180
237
  }
@@ -183,121 +240,82 @@ export class Controller extends PropertyChangeNotifier {
183
240
  * @param styles - the styles to remove.
184
241
  */
185
242
  removeStyles(styles) {
243
+ var _a;
186
244
  if (!styles) {
187
245
  return;
188
246
  }
189
- const target = getShadowRoot(this.element) ||
190
- this.element.getRootNode();
247
+ const source = this.source;
191
248
  if (styles instanceof HTMLElement) {
249
+ const target = (_a = getShadowRoot(source)) !== null && _a !== void 0 ? _a : source;
192
250
  target.removeChild(styles);
193
251
  }
194
- else if (styles.isAttachedTo(target)) {
252
+ else if (styles.isAttachedTo(source)) {
195
253
  const sourceBehaviors = styles.behaviors;
196
- styles.removeStylesFrom(target);
254
+ styles.removeStylesFrom(source);
197
255
  if (sourceBehaviors !== null) {
198
- this.removeBehaviors(sourceBehaviors);
199
- }
200
- }
201
- }
202
- /**
203
- * Adds behaviors to this element.
204
- * @param behaviors - The behaviors to add.
205
- */
206
- addBehaviors(behaviors) {
207
- var _a;
208
- const targetBehaviors = (_a = this.behaviors) !== null && _a !== void 0 ? _a : (this.behaviors = new Map());
209
- const length = behaviors.length;
210
- const behaviorsToBind = [];
211
- for (let i = 0; i < length; ++i) {
212
- const behavior = behaviors[i];
213
- if (targetBehaviors.has(behavior)) {
214
- targetBehaviors.set(behavior, targetBehaviors.get(behavior) + 1);
215
- }
216
- else {
217
- targetBehaviors.set(behavior, 1);
218
- behaviorsToBind.push(behavior);
219
- }
220
- }
221
- if (this._isConnected) {
222
- const element = this.element;
223
- const context = ExecutionContext.default;
224
- for (let i = 0; i < behaviorsToBind.length; ++i) {
225
- behaviorsToBind[i].bind(element, context);
256
+ for (let i = 0, ii = sourceBehaviors.length; i < ii; ++i) {
257
+ this.addBehavior(sourceBehaviors[i]);
258
+ }
226
259
  }
227
260
  }
228
261
  }
229
262
  /**
230
- * Removes behaviors from this element.
231
- * @param behaviors - The behaviors to remove.
232
- * @param force - Forces unbinding of behaviors.
263
+ * Runs connected lifecycle behavior on the associated element.
233
264
  */
234
- removeBehaviors(behaviors, force = false) {
235
- const targetBehaviors = this.behaviors;
236
- if (targetBehaviors === null) {
265
+ connect() {
266
+ if (this.stage !== 3 /* Stages.disconnected */) {
237
267
  return;
238
268
  }
239
- const length = behaviors.length;
240
- const behaviorsToUnbind = [];
241
- for (let i = 0; i < length; ++i) {
242
- const behavior = behaviors[i];
243
- if (targetBehaviors.has(behavior)) {
244
- const count = targetBehaviors.get(behavior) - 1;
245
- count === 0 || force
246
- ? targetBehaviors.delete(behavior) && behaviorsToUnbind.push(behavior)
247
- : targetBehaviors.set(behavior, count);
269
+ this.stage = 0 /* Stages.connecting */;
270
+ // If we have any observables that were bound, re-apply their values.
271
+ if (this.boundObservables !== null) {
272
+ const element = this.source;
273
+ const boundObservables = this.boundObservables;
274
+ const propertyNames = Object.keys(boundObservables);
275
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
276
+ const propertyName = propertyNames[i];
277
+ element[propertyName] = boundObservables[propertyName];
248
278
  }
279
+ this.boundObservables = null;
249
280
  }
250
- if (this._isConnected) {
251
- const element = this.element;
252
- const context = ExecutionContext.default;
253
- for (let i = 0; i < behaviorsToUnbind.length; ++i) {
254
- behaviorsToUnbind[i].unbind(element, context);
281
+ const behaviors = this.behaviors;
282
+ if (behaviors !== null) {
283
+ this.guardBehaviorConnection = true;
284
+ for (const key of behaviors.keys()) {
285
+ key.connectedCallback && key.connectedCallback(this);
255
286
  }
287
+ this.guardBehaviorConnection = false;
256
288
  }
257
- }
258
- /**
259
- * Runs connected lifecycle behavior on the associated element.
260
- */
261
- onConnectedCallback() {
262
- if (this._isConnected) {
263
- return;
264
- }
265
- const element = this.element;
266
- const context = ExecutionContext.default;
267
289
  if (this.needsInitialization) {
268
- this.finishInitialization();
290
+ this.renderTemplate(this.template);
291
+ this.addStyles(this.mainStyles);
292
+ this.needsInitialization = false;
269
293
  }
270
294
  else if (this.view !== null) {
271
- this.view.bind(element, context);
295
+ this.view.bind(this.source);
272
296
  }
273
- const behaviors = this.behaviors;
274
- if (behaviors !== null) {
275
- for (const behavior of behaviors.keys()) {
276
- behavior.bind(element, context);
277
- }
278
- }
279
- this.setIsConnected(true);
297
+ this.stage = 1 /* Stages.connected */;
298
+ Observable.notify(this, isConnectedPropertyName);
280
299
  }
281
300
  /**
282
301
  * Runs disconnected lifecycle behavior on the associated element.
283
302
  */
284
- onDisconnectedCallback() {
285
- if (!this._isConnected) {
303
+ disconnect() {
304
+ if (this.stage !== 1 /* Stages.connected */) {
286
305
  return;
287
306
  }
288
- this.setIsConnected(false);
289
- const view = this.view;
290
- if (view !== null) {
291
- view.unbind();
307
+ this.stage = 2 /* Stages.disconnecting */;
308
+ Observable.notify(this, isConnectedPropertyName);
309
+ if (this.view !== null) {
310
+ this.view.unbind();
292
311
  }
293
312
  const behaviors = this.behaviors;
294
313
  if (behaviors !== null) {
295
- const element = this.element;
296
- const context = ExecutionContext.default;
297
- for (const behavior of behaviors.keys()) {
298
- behavior.unbind(element, context);
314
+ for (const key of behaviors.keys()) {
315
+ key.disconnectedCallback && key.disconnectedCallback(this);
299
316
  }
300
317
  }
318
+ this.stage = 3 /* Stages.disconnected */;
301
319
  }
302
320
  /**
303
321
  * Runs the attribute changed callback for the associated element.
@@ -308,7 +326,7 @@ export class Controller extends PropertyChangeNotifier {
308
326
  onAttributeChangedCallback(name, oldValue, newValue) {
309
327
  const attrDef = this.definition.attributeLookup[name];
310
328
  if (attrDef !== void 0) {
311
- attrDef.onAttributeChangedCallback(this.element, newValue);
329
+ attrDef.onAttributeChangedCallback(this.source, newValue);
312
330
  }
313
331
  }
314
332
  /**
@@ -320,33 +338,17 @@ export class Controller extends PropertyChangeNotifier {
320
338
  * Only emits events if connected.
321
339
  */
322
340
  emit(type, detail, options) {
323
- if (this._isConnected) {
324
- return this.element.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
341
+ if (this.stage === 1 /* Stages.connected */) {
342
+ return this.source.dispatchEvent(new CustomEvent(type, Object.assign(Object.assign({ detail }, defaultEventOptions), options)));
325
343
  }
326
344
  return false;
327
345
  }
328
- finishInitialization() {
329
- const element = this.element;
330
- const boundObservables = this.boundObservables;
331
- // If we have any observables that were bound, re-apply their values.
332
- if (boundObservables !== null) {
333
- const propertyNames = Object.keys(boundObservables);
334
- for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
335
- const propertyName = propertyNames[i];
336
- element[propertyName] = boundObservables[propertyName];
337
- }
338
- this.boundObservables = null;
339
- }
340
- this.renderTemplate(this.template);
341
- this.addStyles(this.styles);
342
- this.needsInitialization = false;
343
- }
344
346
  renderTemplate(template) {
345
347
  var _a;
346
- const element = this.element;
347
348
  // When getting the host to render to, we start by looking
348
349
  // up the shadow root. If there isn't one, then that means
349
350
  // we're doing a Light DOM render to the element's direct children.
351
+ const element = this.source;
350
352
  const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
351
353
  if (this.view !== null) {
352
354
  // If there's already a view, we need to unbind and remove through dispose.
@@ -363,6 +365,8 @@ export class Controller extends PropertyChangeNotifier {
363
365
  if (template) {
364
366
  // If a new template was provided, render it.
365
367
  this.view = template.render(element, host, element);
368
+ this.view.sourceLifetime =
369
+ SourceLifetime.coupled;
366
370
  }
367
371
  }
368
372
  /**
@@ -382,6 +386,103 @@ export class Controller extends PropertyChangeNotifier {
382
386
  if (definition === void 0) {
383
387
  throw FAST.error(1401 /* Message.missingElementDefinition */);
384
388
  }
385
- return (element.$fastController = new Controller(element, definition));
389
+ return (element.$fastController = new elementControllerStrategy(element, definition));
390
+ }
391
+ /**
392
+ * Sets the strategy that ElementController.forCustomElement uses to construct
393
+ * ElementController instances for an element.
394
+ * @param strategy - The strategy to use.
395
+ */
396
+ static setStrategy(strategy) {
397
+ elementControllerStrategy = strategy;
398
+ }
399
+ }
400
+ // Set default strategy for ElementController
401
+ ElementController.setStrategy(ElementController);
402
+ /**
403
+ * Converts a styleTarget into the operative target. When the provided target is an Element
404
+ * that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
405
+ * it will return the root node for the element.
406
+ * @param target
407
+ * @returns
408
+ */
409
+ function normalizeStyleTarget(target) {
410
+ var _a;
411
+ if ("adoptedStyleSheets" in target) {
412
+ return target;
413
+ }
414
+ else {
415
+ return ((_a = getShadowRoot(target)) !== null && _a !== void 0 ? _a : target.getRootNode());
416
+ }
417
+ }
418
+ // Default StyleStrategy implementations are defined in this module because they
419
+ // require access to element shadowRoots, and we don't want to leak shadowRoot
420
+ // objects out of this module.
421
+ /**
422
+ * https://wicg.github.io/construct-stylesheets/
423
+ * https://developers.google.com/web/updates/2019/02/constructable-stylesheets
424
+ *
425
+ * @internal
426
+ */
427
+ export class AdoptedStyleSheetsStrategy {
428
+ constructor(styles) {
429
+ const styleSheetCache = AdoptedStyleSheetsStrategy.styleSheetCache;
430
+ this.sheets = styles.map((x) => {
431
+ if (x instanceof CSSStyleSheet) {
432
+ return x;
433
+ }
434
+ let sheet = styleSheetCache.get(x);
435
+ if (sheet === void 0) {
436
+ sheet = new CSSStyleSheet();
437
+ sheet.replaceSync(x);
438
+ styleSheetCache.set(x, sheet);
439
+ }
440
+ return sheet;
441
+ });
442
+ }
443
+ addStylesTo(target) {
444
+ const t = normalizeStyleTarget(target);
445
+ t.adoptedStyleSheets = [...t.adoptedStyleSheets, ...this.sheets];
446
+ }
447
+ removeStylesFrom(target) {
448
+ const t = normalizeStyleTarget(target);
449
+ const sheets = this.sheets;
450
+ t.adoptedStyleSheets = t.adoptedStyleSheets.filter((x) => sheets.indexOf(x) === -1);
451
+ }
452
+ }
453
+ AdoptedStyleSheetsStrategy.styleSheetCache = new Map();
454
+ let id = 0;
455
+ const nextStyleId = () => `fast-${++id}`;
456
+ function usableStyleTarget(target) {
457
+ return target === document ? document.body : target;
458
+ }
459
+ /**
460
+ * @internal
461
+ */
462
+ export class StyleElementStrategy {
463
+ constructor(styles) {
464
+ this.styles = styles;
465
+ this.styleClass = nextStyleId();
466
+ }
467
+ addStylesTo(target) {
468
+ target = usableStyleTarget(normalizeStyleTarget(target));
469
+ const styles = this.styles;
470
+ const styleClass = this.styleClass;
471
+ for (let i = 0; i < styles.length; i++) {
472
+ const element = document.createElement("style");
473
+ element.innerHTML = styles[i];
474
+ element.className = styleClass;
475
+ target.append(element);
476
+ }
477
+ }
478
+ removeStylesFrom(target) {
479
+ target = usableStyleTarget(normalizeStyleTarget(target));
480
+ const styles = target.querySelectorAll(`.${this.styleClass}`);
481
+ for (let i = 0, ii = styles.length; i < ii; ++i) {
482
+ target.removeChild(styles[i]);
483
+ }
386
484
  }
387
485
  }
486
+ ElementStyles.setDefaultStrategy(ElementStyles.supportsAdoptedStyleSheets
487
+ ? AdoptedStyleSheetsStrategy
488
+ : StyleElementStrategy);
@@ -1,29 +1,28 @@
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
12
13
  */
13
14
  export class FASTElementDefinition {
14
- /**
15
- * Creates an instance of FASTElementDefinition.
16
- * @param type - The type this definition is being created for.
17
- * @param nameOrConfig - The name of the element to define or a config object
18
- * that describes the element to define.
19
- */
20
15
  constructor(type, nameOrConfig = type.definition) {
16
+ var _a;
17
+ this.platformDefined = false;
21
18
  if (isString(nameOrConfig)) {
22
19
  nameOrConfig = { name: nameOrConfig };
23
20
  }
24
21
  this.type = type;
25
22
  this.name = nameOrConfig.name;
26
23
  this.template = nameOrConfig.template;
24
+ this.registry = (_a = nameOrConfig.registry) !== null && _a !== void 0 ? _a : customElements;
25
+ const proto = type.prototype;
27
26
  const attributes = AttributeDefinition.collect(type, nameOrConfig.attributes);
28
27
  const observedAttributes = new Array(attributes.length);
29
28
  const propertyLookup = {};
@@ -33,9 +32,13 @@ export class FASTElementDefinition {
33
32
  observedAttributes[i] = current.attribute;
34
33
  propertyLookup[current.name] = current;
35
34
  attributeLookup[current.attribute] = current;
35
+ Observable.defineProperty(proto, current);
36
36
  }
37
+ Reflect.defineProperty(type, "observedAttributes", {
38
+ value: observedAttributes,
39
+ enumerable: true,
40
+ });
37
41
  this.attributes = attributes;
38
- this.observedAttributes = observedAttributes;
39
42
  this.propertyLookup = propertyLookup;
40
43
  this.attributeLookup = attributeLookup;
41
44
  this.shadowOptions =
@@ -48,20 +51,14 @@ export class FASTElementDefinition {
48
51
  nameOrConfig.elementOptions === void 0
49
52
  ? defaultElementOptions
50
53
  : Object.assign(Object.assign({}, defaultElementOptions), nameOrConfig.elementOptions);
51
- this.styles =
52
- nameOrConfig.styles === void 0
53
- ? void 0
54
- : Array.isArray(nameOrConfig.styles)
55
- ? new ElementStyles(nameOrConfig.styles)
56
- : nameOrConfig.styles instanceof ElementStyles
57
- ? nameOrConfig.styles
58
- : new ElementStyles([nameOrConfig.styles]);
54
+ this.styles = ElementStyles.normalize(nameOrConfig.styles);
55
+ fastElementRegistry.register(this);
59
56
  }
60
57
  /**
61
58
  * Indicates if this element has been defined in at least one registry.
62
59
  */
63
60
  get isDefined() {
64
- return !!fastElementRegistry.getByType(this.type);
61
+ return this.platformDefined;
65
62
  }
66
63
  /**
67
64
  * Defines a custom element based on this definition.
@@ -69,24 +66,35 @@ export class FASTElementDefinition {
69
66
  * @remarks
70
67
  * This operation is idempotent per registry.
71
68
  */
72
- define(registry = customElements) {
69
+ define(registry = this.registry) {
73
70
  const type = this.type;
74
- if (fastElementRegistry.register(this)) {
75
- const attributes = this.attributes;
76
- const proto = type.prototype;
77
- for (let i = 0, ii = attributes.length; i < ii; ++i) {
78
- Observable.defineProperty(proto, attributes[i]);
79
- }
80
- Reflect.defineProperty(type, "observedAttributes", {
81
- value: this.observedAttributes,
82
- enumerable: true,
83
- });
84
- }
85
71
  if (!registry.get(this.name)) {
72
+ this.platformDefined = true;
86
73
  registry.define(this.name, type, this.elementOptions);
87
74
  }
88
75
  return this;
89
76
  }
77
+ /**
78
+ * Creates an instance of FASTElementDefinition.
79
+ * @param type - The type this definition is being created for.
80
+ * @param nameOrDef - The name of the element to define or a config object
81
+ * that describes the element to define.
82
+ */
83
+ static compose(type, nameOrDef) {
84
+ if (fastElementBaseTypes.has(type) || fastElementRegistry.getByType(type)) {
85
+ return new FASTElementDefinition(class extends type {
86
+ }, nameOrDef);
87
+ }
88
+ return new FASTElementDefinition(type, nameOrDef);
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.