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

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 (54) hide show
  1. package/CHANGELOG.json +147 -0
  2. package/CHANGELOG.md +42 -1
  3. package/dist/dts/components/fast-definitions.d.ts +9 -8
  4. package/dist/dts/components/fast-element.d.ts +12 -24
  5. package/dist/dts/context.d.ts +1 -1
  6. package/dist/dts/di/di.d.ts +858 -0
  7. package/dist/dts/hooks.d.ts +2 -2
  8. package/dist/dts/interfaces.d.ts +40 -7
  9. package/dist/dts/observation/observable.d.ts +19 -13
  10. package/dist/dts/styles/element-styles.d.ts +6 -0
  11. package/dist/dts/templating/binding-signal.d.ts +10 -27
  12. package/dist/dts/templating/binding-two-way.d.ts +16 -41
  13. package/dist/dts/templating/binding.d.ts +79 -118
  14. package/dist/dts/templating/html-directive.d.ts +31 -3
  15. package/dist/dts/templating/render.d.ts +277 -0
  16. package/dist/dts/templating/repeat.d.ts +12 -16
  17. package/dist/dts/templating/template.d.ts +3 -3
  18. package/dist/dts/templating/when.d.ts +3 -3
  19. package/dist/dts/testing/exports.d.ts +2 -0
  20. package/dist/dts/testing/fixture.d.ts +90 -0
  21. package/dist/dts/testing/timeout.d.ts +7 -0
  22. package/dist/esm/components/fast-definitions.js +25 -27
  23. package/dist/esm/components/fast-element.js +20 -11
  24. package/dist/esm/context.js +5 -1
  25. package/dist/esm/debug.js +36 -4
  26. package/dist/esm/di/di.js +1351 -0
  27. package/dist/esm/observation/arrays.js +303 -2
  28. package/dist/esm/observation/observable.js +11 -6
  29. package/dist/esm/platform.js +1 -1
  30. package/dist/esm/styles/element-styles.js +14 -0
  31. package/dist/esm/templating/binding-signal.js +56 -61
  32. package/dist/esm/templating/binding-two-way.js +56 -34
  33. package/dist/esm/templating/binding.js +137 -156
  34. package/dist/esm/templating/compiler.js +30 -7
  35. package/dist/esm/templating/html-directive.js +16 -2
  36. package/dist/esm/templating/render.js +392 -0
  37. package/dist/esm/templating/repeat.js +57 -40
  38. package/dist/esm/templating/template.js +8 -5
  39. package/dist/esm/templating/view.js +3 -1
  40. package/dist/esm/templating/when.js +5 -4
  41. package/dist/esm/testing/exports.js +2 -0
  42. package/dist/esm/testing/fixture.js +88 -0
  43. package/dist/esm/testing/timeout.js +24 -0
  44. package/dist/fast-element.api.json +2828 -2758
  45. package/dist/fast-element.d.ts +218 -230
  46. package/dist/fast-element.debug.js +656 -257
  47. package/dist/fast-element.debug.min.js +1 -1
  48. package/dist/fast-element.js +620 -253
  49. package/dist/fast-element.min.js +1 -1
  50. package/dist/fast-element.untrimmed.d.ts +226 -235
  51. package/docs/api-report.md +88 -91
  52. package/package.json +15 -6
  53. package/dist/dts/observation/splice-strategies.d.ts +0 -13
  54. package/dist/esm/observation/splice-strategies.js +0 -400
@@ -67,10 +67,311 @@ export const SpliceStrategySupport = Object.freeze({
67
67
  const reset = new Splice(0, emptyArray, 0);
68
68
  reset.reset = true;
69
69
  const resetSplices = [reset];
70
+ // Note: This function is *based* on the computation of the Levenshtein
71
+ // "edit" distance. The one change is that "updates" are treated as two
72
+ // edits - not one. With Array splices, an update is really a delete
73
+ // followed by an add. By retaining this, we optimize for "keeping" the
74
+ // maximum array items in the original array. For example:
75
+ //
76
+ // 'xxxx123' to '123yyyy'
77
+ //
78
+ // With 1-edit updates, the shortest path would be just to update all seven
79
+ // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
80
+ // leaves the substring '123' intact.
81
+ function calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd) {
82
+ // "Deletion" columns
83
+ const rowCount = oldEnd - oldStart + 1;
84
+ const columnCount = currentEnd - currentStart + 1;
85
+ const distances = new Array(rowCount);
86
+ let north;
87
+ let west;
88
+ // "Addition" rows. Initialize null column.
89
+ for (let i = 0; i < rowCount; ++i) {
90
+ distances[i] = new Array(columnCount);
91
+ distances[i][0] = i;
92
+ }
93
+ // Initialize null row
94
+ for (let j = 0; j < columnCount; ++j) {
95
+ distances[0][j] = j;
96
+ }
97
+ for (let i = 1; i < rowCount; ++i) {
98
+ for (let j = 1; j < columnCount; ++j) {
99
+ if (current[currentStart + j - 1] === old[oldStart + i - 1]) {
100
+ distances[i][j] = distances[i - 1][j - 1];
101
+ }
102
+ else {
103
+ north = distances[i - 1][j] + 1;
104
+ west = distances[i][j - 1] + 1;
105
+ distances[i][j] = north < west ? north : west;
106
+ }
107
+ }
108
+ }
109
+ return distances;
110
+ }
111
+ // This starts at the final weight, and walks "backward" by finding
112
+ // the minimum previous weight recursively until the origin of the weight
113
+ // matrix.
114
+ function spliceOperationsFromEditDistances(distances) {
115
+ let i = distances.length - 1;
116
+ let j = distances[0].length - 1;
117
+ let current = distances[i][j];
118
+ const edits = [];
119
+ while (i > 0 || j > 0) {
120
+ if (i === 0) {
121
+ edits.push(2 /* Edit.add */);
122
+ j--;
123
+ continue;
124
+ }
125
+ if (j === 0) {
126
+ edits.push(3 /* Edit.delete */);
127
+ i--;
128
+ continue;
129
+ }
130
+ const northWest = distances[i - 1][j - 1];
131
+ const west = distances[i - 1][j];
132
+ const north = distances[i][j - 1];
133
+ let min;
134
+ if (west < north) {
135
+ min = west < northWest ? west : northWest;
136
+ }
137
+ else {
138
+ min = north < northWest ? north : northWest;
139
+ }
140
+ if (min === northWest) {
141
+ if (northWest === current) {
142
+ edits.push(0 /* Edit.leave */);
143
+ }
144
+ else {
145
+ edits.push(1 /* Edit.update */);
146
+ current = northWest;
147
+ }
148
+ i--;
149
+ j--;
150
+ }
151
+ else if (min === west) {
152
+ edits.push(3 /* Edit.delete */);
153
+ i--;
154
+ current = west;
155
+ }
156
+ else {
157
+ edits.push(2 /* Edit.add */);
158
+ j--;
159
+ current = north;
160
+ }
161
+ }
162
+ return edits.reverse();
163
+ }
164
+ function sharedPrefix(current, old, searchLength) {
165
+ for (let i = 0; i < searchLength; ++i) {
166
+ if (current[i] !== old[i]) {
167
+ return i;
168
+ }
169
+ }
170
+ return searchLength;
171
+ }
172
+ function sharedSuffix(current, old, searchLength) {
173
+ let index1 = current.length;
174
+ let index2 = old.length;
175
+ let count = 0;
176
+ while (count < searchLength && current[--index1] === old[--index2]) {
177
+ count++;
178
+ }
179
+ return count;
180
+ }
181
+ function intersect(start1, end1, start2, end2) {
182
+ // Disjoint
183
+ if (end1 < start2 || end2 < start1) {
184
+ return -1;
185
+ }
186
+ // Adjacent
187
+ if (end1 === start2 || end2 === start1) {
188
+ return 0;
189
+ }
190
+ // Non-zero intersect, span1 first
191
+ if (start1 < start2) {
192
+ if (end1 < end2) {
193
+ return end1 - start2; // Overlap
194
+ }
195
+ return end2 - start2; // Contained
196
+ }
197
+ // Non-zero intersect, span2 first
198
+ if (end2 < end1) {
199
+ return end2 - start1; // Overlap
200
+ }
201
+ return end1 - start1; // Contained
202
+ }
203
+ /**
204
+ * @remarks
205
+ * Lacking individual splice mutation information, the minimal set of
206
+ * splices can be synthesized given the previous state and final state of an
207
+ * array. The basic approach is to calculate the edit distance matrix and
208
+ * choose the shortest path through it.
209
+ *
210
+ * Complexity: O(l * p)
211
+ * l: The length of the current array
212
+ * p: The length of the old array
213
+ */
214
+ function calc(current, currentStart, currentEnd, old, oldStart, oldEnd) {
215
+ let prefixCount = 0;
216
+ let suffixCount = 0;
217
+ const minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
218
+ if (currentStart === 0 && oldStart === 0) {
219
+ prefixCount = sharedPrefix(current, old, minLength);
220
+ }
221
+ if (currentEnd === current.length && oldEnd === old.length) {
222
+ suffixCount = sharedSuffix(current, old, minLength - prefixCount);
223
+ }
224
+ currentStart += prefixCount;
225
+ oldStart += prefixCount;
226
+ currentEnd -= suffixCount;
227
+ oldEnd -= suffixCount;
228
+ if (currentEnd - currentStart === 0 && oldEnd - oldStart === 0) {
229
+ return emptyArray;
230
+ }
231
+ if (currentStart === currentEnd) {
232
+ const splice = new Splice(currentStart, [], 0);
233
+ while (oldStart < oldEnd) {
234
+ splice.removed.push(old[oldStart++]);
235
+ }
236
+ return [splice];
237
+ }
238
+ else if (oldStart === oldEnd) {
239
+ return [new Splice(currentStart, [], currentEnd - currentStart)];
240
+ }
241
+ const ops = spliceOperationsFromEditDistances(calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd));
242
+ const splices = [];
243
+ let splice = void 0;
244
+ let index = currentStart;
245
+ let oldIndex = oldStart;
246
+ for (let i = 0; i < ops.length; ++i) {
247
+ switch (ops[i]) {
248
+ case 0 /* Edit.leave */:
249
+ if (splice !== void 0) {
250
+ splices.push(splice);
251
+ splice = void 0;
252
+ }
253
+ index++;
254
+ oldIndex++;
255
+ break;
256
+ case 1 /* Edit.update */:
257
+ if (splice === void 0) {
258
+ splice = new Splice(index, [], 0);
259
+ }
260
+ splice.addedCount++;
261
+ index++;
262
+ splice.removed.push(old[oldIndex]);
263
+ oldIndex++;
264
+ break;
265
+ case 2 /* Edit.add */:
266
+ if (splice === void 0) {
267
+ splice = new Splice(index, [], 0);
268
+ }
269
+ splice.addedCount++;
270
+ index++;
271
+ break;
272
+ case 3 /* Edit.delete */:
273
+ if (splice === void 0) {
274
+ splice = new Splice(index, [], 0);
275
+ }
276
+ splice.removed.push(old[oldIndex]);
277
+ oldIndex++;
278
+ break;
279
+ // no default
280
+ }
281
+ }
282
+ if (splice !== void 0) {
283
+ splices.push(splice);
284
+ }
285
+ return splices;
286
+ }
287
+ function merge(splice, splices) {
288
+ let inserted = false;
289
+ let insertionOffset = 0;
290
+ for (let i = 0; i < splices.length; i++) {
291
+ const current = splices[i];
292
+ current.index += insertionOffset;
293
+ if (inserted) {
294
+ continue;
295
+ }
296
+ const intersectCount = intersect(splice.index, splice.index + splice.removed.length, current.index, current.index + current.addedCount);
297
+ if (intersectCount >= 0) {
298
+ // Merge the two splices
299
+ splices.splice(i, 1);
300
+ i--;
301
+ insertionOffset -= current.addedCount - current.removed.length;
302
+ splice.addedCount += current.addedCount - intersectCount;
303
+ const deleteCount = splice.removed.length + current.removed.length - intersectCount;
304
+ if (!splice.addedCount && !deleteCount) {
305
+ // merged splice is a noop. discard.
306
+ inserted = true;
307
+ }
308
+ else {
309
+ let currentRemoved = current.removed;
310
+ if (splice.index < current.index) {
311
+ // some prefix of splice.removed is prepended to current.removed.
312
+ const prepend = splice.removed.slice(0, current.index - splice.index);
313
+ prepend.push(...currentRemoved);
314
+ currentRemoved = prepend;
315
+ }
316
+ if (splice.index + splice.removed.length >
317
+ current.index + current.addedCount) {
318
+ // some suffix of splice.removed is appended to current.removed.
319
+ const append = splice.removed.slice(current.index + current.addedCount - splice.index);
320
+ currentRemoved.push(...append);
321
+ }
322
+ splice.removed = currentRemoved;
323
+ if (current.index < splice.index) {
324
+ splice.index = current.index;
325
+ }
326
+ }
327
+ }
328
+ else if (splice.index < current.index) {
329
+ // Insert splice here.
330
+ inserted = true;
331
+ splices.splice(i, 0, splice);
332
+ i++;
333
+ const offset = splice.addedCount - splice.removed.length;
334
+ current.index += offset;
335
+ insertionOffset += offset;
336
+ }
337
+ }
338
+ if (!inserted) {
339
+ splices.push(splice);
340
+ }
341
+ }
342
+ function project(array, changes) {
343
+ let splices = [];
344
+ const initialSplices = [];
345
+ for (let i = 0, ii = changes.length; i < ii; i++) {
346
+ merge(changes[i], initialSplices);
347
+ }
348
+ for (let i = 0, ii = initialSplices.length; i < ii; ++i) {
349
+ const splice = initialSplices[i];
350
+ if (splice.addedCount === 1 && splice.removed.length === 1) {
351
+ if (splice.removed[0] !== array[splice.index]) {
352
+ splices.push(splice);
353
+ }
354
+ continue;
355
+ }
356
+ splices = splices.concat(calc(array, splice.index, splice.index + splice.addedCount, splice.removed, 0, splice.removed.length));
357
+ }
358
+ return splices;
359
+ }
360
+ /**
361
+ * A SpliceStrategy that attempts to merge all splices into the minimal set of
362
+ * splices needed to represent the change from the old array to the new array.
363
+ * @public
364
+ */
70
365
  let defaultSpliceStrategy = Object.freeze({
71
- support: SpliceStrategySupport.splice,
366
+ support: SpliceStrategySupport.optimized,
72
367
  normalize(previous, current, changes) {
73
- return previous === void 0 ? changes !== null && changes !== void 0 ? changes : emptyArray : resetSplices;
368
+ if (previous === void 0) {
369
+ if (changes === void 0) {
370
+ return emptyArray;
371
+ }
372
+ return changes.length > 1 ? project(current, changes) : changes;
373
+ }
374
+ return resetSplices;
74
375
  },
75
376
  pop(array, observer, pop, args) {
76
377
  const notEmpty = array.length > 0;
@@ -63,7 +63,7 @@ export const Observable = FAST.getById(2 /* KernelServiceId.observable */, () =>
63
63
  }
64
64
  }
65
65
  }
66
- class BindingObserverImplementation extends SubscriberSet {
66
+ class ExpressionNotifierImplementation extends SubscriberSet {
67
67
  constructor(binding, initialSubscriber, isVolatileBinding = false) {
68
68
  super(binding, initialSubscriber);
69
69
  this.binding = binding;
@@ -88,8 +88,13 @@ export const Observable = FAST.getById(2 /* KernelServiceId.observable */, () =>
88
88
  const previousWatcher = watcher;
89
89
  watcher = this.needsRefresh ? this : void 0;
90
90
  this.needsRefresh = this.isVolatileBinding;
91
- const result = this.binding(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
92
- watcher = previousWatcher;
91
+ let result;
92
+ try {
93
+ result = this.binding(source, context !== null && context !== void 0 ? context : ExecutionContext.default);
94
+ }
95
+ finally {
96
+ watcher = previousWatcher;
97
+ }
93
98
  return result;
94
99
  }
95
100
  dispose() {
@@ -218,14 +223,14 @@ export const Observable = FAST.getById(2 /* KernelServiceId.observable */, () =>
218
223
  */
219
224
  getAccessors,
220
225
  /**
221
- * Creates a {@link BindingObserver} that can watch the
222
- * provided {@link Binding} for changes.
226
+ * Creates a {@link ExpressionNotifier} that can watch the
227
+ * provided {@link Expression} for changes.
223
228
  * @param binding - The binding to observe.
224
229
  * @param initialSubscriber - An initial subscriber to changes in the binding value.
225
230
  * @param isVolatileBinding - Indicates whether the binding's dependency list must be re-evaluated on every value evaluation.
226
231
  */
227
232
  binding(binding, initialSubscriber, isVolatileBinding = this.isVolatileBinding(binding)) {
228
- return new BindingObserverImplementation(binding, initialSubscriber, isVolatileBinding);
233
+ return new ExpressionNotifierImplementation(binding, initialSubscriber, isVolatileBinding);
229
234
  },
230
235
  /**
231
236
  * Determines whether a binding expression is volatile and needs to have its dependency list re-evaluated
@@ -26,7 +26,7 @@ if (FAST.error === void 0) {
26
26
  Object.assign(FAST, {
27
27
  warn() { },
28
28
  error(code) {
29
- return new Error(`Code ${code}`);
29
+ return new Error(`Error ${code}`);
30
30
  },
31
31
  addMessages() { },
32
32
  });
@@ -71,6 +71,20 @@ export class ElementStyles {
71
71
  static setDefaultStrategy(Strategy) {
72
72
  DefaultStyleStrategy = Strategy;
73
73
  }
74
+ /**
75
+ * Normalizes a set of composable style options.
76
+ * @param styles - The style options to normalize.
77
+ * @returns A singular ElementStyles instance or undefined.
78
+ */
79
+ static normalize(styles) {
80
+ return styles === void 0
81
+ ? void 0
82
+ : Array.isArray(styles)
83
+ ? new ElementStyles(styles)
84
+ : styles instanceof ElementStyles
85
+ ? styles
86
+ : new ElementStyles([styles]);
87
+ }
74
88
  }
75
89
  /**
76
90
  * Indicates whether the DOM supports the adoptedStyleSheets feature.
@@ -1,84 +1,79 @@
1
1
  import { isString } from "../interfaces.js";
2
- import { BindingMode, UpdateBinding } from "./binding.js";
3
- const signals = Object.create(null);
4
- /**
5
- * A binding behavior for signal bindings.
6
- * @public
7
- */
8
- export class SignalBinding extends UpdateBinding {
9
- constructor() {
10
- super(...arguments);
11
- this.handlerProperty = `${this.directive.id}-h`;
12
- }
13
- /**
14
- * Bind this behavior to the source.
15
- * @param source - The source to bind to.
16
- * @param context - The execution context that the binding is operating within.
17
- * @param targets - The targets that behaviors in a view can attach to.
18
- */
19
- bind(source, context, targets) {
20
- const directive = this.directive;
21
- const target = targets[directive.nodeId];
22
- const signal = this.getSignal(source, context);
23
- const handler = (target[this.handlerProperty] = () => {
24
- this.updateTarget(target, directive.targetAspect, directive.binding(source, context), source, context);
25
- });
26
- handler();
27
- const found = signals[signal];
2
+ import { Binding } from "./html-directive.js";
3
+ const subscribers = Object.create(null);
4
+ export const Signal = Object.freeze({
5
+ subscribe(signal, subscriber) {
6
+ const found = subscribers[signal];
28
7
  if (found) {
29
- Array.isArray(found)
30
- ? found.push(handler)
31
- : (signals[signal] = [found, handler]);
8
+ found instanceof Set
9
+ ? found.add(subscriber)
10
+ : (subscribers[signal] = new Set([found, subscriber]));
32
11
  }
33
12
  else {
34
- signals[signal] = handler;
13
+ subscribers[signal] = subscriber;
35
14
  }
36
- }
37
- /**
38
- * Unbinds this behavior from the source.
39
- * @param source - The source to unbind from.
40
- * @param context - The execution context that the binding is operating within.
41
- * @param targets - The targets that behaviors in a view can attach to.
42
- */
43
- unbind(source, context, targets) {
44
- const signal = this.getSignal(source, context);
45
- const found = signals[signal];
46
- if (found && Array.isArray(found)) {
47
- const directive = this.directive;
48
- const target = targets[directive.nodeId];
49
- const handler = target[this.handlerProperty];
50
- const index = found.indexOf(handler);
51
- if (index !== -1) {
52
- found.splice(index, 1);
53
- }
15
+ },
16
+ unsubscribe(signal, subscriber) {
17
+ const found = subscribers[signal];
18
+ if (found && found instanceof Set) {
19
+ found.delete(subscriber);
54
20
  }
55
21
  else {
56
- signals[signal] = void 0;
22
+ subscribers[signal] = void 0;
57
23
  }
58
- }
59
- getSignal(source, context) {
60
- const options = this.directive.options;
61
- return isString(options) ? options : options(source, context);
62
- }
24
+ },
63
25
  /**
64
26
  * Sends the specified signal to signaled bindings.
65
27
  * @param signal - The signal to send.
66
28
  * @public
67
29
  */
68
- static send(signal) {
69
- const found = signals[signal];
30
+ send(signal) {
31
+ const found = subscribers[signal];
70
32
  if (found) {
71
- Array.isArray(found) ? found.forEach(x => x()) : found();
33
+ found instanceof Set
34
+ ? found.forEach(x => x.handleChange(this, signal))
35
+ : found.handleChange(this, signal);
72
36
  }
37
+ },
38
+ });
39
+ class SignalObserver {
40
+ constructor(dataBinding, subscriber) {
41
+ this.dataBinding = dataBinding;
42
+ this.subscriber = subscriber;
43
+ }
44
+ observe(source, context) {
45
+ const signal = (this.signal = this.getSignal(source, context));
46
+ Signal.subscribe(signal, this);
47
+ return this.dataBinding.evaluate(source, context);
48
+ }
49
+ dispose() {
50
+ Signal.unsubscribe(this.signal, this);
51
+ }
52
+ handleChange() {
53
+ this.subscriber.handleChange(this.dataBinding.evaluate, this);
54
+ }
55
+ getSignal(source, context) {
56
+ const options = this.dataBinding.options;
57
+ return isString(options) ? options : options(source, context);
58
+ }
59
+ }
60
+ class SignalBinding extends Binding {
61
+ constructor(evaluate, options) {
62
+ super();
63
+ this.evaluate = evaluate;
64
+ this.options = options;
65
+ }
66
+ createObserver(directive, subscriber) {
67
+ return new SignalObserver(this, subscriber);
73
68
  }
74
69
  }
75
- const signalMode = BindingMode.define(SignalBinding);
76
70
  /**
77
71
  * Creates a signal binding configuration with the supplied options.
72
+ * @param binding - The binding to refresh when signaled.
78
73
  * @param options - The signal name or a binding to use to retrieve the signal name.
79
74
  * @returns A binding configuration.
80
75
  * @public
81
76
  */
82
- export const signal = (options) => {
83
- return { mode: signalMode, options };
84
- };
77
+ export function signal(binding, options) {
78
+ return new SignalBinding(binding, options);
79
+ }
@@ -1,45 +1,49 @@
1
- import { BindingConfig, BindingMode, ChangeBinding, } from "./binding.js";
1
+ import { isString } from "../interfaces.js";
2
+ import { Observable, } from "../observation/observable.js";
3
+ import { FAST } from "../platform.js";
4
+ import { Binding } from "./html-directive.js";
5
+ const defaultOptions = {
6
+ fromView: v => v,
7
+ };
2
8
  let twoWaySettings = {
3
9
  determineChangeEvent() {
4
10
  return "change";
5
11
  },
6
12
  };
7
- /**
8
- * A binding behavior for bindings that update in two directions.
9
- * @public
10
- */
11
- export class TwoWayBinding extends ChangeBinding {
12
- /**
13
- * Bind this behavior to the source.
14
- * @param source - The source to bind to.
15
- * @param context - The execution context that the binding is operating within.
16
- * @param targets - The targets that behaviors in a view can attach to.
17
- */
18
- bind(source, context, targets) {
13
+ class TwoWayObserver {
14
+ constructor(directive, subscriber, dataBinding) {
15
+ this.directive = directive;
16
+ this.subscriber = subscriber;
17
+ this.dataBinding = dataBinding;
18
+ this.notifier = Observable.binding(dataBinding.evaluate, this, dataBinding.isVolatile);
19
+ }
20
+ observe(source, context) {
19
21
  var _a;
20
- super.bind(source, context, targets);
21
- const directive = this.directive;
22
- const target = targets[directive.nodeId];
23
22
  if (!this.changeEvent) {
24
23
  this.changeEvent =
25
- (_a = directive.options.changeEvent) !== null && _a !== void 0 ? _a : twoWaySettings.determineChangeEvent(directive, target);
24
+ (_a = this.dataBinding.options.changeEvent) !== null && _a !== void 0 ? _a : twoWaySettings.determineChangeEvent(this.directive, this.target);
26
25
  }
27
- target.addEventListener(this.changeEvent, this);
26
+ this.target.addEventListener(this.changeEvent, this);
27
+ return this.notifier.observe(source, context);
28
28
  }
29
- /**
30
- * Unbinds this behavior from the source.
31
- * @param source - The source to unbind from.
32
- * @param context - The execution context that the binding is operating within.
33
- * @param targets - The targets that behaviors in a view can attach to.
34
- */
35
- unbind(source, context, targets) {
36
- super.unbind(source, context, targets);
37
- targets[this.directive.nodeId].removeEventListener(this.changeEvent, this);
29
+ dispose() {
30
+ this.notifier.dispose();
31
+ this.target.removeEventListener(this.changeEvent, this);
32
+ }
33
+ /** @internal */
34
+ handleChange(subject, args) {
35
+ this.subscriber.handleChange(this.dataBinding.evaluate, this);
38
36
  }
39
37
  /** @internal */
40
38
  handleEvent(event) {
41
39
  const directive = this.directive;
42
40
  const target = event.currentTarget;
41
+ const notifier = this.notifier;
42
+ const last = notifier.last; // using internal API!!!
43
+ if (!last) {
44
+ FAST.warn(1203 /* Message.twoWayBindingRequiresObservables */);
45
+ return;
46
+ }
43
47
  let value;
44
48
  switch (directive.aspectType) {
45
49
  case 1:
@@ -55,9 +59,21 @@ export class TwoWayBinding extends ChangeBinding {
55
59
  value = target[directive.targetAspect];
56
60
  break;
57
61
  }
58
- const observer = this.getObserver(target);
59
- const last = observer.last; // using internal API!!!
60
- last.propertySource[last.propertyName] = directive.options.fromView(value);
62
+ last.propertySource[last.propertyName] = this.dataBinding.options.fromView(value);
63
+ }
64
+ }
65
+ class TwoWayBinding extends Binding {
66
+ constructor(evaluate, isVolatile, options = defaultOptions) {
67
+ super();
68
+ this.evaluate = evaluate;
69
+ this.isVolatile = isVolatile;
70
+ this.options = options;
71
+ if (!options.fromView) {
72
+ options.fromView = defaultOptions.fromView;
73
+ }
74
+ }
75
+ createObserver(directive, subscriber) {
76
+ return new TwoWayObserver(directive, subscriber, this);
61
77
  }
62
78
  /**
63
79
  * Configures two-way binding.
@@ -68,9 +84,15 @@ export class TwoWayBinding extends ChangeBinding {
68
84
  }
69
85
  }
70
86
  /**
71
- * The default twoWay binding configuration.
87
+ * Creates a default binding.
88
+ * @param binding - The binding to refresh when changed.
89
+ * @param isBindingVolatile - Indicates whether the binding is volatile or not.
90
+ * @returns A binding configuration.
72
91
  * @public
73
92
  */
74
- export const twoWay = BindingConfig.define(BindingMode.define(TwoWayBinding), {
75
- fromView: v => v,
76
- });
93
+ export function twoWay(binding, optionsOrChangeEvent, isBindingVolatile = Observable.isVolatileBinding(binding)) {
94
+ if (isString(optionsOrChangeEvent)) {
95
+ optionsOrChangeEvent = { changeEvent: optionsOrChangeEvent };
96
+ }
97
+ return new TwoWayBinding(binding, isBindingVolatile, optionsOrChangeEvent);
98
+ }