@microsoft/fast-element 1.10.0 → 2.0.0-beta.1

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 (97) hide show
  1. package/.eslintrc.json +1 -12
  2. package/CHANGELOG.json +396 -1
  3. package/CHANGELOG.md +68 -2
  4. package/README.md +2 -2
  5. package/dist/dts/components/attributes.d.ts +4 -1
  6. package/dist/dts/components/controller.d.ts +12 -11
  7. package/dist/dts/components/fast-definitions.d.ts +8 -2
  8. package/dist/dts/components/fast-element.d.ts +5 -4
  9. package/dist/dts/debug.d.ts +1 -0
  10. package/dist/dts/hooks.d.ts +20 -0
  11. package/dist/dts/index.d.ts +16 -15
  12. package/dist/dts/index.debug.d.ts +2 -0
  13. package/dist/dts/index.rollup.d.ts +2 -0
  14. package/dist/dts/index.rollup.debug.d.ts +3 -0
  15. package/dist/dts/interfaces.d.ts +144 -0
  16. package/dist/dts/observation/arrays.d.ts +207 -0
  17. package/dist/dts/observation/behavior.d.ts +5 -5
  18. package/dist/dts/observation/notifier.d.ts +18 -18
  19. package/dist/dts/observation/observable.d.ts +86 -29
  20. package/dist/dts/observation/splice-strategies.d.ts +13 -0
  21. package/dist/dts/observation/update-queue.d.ts +40 -0
  22. package/dist/dts/platform.d.ts +18 -67
  23. package/dist/dts/polyfills.d.ts +8 -0
  24. package/dist/dts/styles/css-directive.d.ts +43 -5
  25. package/dist/dts/styles/css.d.ts +19 -3
  26. package/dist/dts/styles/element-styles.d.ts +42 -62
  27. package/dist/dts/templating/binding.d.ts +320 -64
  28. package/dist/dts/templating/children.d.ts +18 -15
  29. package/dist/dts/templating/compiler.d.ts +47 -28
  30. package/dist/dts/templating/dom.d.ts +41 -0
  31. package/dist/dts/templating/html-directive.d.ts +179 -43
  32. package/dist/dts/templating/markup.d.ts +48 -0
  33. package/dist/dts/templating/node-observation.d.ts +45 -29
  34. package/dist/dts/templating/ref.d.ts +6 -12
  35. package/dist/dts/templating/repeat.d.ts +72 -14
  36. package/dist/dts/templating/slotted.d.ts +13 -14
  37. package/dist/dts/templating/template.d.ts +78 -23
  38. package/dist/dts/templating/view.d.ts +16 -23
  39. package/dist/dts/utilities.d.ts +40 -0
  40. package/dist/esm/components/attributes.js +25 -24
  41. package/dist/esm/components/controller.js +77 -57
  42. package/dist/esm/components/fast-definitions.js +14 -22
  43. package/dist/esm/debug.js +29 -0
  44. package/dist/esm/hooks.js +32 -0
  45. package/dist/esm/index.debug.js +2 -0
  46. package/dist/esm/index.js +19 -14
  47. package/dist/esm/index.rollup.debug.js +3 -0
  48. package/dist/esm/index.rollup.js +2 -0
  49. package/dist/esm/interfaces.js +8 -1
  50. package/dist/esm/observation/arrays.js +269 -0
  51. package/dist/esm/observation/notifier.js +75 -83
  52. package/dist/esm/observation/observable.js +80 -107
  53. package/dist/esm/observation/{array-change-records.js → splice-strategies.js} +136 -62
  54. package/dist/esm/observation/update-queue.js +67 -0
  55. package/dist/esm/platform.js +36 -42
  56. package/dist/esm/polyfills.js +85 -0
  57. package/dist/esm/styles/css-directive.js +29 -13
  58. package/dist/esm/styles/css.js +27 -40
  59. package/dist/esm/styles/element-styles.js +65 -104
  60. package/dist/esm/templating/binding.js +465 -155
  61. package/dist/esm/templating/children.js +33 -23
  62. package/dist/esm/templating/compiler.js +235 -152
  63. package/dist/esm/templating/dom.js +49 -0
  64. package/dist/esm/templating/html-directive.js +125 -40
  65. package/dist/esm/templating/markup.js +75 -0
  66. package/dist/esm/templating/node-observation.js +50 -45
  67. package/dist/esm/templating/ref.js +7 -16
  68. package/dist/esm/templating/repeat.js +38 -43
  69. package/dist/esm/templating/slotted.js +23 -20
  70. package/dist/esm/templating/template.js +71 -95
  71. package/dist/esm/templating/view.js +44 -43
  72. package/dist/esm/templating/when.js +2 -1
  73. package/dist/esm/utilities.js +139 -0
  74. package/dist/fast-element.api.json +14062 -5235
  75. package/dist/fast-element.d.ts +1434 -579
  76. package/dist/fast-element.debug.js +3824 -0
  77. package/dist/fast-element.debug.min.js +1 -0
  78. package/dist/fast-element.js +3565 -4014
  79. package/dist/fast-element.min.js +1 -1
  80. package/dist/fast-element.untrimmed.d.ts +2908 -0
  81. package/dist/tsdoc-metadata.json +1 -1
  82. package/docs/api-report.md +590 -231
  83. package/docs/fast-element-2-changes.md +15 -0
  84. package/docs/guide/declaring-templates.md +5 -4
  85. package/docs/guide/defining-elements.md +3 -2
  86. package/docs/guide/leveraging-css.md +1 -0
  87. package/docs/guide/next-steps.md +3 -2
  88. package/docs/guide/observables-and-state.md +2 -1
  89. package/docs/guide/using-directives.md +2 -1
  90. package/docs/guide/working-with-shadow-dom.md +1 -0
  91. package/karma.conf.cjs +6 -17
  92. package/package.json +48 -14
  93. package/dist/dts/dom.d.ts +0 -112
  94. package/dist/dts/observation/array-change-records.d.ts +0 -48
  95. package/dist/dts/observation/array-observer.d.ts +0 -9
  96. package/dist/esm/dom.js +0 -207
  97. package/dist/esm/observation/array-observer.js +0 -173
@@ -1,46 +1,113 @@
1
- import { DOM } from "../dom.js";
1
+ import { isString } from "../interfaces.js";
2
2
  import { ExecutionContext, Observable, } from "../observation/observable.js";
3
- import { TargetedHTMLDirective } from "./html-directive.js";
4
- function normalBind(source, context) {
5
- this.source = source;
6
- this.context = context;
7
- if (this.bindingObserver === null) {
8
- this.bindingObserver = Observable.binding(this.binding, this, this.isBindingVolatile);
9
- }
10
- this.updateTarget(this.bindingObserver.observe(source, context));
11
- }
12
- function triggerBind(source, context) {
13
- this.source = source;
14
- this.context = context;
15
- this.target.addEventListener(this.targetName, this);
16
- }
17
- function normalUnbind() {
18
- this.bindingObserver.disconnect();
19
- this.source = null;
20
- this.context = null;
21
- }
22
- function contentUnbind() {
23
- this.bindingObserver.disconnect();
24
- this.source = null;
25
- this.context = null;
26
- const view = this.target.$fastView;
27
- if (view !== void 0 && view.isComposed) {
28
- view.unbind();
29
- view.needsBindOnly = true;
3
+ import { FAST } from "../platform.js";
4
+ import { DOM } from "./dom.js";
5
+ import { Aspect, HTMLDirective, } from "./html-directive.js";
6
+ import { Markup } from "./markup.js";
7
+ const createInnerHTMLBinding = globalThis.TrustedHTML
8
+ ? (binding) => (s, c) => {
9
+ const value = binding(s, c);
10
+ if (value instanceof TrustedHTML) {
11
+ return value;
12
+ }
13
+ throw FAST.error(1202 /* Message.bindingInnerHTMLRequiresTrustedTypes */);
14
+ }
15
+ : (binding) => binding;
16
+ /**
17
+ * Describes how aspects of an HTML element will be affected by bindings.
18
+ * @public
19
+ */
20
+ export const BindingMode = Object.freeze({
21
+ /**
22
+ * Creates a binding mode based on the supplied behavior types.
23
+ * @param UpdateType - The base behavior type used to update aspects.
24
+ * @param EventType - The base behavior type used to respond to events.
25
+ * @returns A new binding mode.
26
+ */
27
+ define(UpdateType, EventType = EventBinding) {
28
+ return Object.freeze({
29
+ [1]: d => new UpdateType(d, DOM.setAttribute),
30
+ [2]: d => new UpdateType(d, DOM.setBooleanAttribute),
31
+ [3]: d => new UpdateType(d, (t, a, v) => (t[a] = v)),
32
+ [4]: d => new (createContentBinding(UpdateType))(d, updateContentTarget),
33
+ [5]: d => new UpdateType(d, updateTokenListTarget),
34
+ [6]: d => new EventType(d),
35
+ });
36
+ },
37
+ });
38
+ /**
39
+ * Describes the configuration for a binding expression.
40
+ * @public
41
+ */
42
+ export const BindingConfig = Object.freeze({
43
+ /**
44
+ * Creates a binding configuration based on the provided mode and options.
45
+ * @param mode - The mode to use for the configuration.
46
+ * @param defaultOptions - The default options to use for the configuration.
47
+ * @returns A new binding configuration.
48
+ */
49
+ define(mode, defaultOptions) {
50
+ const config = (options) => {
51
+ return {
52
+ mode: config.mode,
53
+ options: Object.assign({}, defaultOptions, options),
54
+ };
55
+ };
56
+ config.options = defaultOptions;
57
+ config.mode = mode;
58
+ return config;
59
+ },
60
+ });
61
+ /**
62
+ * A base binding behavior for DOM updates.
63
+ * @public
64
+ */
65
+ export class UpdateBinding {
66
+ /**
67
+ * Creates an instance of UpdateBinding.
68
+ * @param directive - The directive that has the configuration for this behavior.
69
+ * @param updateTarget - The function used to update the target with the latest value.
70
+ */
71
+ constructor(directive, updateTarget) {
72
+ this.directive = directive;
73
+ this.updateTarget = updateTarget;
74
+ }
75
+ /**
76
+ * Bind this behavior to the source.
77
+ * @param source - The source to bind to.
78
+ * @param context - The execution context that the binding is operating within.
79
+ * @param targets - The targets that behaviors in a view can attach to.
80
+ */
81
+ bind(source, context, targets) { }
82
+ /**
83
+ * Unbinds this behavior from the source.
84
+ * @param source - The source to unbind from.
85
+ * @param context - The execution context that the binding is operating within.
86
+ * @param targets - The targets that behaviors in a view can attach to.
87
+ */
88
+ unbind(source, context, targets) { }
89
+ /**
90
+ * Creates a behavior.
91
+ * @param targets - The targets available for behaviors to be attached to.
92
+ */
93
+ createBehavior(targets) {
94
+ return this;
30
95
  }
31
96
  }
32
- function triggerUnbind() {
33
- this.target.removeEventListener(this.targetName, this);
34
- this.source = null;
35
- this.context = null;
36
- }
37
- function updateAttributeTarget(value) {
38
- DOM.setAttribute(this.target, this.targetName, value);
39
- }
40
- function updateBooleanAttributeTarget(value) {
41
- DOM.setBooleanAttribute(this.target, this.targetName, value);
97
+ function createContentBinding(Type) {
98
+ return class extends Type {
99
+ unbind(source, context, targets) {
100
+ super.unbind(source, context, targets);
101
+ const target = targets[this.directive.nodeId];
102
+ const view = target.$fastView;
103
+ if (view !== void 0 && view.isComposed) {
104
+ view.unbind();
105
+ view.needsBindOnly = true;
106
+ }
107
+ }
108
+ };
42
109
  }
43
- function updateContentTarget(value) {
110
+ function updateContentTarget(target, aspect, value, source, context) {
44
111
  // If there's no actual value, then this equates to the
45
112
  // empty string for the purposes of content bindings.
46
113
  if (value === null || value === undefined) {
@@ -48,8 +115,8 @@ function updateContentTarget(value) {
48
115
  }
49
116
  // If the value has a "create" method, then it's a template-like.
50
117
  if (value.create) {
51
- this.target.textContent = "";
52
- let view = this.target.$fastView;
118
+ target.textContent = "";
119
+ let view = target.$fastView;
53
120
  // If there's no previous view that we might be able to
54
121
  // reuse then create a new view from the template.
55
122
  if (view === void 0) {
@@ -60,7 +127,7 @@ function updateContentTarget(value) {
60
127
  // from the same template as the new value, then we
61
128
  // need to remove the old view if it's still in the DOM
62
129
  // and create a new view from the template.
63
- if (this.target.$fastTemplate !== value) {
130
+ if (target.$fastTemplate !== value) {
64
131
  if (view.isComposed) {
65
132
  view.remove();
66
133
  view.unbind();
@@ -72,18 +139,18 @@ function updateContentTarget(value) {
72
139
  // and that there's actually no need to compose it.
73
140
  if (!view.isComposed) {
74
141
  view.isComposed = true;
75
- view.bind(this.source, this.context);
76
- view.insertBefore(this.target);
77
- this.target.$fastView = view;
78
- this.target.$fastTemplate = value;
142
+ view.bind(source, context);
143
+ view.insertBefore(target);
144
+ target.$fastView = view;
145
+ target.$fastTemplate = value;
79
146
  }
80
147
  else if (view.needsBindOnly) {
81
148
  view.needsBindOnly = false;
82
- view.bind(this.source, this.context);
149
+ view.bind(source, context);
83
150
  }
84
151
  }
85
152
  else {
86
- const view = this.target.$fastView;
153
+ const view = target.$fastView;
87
154
  // If there is a view and it's currently composed into
88
155
  // the DOM, then we need to remove it.
89
156
  if (view !== void 0 && view.isComposed) {
@@ -96,16 +163,17 @@ function updateContentTarget(value) {
96
163
  view.unbind();
97
164
  }
98
165
  }
99
- this.target.textContent = value;
166
+ target.textContent = value;
100
167
  }
101
168
  }
102
- function updatePropertyTarget(value) {
103
- this.target[this.targetName] = value;
104
- }
105
- function updateClassTarget(value) {
106
- const classVersions = this.classVersions || Object.create(null);
107
- const target = this.target;
108
- let version = this.version || 0;
169
+ function updateTokenListTarget(target, aspect, value) {
170
+ var _a;
171
+ const directive = this.directive;
172
+ const lookup = `${directive.id}-t`;
173
+ const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : (target[lookup] = { c: 0, v: Object.create(null) });
174
+ const versions = state.v;
175
+ let currentVersion = state.c;
176
+ const tokenList = target[aspect];
109
177
  // Add the classes, tracking the version at which they were added.
110
178
  if (value !== null && value !== undefined && value.length) {
111
179
  const names = value.split(/\s+/);
@@ -114,139 +182,381 @@ function updateClassTarget(value) {
114
182
  if (currentName === "") {
115
183
  continue;
116
184
  }
117
- classVersions[currentName] = version;
118
- target.classList.add(currentName);
185
+ versions[currentName] = currentVersion;
186
+ tokenList.add(currentName);
119
187
  }
120
188
  }
121
- this.classVersions = classVersions;
122
- this.version = version + 1;
189
+ state.v = currentVersion + 1;
123
190
  // If this is the first call to add classes, there's no need to remove old ones.
124
- if (version === 0) {
191
+ if (currentVersion === 0) {
125
192
  return;
126
193
  }
127
194
  // Remove classes from the previous version.
128
- version -= 1;
129
- for (const name in classVersions) {
130
- if (classVersions[name] === version) {
131
- target.classList.remove(name);
195
+ currentVersion -= 1;
196
+ for (const name in versions) {
197
+ if (versions[name] === currentVersion) {
198
+ tokenList.remove(name);
132
199
  }
133
200
  }
134
201
  }
135
202
  /**
136
- * A directive that configures data binding to element content and attributes.
203
+ * A binding behavior for one-time bindings.
137
204
  * @public
138
205
  */
139
- export class HTMLBindingDirective extends TargetedHTMLDirective {
206
+ export class OneTimeBinding extends UpdateBinding {
140
207
  /**
141
- * Creates an instance of BindingDirective.
142
- * @param binding - A binding that returns the data used to update the DOM.
208
+ * Bind this behavior to the source.
209
+ * @param source - The source to bind to.
210
+ * @param context - The execution context that the binding is operating within.
211
+ * @param targets - The targets that behaviors in a view can attach to.
143
212
  */
144
- constructor(binding) {
145
- super();
146
- this.binding = binding;
147
- this.bind = normalBind;
148
- this.unbind = normalUnbind;
149
- this.updateTarget = updateAttributeTarget;
150
- this.isBindingVolatile = Observable.isVolatileBinding(this.binding);
151
- }
152
- /**
153
- * Gets/sets the name of the attribute or property that this
154
- * binding is targeting.
155
- */
156
- get targetName() {
157
- return this.originalTargetName;
158
- }
159
- set targetName(value) {
160
- this.originalTargetName = value;
161
- if (value === void 0) {
162
- return;
163
- }
164
- switch (value[0]) {
165
- case ":":
166
- this.cleanedTargetName = value.substr(1);
167
- this.updateTarget = updatePropertyTarget;
168
- if (this.cleanedTargetName === "innerHTML") {
169
- const binding = this.binding;
170
- this.binding = (s, c) => DOM.createHTML(binding(s, c));
171
- }
172
- break;
173
- case "?":
174
- this.cleanedTargetName = value.substr(1);
175
- this.updateTarget = updateBooleanAttributeTarget;
176
- break;
177
- case "@":
178
- this.cleanedTargetName = value.substr(1);
179
- this.bind = triggerBind;
180
- this.unbind = triggerUnbind;
181
- break;
182
- default:
183
- this.cleanedTargetName = value;
184
- if (value === "class") {
185
- this.updateTarget = updateClassTarget;
186
- }
187
- break;
213
+ bind(source, context, targets) {
214
+ const directive = this.directive;
215
+ this.updateTarget(targets[directive.nodeId], directive.targetAspect, directive.binding(source, context), source, context);
216
+ }
217
+ }
218
+ const signals = Object.create(null);
219
+ /**
220
+ * A binding behavior for signal bindings.
221
+ * @public
222
+ */
223
+ export class SignalBinding extends UpdateBinding {
224
+ constructor() {
225
+ super(...arguments);
226
+ this.handlerProperty = `${this.directive.id}-h`;
227
+ }
228
+ /**
229
+ * Bind this behavior to the source.
230
+ * @param source - The source to bind to.
231
+ * @param context - The execution context that the binding is operating within.
232
+ * @param targets - The targets that behaviors in a view can attach to.
233
+ */
234
+ bind(source, context, targets) {
235
+ const directive = this.directive;
236
+ const target = targets[directive.nodeId];
237
+ const signal = this.getSignal(source, context);
238
+ const handler = (target[this.handlerProperty] = () => {
239
+ this.updateTarget(target, directive.targetAspect, directive.binding(source, context), source, context);
240
+ });
241
+ handler();
242
+ const found = signals[signal];
243
+ if (found) {
244
+ Array.isArray(found)
245
+ ? found.push(handler)
246
+ : (signals[signal] = [found, handler]);
247
+ }
248
+ else {
249
+ signals[signal] = handler;
188
250
  }
189
251
  }
190
252
  /**
191
- * Makes this binding target the content of an element rather than
192
- * a particular attribute or property.
253
+ * Unbinds this behavior from the source.
254
+ * @param source - The source to unbind from.
255
+ * @param context - The execution context that the binding is operating within.
256
+ * @param targets - The targets that behaviors in a view can attach to.
193
257
  */
194
- targetAtContent() {
195
- this.updateTarget = updateContentTarget;
196
- this.unbind = contentUnbind;
258
+ unbind(source, context, targets) {
259
+ const signal = this.getSignal(source, context);
260
+ const found = signals[signal];
261
+ if (found && Array.isArray(found)) {
262
+ const directive = this.directive;
263
+ const target = targets[directive.nodeId];
264
+ const handler = target[this.handlerProperty];
265
+ const index = found.indexOf(handler);
266
+ if (index !== -1) {
267
+ found.splice(index, 1);
268
+ }
269
+ }
270
+ else {
271
+ signals[signal] = void 0;
272
+ }
273
+ }
274
+ getSignal(source, context) {
275
+ const options = this.directive.options;
276
+ return isString(options) ? options : options(source, context);
197
277
  }
198
278
  /**
199
- * Creates the runtime BindingBehavior instance based on the configuration
200
- * information stored in the BindingDirective.
201
- * @param target - The target node that the binding behavior should attach to.
279
+ * Sends the specified signal to signaled bindings.
280
+ * @param signal - The signal to send.
281
+ * @public
202
282
  */
203
- createBehavior(target) {
204
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
205
- return new BindingBehavior(target, this.binding, this.isBindingVolatile, this.bind, this.unbind, this.updateTarget, this.cleanedTargetName);
283
+ static send(signal) {
284
+ const found = signals[signal];
285
+ if (found) {
286
+ Array.isArray(found) ? found.forEach(x => x()) : found();
287
+ }
206
288
  }
207
289
  }
208
290
  /**
209
- * A behavior that updates content and attributes based on a configured
210
- * BindingDirective.
291
+ * A binding behavior for bindings that change.
211
292
  * @public
212
293
  */
213
- export class BindingBehavior {
214
- /**
215
- * Creates an instance of BindingBehavior.
216
- * @param target - The target of the data updates.
217
- * @param binding - The binding that returns the latest value for an update.
218
- * @param isBindingVolatile - Indicates whether the binding has volatile dependencies.
219
- * @param bind - The operation to perform during binding.
220
- * @param unbind - The operation to perform during unbinding.
221
- * @param updateTarget - The operation to perform when updating.
222
- * @param targetName - The name of the target attribute or property to update.
223
- */
224
- constructor(target, binding, isBindingVolatile, bind, unbind, updateTarget, targetName) {
225
- /** @internal */
226
- this.source = null;
227
- /** @internal */
228
- this.context = null;
229
- /** @internal */
230
- this.bindingObserver = null;
231
- this.target = target;
232
- this.binding = binding;
233
- this.isBindingVolatile = isBindingVolatile;
234
- this.bind = bind;
235
- this.unbind = unbind;
236
- this.updateTarget = updateTarget;
237
- this.targetName = targetName;
294
+ export class ChangeBinding extends UpdateBinding {
295
+ /**
296
+ * Creates an instance of ChangeBinding.
297
+ * @param directive - The directive that has the configuration for this behavior.
298
+ * @param updateTarget - The function used to update the target with the latest value.
299
+ */
300
+ constructor(directive, updateTarget) {
301
+ super(directive, updateTarget);
302
+ this.isBindingVolatile = Observable.isVolatileBinding(directive.binding);
303
+ this.observerProperty = `${directive.id}-o`;
238
304
  }
239
- /** @internal */
240
- handleChange() {
241
- this.updateTarget(this.bindingObserver.observe(this.source, this.context));
305
+ /**
306
+ * Returns the binding observer used to update the node.
307
+ * @param target - The target node.
308
+ * @returns A BindingObserver.
309
+ */
310
+ getObserver(target) {
311
+ var _a;
312
+ return ((_a = target[this.observerProperty]) !== null && _a !== void 0 ? _a : (target[this.observerProperty] = Observable.binding(this.directive.binding, this, this.isBindingVolatile)));
313
+ }
314
+ /**
315
+ * Bind this behavior to the source.
316
+ * @param source - The source to bind to.
317
+ * @param context - The execution context that the binding is operating within.
318
+ * @param targets - The targets that behaviors in a view can attach to.
319
+ */
320
+ bind(source, context, targets) {
321
+ const directive = this.directive;
322
+ const target = targets[directive.nodeId];
323
+ const observer = this.getObserver(target);
324
+ observer.target = target;
325
+ observer.source = source;
326
+ observer.context = context;
327
+ this.updateTarget(target, directive.targetAspect, observer.observe(source, context), source, context);
328
+ }
329
+ /**
330
+ * Unbinds this behavior from the source.
331
+ * @param source - The source to unbind from.
332
+ * @param context - The execution context that the binding is operating within.
333
+ * @param targets - The targets that behaviors in a view can attach to.
334
+ */
335
+ unbind(source, context, targets) {
336
+ const target = targets[this.directive.nodeId];
337
+ const observer = this.getObserver(target);
338
+ observer.dispose();
339
+ observer.target = null;
340
+ observer.source = null;
341
+ observer.context = null;
242
342
  }
243
343
  /** @internal */
344
+ handleChange(binding, observer) {
345
+ const target = observer.target;
346
+ const source = observer.source;
347
+ const context = observer.context;
348
+ this.updateTarget(target, this.directive.targetAspect, observer.observe(source, context), source, context);
349
+ }
350
+ }
351
+ /**
352
+ * A binding behavior for handling events.
353
+ * @public
354
+ */
355
+ export class EventBinding {
356
+ /**
357
+ * Creates an instance of EventBinding.
358
+ * @param directive - The directive that has the configuration for this behavior.
359
+ */
360
+ constructor(directive) {
361
+ this.directive = directive;
362
+ this.sourceProperty = `${directive.id}-s`;
363
+ this.contextProperty = `${directive.id}-c`;
364
+ }
365
+ /**
366
+ * Bind this behavior to the source.
367
+ * @param source - The source to bind to.
368
+ * @param context - The execution context that the binding is operating within.
369
+ * @param targets - The targets that behaviors in a view can attach to.
370
+ */
371
+ bind(source, context, targets) {
372
+ const directive = this.directive;
373
+ const target = targets[directive.nodeId];
374
+ target[this.sourceProperty] = source;
375
+ target[this.contextProperty] = context;
376
+ target.addEventListener(directive.targetAspect, this, directive.options);
377
+ }
378
+ /**
379
+ * Unbinds this behavior from the source.
380
+ * @param source - The source to unbind from.
381
+ * @param context - The execution context that the binding is operating within.
382
+ * @param targets - The targets that behaviors in a view can attach to.
383
+ */
384
+ unbind(source, context, targets) {
385
+ const directive = this.directive;
386
+ const target = targets[directive.nodeId];
387
+ target[this.sourceProperty] = target[this.contextProperty] = null;
388
+ target.removeEventListener(directive.targetAspect, this, directive.options);
389
+ }
390
+ /**
391
+ * Creates a behavior.
392
+ * @param targets - The targets available for behaviors to be attached to.
393
+ */
394
+ createBehavior(targets) {
395
+ return this;
396
+ }
397
+ /**
398
+ * @internal
399
+ */
244
400
  handleEvent(event) {
401
+ const target = event.currentTarget;
245
402
  ExecutionContext.setEvent(event);
246
- const result = this.binding(this.source, this.context);
403
+ const result = this.directive.binding(target[this.sourceProperty], target[this.contextProperty]);
247
404
  ExecutionContext.setEvent(null);
248
405
  if (result !== true) {
249
406
  event.preventDefault();
250
407
  }
251
408
  }
252
409
  }
410
+ let twoWaySettings = {
411
+ determineChangeEvent() {
412
+ return "change";
413
+ },
414
+ };
415
+ /**
416
+ * A binding behavior for bindings that update in two directions.
417
+ * @public
418
+ */
419
+ export class TwoWayBinding extends ChangeBinding {
420
+ /**
421
+ * Bind this behavior to the source.
422
+ * @param source - The source to bind to.
423
+ * @param context - The execution context that the binding is operating within.
424
+ * @param targets - The targets that behaviors in a view can attach to.
425
+ */
426
+ bind(source, context, targets) {
427
+ var _a;
428
+ super.bind(source, context, targets);
429
+ const directive = this.directive;
430
+ const target = targets[directive.nodeId];
431
+ if (!this.changeEvent) {
432
+ this.changeEvent =
433
+ (_a = directive.options.changeEvent) !== null && _a !== void 0 ? _a : twoWaySettings.determineChangeEvent(directive, target);
434
+ }
435
+ target.addEventListener(this.changeEvent, this);
436
+ }
437
+ /**
438
+ * Unbinds this behavior from the source.
439
+ * @param source - The source to unbind from.
440
+ * @param context - The execution context that the binding is operating within.
441
+ * @param targets - The targets that behaviors in a view can attach to.
442
+ */
443
+ unbind(source, context, targets) {
444
+ super.unbind(source, context, targets);
445
+ targets[this.directive.nodeId].removeEventListener(this.changeEvent, this);
446
+ }
447
+ /** @internal */
448
+ handleEvent(event) {
449
+ const directive = this.directive;
450
+ const target = event.currentTarget;
451
+ let value;
452
+ switch (directive.aspectType) {
453
+ case 1:
454
+ value = target.getAttribute(directive.targetAspect);
455
+ break;
456
+ case 2:
457
+ value = target.hasAttribute(directive.targetAspect);
458
+ break;
459
+ case 4:
460
+ value = target.innerText;
461
+ break;
462
+ default:
463
+ value = target[directive.targetAspect];
464
+ break;
465
+ }
466
+ const observer = this.getObserver(target);
467
+ const last = observer.last; // using internal API!!!
468
+ last.propertySource[last.propertyName] = directive.options.fromView(value);
469
+ }
470
+ /**
471
+ * Configures two-way binding.
472
+ * @param settings - The settings to use for the two-way binding system.
473
+ */
474
+ static configure(settings) {
475
+ twoWaySettings = settings;
476
+ }
477
+ }
478
+ /**
479
+ * The default onChange binding configuration.
480
+ * @public
481
+ */
482
+ export const onChange = BindingConfig.define(BindingMode.define(ChangeBinding), {});
483
+ /**
484
+ * The default twoWay binding configuration.
485
+ * @public
486
+ */
487
+ export const twoWay = BindingConfig.define(BindingMode.define(TwoWayBinding), {
488
+ fromView: v => v,
489
+ });
490
+ /**
491
+ * The default onTime binding configuration.
492
+ * @public
493
+ */
494
+ export const oneTime = BindingConfig.define(BindingMode.define(OneTimeBinding), {
495
+ once: true,
496
+ });
497
+ const signalMode = BindingMode.define(SignalBinding);
498
+ /**
499
+ * Creates a signal binding configuration with the supplied options.
500
+ * @param options - The signal name or a binding to use to retrieve the signal name.
501
+ * @returns A binding configuration.
502
+ * @public
503
+ */
504
+ export const signal = (options) => {
505
+ return { mode: signalMode, options };
506
+ };
507
+ /**
508
+ * A directive that applies bindings.
509
+ * @public
510
+ */
511
+ export class HTMLBindingDirective {
512
+ /**
513
+ * Creates an instance of HTMLBindingDirective.
514
+ * @param binding - The binding to apply.
515
+ * @param mode - The binding mode to use when applying the binding.
516
+ * @param options - The options to configure the binding with.
517
+ */
518
+ constructor(binding, mode, options) {
519
+ this.binding = binding;
520
+ this.mode = mode;
521
+ this.options = options;
522
+ this.factory = null;
523
+ /**
524
+ * The type of aspect to target.
525
+ */
526
+ this.aspectType = Aspect.content;
527
+ }
528
+ /**
529
+ * Creates HTML to be used within a template.
530
+ * @param add - Can be used to add behavior factories to a template.
531
+ */
532
+ createHTML(add) {
533
+ return Markup.interpolation(add(this));
534
+ }
535
+ /**
536
+ * Creates a behavior.
537
+ * @param targets - The targets available for behaviors to be attached to.
538
+ */
539
+ createBehavior(targets) {
540
+ if (this.factory == null) {
541
+ if (this.targetAspect === "innerHTML") {
542
+ this.binding = createInnerHTMLBinding(this.binding);
543
+ }
544
+ this.factory = this.mode[this.aspectType](this);
545
+ }
546
+ return this.factory.createBehavior(targets);
547
+ }
548
+ }
549
+ HTMLDirective.define(HTMLBindingDirective, { aspected: true });
550
+ /**
551
+ * Creates a binding directive with the specified configuration.
552
+ * @param binding - The binding expression.
553
+ * @param config - The binding configuration.
554
+ * @returns A binding directive.
555
+ * @public
556
+ */
557
+ export function bind(binding, config = onChange) {
558
+ if (!("mode" in config)) {
559
+ config = onChange(config);
560
+ }
561
+ return new HTMLBindingDirective(binding, config.mode, config.options);
562
+ }