@microsoft/fast-element 2.0.0-beta.3 → 2.0.0-beta.6
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 +147 -0
- package/CHANGELOG.md +42 -1
- package/dist/dts/components/fast-definitions.d.ts +9 -8
- package/dist/dts/components/fast-element.d.ts +12 -24
- package/dist/dts/context.d.ts +1 -1
- package/dist/dts/di/di.d.ts +858 -0
- package/dist/dts/interfaces.d.ts +43 -7
- package/dist/dts/observation/observable.d.ts +19 -13
- package/dist/dts/state/exports.d.ts +3 -0
- package/dist/dts/state/reactive.d.ts +8 -0
- package/dist/dts/state/state.d.ts +141 -0
- package/dist/dts/state/visitor.d.ts +6 -0
- package/dist/dts/state/watch.d.ts +10 -0
- package/dist/dts/styles/element-styles.d.ts +6 -0
- package/dist/dts/templating/binding-signal.d.ts +10 -27
- package/dist/dts/templating/binding-two-way.d.ts +16 -41
- package/dist/dts/templating/binding.d.ts +79 -118
- package/dist/dts/templating/html-directive.d.ts +28 -2
- package/dist/dts/templating/render.d.ts +277 -0
- package/dist/dts/templating/repeat.d.ts +12 -16
- package/dist/dts/templating/template.d.ts +3 -3
- package/dist/dts/templating/when.d.ts +3 -3
- package/dist/dts/testing/exports.d.ts +2 -0
- package/dist/dts/testing/fixture.d.ts +90 -0
- package/dist/dts/testing/timeout.d.ts +7 -0
- package/dist/dts/utilities.d.ts +0 -18
- package/dist/esm/components/fast-definitions.js +25 -27
- package/dist/esm/components/fast-element.js +20 -11
- package/dist/esm/context.js +5 -1
- package/dist/esm/debug.js +35 -4
- package/dist/esm/di/di.js +1351 -0
- package/dist/esm/interfaces.js +4 -0
- package/dist/esm/observation/arrays.js +303 -2
- package/dist/esm/observation/observable.js +11 -6
- package/dist/esm/platform.js +1 -1
- package/dist/esm/state/exports.js +3 -0
- package/dist/esm/state/reactive.js +34 -0
- package/dist/esm/state/state.js +148 -0
- package/dist/esm/state/visitor.js +28 -0
- package/dist/esm/state/watch.js +36 -0
- package/dist/esm/styles/element-styles.js +14 -0
- package/dist/esm/templating/binding-signal.js +56 -61
- package/dist/esm/templating/binding-two-way.js +51 -35
- package/dist/esm/templating/binding.js +137 -156
- package/dist/esm/templating/compiler.js +29 -7
- package/dist/esm/templating/html-directive.js +12 -1
- package/dist/esm/templating/render.js +392 -0
- package/dist/esm/templating/repeat.js +57 -40
- package/dist/esm/templating/template.js +8 -5
- package/dist/esm/templating/view.js +3 -1
- package/dist/esm/templating/when.js +5 -4
- package/dist/esm/testing/exports.js +2 -0
- package/dist/esm/testing/fixture.js +88 -0
- package/dist/esm/testing/timeout.js +24 -0
- package/dist/esm/utilities.js +0 -95
- package/dist/fast-element.api.json +2827 -2757
- package/dist/fast-element.d.ts +215 -229
- package/dist/fast-element.debug.js +650 -256
- package/dist/fast-element.debug.min.js +1 -1
- package/dist/fast-element.js +615 -252
- package/dist/fast-element.min.js +1 -1
- package/dist/fast-element.untrimmed.d.ts +223 -234
- package/docs/api-report.md +87 -90
- package/package.json +18 -9
- package/dist/dts/hooks.d.ts +0 -20
- package/dist/dts/observation/splice-strategies.d.ts +0 -13
- package/dist/esm/hooks.js +0 -32
- package/dist/esm/observation/splice-strategies.js +0 -400
package/dist/esm/interfaces.js
CHANGED
|
@@ -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.
|
|
366
|
+
support: SpliceStrategySupport.optimized,
|
|
72
367
|
normalize(previous, current, changes) {
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
92
|
-
|
|
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
|
|
222
|
-
* provided {@link
|
|
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
|
|
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
|
package/dist/esm/platform.js
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { noop } from "../interfaces.js";
|
|
2
|
+
import { Observable } from "../observation/observable.js";
|
|
3
|
+
import { visitObject } from "./visitor.js";
|
|
4
|
+
const observed = new WeakSet();
|
|
5
|
+
const makeObserverVisitor = {
|
|
6
|
+
visitObject: noop,
|
|
7
|
+
visitArray: noop,
|
|
8
|
+
visitProperty(object, propertyName, value) {
|
|
9
|
+
Reflect.defineProperty(object, propertyName, {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get() {
|
|
12
|
+
Observable.track(object, propertyName);
|
|
13
|
+
return value;
|
|
14
|
+
},
|
|
15
|
+
set(newValue) {
|
|
16
|
+
if (value !== newValue) {
|
|
17
|
+
value = newValue;
|
|
18
|
+
Observable.notify(object, propertyName);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Converts a plain object to a reactive, observable object.
|
|
26
|
+
* @param object - The object to make reactive.
|
|
27
|
+
* @param deep - Indicates whether or not to deeply convert the oject.
|
|
28
|
+
* @returns The converted object.
|
|
29
|
+
* @beta
|
|
30
|
+
*/
|
|
31
|
+
export function reactive(object, deep = false) {
|
|
32
|
+
visitObject(object, deep, makeObserverVisitor, void 0, observed);
|
|
33
|
+
return object;
|
|
34
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Inspired by https://www.starbeamjs.com/
|
|
2
|
+
import { isFunction, isString } from "../interfaces.js";
|
|
3
|
+
import { Observable } from "../observation/observable.js";
|
|
4
|
+
import { reactive } from "./reactive.js";
|
|
5
|
+
const defaultStateOptions = {
|
|
6
|
+
deep: false,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Creates a reactive state value.
|
|
10
|
+
* @param value - The initial state value.
|
|
11
|
+
* @param options - Options to customize the state or a friendly name.
|
|
12
|
+
* @returns A State instance.
|
|
13
|
+
* @beta
|
|
14
|
+
*/
|
|
15
|
+
export function state(value, options = defaultStateOptions) {
|
|
16
|
+
var _a;
|
|
17
|
+
if (isString(options)) {
|
|
18
|
+
options = { deep: false, name: options };
|
|
19
|
+
}
|
|
20
|
+
const host = reactive({ value }, options.deep);
|
|
21
|
+
const state = (() => host.value);
|
|
22
|
+
Object.defineProperty(state, "current", {
|
|
23
|
+
get: () => host.value,
|
|
24
|
+
set: (value) => (host.value = value),
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(state, "name", {
|
|
27
|
+
value: (_a = options.name) !== null && _a !== void 0 ? _a : "SharedState",
|
|
28
|
+
});
|
|
29
|
+
state.set = (value) => (host.value = value);
|
|
30
|
+
state.asReadonly = () => {
|
|
31
|
+
const readonlyState = (() => host.value);
|
|
32
|
+
Object.defineProperty(readonlyState, "current", {
|
|
33
|
+
get: () => host.value,
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(readonlyState, "name", {
|
|
36
|
+
value: `${state.name} (Readonly)`,
|
|
37
|
+
});
|
|
38
|
+
return Object.freeze(readonlyState);
|
|
39
|
+
};
|
|
40
|
+
return state;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a reactive state that has its value associated with a specific owner.
|
|
44
|
+
* @param value - The initial value or a factory that provides an initial value for each owner.
|
|
45
|
+
* @param options - Options to customize the state or a friendly name.
|
|
46
|
+
* @returns An OwnedState instance.
|
|
47
|
+
* @beta
|
|
48
|
+
*/
|
|
49
|
+
export function ownedState(value, options = defaultStateOptions) {
|
|
50
|
+
var _a;
|
|
51
|
+
if (isString(options)) {
|
|
52
|
+
options = { deep: false, name: options };
|
|
53
|
+
}
|
|
54
|
+
if (!isFunction(value)) {
|
|
55
|
+
const v = value;
|
|
56
|
+
value = () => v;
|
|
57
|
+
}
|
|
58
|
+
const storage = new WeakMap();
|
|
59
|
+
const getHost = (owner) => {
|
|
60
|
+
let host = storage.get(owner);
|
|
61
|
+
if (host === void 0) {
|
|
62
|
+
host = reactive({ value: value() }, options.deep);
|
|
63
|
+
storage.set(owner, host);
|
|
64
|
+
}
|
|
65
|
+
return host;
|
|
66
|
+
};
|
|
67
|
+
const state = ((owner) => getHost(owner).value);
|
|
68
|
+
Object.defineProperty(state, "name", {
|
|
69
|
+
value: (_a = options.name) !== null && _a !== void 0 ? _a : "OwnedState",
|
|
70
|
+
});
|
|
71
|
+
state.set = (owner, value) => (getHost(owner).value = value);
|
|
72
|
+
state.asReadonly = () => {
|
|
73
|
+
const readonlyState = ((owner) => getHost(owner).value);
|
|
74
|
+
Object.defineProperty(readonlyState, "name", {
|
|
75
|
+
value: `${state.name} (Readonly)`,
|
|
76
|
+
});
|
|
77
|
+
return Object.freeze(readonlyState);
|
|
78
|
+
};
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Creates a ComputedState.
|
|
83
|
+
* @param initialize - The initialization callback.
|
|
84
|
+
* @param name - A friendly name for this computation.
|
|
85
|
+
* @returns A ComputedState
|
|
86
|
+
* @beta
|
|
87
|
+
*/
|
|
88
|
+
export function computedState(initialize, name = "ComputedState") {
|
|
89
|
+
let setupCallback = null;
|
|
90
|
+
const builder = {
|
|
91
|
+
on: {
|
|
92
|
+
setup(callback) {
|
|
93
|
+
setupCallback = callback;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const computer = initialize(builder);
|
|
98
|
+
const host = reactive({ value: null }, false);
|
|
99
|
+
const output = (() => host.value);
|
|
100
|
+
Object.defineProperty(output, "current", {
|
|
101
|
+
get: () => host.value,
|
|
102
|
+
});
|
|
103
|
+
Object.defineProperty(output, "name", {
|
|
104
|
+
value: name,
|
|
105
|
+
});
|
|
106
|
+
// eslint-disable-next-line prefer-const
|
|
107
|
+
let computedNotifier;
|
|
108
|
+
const computedSubscriber = {
|
|
109
|
+
handleChange() {
|
|
110
|
+
host.value = computedNotifier.observe(null);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
computedNotifier = Observable.binding(computer, computedSubscriber);
|
|
114
|
+
computedNotifier.setMode(false);
|
|
115
|
+
let cleanup;
|
|
116
|
+
let setupNotifier;
|
|
117
|
+
if (setupCallback) {
|
|
118
|
+
const setupSubscriber = {
|
|
119
|
+
handleChange() {
|
|
120
|
+
if (cleanup) {
|
|
121
|
+
cleanup();
|
|
122
|
+
}
|
|
123
|
+
cleanup = setupNotifier.observe(null);
|
|
124
|
+
host.value = computer();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
setupNotifier = Observable.binding(setupCallback, setupSubscriber);
|
|
128
|
+
setupNotifier.setMode(false);
|
|
129
|
+
cleanup = setupNotifier.observe(null);
|
|
130
|
+
}
|
|
131
|
+
host.value = computedNotifier.observe(null);
|
|
132
|
+
output.dispose = () => {
|
|
133
|
+
if (cleanup) {
|
|
134
|
+
cleanup();
|
|
135
|
+
}
|
|
136
|
+
if (setupNotifier) {
|
|
137
|
+
setupNotifier.dispose();
|
|
138
|
+
}
|
|
139
|
+
computedNotifier.dispose();
|
|
140
|
+
};
|
|
141
|
+
output.subscribe = (subscriber) => {
|
|
142
|
+
computedNotifier.subscribe(subscriber);
|
|
143
|
+
};
|
|
144
|
+
output.unsubscribe = (subscriber) => {
|
|
145
|
+
computedNotifier.unsubscribe(subscriber);
|
|
146
|
+
};
|
|
147
|
+
return output;
|
|
148
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function shouldTraverse(value, traversed) {
|
|
2
|
+
return (value !== null &&
|
|
3
|
+
value !== void 0 &&
|
|
4
|
+
typeof value === "object" &&
|
|
5
|
+
!traversed.has(value));
|
|
6
|
+
}
|
|
7
|
+
export function visitObject(object, deep, visitor, data, traversed) {
|
|
8
|
+
if (!shouldTraverse(object, traversed)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
traversed.add(object);
|
|
12
|
+
if (Array.isArray(object)) {
|
|
13
|
+
visitor.visitArray(object, data);
|
|
14
|
+
for (const item of object) {
|
|
15
|
+
visitObject(item, deep, visitor, data, traversed);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
visitor.visitObject(object, data);
|
|
20
|
+
for (const key in object) {
|
|
21
|
+
const value = object[key];
|
|
22
|
+
visitor.visitProperty(object, key, value, data);
|
|
23
|
+
if (deep) {
|
|
24
|
+
visitObject(value, deep, visitor, data, traversed);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isFunction, noop } from "../interfaces.js";
|
|
2
|
+
import { ArrayObserver } from "../observation/arrays.js";
|
|
3
|
+
import { Observable } from "../observation/observable.js";
|
|
4
|
+
import { visitObject } from "./visitor.js";
|
|
5
|
+
function watchObject(object, data) {
|
|
6
|
+
const notifier = Observable.getNotifier(object);
|
|
7
|
+
notifier.subscribe(data.subscriber);
|
|
8
|
+
data.notifiers.push(notifier);
|
|
9
|
+
}
|
|
10
|
+
const watchVisitor = {
|
|
11
|
+
visitProperty: noop,
|
|
12
|
+
visitObject: watchObject,
|
|
13
|
+
visitArray: watchObject,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Deeply subscribes to changes in existing observable objects.
|
|
17
|
+
* @param object - The observable object to watch.
|
|
18
|
+
* @param subscriber - The handler to call when changes are made to the object.
|
|
19
|
+
* @returns A disposable that can be used to unsubscribe from change updates.
|
|
20
|
+
* @beta
|
|
21
|
+
*/
|
|
22
|
+
export function watch(object, subscriber) {
|
|
23
|
+
const data = {
|
|
24
|
+
notifiers: [],
|
|
25
|
+
subscriber: isFunction(subscriber) ? { handleChange: subscriber } : subscriber,
|
|
26
|
+
};
|
|
27
|
+
ArrayObserver.enable();
|
|
28
|
+
visitObject(object, true, watchVisitor, data, new Set());
|
|
29
|
+
return {
|
|
30
|
+
dispose() {
|
|
31
|
+
for (const n of data.notifiers) {
|
|
32
|
+
n.unsubscribe(data.subscriber);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -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.
|