@microsoft/fast-element 2.0.0-beta.13 → 2.0.0-beta.15

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.
package/CHANGELOG.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@microsoft/fast-element",
3
3
  "entries": [
4
+ {
5
+ "date": "Tue, 25 Oct 2022 20:24:32 GMT",
6
+ "tag": "@microsoft/fast-element_v2.0.0-beta.15",
7
+ "version": "2.0.0-beta.15",
8
+ "comments": {
9
+ "prerelease": [
10
+ {
11
+ "author": "nicholasrice@users.noreply.github.com",
12
+ "package": "@microsoft/fast-element",
13
+ "commit": "220cc7e0e0de490e51cc8b6f42ff46b03228beaa",
14
+ "comment": "Updated the ElementController to connect behaviors prior to rendering element templates"
15
+ },
16
+ {
17
+ "author": "nicholasrice@users.noreply.github.com",
18
+ "package": "@microsoft/fast-element",
19
+ "commit": "32ca5de5feda4870ed36329f58ded4a0a0421f02",
20
+ "comment": "Added support to fast-element to support element hydration and the `defer-hydration` attribute"
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ {
26
+ "date": "Fri, 14 Oct 2022 18:26:11 GMT",
27
+ "tag": "@microsoft/fast-element_v2.0.0-beta.14",
28
+ "version": "2.0.0-beta.14",
29
+ "comments": {
30
+ "prerelease": [
31
+ {
32
+ "author": "roeisenb@microsoft.com",
33
+ "package": "@microsoft/fast-element",
34
+ "commit": "19f1ae727e2ff6e629d2b134fc045a81d83caaed",
35
+ "comment": "fix: tokenList bindings track adds and removes from multiple sources"
36
+ },
37
+ {
38
+ "author": "roeisenb@microsoft.com",
39
+ "package": "@microsoft/fast-element",
40
+ "commit": "0248fa906f986d6b0cbdeb26cf388742e871449e",
41
+ "comment": "refactor: remove duplication and re-organize binding behavior logic"
42
+ }
43
+ ]
44
+ }
45
+ },
4
46
  {
5
47
  "date": "Mon, 10 Oct 2022 20:28:02 GMT",
6
48
  "tag": "@microsoft/fast-element_v2.0.0-beta.13",
package/CHANGELOG.md CHANGED
@@ -1,9 +1,27 @@
1
1
  # Change Log - @microsoft/fast-element
2
2
 
3
- This log was last generated on Mon, 10 Oct 2022 20:28:02 GMT and should not be manually modified.
3
+ This log was last generated on Tue, 25 Oct 2022 20:24:32 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 2.0.0-beta.15
8
+
9
+ Tue, 25 Oct 2022 20:24:32 GMT
10
+
11
+ ### Changes
12
+
13
+ - Updated the ElementController to connect behaviors prior to rendering element templates (nicholasrice@users.noreply.github.com)
14
+ - Added support to fast-element to support element hydration and the `defer-hydration` attribute (nicholasrice@users.noreply.github.com)
15
+
16
+ ## 2.0.0-beta.14
17
+
18
+ Fri, 14 Oct 2022 18:26:11 GMT
19
+
20
+ ### Changes
21
+
22
+ - fix: tokenList bindings track adds and removes from multiple sources (roeisenb@microsoft.com)
23
+ - refactor: remove duplication and re-organize binding behavior logic (roeisenb@microsoft.com)
24
+
7
25
  ## 2.0.0-beta.13
8
26
 
9
27
  Mon, 10 Oct 2022 20:28:02 GMT
@@ -1,10 +1,17 @@
1
1
  import { StyleStrategy, StyleTarget } from "../interfaces.js";
2
- import type { HostBehavior, HostController } from "../styles/host.js";
3
2
  import { PropertyChangeNotifier } from "../observation/notifier.js";
3
+ import { ElementStyles } from "../styles/element-styles.js";
4
+ import type { HostBehavior, HostController } from "../styles/host.js";
4
5
  import type { ElementViewTemplate } from "../templating/template.js";
5
6
  import type { ElementView } from "../templating/view.js";
6
- import { ElementStyles } from "../styles/element-styles.js";
7
7
  import { FASTElementDefinition } from "./fast-definitions.js";
8
+ /**
9
+ * A type that instantiates an ElementController
10
+ * @public
11
+ */
12
+ export interface ElementControllerStrategy {
13
+ new (element: HTMLElement, definition: FASTElementDefinition): ElementController;
14
+ }
8
15
  /**
9
16
  * Controls the lifecycle and rendering of a `FASTElement`.
10
17
  * @public
@@ -113,7 +120,6 @@ export declare class ElementController<TElement extends HTMLElement = HTMLElemen
113
120
  * Only emits events if connected.
114
121
  */
115
122
  emit(type: string, detail?: any, options?: Omit<CustomEventInit, "detail">): void | boolean;
116
- private finishInitialization;
117
123
  private renderTemplate;
118
124
  /**
119
125
  * Locates or creates a controller for the specified element.
@@ -124,6 +130,12 @@ export declare class ElementController<TElement extends HTMLElement = HTMLElemen
124
130
  * decorator or a call to `FASTElement.define`.
125
131
  */
126
132
  static forCustomElement(element: HTMLElement): ElementController;
133
+ /**
134
+ * Sets the strategy that ElementController.forCustomElement uses to construct
135
+ * ElementController instances for an element.
136
+ * @param strategy - The strategy to use.
137
+ */
138
+ static setStrategy(strategy: ElementControllerStrategy): void;
127
139
  }
128
140
  /**
129
141
  * https://wicg.github.io/construct-stylesheets/
@@ -0,0 +1,14 @@
1
+ import { ElementController } from "./element-controller.js";
2
+ /**
3
+ * An ElementController capable of hydrating FAST elements from
4
+ * Declarative Shadow DOM.
5
+ *
6
+ * @beta
7
+ */
8
+ export declare class HydratableElementController<TElement extends HTMLElement = HTMLElement> extends ElementController<TElement> {
9
+ private static hydrationObserver;
10
+ private static hydrationObserverHandler;
11
+ connect(): void;
12
+ disconnect(): void;
13
+ static install(): void;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -24,4 +24,4 @@ export * from "./templating/node-observation.js";
24
24
  export * from "./components/fast-element.js";
25
25
  export * from "./components/fast-definitions.js";
26
26
  export * from "./components/attributes.js";
27
- export { ElementController } from "./components/element-controller.js";
27
+ export { ElementController, ElementControllerStrategy, } from "./components/element-controller.js";
@@ -80,13 +80,7 @@ export declare class HTMLBindingDirective implements HTMLDirective, ViewBehavior
80
80
  */
81
81
  createBehavior(): ViewBehavior;
82
82
  /** @internal */
83
- bindDefault(controller: ViewController): void;
84
- /** @internal */
85
- bind: (controller: ViewController) => void;
86
- /** @internal */
87
- bindContent(controller: ViewController): void;
88
- /** @internal */
89
- bindEvent(controller: ViewController): void;
83
+ bind(controller: ViewController): void;
90
84
  /** @internal */
91
85
  unbind(controller: ViewController): void;
92
86
  /** @internal */
@@ -96,27 +90,27 @@ export declare class HTMLBindingDirective implements HTMLDirective, ViewBehavior
96
90
  }
97
91
  /**
98
92
  * Creates an standard binding.
99
- * @param binding - The binding to refresh when changed.
93
+ * @param expression - The binding to refresh when changed.
100
94
  * @param isVolatile - Indicates whether the binding is volatile or not.
101
95
  * @returns A binding configuration.
102
96
  * @public
103
97
  */
104
- export declare function bind<T = any>(binding: Expression<T>, isVolatile?: boolean): Binding<T>;
98
+ export declare function bind<T = any>(expression: Expression<T>, isVolatile?: boolean): Binding<T>;
105
99
  /**
106
100
  * Creates a one time binding
107
- * @param binding - The binding to refresh when signaled.
101
+ * @param expression - The binding to refresh when signaled.
108
102
  * @returns A binding configuration.
109
103
  * @public
110
104
  */
111
- export declare function oneTime<T = any>(binding: Expression<T>): Binding<T>;
105
+ export declare function oneTime<T = any>(expression: Expression<T>): Binding<T>;
112
106
  /**
113
107
  * Creates an event listener binding.
114
- * @param binding - The binding to invoke when the event is raised.
108
+ * @param expression - The binding to invoke when the event is raised.
115
109
  * @param options - Event listener options.
116
110
  * @returns A binding configuration.
117
111
  * @public
118
112
  */
119
- export declare function listener<T = any>(binding: Expression<T>, options?: AddEventListenerOptions): Binding<T>;
113
+ export declare function listener<T = any>(expression: Expression<T>, options?: AddEventListenerOptions): Binding<T>;
120
114
  /**
121
115
  * Normalizes the input value into a binding.
122
116
  * @param value - The value to create the default binding for.
@@ -1,7 +1,7 @@
1
1
  import { ExecutionContext, ViewBehavior, ViewBehaviorTargets } from "../index.js";
2
2
  export declare const Fake: Readonly<{
3
3
  executionContext<TParent = any>(parent?: TParent | undefined, parentContext?: ExecutionContext<TParent> | undefined): ExecutionContext<TParent>;
4
- viewController<TSource = any, TParent_1 = any>(targets: ViewBehaviorTargets, ...behaviors: ViewBehavior<TSource, TParent_1>[]): {
4
+ viewController<TSource = any, TParent_1 = any>(targets?: ViewBehaviorTargets, ...behaviors: ViewBehavior<TSource, TParent_1>[]): {
5
5
  isBound: boolean;
6
6
  context: ExecutionContext<TParent_1>;
7
7
  onUnbind(object: any): void;
@@ -20,3 +20,17 @@ export declare function composedParent<T extends HTMLElement>(element: T): HTMLE
20
20
  * @public
21
21
  */
22
22
  export declare function composedContains(reference: HTMLElement, test: HTMLElement): boolean;
23
+ /**
24
+ * @internal
25
+ */
26
+ export declare class UnobservableMutationObserver extends MutationObserver {
27
+ private readonly callback;
28
+ private observedNodes;
29
+ /**
30
+ * An extension of MutationObserver that supports unobserving nodes.
31
+ * @param callback - The callback to invoke when observed nodes are changed.
32
+ */
33
+ constructor(callback: MutationCallback);
34
+ observe(target: Node, options?: MutationObserverInit | undefined): void;
35
+ unobserve(target: Node): void;
36
+ }
@@ -15,6 +15,7 @@ function getShadowRoot(element) {
15
15
  var _a, _b;
16
16
  return (_b = (_a = element.shadowRoot) !== null && _a !== void 0 ? _a : shadowRoots.get(element)) !== null && _b !== void 0 ? _b : null;
17
17
  }
18
+ let elementControllerStrategy;
18
19
  /**
19
20
  * Controls the lifecycle and rendering of a `FASTElement`.
20
21
  * @public
@@ -256,11 +257,16 @@ export class ElementController extends PropertyChangeNotifier {
256
257
  if (this._isConnected) {
257
258
  return;
258
259
  }
259
- if (this.needsInitialization) {
260
- this.finishInitialization();
261
- }
262
- else if (this.view !== null) {
263
- this.view.bind(this.source);
260
+ // If we have any observables that were bound, re-apply their values.
261
+ if (this.boundObservables !== null) {
262
+ const element = this.source;
263
+ const boundObservables = this.boundObservables;
264
+ const propertyNames = Object.keys(boundObservables);
265
+ for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
266
+ const propertyName = propertyNames[i];
267
+ element[propertyName] = boundObservables[propertyName];
268
+ }
269
+ this.boundObservables = null;
264
270
  }
265
271
  const behaviors = this.behaviors;
266
272
  if (behaviors !== null) {
@@ -268,6 +274,14 @@ export class ElementController extends PropertyChangeNotifier {
268
274
  key.connectedCallback && key.connectedCallback(this);
269
275
  }
270
276
  }
277
+ if (this.needsInitialization) {
278
+ this.renderTemplate(this.template);
279
+ this.addStyles(this.mainStyles);
280
+ this.needsInitialization = false;
281
+ }
282
+ else if (this.view !== null) {
283
+ this.view.bind(this.source);
284
+ }
271
285
  this.setIsConnected(true);
272
286
  }
273
287
  /**
@@ -314,22 +328,6 @@ export class ElementController extends PropertyChangeNotifier {
314
328
  }
315
329
  return false;
316
330
  }
317
- finishInitialization() {
318
- const element = this.source;
319
- const boundObservables = this.boundObservables;
320
- // If we have any observables that were bound, re-apply their values.
321
- if (boundObservables !== null) {
322
- const propertyNames = Object.keys(boundObservables);
323
- for (let i = 0, ii = propertyNames.length; i < ii; ++i) {
324
- const propertyName = propertyNames[i];
325
- element[propertyName] = boundObservables[propertyName];
326
- }
327
- this.boundObservables = null;
328
- }
329
- this.renderTemplate(this.template);
330
- this.addStyles(this.mainStyles);
331
- this.needsInitialization = false;
332
- }
333
331
  renderTemplate(template) {
334
332
  var _a;
335
333
  // When getting the host to render to, we start by looking
@@ -373,9 +371,19 @@ export class ElementController extends PropertyChangeNotifier {
373
371
  if (definition === void 0) {
374
372
  throw FAST.error(1401 /* Message.missingElementDefinition */);
375
373
  }
376
- return (element.$fastController = new ElementController(element, definition));
374
+ return (element.$fastController = new elementControllerStrategy(element, definition));
375
+ }
376
+ /**
377
+ * Sets the strategy that ElementController.forCustomElement uses to construct
378
+ * ElementController instances for an element.
379
+ * @param strategy - The strategy to use.
380
+ */
381
+ static setStrategy(strategy) {
382
+ elementControllerStrategy = strategy;
377
383
  }
378
384
  }
385
+ // Set default strategy for ElementController
386
+ ElementController.setStrategy(ElementController);
379
387
  /**
380
388
  * Converts a styleTarget into the operative target. When the provided target is an Element
381
389
  * that is a FASTElement, the function will return the ShadowRoot for that element. Otherwise,
@@ -0,0 +1,35 @@
1
+ import { UnobservableMutationObserver } from "../utilities.js";
2
+ import { ElementController } from "./element-controller.js";
3
+ const deferHydrationAttribute = "defer-hydration";
4
+ /**
5
+ * An ElementController capable of hydrating FAST elements from
6
+ * Declarative Shadow DOM.
7
+ *
8
+ * @beta
9
+ */
10
+ export class HydratableElementController extends ElementController {
11
+ static hydrationObserverHandler(records) {
12
+ for (const record of records) {
13
+ HydratableElementController.hydrationObserver.unobserve(record.target);
14
+ record.target.$fastController.connect();
15
+ }
16
+ }
17
+ connect() {
18
+ if (this.source.hasAttribute(deferHydrationAttribute)) {
19
+ HydratableElementController.hydrationObserver.observe(this.source, {
20
+ attributeFilter: [deferHydrationAttribute],
21
+ });
22
+ }
23
+ else {
24
+ super.connect();
25
+ }
26
+ }
27
+ disconnect() {
28
+ super.disconnect();
29
+ HydratableElementController.hydrationObserver.unobserve(this.source);
30
+ }
31
+ static install() {
32
+ ElementController.setStrategy(HydratableElementController);
33
+ }
34
+ }
35
+ HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
@@ -0,0 +1,2 @@
1
+ import { HydratableElementController } from "./hydration.js";
2
+ HydratableElementController.install();
package/dist/esm/index.js CHANGED
@@ -27,4 +27,4 @@ export * from "./templating/node-observation.js";
27
27
  export * from "./components/fast-element.js";
28
28
  export * from "./components/fast-definitions.js";
29
29
  export * from "./components/attributes.js";
30
- export { ElementController } from "./components/element-controller.js";
30
+ export { ElementController, } from "./components/element-controller.js";
@@ -88,9 +88,9 @@ function updateContent(target, aspect, value, controller) {
88
88
  function updateTokenList(target, aspect, value) {
89
89
  var _a;
90
90
  const lookup = `${this.id}-t`;
91
- const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
92
- const versions = state.v;
93
- let currentVersion = state.c;
91
+ const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { v: 0, cv: Object.create(null) });
92
+ const classVersions = state.cv;
93
+ let version = state.v;
94
94
  const tokenList = target[aspect];
95
95
  // Add the classes, tracking the version at which they were added.
96
96
  if (value !== null && value !== undefined && value.length) {
@@ -100,19 +100,19 @@ function updateTokenList(target, aspect, value) {
100
100
  if (currentName === "") {
101
101
  continue;
102
102
  }
103
- versions[currentName] = currentVersion;
103
+ classVersions[currentName] = version;
104
104
  tokenList.add(currentName);
105
105
  }
106
106
  }
107
- state.v = currentVersion + 1;
107
+ state.v = version + 1;
108
108
  // If this is the first call to add classes, there's no need to remove old ones.
109
- if (currentVersion === 0) {
109
+ if (version === 0) {
110
110
  return;
111
111
  }
112
112
  // Remove classes from the previous version.
113
- currentVersion -= 1;
114
- for (const name in versions) {
115
- if (versions[name] === currentVersion) {
113
+ version -= 1;
114
+ for (const name in classVersions) {
115
+ if (classVersions[name] === version) {
116
116
  tokenList.remove(name);
117
117
  }
118
118
  }
@@ -139,8 +139,6 @@ export class HTMLBindingDirective {
139
139
  * The type of aspect to target.
140
140
  */
141
141
  this.aspectType = Aspect.content;
142
- /** @internal */
143
- this.bind = this.bindDefault;
144
142
  this.data = `${this.id}-d`;
145
143
  }
146
144
  /**
@@ -169,14 +167,12 @@ export class HTMLBindingDirective {
169
167
  this.updateTarget = setProperty;
170
168
  break;
171
169
  case 4:
172
- this.bind = this.bindContent;
173
170
  this.updateTarget = updateContent;
174
171
  break;
175
172
  case 5:
176
173
  this.updateTarget = updateTokenList;
177
174
  break;
178
175
  case 6:
179
- this.bind = this.bindEvent;
180
176
  this.updateTarget = eventTarget;
181
177
  break;
182
178
  default:
@@ -186,29 +182,26 @@ export class HTMLBindingDirective {
186
182
  return this;
187
183
  }
188
184
  /** @internal */
189
- bindDefault(controller) {
185
+ bind(controller) {
190
186
  var _a;
191
187
  const target = controller.targets[this.nodeId];
192
- const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
193
- observer.target = target;
194
- observer.controller = controller;
195
- this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
196
- if (this.updateTarget === updateContent) {
197
- controller.onUnbind(this);
188
+ switch (this.updateTarget) {
189
+ case eventTarget:
190
+ target[this.data] = controller;
191
+ target.addEventListener(this.targetAspect, this, this.dataBinding.options);
192
+ break;
193
+ case updateContent:
194
+ controller.onUnbind(this);
195
+ // intentional fall through
196
+ default:
197
+ const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : (target[this.data] = this.dataBinding.createObserver(this, this));
198
+ observer.target = target;
199
+ observer.controller = controller;
200
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
201
+ break;
198
202
  }
199
203
  }
200
204
  /** @internal */
201
- bindContent(controller) {
202
- this.bindDefault(controller);
203
- controller.onUnbind(this);
204
- }
205
- /** @internal */
206
- bindEvent(controller) {
207
- const target = controller.targets[this.nodeId];
208
- target[this.data] = controller;
209
- target.addEventListener(this.targetAspect, this, this.dataBinding.options);
210
- }
211
- /** @internal */
212
205
  unbind(controller) {
213
206
  const target = controller.targets[this.nodeId];
214
207
  const view = target.$fastView;
@@ -239,32 +232,32 @@ export class HTMLBindingDirective {
239
232
  HTMLDirective.define(HTMLBindingDirective, { aspected: true });
240
233
  /**
241
234
  * Creates an standard binding.
242
- * @param binding - The binding to refresh when changed.
235
+ * @param expression - The binding to refresh when changed.
243
236
  * @param isVolatile - Indicates whether the binding is volatile or not.
244
237
  * @returns A binding configuration.
245
238
  * @public
246
239
  */
247
- export function bind(binding, isVolatile = Observable.isVolatileBinding(binding)) {
248
- return new OnChangeBinding(binding, isVolatile);
240
+ export function bind(expression, isVolatile = Observable.isVolatileBinding(expression)) {
241
+ return new OnChangeBinding(expression, isVolatile);
249
242
  }
250
243
  /**
251
244
  * Creates a one time binding
252
- * @param binding - The binding to refresh when signaled.
245
+ * @param expression - The binding to refresh when signaled.
253
246
  * @returns A binding configuration.
254
247
  * @public
255
248
  */
256
- export function oneTime(binding) {
257
- return new OneTimeBinding(binding);
249
+ export function oneTime(expression) {
250
+ return new OneTimeBinding(expression);
258
251
  }
259
252
  /**
260
253
  * Creates an event listener binding.
261
- * @param binding - The binding to invoke when the event is raised.
254
+ * @param expression - The binding to invoke when the event is raised.
262
255
  * @param options - Event listener options.
263
256
  * @returns A binding configuration.
264
257
  * @public
265
258
  */
266
- export function listener(binding, options) {
267
- const config = new OnChangeBinding(binding, false);
259
+ export function listener(expression, options) {
260
+ const config = new OnChangeBinding(expression, false);
268
261
  config.options = options;
269
262
  return config;
270
263
  }
@@ -73,7 +73,7 @@ export const Fake = Object.freeze({
73
73
  },
74
74
  };
75
75
  },
76
- viewController(targets, ...behaviors) {
76
+ viewController(targets = {}, ...behaviors) {
77
77
  const unbindables = new Set();
78
78
  return {
79
79
  isBound: false,
@@ -42,3 +42,30 @@ export function composedContains(reference, test) {
42
42
  }
43
43
  return false;
44
44
  }
45
+ /**
46
+ * @internal
47
+ */
48
+ export class UnobservableMutationObserver extends MutationObserver {
49
+ /**
50
+ * An extension of MutationObserver that supports unobserving nodes.
51
+ * @param callback - The callback to invoke when observed nodes are changed.
52
+ */
53
+ constructor(callback) {
54
+ function handler(mutations) {
55
+ this.callback.call(null, mutations.filter(record => this.observedNodes.has(record.target)));
56
+ }
57
+ super(handler);
58
+ this.callback = callback;
59
+ this.observedNodes = new Set();
60
+ }
61
+ observe(target, options) {
62
+ this.observedNodes.add(target);
63
+ super.observe(target, options);
64
+ }
65
+ unobserve(target) {
66
+ this.observedNodes.delete(target);
67
+ if (this.observedNodes.size < 1) {
68
+ this.disconnect();
69
+ }
70
+ }
71
+ }