aberdeen 0.0.16 → 0.1.0
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/README.md +13 -1
- package/dist/aberdeen.d.ts +169 -33
- package/dist/aberdeen.js +391 -90
- package/dist/aberdeen.min.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -115,4 +115,16 @@ mount(document.body, () => {
|
|
|
115
115
|
|
|
116
116
|
## Reference documentation
|
|
117
117
|
|
|
118
|
-
https://vanviegen.github.io/aberdeen/
|
|
118
|
+
https://vanviegen.github.io/aberdeen/modules.html
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Roadmap
|
|
122
|
+
|
|
123
|
+
- [x] Support for (dis)appear transitions.
|
|
124
|
+
- [x] A better alternative for scheduleTask.
|
|
125
|
+
- [ ] A simple router.
|
|
126
|
+
- [ ] Architecture document.
|
|
127
|
+
- [x] Optimistic client-side predictions.
|
|
128
|
+
- [ ] SVG support.
|
|
129
|
+
- [ ] More user friendly documentation generator.
|
|
130
|
+
- [ ] Performance profiling and tuning regarding lists.
|
package/dist/aberdeen.d.ts
CHANGED
|
@@ -2,6 +2,43 @@ interface QueueRunner {
|
|
|
2
2
|
queueOrder: number;
|
|
3
3
|
queueRun(): void;
|
|
4
4
|
}
|
|
5
|
+
type Patch = Map<ObsCollection, Map<any, [any, any]>>;
|
|
6
|
+
/**
|
|
7
|
+
* Schedule a DOM read operation to be executed in Aberdeen's internal task queue.
|
|
8
|
+
*
|
|
9
|
+
* This function is used to batch DOM read operations together, avoiding unnecessary
|
|
10
|
+
* layout recalculations and improving browser performance. A DOM read operation should
|
|
11
|
+
* only *read* from the DOM, such as measuring element dimensions or retrieving computed styles.
|
|
12
|
+
*
|
|
13
|
+
* By batching DOM reads separately from DOM writes, this prevents the browser from
|
|
14
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
15
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
16
|
+
* intermediate DOM states during updates.
|
|
17
|
+
*
|
|
18
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
|
|
19
|
+
* operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
|
|
20
|
+
*
|
|
21
|
+
* @param func The function to be executed as a DOM read operation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function scheduleDomReader(func: () => void): void;
|
|
24
|
+
/**
|
|
25
|
+
* Schedule a DOM write operation to be executed in Aberdeen's internal task queue.
|
|
26
|
+
*
|
|
27
|
+
* This function is used to batch DOM write operations together, avoiding unnecessary
|
|
28
|
+
* layout recalculations and improving browser performance. A DOM write operation should
|
|
29
|
+
* only *write* to the DOM, such as modifying element properties or applying styles.
|
|
30
|
+
*
|
|
31
|
+
* By batching DOM writes separately from DOM reads, this prevents the browser from
|
|
32
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
33
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
34
|
+
* intermediate DOM states during updates.
|
|
35
|
+
*
|
|
36
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
|
|
37
|
+
* operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
|
|
38
|
+
*
|
|
39
|
+
* @param func The function to be executed as a DOM write operation.
|
|
40
|
+
*/
|
|
41
|
+
export declare function scheduleDomWriter(func: () => void): void;
|
|
5
42
|
type SortKeyType = number | string | Array<number | string>;
|
|
6
43
|
interface Observer {
|
|
7
44
|
onChange(index: any, newData: DatumType, oldData: DatumType): void;
|
|
@@ -16,7 +53,7 @@ declare abstract class Scope implements QueueRunner, Observer {
|
|
|
16
53
|
}>;
|
|
17
54
|
isDead: boolean;
|
|
18
55
|
constructor(parentElement: Element | undefined, precedingSibling: Node | Scope | undefined, queueOrder: number);
|
|
19
|
-
findPrecedingNode(): Node | undefined;
|
|
56
|
+
findPrecedingNode(stopAt?: Scope | Node | undefined): Node | undefined;
|
|
20
57
|
findLastNode(): Node | undefined;
|
|
21
58
|
addNode(node: Node): void;
|
|
22
59
|
remove(): void;
|
|
@@ -102,7 +139,7 @@ declare class ObsMap extends ObsCollection {
|
|
|
102
139
|
*
|
|
103
140
|
* Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
|
|
104
141
|
* `Array`, `object` and `Map`. The latter three will always have `Store` objects as
|
|
105
|
-
* values, creating a tree of `Store
|
|
142
|
+
* values, creating a tree of `Store`-objects.
|
|
106
143
|
*/
|
|
107
144
|
export declare class Store {
|
|
108
145
|
private collection;
|
|
@@ -110,7 +147,7 @@ export declare class Store {
|
|
|
110
147
|
/**
|
|
111
148
|
* Create a new store with the given `value` as its value. Defaults to `undefined` if no value is given.
|
|
112
149
|
* When the value is a plain JavaScript object, an `Array` or a `Map`, it will be stored as a tree of
|
|
113
|
-
* `Store`s. (Calling
|
|
150
|
+
* `Store`s. (Calling {@link Store.get} on the store will recreate the original data strucure, though.)
|
|
114
151
|
*
|
|
115
152
|
* @example
|
|
116
153
|
* ```
|
|
@@ -153,46 +190,46 @@ export declare class Store {
|
|
|
153
190
|
*/
|
|
154
191
|
get(...path: any[]): any;
|
|
155
192
|
/**
|
|
156
|
-
* Like
|
|
193
|
+
* Like {@link Store.get}, but doesn't subscribe to changes.
|
|
157
194
|
*/
|
|
158
195
|
peek(...path: any[]): any;
|
|
159
196
|
/**
|
|
160
|
-
* @returns Like
|
|
161
|
-
* Using this instead of just
|
|
197
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
|
|
198
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
162
199
|
*/
|
|
163
200
|
getNumber(...path: any[]): number;
|
|
164
201
|
/**
|
|
165
|
-
* @returns Like
|
|
166
|
-
* Using this instead of just
|
|
202
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
|
|
203
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
167
204
|
*/
|
|
168
205
|
getString(...path: any[]): string;
|
|
169
206
|
/**
|
|
170
|
-
* @returns Like
|
|
171
|
-
* Using this instead of just
|
|
207
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
|
|
208
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
172
209
|
*/
|
|
173
210
|
getBoolean(...path: any[]): boolean;
|
|
174
211
|
/**
|
|
175
|
-
* @returns Like
|
|
176
|
-
* Using this instead of just
|
|
212
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
|
|
213
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
177
214
|
*/
|
|
178
215
|
getFunction(...path: any[]): (Function);
|
|
179
216
|
/**
|
|
180
|
-
* @returns Like
|
|
181
|
-
* Using this instead of just
|
|
217
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
|
|
218
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
182
219
|
*/
|
|
183
220
|
getArray(...path: any[]): any[];
|
|
184
221
|
/**
|
|
185
|
-
* @returns Like
|
|
186
|
-
* Using this instead of just
|
|
222
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
|
|
223
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
187
224
|
*/
|
|
188
225
|
getObject(...path: any[]): object;
|
|
189
226
|
/**
|
|
190
|
-
* @returns Like
|
|
191
|
-
* Using this instead of just
|
|
227
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
|
|
228
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
192
229
|
*/
|
|
193
230
|
getMap(...path: any[]): Map<any, any>;
|
|
194
231
|
/**
|
|
195
|
-
* Like
|
|
232
|
+
* Like {@link Store.get}, but the first parameter is the default value (returned when the Store
|
|
196
233
|
* contains `undefined`). This default value is also used to determine the expected type,
|
|
197
234
|
* and to throw otherwise.
|
|
198
235
|
*
|
|
@@ -206,7 +243,7 @@ export declare class Store {
|
|
|
206
243
|
*/
|
|
207
244
|
getOr<T>(defaultValue: T, ...path: any[]): T;
|
|
208
245
|
/** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
|
|
209
|
-
* form of the
|
|
246
|
+
* form of the {@link Store.get} and {@link Store.peek} methods.
|
|
210
247
|
*
|
|
211
248
|
* @returns The resulting value, or `undefined` if the `path` does not exist.
|
|
212
249
|
*/
|
|
@@ -263,15 +300,15 @@ export declare class Store {
|
|
|
263
300
|
getType(...path: any[]): string;
|
|
264
301
|
/**
|
|
265
302
|
* Sets the value to the last given argument. Any earlier argument are a Store-path that is first
|
|
266
|
-
* resolved/created using
|
|
303
|
+
* resolved/created using {@link Store.makeRef}.
|
|
267
304
|
*
|
|
268
305
|
* When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
|
|
269
306
|
* case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
|
|
270
307
|
* be created, so that changes made through one `Store` will be reflected through the other. Be
|
|
271
308
|
* carefull not to create loops in your `Store` tree that way, as that would cause any future
|
|
272
|
-
* call to
|
|
309
|
+
* call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
|
|
273
310
|
*
|
|
274
|
-
* If you intent to make a copy instead of a reference, call
|
|
311
|
+
* If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
|
|
275
312
|
*
|
|
276
313
|
*
|
|
277
314
|
* @example
|
|
@@ -340,7 +377,7 @@ export declare class Store {
|
|
|
340
377
|
*/
|
|
341
378
|
push(...pathAndValue: any[]): number;
|
|
342
379
|
/**
|
|
343
|
-
*
|
|
380
|
+
* {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
|
|
344
381
|
* value.
|
|
345
382
|
* @param func The function transforming the value.
|
|
346
383
|
*/
|
|
@@ -350,7 +387,7 @@ export declare class Store {
|
|
|
350
387
|
* subscribing to every level.
|
|
351
388
|
* In case `undefined` is encountered while resolving the path, a newly
|
|
352
389
|
* created `Store` containing `undefined` is returned. In that case, the
|
|
353
|
-
* `Store`'s
|
|
390
|
+
* `Store`'s {@link Store.isDetached} method will return `true`.
|
|
354
391
|
* In case something other than a collection is encountered, an error is thrown.
|
|
355
392
|
*/
|
|
356
393
|
ref(...path: any[]): Store;
|
|
@@ -375,12 +412,12 @@ export declare class Store {
|
|
|
375
412
|
* ```
|
|
376
413
|
*/
|
|
377
414
|
makeRef(...path: any[]): Store;
|
|
378
|
-
/** @
|
|
415
|
+
/** @internal */
|
|
379
416
|
_observe(): DatumType;
|
|
380
417
|
/**
|
|
381
418
|
* Iterate the specified collection (Array, Map or object), running the given code block for each item.
|
|
382
419
|
* When items are added to the collection at some later point, the code block will be ran for them as well.
|
|
383
|
-
* When an item is removed, the
|
|
420
|
+
* When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
|
|
384
421
|
*
|
|
385
422
|
*
|
|
386
423
|
*
|
|
@@ -420,7 +457,7 @@ export declare class Store {
|
|
|
420
457
|
*/
|
|
421
458
|
multiMap(func: (store: Store) => any): Store;
|
|
422
459
|
/**
|
|
423
|
-
* @returns Returns `true` when the `Store` was created by
|
|
460
|
+
* @returns Returns `true` when the `Store` was created by {@link Store.ref}ing a path that
|
|
424
461
|
* does not exist.
|
|
425
462
|
*/
|
|
426
463
|
isDetached(): boolean;
|
|
@@ -431,7 +468,7 @@ export declare class Store {
|
|
|
431
468
|
* @param tag - The tag of the element to be created and optionally dot-separated class names. For example: `h1` or `p.intro.has_avatar`.
|
|
432
469
|
* @param rest - The other arguments are flexible and interpreted based on their types:
|
|
433
470
|
* - `string`: Used as textContent for the element.
|
|
434
|
-
* - `object`: Used as attributes, properties or event listeners for the element. See
|
|
471
|
+
* - `object`: Used as attributes, properties or event listeners for the element. See {@link Store.prop} on how the distinction is made and to read about a couple of special keys.
|
|
435
472
|
* - `function`: The render function used to draw the scope of the element. This function gets its own `Scope`, so that if any `Store` it reads changes, it will redraw by itself.
|
|
436
473
|
* - `Store`: Presuming `tag` is `"input"`, `"textarea"` or `"select"`, create a two-way binding between this `Store` value and the input element. The initial value of the input will be set to the initial value of the `Store`, or the other way around if the `Store` holds `undefined`. After that, the `Store` will be updated when the input changes and vice versa.
|
|
437
474
|
* @example
|
|
@@ -457,8 +494,63 @@ export declare function text(text: string): void;
|
|
|
457
494
|
* without recreating the element itself. Also, code can be more readable this way.
|
|
458
495
|
* Note that when a nested `observe()` is used, properties set this way do NOT
|
|
459
496
|
* automatically revert to their previous values.
|
|
497
|
+
*
|
|
498
|
+
* Here's how properties are handled:
|
|
499
|
+
* - If `name` is `"create"`, `value` should be either a function that gets
|
|
500
|
+
* called with the element as its only argument immediately after creation,
|
|
501
|
+
* or a string being the name of a CSS class that gets added immediately
|
|
502
|
+
* after element creation, and removed shortly afterwards. This allows for
|
|
503
|
+
* reveal animations. However, this is intentionally *not* done
|
|
504
|
+
* for elements that are created as part of a larger (re)draw, to prevent
|
|
505
|
+
* all elements from individually animating on page creation.
|
|
506
|
+
* - If `name` is `"destroy"`, `value` should be a function that gets called
|
|
507
|
+
* with the element as its only argument, *instead of* the element being
|
|
508
|
+
* removed from the DOM (which the function will presumably need to do
|
|
509
|
+
* eventually). This can be used for a conceal animation.
|
|
510
|
+
* As a convenience, it's also possible to provide a string instead of
|
|
511
|
+
* a function, which will be added to the element as a CSS class, allowing
|
|
512
|
+
* for simple transitions. In this case, the DOM element in removed 2 seconds
|
|
513
|
+
* later (currently not configurable).
|
|
514
|
+
* Similar to `"create"` (and in this case doing anything else would make little
|
|
515
|
+
* sense), this only happens when the element being is the top-level element
|
|
516
|
+
* being removed from the DOM.
|
|
517
|
+
* - If `value` is a function, it is registered as an event handler for the
|
|
518
|
+
* `name` event.
|
|
519
|
+
* - If `name` is `"class"` or `"className"` and the `value` is an
|
|
520
|
+
* object, all keys of the object are either added or removed from `classList`,
|
|
521
|
+
* depending on whether `value` is true-like or false-like.
|
|
522
|
+
* - If `value` is a boolean *or* `name` is `"value"`, `"className"` or
|
|
523
|
+
* `"selectedIndex"`, it is set as a DOM element *property*.
|
|
524
|
+
* - If `name` is `"text"`, the `value` is set as the element's `textContent`.
|
|
525
|
+
* - If `name` is `"style"` and `value` is an object, each of its
|
|
526
|
+
* key/value pairs are assigned to the element's `.style`.
|
|
527
|
+
* - In other cases, the `value` is set as the `name` HTML *attribute*.
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* ```
|
|
531
|
+
* node('input', () => {
|
|
532
|
+
* prop('type', 'password')
|
|
533
|
+
* prop('readOnly', true)
|
|
534
|
+
* prop('class', 'my-class')
|
|
535
|
+
* prop('class', {
|
|
536
|
+
* 'my-disabled-class': false,
|
|
537
|
+
* 'my-enabled-class': true,
|
|
538
|
+
* })
|
|
539
|
+
* prop({
|
|
540
|
+
* class: 'my-class',
|
|
541
|
+
* text: 'Here is something to read...',
|
|
542
|
+
* style: {
|
|
543
|
+
* backgroundColor: 'red',
|
|
544
|
+
* fontWeight: 'bold',
|
|
545
|
+
* },
|
|
546
|
+
* create: aberdeen.fadeIn,
|
|
547
|
+
* destroy: 'my-fade-out-class',
|
|
548
|
+
* click: myClickHandler,
|
|
549
|
+
* })
|
|
550
|
+
* })
|
|
551
|
+
* ```
|
|
460
552
|
*/
|
|
461
|
-
export declare function prop(
|
|
553
|
+
export declare function prop(name: string, value: any): void;
|
|
462
554
|
export declare function prop(props: object): void;
|
|
463
555
|
/**
|
|
464
556
|
* Return the browser Element that `node()`s would be rendered to at this point.
|
|
@@ -497,7 +589,7 @@ export declare function clean(clean: (scope: Scope) => void): void;
|
|
|
497
589
|
*/
|
|
498
590
|
export declare function observe(func: () => void): void;
|
|
499
591
|
/**
|
|
500
|
-
* Like
|
|
592
|
+
* Like {@link Store.observe}, but allow the function to create DOM elements using {@link Store.node}.
|
|
501
593
|
|
|
502
594
|
* @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
|
|
503
595
|
* @param parentElement - A DOM element that will be used as the parent element for calls to `node`.
|
|
@@ -512,7 +604,7 @@ export declare function observe(func: () => void): void;
|
|
|
512
604
|
* })
|
|
513
605
|
* ```
|
|
514
606
|
*
|
|
515
|
-
* An example nesting
|
|
607
|
+
* An example nesting {@link Store.observe} within `mount`:
|
|
516
608
|
* ```
|
|
517
609
|
* let selected = new Store(0)
|
|
518
610
|
* let colors = new Store(new Map())
|
|
@@ -537,7 +629,7 @@ export declare function observe(func: () => void): void;
|
|
|
537
629
|
* ```
|
|
538
630
|
*/
|
|
539
631
|
export declare function mount(parentElement: Element | undefined, func: () => void): void;
|
|
540
|
-
/** Runs the given function, while not subscribing the current scope when reading
|
|
632
|
+
/** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
|
|
541
633
|
*
|
|
542
634
|
* @param func Function to be executed immediately.
|
|
543
635
|
* @returns Whatever `func()` returns.
|
|
@@ -559,4 +651,48 @@ export declare function mount(parentElement: Element | undefined, func: () => vo
|
|
|
559
651
|
* for `count()` however.
|
|
560
652
|
*/
|
|
561
653
|
export declare function peek<T>(func: () => T): T;
|
|
654
|
+
/** Do a grow transition for the given element. This is meant to be used as a
|
|
655
|
+
* handler for the `create` property.
|
|
656
|
+
*
|
|
657
|
+
* @param el The element to transition.
|
|
658
|
+
*
|
|
659
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
660
|
+
* for other specific cases as well.
|
|
661
|
+
*/
|
|
662
|
+
export declare function grow(el: HTMLElement): void;
|
|
663
|
+
/** Do a shrink transition for the given element, and remove it from the DOM
|
|
664
|
+
* afterwards. This is meant to be used as a handler for the `destroy` property.
|
|
665
|
+
*
|
|
666
|
+
* @param el The element to transition and remove.
|
|
667
|
+
*
|
|
668
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
669
|
+
* for other specific cases as well.
|
|
670
|
+
*/
|
|
671
|
+
export declare function shrink(el: HTMLElement): void;
|
|
672
|
+
/**
|
|
673
|
+
* Run the provided function, while treating all changes to Observables as predictions,
|
|
674
|
+
* meaning they will be reverted when changes come back from the server (or some other
|
|
675
|
+
* async source).
|
|
676
|
+
* @param predictFunc The function to run. It will generally modify some Observables
|
|
677
|
+
* to immediately reflect state (as closely as possible) that we expect the server
|
|
678
|
+
* to communicate back to us later on.
|
|
679
|
+
* @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
|
|
680
|
+
*/
|
|
681
|
+
export declare function applyPrediction(predictFunc: () => void): Patch;
|
|
682
|
+
/**
|
|
683
|
+
* Temporarily revert all outstanding predictions, optionally run the provided function
|
|
684
|
+
* (which will generally make authoritative changes to the data based on a server response),
|
|
685
|
+
* and then attempt to reapply the predictions on top of the new canonical state, dropping
|
|
686
|
+
* any predictions that can no longer be applied cleanly (the data has been modified) or
|
|
687
|
+
* that were specified in `dropPredictions`.
|
|
688
|
+
*
|
|
689
|
+
* All of this is done such that redraws are only triggered if the overall effect is an
|
|
690
|
+
* actual change to an `Observable`.
|
|
691
|
+
* @param canonFunc The function to run without any predictions applied. This will typically
|
|
692
|
+
* make authoritative changes to the data, based on a server response.
|
|
693
|
+
* @param dropPredictions An optional list of predictions (as returned by `applyPrediction`)
|
|
694
|
+
* to undo. Typically, when a server response for a certain request is being handled,
|
|
695
|
+
* you'd want to drop the prediction that was done for that request.
|
|
696
|
+
*/
|
|
697
|
+
export declare function applyCanon(canonFunc?: (() => void), dropPredictions?: Array<Patch>): void;
|
|
562
698
|
export {};
|
package/dist/aberdeen.js
CHANGED
|
@@ -2,6 +2,8 @@ let queueArray = [];
|
|
|
2
2
|
let queueSet = new Set();
|
|
3
3
|
let queueOrdered = true;
|
|
4
4
|
let runQueueDepth = 0;
|
|
5
|
+
let queueIndex;
|
|
6
|
+
let recordingPatch;
|
|
5
7
|
function queue(runner) {
|
|
6
8
|
if (queueSet.has(runner))
|
|
7
9
|
return;
|
|
@@ -18,24 +20,74 @@ function queue(runner) {
|
|
|
18
20
|
queueSet.add(runner);
|
|
19
21
|
}
|
|
20
22
|
function runQueue() {
|
|
21
|
-
|
|
23
|
+
onCreateEnabled = true;
|
|
24
|
+
for (queueIndex = 0; queueIndex < queueArray.length;) {
|
|
25
|
+
// Sort queue if new unordered items have been added since last time.
|
|
22
26
|
if (!queueOrdered) {
|
|
23
|
-
queueArray.splice(0,
|
|
24
|
-
|
|
25
|
-
// Order queued observers by depth, lowest first
|
|
27
|
+
queueArray.splice(0, queueIndex);
|
|
28
|
+
queueIndex = 0;
|
|
29
|
+
// Order queued observers by depth, lowest first.
|
|
26
30
|
queueArray.sort((a, b) => a.queueOrder - b.queueOrder);
|
|
27
31
|
queueOrdered = true;
|
|
28
32
|
}
|
|
33
|
+
// Process the rest of what's currently in the queue.
|
|
29
34
|
let batchEndIndex = queueArray.length;
|
|
30
|
-
for (;
|
|
31
|
-
let runner = queueArray[
|
|
35
|
+
for (; queueIndex < batchEndIndex && queueOrdered; queueIndex++) {
|
|
36
|
+
let runner = queueArray[queueIndex];
|
|
32
37
|
queueSet.delete(runner);
|
|
33
38
|
runner.queueRun();
|
|
34
39
|
}
|
|
40
|
+
// If new items have been added to the queue while processing the previous
|
|
41
|
+
// batch, we'll need to run this loop again.
|
|
35
42
|
runQueueDepth++;
|
|
36
43
|
}
|
|
37
44
|
queueArray.length = 0;
|
|
45
|
+
queueIndex = undefined;
|
|
38
46
|
runQueueDepth = 0;
|
|
47
|
+
onCreateEnabled = false;
|
|
48
|
+
}
|
|
49
|
+
let scheduleOrder = 1000;
|
|
50
|
+
/**
|
|
51
|
+
* Schedule a DOM read operation to be executed in Aberdeen's internal task queue.
|
|
52
|
+
*
|
|
53
|
+
* This function is used to batch DOM read operations together, avoiding unnecessary
|
|
54
|
+
* layout recalculations and improving browser performance. A DOM read operation should
|
|
55
|
+
* only *read* from the DOM, such as measuring element dimensions or retrieving computed styles.
|
|
56
|
+
*
|
|
57
|
+
* By batching DOM reads separately from DOM writes, this prevents the browser from
|
|
58
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
59
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
60
|
+
* intermediate DOM states during updates.
|
|
61
|
+
*
|
|
62
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
|
|
63
|
+
* operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
|
|
64
|
+
*
|
|
65
|
+
* @param func The function to be executed as a DOM read operation.
|
|
66
|
+
*/
|
|
67
|
+
export function scheduleDomReader(func) {
|
|
68
|
+
let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex].queueOrder >= 1000) ? ((queueArray[queueIndex].queueOrder + 1) & (~1)) : 1000;
|
|
69
|
+
queue({ queueOrder: order, queueRun: func });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Schedule a DOM write operation to be executed in Aberdeen's internal task queue.
|
|
73
|
+
*
|
|
74
|
+
* This function is used to batch DOM write operations together, avoiding unnecessary
|
|
75
|
+
* layout recalculations and improving browser performance. A DOM write operation should
|
|
76
|
+
* only *write* to the DOM, such as modifying element properties or applying styles.
|
|
77
|
+
*
|
|
78
|
+
* By batching DOM writes separately from DOM reads, this prevents the browser from
|
|
79
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
80
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
81
|
+
* intermediate DOM states during updates.
|
|
82
|
+
*
|
|
83
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
|
|
84
|
+
* operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
|
|
85
|
+
*
|
|
86
|
+
* @param func The function to be executed as a DOM write operation.
|
|
87
|
+
*/
|
|
88
|
+
export function scheduleDomWriter(func) {
|
|
89
|
+
let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex].queueOrder >= 1000) ? (queueArray[queueIndex].queueOrder | 1) : 1001;
|
|
90
|
+
queue({ queueOrder: order, queueRun: func });
|
|
39
91
|
}
|
|
40
92
|
/**
|
|
41
93
|
* Given an integer number, a string or an array of these, this function returns a string that can be used
|
|
@@ -96,24 +148,27 @@ class Scope {
|
|
|
96
148
|
this.precedingSibling = precedingSibling;
|
|
97
149
|
this.queueOrder = queueOrder;
|
|
98
150
|
}
|
|
99
|
-
// Get a reference to the last Node preceding
|
|
100
|
-
findPrecedingNode() {
|
|
101
|
-
let
|
|
102
|
-
|
|
151
|
+
// Get a reference to the last Node preceding this Scope, or undefined if there is none
|
|
152
|
+
findPrecedingNode(stopAt = undefined) {
|
|
153
|
+
let cur = this;
|
|
154
|
+
let pre;
|
|
155
|
+
while ((pre = cur.precedingSibling) && pre !== stopAt) {
|
|
103
156
|
if (pre instanceof Node)
|
|
104
157
|
return pre;
|
|
105
158
|
let node = pre.findLastNode();
|
|
106
159
|
if (node)
|
|
107
160
|
return node;
|
|
108
|
-
|
|
161
|
+
cur = pre;
|
|
109
162
|
}
|
|
110
163
|
}
|
|
111
164
|
// Get a reference to the last Node within this scope and parentElement
|
|
112
165
|
findLastNode() {
|
|
113
|
-
if (this.lastChild
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
166
|
+
if (this.lastChild) {
|
|
167
|
+
if (this.lastChild instanceof Node)
|
|
168
|
+
return this.lastChild;
|
|
169
|
+
else
|
|
170
|
+
return this.lastChild.findLastNode() || this.lastChild.findPrecedingNode(this.precedingSibling);
|
|
171
|
+
}
|
|
117
172
|
}
|
|
118
173
|
addNode(node) {
|
|
119
174
|
if (!this.parentElement)
|
|
@@ -127,20 +182,37 @@ class Scope {
|
|
|
127
182
|
let lastNode = this.findLastNode();
|
|
128
183
|
if (lastNode) {
|
|
129
184
|
// at least one DOM node to be removed
|
|
130
|
-
let
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
185
|
+
let nextNode = this.findPrecedingNode();
|
|
186
|
+
nextNode = (nextNode ? nextNode.nextSibling : this.parentElement.firstChild);
|
|
187
|
+
this.lastChild = undefined;
|
|
188
|
+
// Keep removing DOM nodes starting at our first node, until we encounter the last node
|
|
189
|
+
while (true) {
|
|
134
190
|
/* istanbul ignore next */
|
|
135
|
-
if (!
|
|
191
|
+
if (!nextNode)
|
|
136
192
|
return internalError(1);
|
|
193
|
+
const node = nextNode;
|
|
194
|
+
nextNode = node.nextSibling || undefined;
|
|
195
|
+
let onDestroy = onDestroyMap.get(node);
|
|
196
|
+
if (onDestroy && node instanceof Element) {
|
|
197
|
+
if (onDestroy !== true) {
|
|
198
|
+
if (typeof onDestroy === 'function') {
|
|
199
|
+
onDestroy(node);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
destroyWithClass(node, onDestroy);
|
|
203
|
+
}
|
|
204
|
+
// This causes the element to be ignored from this function from now on:
|
|
205
|
+
onDestroyMap.set(node, true);
|
|
206
|
+
}
|
|
207
|
+
// Ignore the deleting element
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this.parentElement.removeChild(node);
|
|
137
211
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
lastNode = nextLastNode;
|
|
212
|
+
if (node === lastNode)
|
|
213
|
+
break;
|
|
141
214
|
}
|
|
142
215
|
}
|
|
143
|
-
this.lastChild = undefined;
|
|
144
216
|
}
|
|
145
217
|
// run cleaners
|
|
146
218
|
this._clean();
|
|
@@ -223,6 +295,9 @@ class OnEachScope extends Scope {
|
|
|
223
295
|
this.renderer = renderer;
|
|
224
296
|
this.makeSortKey = makeSortKey;
|
|
225
297
|
}
|
|
298
|
+
// toString(): string {
|
|
299
|
+
// return `OnEachScope(collection=${this.collection})`
|
|
300
|
+
// }
|
|
226
301
|
onChange(index, newData, oldData) {
|
|
227
302
|
if (oldData === undefined) {
|
|
228
303
|
if (this.removedIndexes.has(index)) {
|
|
@@ -288,9 +363,9 @@ class OnEachScope extends Scope {
|
|
|
288
363
|
if (!scope) {
|
|
289
364
|
return internalError(6);
|
|
290
365
|
}
|
|
366
|
+
scope.remove();
|
|
291
367
|
this.byIndex.delete(itemIndex);
|
|
292
368
|
this.removeFromPosition(scope);
|
|
293
|
-
scope.remove();
|
|
294
369
|
}
|
|
295
370
|
findPosition(sortStr) {
|
|
296
371
|
// In case of duplicate `sortStr`s, this will return the first match.
|
|
@@ -315,12 +390,14 @@ class OnEachScope extends Scope {
|
|
|
315
390
|
let pos = this.findPosition(child.sortStr);
|
|
316
391
|
this.byPosition.splice(pos, 0, child);
|
|
317
392
|
// Based on the position in the list, set the precedingSibling for the new Scope
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (
|
|
321
|
-
|
|
393
|
+
// and for the next sibling.
|
|
394
|
+
let nextSibling = this.byPosition[pos + 1];
|
|
395
|
+
if (nextSibling) {
|
|
396
|
+
child.precedingSibling = nextSibling.precedingSibling;
|
|
397
|
+
nextSibling.precedingSibling = child;
|
|
322
398
|
}
|
|
323
399
|
else {
|
|
400
|
+
child.precedingSibling = this.lastChild || this.precedingSibling;
|
|
324
401
|
this.lastChild = child;
|
|
325
402
|
}
|
|
326
403
|
}
|
|
@@ -333,10 +410,20 @@ class OnEachScope extends Scope {
|
|
|
333
410
|
// Yep, this is the right scope
|
|
334
411
|
this.byPosition.splice(pos, 1);
|
|
335
412
|
if (pos < this.byPosition.length) {
|
|
336
|
-
|
|
413
|
+
let nextSibling = this.byPosition[pos];
|
|
414
|
+
/* istanbul ignore next */
|
|
415
|
+
if (!nextSibling)
|
|
416
|
+
return internalError(8);
|
|
417
|
+
/* istanbul ignore next */
|
|
418
|
+
if (nextSibling.precedingSibling !== child)
|
|
419
|
+
return internalError(13);
|
|
420
|
+
nextSibling.precedingSibling = child.precedingSibling;
|
|
337
421
|
}
|
|
338
422
|
else {
|
|
339
|
-
|
|
423
|
+
/* istanbul ignore next */
|
|
424
|
+
if (child !== this.lastChild)
|
|
425
|
+
return internalError(12);
|
|
426
|
+
this.lastChild = child.precedingSibling === this.precedingSibling ? undefined : child.precedingSibling;
|
|
340
427
|
}
|
|
341
428
|
return;
|
|
342
429
|
}
|
|
@@ -355,6 +442,9 @@ class OnEachItemScope extends Scope {
|
|
|
355
442
|
this.parent = parent;
|
|
356
443
|
this.itemIndex = itemIndex;
|
|
357
444
|
}
|
|
445
|
+
// toString(): string {
|
|
446
|
+
// return `OnEachItemScope(itemIndex=${this.itemIndex} parentElement=${this.parentElement} parent=${this.parent} precedingSibling=${this.precedingSibling} lastChild=${this.lastChild})`
|
|
447
|
+
// }
|
|
358
448
|
queueRun() {
|
|
359
449
|
/* istanbul ignore next */
|
|
360
450
|
if (currentScope) {
|
|
@@ -412,6 +502,9 @@ class ObsCollection {
|
|
|
412
502
|
constructor() {
|
|
413
503
|
this.observers = new Map();
|
|
414
504
|
}
|
|
505
|
+
// toString(): string {
|
|
506
|
+
// return JSON.stringify(peek(() => this.getRecursive(3)))
|
|
507
|
+
// }
|
|
415
508
|
addObserver(index, observer) {
|
|
416
509
|
observer = observer;
|
|
417
510
|
let obsSet = this.observers.get(index);
|
|
@@ -430,12 +523,17 @@ class ObsCollection {
|
|
|
430
523
|
obsSet.delete(observer);
|
|
431
524
|
}
|
|
432
525
|
emitChange(index, newData, oldData) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
526
|
+
if (recordingPatch) {
|
|
527
|
+
addToPatch(recordingPatch, this, index, newData, oldData);
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
let obsSet = this.observers.get(index);
|
|
531
|
+
if (obsSet)
|
|
532
|
+
obsSet.forEach(observer => observer.onChange(index, newData, oldData));
|
|
533
|
+
obsSet = this.observers.get(ANY_INDEX);
|
|
534
|
+
if (obsSet)
|
|
535
|
+
obsSet.forEach(observer => observer.onChange(index, newData, oldData));
|
|
536
|
+
}
|
|
439
537
|
}
|
|
440
538
|
_clean(observer) {
|
|
441
539
|
this.removeObserver(ANY_INDEX, observer);
|
|
@@ -639,7 +737,7 @@ class ObsObject extends ObsMap {
|
|
|
639
737
|
*
|
|
640
738
|
* Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
|
|
641
739
|
* `Array`, `object` and `Map`. The latter three will always have `Store` objects as
|
|
642
|
-
* values, creating a tree of `Store
|
|
740
|
+
* values, creating a tree of `Store`-objects.
|
|
643
741
|
*/
|
|
644
742
|
export class Store {
|
|
645
743
|
constructor(value = undefined, index = undefined) {
|
|
@@ -694,48 +792,48 @@ export class Store {
|
|
|
694
792
|
return this.query({ path });
|
|
695
793
|
}
|
|
696
794
|
/**
|
|
697
|
-
* Like
|
|
795
|
+
* Like {@link Store.get}, but doesn't subscribe to changes.
|
|
698
796
|
*/
|
|
699
797
|
peek(...path) {
|
|
700
798
|
return this.query({ path, peek: true });
|
|
701
799
|
}
|
|
702
800
|
/**
|
|
703
|
-
* @returns Like
|
|
704
|
-
* Using this instead of just
|
|
801
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
|
|
802
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
705
803
|
*/
|
|
706
804
|
getNumber(...path) { return this.query({ path, type: 'number' }); }
|
|
707
805
|
/**
|
|
708
|
-
* @returns Like
|
|
709
|
-
* Using this instead of just
|
|
806
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
|
|
807
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
710
808
|
*/
|
|
711
809
|
getString(...path) { return this.query({ path, type: 'string' }); }
|
|
712
810
|
/**
|
|
713
|
-
* @returns Like
|
|
714
|
-
* Using this instead of just
|
|
811
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
|
|
812
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
715
813
|
*/
|
|
716
814
|
getBoolean(...path) { return this.query({ path, type: 'boolean' }); }
|
|
717
815
|
/**
|
|
718
|
-
* @returns Like
|
|
719
|
-
* Using this instead of just
|
|
816
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
|
|
817
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
720
818
|
*/
|
|
721
819
|
getFunction(...path) { return this.query({ path, type: 'function' }); }
|
|
722
820
|
/**
|
|
723
|
-
* @returns Like
|
|
724
|
-
* Using this instead of just
|
|
821
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
|
|
822
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
725
823
|
*/
|
|
726
824
|
getArray(...path) { return this.query({ path, type: 'array' }); }
|
|
727
825
|
/**
|
|
728
|
-
* @returns Like
|
|
729
|
-
* Using this instead of just
|
|
826
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
|
|
827
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
730
828
|
*/
|
|
731
829
|
getObject(...path) { return this.query({ path, type: 'object' }); }
|
|
732
830
|
/**
|
|
733
|
-
* @returns Like
|
|
734
|
-
* Using this instead of just
|
|
831
|
+
* @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
|
|
832
|
+
* Using this instead of just {@link Store.get} is especially useful from within TypeScript.
|
|
735
833
|
*/
|
|
736
834
|
getMap(...path) { return this.query({ path, type: 'map' }); }
|
|
737
835
|
/**
|
|
738
|
-
* Like
|
|
836
|
+
* Like {@link Store.get}, but the first parameter is the default value (returned when the Store
|
|
739
837
|
* contains `undefined`). This default value is also used to determine the expected type,
|
|
740
838
|
* and to throw otherwise.
|
|
741
839
|
*
|
|
@@ -758,7 +856,7 @@ export class Store {
|
|
|
758
856
|
return this.query({ type, defaultValue, path });
|
|
759
857
|
}
|
|
760
858
|
/** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
|
|
761
|
-
* form of the
|
|
859
|
+
* form of the {@link Store.get} and {@link Store.peek} methods.
|
|
762
860
|
*
|
|
763
861
|
* @returns The resulting value, or `undefined` if the `path` does not exist.
|
|
764
862
|
*/
|
|
@@ -850,15 +948,15 @@ export class Store {
|
|
|
850
948
|
}
|
|
851
949
|
/**
|
|
852
950
|
* Sets the value to the last given argument. Any earlier argument are a Store-path that is first
|
|
853
|
-
* resolved/created using
|
|
951
|
+
* resolved/created using {@link Store.makeRef}.
|
|
854
952
|
*
|
|
855
953
|
* When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
|
|
856
954
|
* case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
|
|
857
955
|
* be created, so that changes made through one `Store` will be reflected through the other. Be
|
|
858
956
|
* carefull not to create loops in your `Store` tree that way, as that would cause any future
|
|
859
|
-
* call to
|
|
957
|
+
* call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
|
|
860
958
|
*
|
|
861
|
-
* If you intent to make a copy instead of a reference, call
|
|
959
|
+
* If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
|
|
862
960
|
*
|
|
863
961
|
*
|
|
864
962
|
* @example
|
|
@@ -954,7 +1052,7 @@ export class Store {
|
|
|
954
1052
|
return pos;
|
|
955
1053
|
}
|
|
956
1054
|
/**
|
|
957
|
-
*
|
|
1055
|
+
* {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
|
|
958
1056
|
* value.
|
|
959
1057
|
* @param func The function transforming the value.
|
|
960
1058
|
*/
|
|
@@ -966,7 +1064,7 @@ export class Store {
|
|
|
966
1064
|
* subscribing to every level.
|
|
967
1065
|
* In case `undefined` is encountered while resolving the path, a newly
|
|
968
1066
|
* created `Store` containing `undefined` is returned. In that case, the
|
|
969
|
-
* `Store`'s
|
|
1067
|
+
* `Store`'s {@link Store.isDetached} method will return `true`.
|
|
970
1068
|
* In case something other than a collection is encountered, an error is thrown.
|
|
971
1069
|
*/
|
|
972
1070
|
ref(...path) {
|
|
@@ -1019,7 +1117,7 @@ export class Store {
|
|
|
1019
1117
|
}
|
|
1020
1118
|
return store;
|
|
1021
1119
|
}
|
|
1022
|
-
/** @
|
|
1120
|
+
/** @internal */
|
|
1023
1121
|
_observe() {
|
|
1024
1122
|
if (currentScope) {
|
|
1025
1123
|
if (this.collection.addObserver(this.idx, currentScope)) {
|
|
@@ -1031,7 +1129,7 @@ export class Store {
|
|
|
1031
1129
|
/**
|
|
1032
1130
|
* Iterate the specified collection (Array, Map or object), running the given code block for each item.
|
|
1033
1131
|
* When items are added to the collection at some later point, the code block will be ran for them as well.
|
|
1034
|
-
* When an item is removed, the
|
|
1132
|
+
* When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
|
|
1035
1133
|
*
|
|
1036
1134
|
*
|
|
1037
1135
|
*
|
|
@@ -1122,7 +1220,7 @@ export class Store {
|
|
|
1122
1220
|
result.forEach((value, key) => {
|
|
1123
1221
|
out.set(key, value);
|
|
1124
1222
|
});
|
|
1125
|
-
keys =
|
|
1223
|
+
keys = [...result.keys()];
|
|
1126
1224
|
}
|
|
1127
1225
|
else {
|
|
1128
1226
|
return;
|
|
@@ -1138,7 +1236,7 @@ export class Store {
|
|
|
1138
1236
|
return out;
|
|
1139
1237
|
}
|
|
1140
1238
|
/**
|
|
1141
|
-
* @returns Returns `true` when the `Store` was created by
|
|
1239
|
+
* @returns Returns `true` when the `Store` was created by {@link Store.ref}ing a path that
|
|
1142
1240
|
* does not exist.
|
|
1143
1241
|
*/
|
|
1144
1242
|
isDetached() { return false; }
|
|
@@ -1167,12 +1265,18 @@ export class Store {
|
|
|
1167
1265
|
class DetachedStore extends Store {
|
|
1168
1266
|
isDetached() { return true; }
|
|
1169
1267
|
}
|
|
1268
|
+
let onCreateEnabled = false;
|
|
1269
|
+
let onDestroyMap = new WeakMap();
|
|
1270
|
+
function destroyWithClass(element, cls) {
|
|
1271
|
+
element.classList.add(cls);
|
|
1272
|
+
setTimeout(() => element.remove(), 2000);
|
|
1273
|
+
}
|
|
1170
1274
|
/**
|
|
1171
1275
|
* Create a new DOM element, and insert it into the DOM at the position held by the current scope.
|
|
1172
1276
|
* @param tag - The tag of the element to be created and optionally dot-separated class names. For example: `h1` or `p.intro.has_avatar`.
|
|
1173
1277
|
* @param rest - The other arguments are flexible and interpreted based on their types:
|
|
1174
1278
|
* - `string`: Used as textContent for the element.
|
|
1175
|
-
* - `object`: Used as attributes, properties or event listeners for the element. See
|
|
1279
|
+
* - `object`: Used as attributes, properties or event listeners for the element. See {@link Store.prop} on how the distinction is made and to read about a couple of special keys.
|
|
1176
1280
|
* - `function`: The render function used to draw the scope of the element. This function gets its own `Scope`, so that if any `Store` it reads changes, it will redraw by itself.
|
|
1177
1281
|
* - `Store`: Presuming `tag` is `"input"`, `"textarea"` or `"select"`, create a two-way binding between this `Store` value and the input element. The initial value of the input will be set to the initial value of the `Store`, or the other way around if the `Store` holds `undefined`. After that, the `Store` will be updated when the input changes and vice versa.
|
|
1178
1282
|
* @example
|
|
@@ -1207,7 +1311,14 @@ export function node(tag = "", ...rest) {
|
|
|
1207
1311
|
let type = typeof item;
|
|
1208
1312
|
if (type === 'function') {
|
|
1209
1313
|
let scope = new SimpleScope(el, undefined, currentScope.queueOrder + 1, item);
|
|
1210
|
-
|
|
1314
|
+
if (onCreateEnabled) {
|
|
1315
|
+
onCreateEnabled = false;
|
|
1316
|
+
scope.update();
|
|
1317
|
+
onCreateEnabled = true;
|
|
1318
|
+
}
|
|
1319
|
+
else {
|
|
1320
|
+
scope.update();
|
|
1321
|
+
}
|
|
1211
1322
|
// Add it to our list of cleaners. Even if `scope` currently has
|
|
1212
1323
|
// no cleaners, it may get them in a future refresh.
|
|
1213
1324
|
currentScope.cleaners.push(scope);
|
|
@@ -1262,13 +1373,13 @@ function bindInput(el, store) {
|
|
|
1262
1373
|
};
|
|
1263
1374
|
}
|
|
1264
1375
|
else {
|
|
1376
|
+
onInputChange = () => store.set(type === 'number' || type === 'range' ? (el.value === '' ? null : +el.value) : el.value);
|
|
1265
1377
|
if (value === undefined)
|
|
1266
|
-
|
|
1378
|
+
onInputChange();
|
|
1267
1379
|
onStoreChange = value => {
|
|
1268
1380
|
if (el.value !== value)
|
|
1269
1381
|
el.value = value;
|
|
1270
1382
|
};
|
|
1271
|
-
onInputChange = () => store.set(el.value);
|
|
1272
1383
|
}
|
|
1273
1384
|
observe(() => {
|
|
1274
1385
|
onStoreChange(store.get());
|
|
@@ -1288,16 +1399,16 @@ export function text(text) {
|
|
|
1288
1399
|
return;
|
|
1289
1400
|
currentScope.addNode(document.createTextNode(text));
|
|
1290
1401
|
}
|
|
1291
|
-
export function prop(
|
|
1402
|
+
export function prop(name, value = undefined) {
|
|
1292
1403
|
if (!currentScope || !currentScope.parentElement)
|
|
1293
1404
|
throw new ScopeError(true);
|
|
1294
|
-
if (typeof
|
|
1295
|
-
for (let k in
|
|
1296
|
-
applyProp(currentScope.parentElement, k,
|
|
1405
|
+
if (typeof name === 'object') {
|
|
1406
|
+
for (let k in name) {
|
|
1407
|
+
applyProp(currentScope.parentElement, k, name[k]);
|
|
1297
1408
|
}
|
|
1298
1409
|
}
|
|
1299
1410
|
else {
|
|
1300
|
-
applyProp(currentScope.parentElement,
|
|
1411
|
+
applyProp(currentScope.parentElement, name, value);
|
|
1301
1412
|
}
|
|
1302
1413
|
}
|
|
1303
1414
|
/**
|
|
@@ -1347,7 +1458,7 @@ export function observe(func) {
|
|
|
1347
1458
|
mount(undefined, func);
|
|
1348
1459
|
}
|
|
1349
1460
|
/**
|
|
1350
|
-
* Like
|
|
1461
|
+
* Like {@link Store.observe}, but allow the function to create DOM elements using {@link Store.node}.
|
|
1351
1462
|
|
|
1352
1463
|
* @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
|
|
1353
1464
|
* @param parentElement - A DOM element that will be used as the parent element for calls to `node`.
|
|
@@ -1362,7 +1473,7 @@ export function observe(func) {
|
|
|
1362
1473
|
* })
|
|
1363
1474
|
* ```
|
|
1364
1475
|
*
|
|
1365
|
-
* An example nesting
|
|
1476
|
+
* An example nesting {@link Store.observe} within `mount`:
|
|
1366
1477
|
* ```
|
|
1367
1478
|
* let selected = new Store(0)
|
|
1368
1479
|
* let colors = new Store(new Map())
|
|
@@ -1403,7 +1514,7 @@ export function mount(parentElement, func) {
|
|
|
1403
1514
|
currentScope.cleaners.push(scope);
|
|
1404
1515
|
}
|
|
1405
1516
|
}
|
|
1406
|
-
/** Runs the given function, while not subscribing the current scope when reading
|
|
1517
|
+
/** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
|
|
1407
1518
|
*
|
|
1408
1519
|
* @param func Function to be executed immediately.
|
|
1409
1520
|
* @returns Whatever `func()` returns.
|
|
@@ -1438,33 +1549,47 @@ export function peek(func) {
|
|
|
1438
1549
|
* Helper functions
|
|
1439
1550
|
*/
|
|
1440
1551
|
function applyProp(el, prop, value) {
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
el.classList.remove(
|
|
1552
|
+
if (prop === 'create') {
|
|
1553
|
+
if (onCreateEnabled) {
|
|
1554
|
+
if (typeof value === 'function') {
|
|
1555
|
+
value(el);
|
|
1556
|
+
}
|
|
1557
|
+
else {
|
|
1558
|
+
el.classList.add(value);
|
|
1559
|
+
setTimeout(function () { el.classList.remove(value); }, 0);
|
|
1560
|
+
}
|
|
1449
1561
|
}
|
|
1450
1562
|
}
|
|
1451
|
-
else if (prop === '
|
|
1452
|
-
|
|
1453
|
-
el[prop] = value;
|
|
1563
|
+
else if (prop === 'destroy') {
|
|
1564
|
+
onDestroyMap.set(el, value);
|
|
1454
1565
|
}
|
|
1455
1566
|
else if (typeof value === 'function') {
|
|
1456
1567
|
// Set an event listener; remove it again on clean.
|
|
1457
1568
|
el.addEventListener(prop, value);
|
|
1458
1569
|
clean(() => el.removeEventListener(prop, value));
|
|
1459
1570
|
}
|
|
1460
|
-
else if (prop === '
|
|
1461
|
-
//
|
|
1462
|
-
|
|
1571
|
+
else if (prop === 'value' || prop === 'className' || prop === 'selectedIndex' || value === true || value === false) {
|
|
1572
|
+
// All boolean values and a few specific keys should be set as a property
|
|
1573
|
+
el[prop] = value;
|
|
1463
1574
|
}
|
|
1464
1575
|
else if (prop === 'text') {
|
|
1465
1576
|
// `text` is set as textContent
|
|
1466
1577
|
el.textContent = value;
|
|
1467
1578
|
}
|
|
1579
|
+
else if ((prop === 'class' || prop === 'className') && typeof value === 'object') {
|
|
1580
|
+
// Allow setting classes using an object where the keys are the names and
|
|
1581
|
+
// the values are booleans stating whether to set or remove.
|
|
1582
|
+
for (let name in value) {
|
|
1583
|
+
if (value[name])
|
|
1584
|
+
el.classList.add(name);
|
|
1585
|
+
else
|
|
1586
|
+
el.classList.remove(name);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
else if (prop === 'style' && typeof value === 'object') {
|
|
1590
|
+
// `style` can receive an object
|
|
1591
|
+
Object.assign(el.style, value);
|
|
1592
|
+
}
|
|
1468
1593
|
else {
|
|
1469
1594
|
// Everything else is an HTML attribute
|
|
1470
1595
|
el.setAttribute(prop, value);
|
|
@@ -1530,6 +1655,182 @@ class ScopeError extends Error {
|
|
|
1530
1655
|
super(`Operation not permitted outside of ${mount ? "a mount" : "an observe"}() scope`);
|
|
1531
1656
|
}
|
|
1532
1657
|
}
|
|
1658
|
+
const FADE_TIME = 400;
|
|
1659
|
+
const GROW_SHRINK_TRANSITION = `margin ${FADE_TIME}ms ease-out, transform ${FADE_TIME}ms ease-out`;
|
|
1660
|
+
function getGrowShrinkProps(el) {
|
|
1661
|
+
const parentStyle = el.parentElement ? getComputedStyle(el.parentElement) : {};
|
|
1662
|
+
const isHorizontal = parentStyle.display === 'flex' && (parentStyle.flexDirection || '').startsWith('row');
|
|
1663
|
+
return isHorizontal ?
|
|
1664
|
+
{ marginLeft: `-${el.offsetWidth / 2}px`, marginRight: `-${el.offsetWidth / 2}px`, transform: "scaleX(0)" } :
|
|
1665
|
+
{ marginBottom: `-${el.offsetHeight / 2}px`, marginTop: `-${el.offsetHeight / 2}px`, transform: "scaleY(0)" };
|
|
1666
|
+
}
|
|
1667
|
+
/** Do a grow transition for the given element. This is meant to be used as a
|
|
1668
|
+
* handler for the `create` property.
|
|
1669
|
+
*
|
|
1670
|
+
* @param el The element to transition.
|
|
1671
|
+
*
|
|
1672
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
1673
|
+
* for other specific cases as well.
|
|
1674
|
+
*/
|
|
1675
|
+
export function grow(el) {
|
|
1676
|
+
// This timeout is to await all other elements having been added to the Dom
|
|
1677
|
+
scheduleDomReader(() => {
|
|
1678
|
+
// Make the element size 0 using transforms and negative margins.
|
|
1679
|
+
// This causes a browser layout, as we're querying el.offset<>.
|
|
1680
|
+
let props = getGrowShrinkProps(el);
|
|
1681
|
+
// The timeout is in order to batch all reads and then all writes when there
|
|
1682
|
+
// are multiple simultaneous grow transitions.
|
|
1683
|
+
scheduleDomWriter(() => {
|
|
1684
|
+
Object.assign(el.style, props);
|
|
1685
|
+
// This timeout is to combine multiple transitions into a single browser layout
|
|
1686
|
+
scheduleDomReader(() => {
|
|
1687
|
+
// Make sure the layouting has been performed, to cause transitions to trigger
|
|
1688
|
+
el.offsetHeight;
|
|
1689
|
+
scheduleDomWriter(() => {
|
|
1690
|
+
// Do the transitions
|
|
1691
|
+
el.style.transition = GROW_SHRINK_TRANSITION;
|
|
1692
|
+
for (let prop in props)
|
|
1693
|
+
el.style[prop] = "";
|
|
1694
|
+
setTimeout(() => {
|
|
1695
|
+
// Reset the element to a clean state
|
|
1696
|
+
el.style.transition = "";
|
|
1697
|
+
}, FADE_TIME);
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
});
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
/** Do a shrink transition for the given element, and remove it from the DOM
|
|
1704
|
+
* afterwards. This is meant to be used as a handler for the `destroy` property.
|
|
1705
|
+
*
|
|
1706
|
+
* @param el The element to transition and remove.
|
|
1707
|
+
*
|
|
1708
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
1709
|
+
* for other specific cases as well.
|
|
1710
|
+
*/
|
|
1711
|
+
export function shrink(el) {
|
|
1712
|
+
scheduleDomReader(() => {
|
|
1713
|
+
const props = getGrowShrinkProps(el);
|
|
1714
|
+
// The timeout is in order to batch all reads and then all writes when there
|
|
1715
|
+
// are multiple simultaneous shrink transitions.
|
|
1716
|
+
scheduleDomWriter(() => {
|
|
1717
|
+
el.style.transition = GROW_SHRINK_TRANSITION;
|
|
1718
|
+
Object.assign(el.style, props);
|
|
1719
|
+
setTimeout(() => el.remove(), FADE_TIME);
|
|
1720
|
+
});
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
function recordPatch(func) {
|
|
1724
|
+
if (recordingPatch)
|
|
1725
|
+
throw new Error(`already recording a patch`);
|
|
1726
|
+
recordingPatch = new Map();
|
|
1727
|
+
try {
|
|
1728
|
+
func();
|
|
1729
|
+
}
|
|
1730
|
+
catch (e) {
|
|
1731
|
+
recordingPatch = undefined;
|
|
1732
|
+
throw e;
|
|
1733
|
+
}
|
|
1734
|
+
const result = recordingPatch;
|
|
1735
|
+
recordingPatch = undefined;
|
|
1736
|
+
return result;
|
|
1737
|
+
}
|
|
1738
|
+
function addToPatch(patch, collection, index, newData, oldData) {
|
|
1739
|
+
let collectionMap = patch.get(collection);
|
|
1740
|
+
if (collectionMap == null) {
|
|
1741
|
+
collectionMap = new Map();
|
|
1742
|
+
patch.set(collection, collectionMap);
|
|
1743
|
+
}
|
|
1744
|
+
let prev = collectionMap.get(index);
|
|
1745
|
+
collectionMap.set(index, [newData, prev == null ? oldData : prev[1]]);
|
|
1746
|
+
}
|
|
1747
|
+
function emitPatch(patch) {
|
|
1748
|
+
for (let [collection, collectionMap] of patch) {
|
|
1749
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1750
|
+
collection.emitChange(index, newData, oldData);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
function mergePatch(target, source, reverse = false) {
|
|
1755
|
+
for (let [collection, collectionMap] of source) {
|
|
1756
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1757
|
+
addToPatch(target, collection, index, reverse ? oldData : newData, reverse ? newData : oldData);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function silentlyApplyPatch(patch, force = false) {
|
|
1762
|
+
for (let [collection, collectionMap] of patch) {
|
|
1763
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1764
|
+
let actualData = collection.rawGet(index);
|
|
1765
|
+
if (actualData !== oldData) {
|
|
1766
|
+
if (force)
|
|
1767
|
+
handleError(new Error(`Applying invalid patch: data ${actualData} is unequal to expected old data ${oldData} for index ${index}`));
|
|
1768
|
+
else
|
|
1769
|
+
return false;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
for (let [collection, collectionMap] of patch) {
|
|
1774
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1775
|
+
collection.rawSet(index, newData);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
const appliedPredictions = [];
|
|
1781
|
+
/**
|
|
1782
|
+
* Run the provided function, while treating all changes to Observables as predictions,
|
|
1783
|
+
* meaning they will be reverted when changes come back from the server (or some other
|
|
1784
|
+
* async source).
|
|
1785
|
+
* @param predictFunc The function to run. It will generally modify some Observables
|
|
1786
|
+
* to immediately reflect state (as closely as possible) that we expect the server
|
|
1787
|
+
* to communicate back to us later on.
|
|
1788
|
+
* @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
|
|
1789
|
+
*/
|
|
1790
|
+
export function applyPrediction(predictFunc) {
|
|
1791
|
+
let patch = recordPatch(predictFunc);
|
|
1792
|
+
appliedPredictions.push(patch);
|
|
1793
|
+
emitPatch(patch);
|
|
1794
|
+
return patch;
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Temporarily revert all outstanding predictions, optionally run the provided function
|
|
1798
|
+
* (which will generally make authoritative changes to the data based on a server response),
|
|
1799
|
+
* and then attempt to reapply the predictions on top of the new canonical state, dropping
|
|
1800
|
+
* any predictions that can no longer be applied cleanly (the data has been modified) or
|
|
1801
|
+
* that were specified in `dropPredictions`.
|
|
1802
|
+
*
|
|
1803
|
+
* All of this is done such that redraws are only triggered if the overall effect is an
|
|
1804
|
+
* actual change to an `Observable`.
|
|
1805
|
+
* @param canonFunc The function to run without any predictions applied. This will typically
|
|
1806
|
+
* make authoritative changes to the data, based on a server response.
|
|
1807
|
+
* @param dropPredictions An optional list of predictions (as returned by `applyPrediction`)
|
|
1808
|
+
* to undo. Typically, when a server response for a certain request is being handled,
|
|
1809
|
+
* you'd want to drop the prediction that was done for that request.
|
|
1810
|
+
*/
|
|
1811
|
+
export function applyCanon(canonFunc, dropPredictions = []) {
|
|
1812
|
+
let resultPatch = new Map();
|
|
1813
|
+
for (let prediction of appliedPredictions)
|
|
1814
|
+
mergePatch(resultPatch, prediction, true);
|
|
1815
|
+
silentlyApplyPatch(resultPatch, true);
|
|
1816
|
+
for (let prediction of dropPredictions) {
|
|
1817
|
+
let pos = appliedPredictions.indexOf(prediction);
|
|
1818
|
+
if (pos >= 0)
|
|
1819
|
+
appliedPredictions.splice(pos, 1);
|
|
1820
|
+
}
|
|
1821
|
+
if (canonFunc)
|
|
1822
|
+
mergePatch(resultPatch, recordPatch(canonFunc));
|
|
1823
|
+
for (let idx = 0; idx < appliedPredictions.length; idx++) {
|
|
1824
|
+
if (silentlyApplyPatch(appliedPredictions[idx])) {
|
|
1825
|
+
mergePatch(resultPatch, appliedPredictions[idx]);
|
|
1826
|
+
}
|
|
1827
|
+
else {
|
|
1828
|
+
appliedPredictions.splice(idx, 1);
|
|
1829
|
+
idx--;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
emitPatch(resultPatch);
|
|
1833
|
+
}
|
|
1533
1834
|
// @ts-ignore
|
|
1534
1835
|
// istanbul ignore next
|
|
1535
1836
|
if (!String.prototype.replaceAll)
|
package/dist/aberdeen.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e,t=[],i=new Set,n=!0,r=0;function s(e){if(!i.has(e)){if(r>42)throw new Error("Too many recursive updates from observes");t.length?e.queueOrder<t[t.length-1].queueOrder&&(n=!1):setTimeout(o,0),t.push(e),i.add(e)}}function o(){for(let e=0;e<t.length;){n||(t.splice(0,e),e=0,t.sort((e,t)=>e.queueOrder-t.queueOrder),n=!0);let s=t.length;for(;e<s&&n;e++){let n=t[e];i.delete(n),n.queueRun()}r++}t.length=0,r=0}function a(e){if("string"==typeof e)return e+"";{let t=function(e,t){let i="";for(;e>0;)i+=String.fromCharCode(t?65535-e%65533:2+e%65533),e=Math.floor(e/65533);return i}(Math.abs(Math.round(e)),e<0);return String.fromCharCode(128+(e>0?t.length:-t.length))+t}}class l{constructor(e,t,i){this.cleaners=[],this.isDead=!1,this.parentElement=e,this.precedingSibling=t,this.queueOrder=i}findPrecedingNode(){let e=this.precedingSibling;for(;e;){if(e instanceof Node)return e;let t=e.findLastNode();if(t)return t;e=e.precedingSibling}}findLastNode(){return this.lastChild instanceof Node?this.lastChild:this.lastChild instanceof l?this.lastChild.findLastNode()||this.lastChild.findPrecedingNode():void 0}addNode(e){if(!this.parentElement)throw new O(!0);let t=this.findLastNode()||this.findPrecedingNode();this.parentElement.insertBefore(e,t?t.nextSibling:this.parentElement.firstChild),this.lastChild=e}remove(){if(this.parentElement){let e=this.findLastNode();if(e){let t=this.findPrecedingNode();for(;e!==t;){if(!e)return E(1);let t=e.previousSibling||void 0;this.parentElement.removeChild(e),e=t}}this.lastChild=void 0}this._clean()}_clean(){this.isDead=!0;for(let e of this.cleaners)e._clean(this);this.cleaners.length=0}onChange(e,t,i){s(this)}}class h extends l{constructor(e,t,i,n){super(e,t,i),this.renderer=n}queueRun(){e&&E(2),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let t=e;e=this;try{this.renderer()}catch(e){C(e)}e=t}}class d{constructor(e,t,i){this.scope=e,this.collection=t,this.triggerCount=i,this.count=t.getCount(),t.addObserver(f,this),e.cleaners.push(this)}onChange(e,t,i){void 0===t?!this.triggerCount&&--this.count||s(this.scope):void 0===i&&(!this.triggerCount&&this.count++||s(this.scope))}_clean(){this.collection.removeObserver(f,this)}}class c extends l{constructor(e,t,i,n,r,s){super(e,t,i),this.byPosition=[],this.byIndex=new Map,this.newIndexes=new Set,this.removedIndexes=new Set,this.collection=n,this.renderer=r,this.makeSortKey=s}onChange(e,t,i){void 0===i?this.removedIndexes.has(e)?this.removedIndexes.delete(e):(this.newIndexes.add(e),s(this)):void 0===t&&(this.newIndexes.has(e)?this.newIndexes.delete(e):(this.removedIndexes.add(e),s(this)))}queueRun(){if(this.isDead)return;let e=this.removedIndexes;this.removedIndexes=new Set,e.forEach(e=>{this.removeChild(e)}),e=this.newIndexes,this.newIndexes=new Set,e.forEach(e=>{this.addChild(e)})}_clean(){super._clean(),this.collection.observers.delete(this);for(const[e,t]of this.byIndex)t._clean();this.byPosition.length=0,this.byIndex.clear()}renderInitial(){if(!e)return E(3);let t=e;this.collection.iterateIndexes(this),e=t}addChild(e){let t=new u(this.parentElement,void 0,this.queueOrder+1,this,e);this.byIndex.set(e,t),t.update()}removeChild(e){let t=this.byIndex.get(e);if(!t)return E(6);this.byIndex.delete(e),this.removeFromPosition(t),t.remove()}findPosition(e){let t=this.byPosition,i=0,n=t.length;if(!n||e>t[n-1].sortStr)return n;for(;i<n;){let r=i+n>>1;t[r].sortStr<e?i=r+1:n=r}return i}insertAtPosition(e){let t=this.findPosition(e.sortStr);this.byPosition.splice(t,0,e),e.precedingSibling=t>0?this.byPosition[t-1]:this.precedingSibling,t+1<this.byPosition.length?this.byPosition[t+1].precedingSibling=e:this.lastChild=e}removeFromPosition(e){if(""===e.sortStr)return;let t=this.findPosition(e.sortStr);for(;;){if(this.byPosition[t]===e)return this.byPosition.splice(t,1),void(t<this.byPosition.length?this.byPosition[t].precedingSibling=t>0?this.byPosition[t-1]:this.precedingSibling:this.lastChild=this.byPosition.length?this.byPosition[this.byPosition.length-1]:void 0);if(++t>=this.byPosition.length||this.byPosition[t].sortStr!==e.sortStr)return E(5)}}}class u extends l{constructor(e,t,i,n,r){super(e,t,i),this.sortStr="",this.parent=n,this.itemIndex=r}queueRun(){e&&E(4),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let t=e;e=this;let i,n=new Store(this.parent.collection,this.itemIndex);try{i=this.parent.makeSortKey(n)}catch(e){C(e)}let r=this.sortStr,s=null==i?"":(o=i)instanceof Array?o.map(a).join(""):a(o);var o;if(""!==r&&r!==s&&this.parent.removeFromPosition(this),this.sortStr=s,""!==s){s!==r&&this.parent.insertAtPosition(this);try{this.parent.renderer(n)}catch(e){C(e)}}e=t}}const f={};class p{constructor(){this.observers=new Map}addObserver(e,t){t=t;let i=this.observers.get(e);if(i){if(i.has(t))return!1;i.add(t)}else this.observers.set(e,new Set([t]));return!0}removeObserver(e,t){this.observers.get(e).delete(t)}emitChange(e,t,i){let n=this.observers.get(e);n&&n.forEach(n=>n.onChange(e,t,i)),n=this.observers.get(f),n&&n.forEach(n=>n.onChange(e,t,i))}_clean(e){this.removeObserver(f,e)}setIndex(e,t,i){const n=this.rawGet(e);if(!(n instanceof p)||t instanceof Store||!n.merge(t,i)){let i=x(t);i!==n&&(this.rawSet(e,i),this.emitChange(e,i,n))}}}class g extends p{constructor(){super(...arguments),this.data=[]}getType(){return"array"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i=[];for(let e=0;e<this.data.length;e++){let n=this.data[e];i.push(n instanceof p?t?n.getRecursive(t-1):new Store(this,e):n)}return i}rawGet(e){return this.data[e]}rawSet(e,t){if(e!==(0|e)||e<0||e>999999)throw new Error("Invalid array index "+JSON.stringify(e));for(this.data[e]=t;this.data.length>0&&void 0===this.data[this.data.length-1];)this.data.pop()}merge(e,t){if(!(e instanceof Array))return!1;for(let i=0;i<e.length;i++)this.setIndex(i,e[i],t);if(t&&this.data.length>e.length){for(let t=e.length;t<this.data.length;t++){let e=this.data[t];void 0!==e&&this.emitChange(t,void 0,e)}this.data.length=e.length}return!0}iterateIndexes(e){for(let t=0;t<this.data.length;t++)void 0!==this.data[t]&&e.addChild(t)}normalizeIndex(e){if("number"==typeof e)return e;if("string"==typeof e){let t=0|e;if(e.length&&t==e)return e}throw new Error("Invalid array index "+JSON.stringify(e))}getCount(){return this.data.length}}class v extends p{constructor(){super(...arguments),this.data=new Map}getType(){return"map"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i=new Map;return this.data.forEach((e,n)=>{i.set(n,e instanceof p?t?e.getRecursive(t-1):new Store(this,n):e)}),i}rawGet(e){return this.data.get(e)}rawSet(e,t){void 0===t?this.data.delete(e):this.data.set(e,t)}merge(e,t){return e instanceof Map&&(e.forEach((e,i)=>{this.setIndex(i,e,t)}),t&&this.data.forEach((t,i)=>{e.has(i)||this.setIndex(i,void 0,!1)}),!0)}iterateIndexes(e){this.data.forEach((t,i)=>{e.addChild(i)})}normalizeIndex(e){return e}getCount(){return this.data.size}}class y extends v{getType(){return"object"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i={};return this.data.forEach((e,n)=>{i[n]=e instanceof p?t?e.getRecursive(t-1):new Store(this,n):e}),i}merge(e,t){if(!e||e.constructor!==Object)return!1;for(let i in e)this.setIndex(i,e[i],t);return t&&this.data.forEach((t,i)=>{e.hasOwnProperty(i)||this.setIndex(i,void 0,!1)}),!0}normalizeIndex(e){let t=typeof e;if("string"===t)return e;if("number"===t)return""+e;throw new Error("Invalid object index "+JSON.stringify(e))}getCount(){let e=0;for(let t of this.data)e++;return e}}export class Store{constructor(e,t){if(void 0===t)this.collection=new g,this.idx=0,void 0!==e&&this.collection.rawSet(0,x(e));else{if(!(e instanceof p))throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");this.collection=e,this.idx=t}}index(){return this.idx}_clean(e){this.collection.removeObserver(this.idx,e)}get(...e){return this.query({path:e})}peek(...e){return this.query({path:e,peek:!0})}getNumber(...e){return this.query({path:e,type:"number"})}getString(...e){return this.query({path:e,type:"string"})}getBoolean(...e){return this.query({path:e,type:"boolean"})}getFunction(...e){return this.query({path:e,type:"function"})}getArray(...e){return this.query({path:e,type:"array"})}getObject(...e){return this.query({path:e,type:"object"})}getMap(...e){return this.query({path:e,type:"map"})}getOr(e,...t){let i=typeof e;return"object"===i&&(e instanceof Map?i="map":e instanceof Array&&(i="array")),this.query({type:i,defaultValue:e,path:t})}query(t){if(t.peek&&e){let i=e;e=void 0;let n=this.query(t);return e=i,n}let i=(t.path&&t.path.length?this.ref(...t.path):this)._observe();if(t.type&&(void 0!==i||void 0===t.defaultValue)){let e=i instanceof p?i.getType():null===i?"null":typeof i;if(e!==t.type)throw new TypeError(`Expecting ${t.type} but got ${e}`)}return i instanceof p?i.getRecursive(null==t.depth?-1:t.depth-1):void 0===i?t.defaultValue:i}isEmpty(...t){let i=this.ref(...t)._observe();if(i instanceof p){if(e){return!new d(e,i,!1).count}return!i.getCount()}if(void 0===i)return!0;throw new Error("isEmpty() expects a collection or undefined, but got "+JSON.stringify(i))}count(...t){let i=this.ref(...t)._observe();if(i instanceof p){if(e){return new d(e,i,!0).count}return i.getCount()}if(void 0===i)return 0;throw new Error("count() expects a collection or undefined, but got "+JSON.stringify(i))}getType(...e){let t=this.ref(...e)._observe();return t instanceof p?t.getType():null===t?"null":typeof t}set(...e){let t=e.pop(),i=this.makeRef(...e);i.collection.setIndex(i.idx,t,!0)}merge(...e){let t=e.pop(),i=this.makeRef(...e);i.collection.setIndex(i.idx,t,!1)}delete(...e){let t=this.makeRef(...e);t.collection.setIndex(t.idx,void 0,!0)}push(...e){let t=e.pop(),i=this.makeRef(...e),n=i.collection.rawGet(i.idx);if(void 0===n)n=new g,i.collection.setIndex(i.idx,n,!0);else if(!(n instanceof g))throw new Error("push() is only allowed for an array or undefined (which would become an array)");let r=x(t),s=n.data.length;return n.data.push(r),n.emitChange(s,r,void 0),s}modify(e){this.set(e(this.query({peek:!0})))}ref(...e){let t=this;for(let i=0;i<e.length;i++){let n=t._observe();if(!(n instanceof p)){if(void 0!==n)throw new Error(`Value ${JSON.stringify(n)} is not a collection (nor undefined) in step ${i} of $(${JSON.stringify(e)})`);return new m}t=new Store(n,n.normalizeIndex(e[i]))}return t}makeRef(...e){let t=this;for(let i=0;i<e.length;i++){let n=t.collection.rawGet(t.idx);if(!(n instanceof p)){if(void 0!==n)throw new Error(`Value ${JSON.stringify(n)} is not a collection (nor undefined) in step ${i} of $(${JSON.stringify(e)})`);n=new y,t.collection.rawSet(t.idx,n),t.collection.emitChange(t.idx,n,void 0)}t=new Store(n,n.normalizeIndex(e[i]))}return t}_observe(){return e&&this.collection.addObserver(this.idx,e)&&e.cleaners.push(this),this.collection.rawGet(this.idx)}onEach(...t){let i=S,n=t.pop();if("function"!=typeof t[t.length-1]||"function"!=typeof n&&null!=n||(null!=n&&(i=n),n=t.pop()),"function"!=typeof n)throw new Error("onEach() expects a render function as its last argument but got "+JSON.stringify(n));if(!e)throw new O(!1);let r=this.ref(...t)._observe();if(r instanceof p){let t=new c(e.parentElement,e.lastChild||e.precedingSibling,e.queueOrder+1,r,n,i);r.addObserver(f,t),e.cleaners.push(t),e.lastChild=t,t.renderInitial()}else if(void 0!==r)throw new Error("onEach() attempted on a value that is neither a collection nor undefined")}map(e){let t=new Store(new Map);return this.onEach(i=>{let n=e(i);if(void 0!==n){let e=i.index();t.set(e,n),clean(()=>{t.delete(e)})}}),t}multiMap(e){let t=new Store(new Map);return this.onEach(i=>{let n,r=e(i);if(r.constructor===Object){for(let e in r)t.set(e,r[e]);n=Object.keys(r)}else{if(!(r instanceof Map))return;r.forEach((e,i)=>{t.set(i,e)}),n=Array.from(r.keys())}n.length&&clean(()=>{for(let e of n)t.delete(e)})}),t}isDetached(){return!1}dump(){let e=this.getType();"array"===e||"object"===e||"map"===e?(text("<"+e+">"),node("ul",()=>{this.onEach(e=>{node("li",()=>{text(JSON.stringify(e.index())+": "),e.dump()})})})):text(JSON.stringify(this.get()))}}class m extends Store{isDetached(){return!0}}export function node(t="",...i){if(!e)throw new O(!0);let n;if(t instanceof Element)n=t;else{let e,i=t.indexOf(".");i>=0&&(e=t.substr(i+1),t=t.substr(0,i)),n=document.createElement(t||"div"),e&&(n.className=e.replaceAll("."," "))}e.addNode(n);for(let t of i){let i=typeof t;if("function"===i){let i=new h(n,void 0,e.queueOrder+1,t);i.update(),e.cleaners.push(i)}else if("string"===i||"number"===i)n.textContent=t;else if("object"===i&&t&&t.constructor===Object)for(let e in t)b(n,e,t[e]);else if(t instanceof Store)w(n,t);else if(null!=t)throw new Error("Unexpected argument "+JSON.stringify(t))}}export function html(t){if(!e||!e.parentElement)throw new O(!0);let i=document.createElement(e.parentElement.tagName);for(i.innerHTML=""+t;i.firstChild;)e.addNode(i.firstChild)}function w(e,t){let i,n,r=e.getAttribute("type"),s=t.query({peek:!0});"checkbox"===r?(void 0===s&&t.set(e.checked),i=t=>e.checked=t,n=()=>t.set(e.checked)):"radio"===r?(void 0===s&&e.checked&&t.set(e.value),i=t=>e.checked=t===e.value,n=()=>{e.checked&&t.set(e.value)}):(void 0===s&&t.set(e.value),i=t=>{e.value!==t&&(e.value=t)},n=()=>t.set(e.value)),observe(()=>{i(t.get())}),e.addEventListener("input",n),clean(()=>{e.removeEventListener("input",n)})}export function text(t){if(!e)throw new O(!0);null!=t&&e.addNode(document.createTextNode(t))}export function prop(t,i){if(!e||!e.parentElement)throw new O(!0);if("object"==typeof t)for(let i in t)b(e.parentElement,i,t[i]);else b(e.parentElement,t,i)}export function getParentElement(){if(!e||!e.parentElement)throw new O(!0);return e.parentElement}export function clean(t){if(!e)throw new O(!1);e.cleaners.push({_clean:t})}export function observe(e){mount(void 0,e)}export function mount(t,i){let n;t||!e?n=new h(t,void 0,0,i):(n=new h(e.parentElement,e.lastChild||e.precedingSibling,e.queueOrder+1,i),e.lastChild=n),n.update(),e&&e.cleaners.push(n)}export function peek(t){let i=e;e=void 0;try{return t()}finally{e=i}}function b(e,t,i){if("class"!==t&&"className"!==t||"object"!=typeof i)"value"===t||"className"===t||"selectedIndex"===t||!0===i||!1===i?e[t]=i:"function"==typeof i?(e.addEventListener(t,i),clean(()=>e.removeEventListener(t,i))):"style"===t&&"object"==typeof i?Object.assign(e.style,i):"text"===t?e.textContent=i:e.setAttribute(t,i);else for(let t in i)i[t]?e.classList.add(t):e.classList.remove(t)}function x(e){if("object"==typeof e&&e){if(e instanceof Store)return e._observe();if(e instanceof Map){let t=new v;return e.forEach((e,i)=>{let n=x(e);void 0!==n&&t.rawSet(i,n)}),t}if(e instanceof Array){let t=new g;for(let i=0;i<e.length;i++){let n=x(e[i]);void 0!==n&&t.rawSet(i,n)}return t}if(e.constructor===Object){let t=new y;for(let i in e){let n=x(e[i]);void 0!==n&&t.rawSet(i,n)}return t}return e}return e}function S(e){return e.index()}function E(e){let t=new Error("Aberdeen internal error "+e);setTimeout(()=>{throw t},0)}function C(e){setTimeout(()=>{throw e},0)}class O extends Error{constructor(e){super(`Operation not permitted outside of ${e?"a mount":"an observe"}() scope`)}}String.prototype.replaceAll||(String.prototype.replaceAll=function(e,t){return this.split(e).join(t)});
|
|
1
|
+
let e,t,n=[],i=new Set,r=!0,s=0;function o(e){if(!i.has(e)){if(s>42)throw new Error("Too many recursive updates from observes");n.length?e.queueOrder<n[n.length-1].queueOrder&&(r=!1):setTimeout(l,0),n.push(e),i.add(e)}}function l(){for(b=!0,e=0;e<n.length;){r||(n.splice(0,e),e=0,n.sort((e,t)=>e.queueOrder-t.queueOrder),r=!0);let t=n.length;for(;e<t&&r;e++){let t=n[e];i.delete(t),t.queueRun()}s++}n.length=0,e=void 0,s=0,b=!1}let a;export function scheduleDomReader(t){o({queueOrder:null!=e&&e<n.length&&n[e].queueOrder>=1e3?n[e].queueOrder+1&-2:1e3,queueRun:t})}export function scheduleDomWriter(t){o({queueOrder:null!=e&&e<n.length&&n[e].queueOrder>=1e3?1|n[e].queueOrder:1001,queueRun:t})}function h(e){if("string"==typeof e)return e+"";{let t=function(e,t){let n="";for(;e>0;)n+=String.fromCharCode(t?65535-e%65533:2+e%65533),e=Math.floor(e/65533);return n}(Math.abs(Math.round(e)),e<0);return String.fromCharCode(128+(e>0?t.length:-t.length))+t}}class d{constructor(e,t,n){this.cleaners=[],this.isDead=!1,this.parentElement=e,this.precedingSibling=t,this.queueOrder=n}findPrecedingNode(e){let t,n=this;for(;(t=n.precedingSibling)&&t!==e;){if(t instanceof Node)return t;let e=t.findLastNode();if(e)return e;n=t}}findLastNode(){if(this.lastChild)return this.lastChild instanceof Node?this.lastChild:this.lastChild.findLastNode()||this.lastChild.findPrecedingNode(this.precedingSibling)}addNode(e){if(!this.parentElement)throw new k(!0);let t=this.findLastNode()||this.findPrecedingNode();this.parentElement.insertBefore(e,t?t.nextSibling:this.parentElement.firstChild),this.lastChild=e}remove(){if(this.parentElement){let e=this.findLastNode();if(e){let t=this.findPrecedingNode();for(t=t?t.nextSibling:this.parentElement.firstChild,this.lastChild=void 0;;){if(!t)return N(1);const n=t;t=n.nextSibling||void 0;let i=S.get(n);if(i&&n instanceof Element?!0!==i&&("function"==typeof i?i(n):E(n,i),S.set(n,!0)):this.parentElement.removeChild(n),n===e)break}}}this._clean()}_clean(){this.isDead=!0;for(let e of this.cleaners)e._clean(this);this.cleaners.length=0}onChange(e,t,n){o(this)}}class u extends d{constructor(e,t,n,i){super(e,t,n),this.renderer=i}queueRun(){a&&N(2),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let e=a;a=this;try{this.renderer()}catch(e){P(e)}a=e}}class c{constructor(e,t,n){this.scope=e,this.collection=t,this.triggerCount=n,this.count=t.getCount(),t.addObserver(g,this),e.cleaners.push(this)}onChange(e,t,n){void 0===t?!this.triggerCount&&--this.count||o(this.scope):void 0===n&&(!this.triggerCount&&this.count++||o(this.scope))}_clean(){this.collection.removeObserver(g,this)}}class f extends d{constructor(e,t,n,i,r,s){super(e,t,n),this.byPosition=[],this.byIndex=new Map,this.newIndexes=new Set,this.removedIndexes=new Set,this.collection=i,this.renderer=r,this.makeSortKey=s}onChange(e,t,n){void 0===n?this.removedIndexes.has(e)?this.removedIndexes.delete(e):(this.newIndexes.add(e),o(this)):void 0===t&&(this.newIndexes.has(e)?this.newIndexes.delete(e):(this.removedIndexes.add(e),o(this)))}queueRun(){if(this.isDead)return;let e=this.removedIndexes;this.removedIndexes=new Set,e.forEach(e=>{this.removeChild(e)}),e=this.newIndexes,this.newIndexes=new Set,e.forEach(e=>{this.addChild(e)})}_clean(){super._clean(),this.collection.observers.delete(this);for(const[e,t]of this.byIndex)t._clean();this.byPosition.length=0,this.byIndex.clear()}renderInitial(){if(!a)return N(3);let e=a;this.collection.iterateIndexes(this),a=e}addChild(e){let t=new p(this.parentElement,void 0,this.queueOrder+1,this,e);this.byIndex.set(e,t),t.update()}removeChild(e){let t=this.byIndex.get(e);if(!t)return N(6);t.remove(),this.byIndex.delete(e),this.removeFromPosition(t)}findPosition(e){let t=this.byPosition,n=0,i=t.length;if(!i||e>t[i-1].sortStr)return i;for(;n<i;){let r=n+i>>1;t[r].sortStr<e?n=r+1:i=r}return n}insertAtPosition(e){let t=this.findPosition(e.sortStr);this.byPosition.splice(t,0,e);let n=this.byPosition[t+1];n?(e.precedingSibling=n.precedingSibling,n.precedingSibling=e):(e.precedingSibling=this.lastChild||this.precedingSibling,this.lastChild=e)}removeFromPosition(e){if(""===e.sortStr)return;let t=this.findPosition(e.sortStr);for(;;){if(this.byPosition[t]===e){if(this.byPosition.splice(t,1),t<this.byPosition.length){let n=this.byPosition[t];if(!n)return N(8);if(n.precedingSibling!==e)return N(13);n.precedingSibling=e.precedingSibling}else{if(e!==this.lastChild)return N(12);this.lastChild=e.precedingSibling===this.precedingSibling?void 0:e.precedingSibling}return}if(++t>=this.byPosition.length||this.byPosition[t].sortStr!==e.sortStr)return N(5)}}}class p extends d{constructor(e,t,n,i,r){super(e,t,n),this.sortStr="",this.parent=i,this.itemIndex=r}queueRun(){a&&N(4),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let e=a;a=this;let t,n=new Store(this.parent.collection,this.itemIndex);try{t=this.parent.makeSortKey(n)}catch(e){P(e)}let i=this.sortStr,r=null==t?"":(s=t)instanceof Array?s.map(h).join(""):h(s);var s;if(""!==i&&i!==r&&this.parent.removeFromPosition(this),this.sortStr=r,""!==r){r!==i&&this.parent.insertAtPosition(this);try{this.parent.renderer(n)}catch(e){P(e)}}a=e}}const g={};class m{constructor(){this.observers=new Map}addObserver(e,t){t=t;let n=this.observers.get(e);if(n){if(n.has(t))return!1;n.add(t)}else this.observers.set(e,new Set([t]));return!0}removeObserver(e,t){this.observers.get(e).delete(t)}emitChange(e,n,i){if(t)M(t,this,e,n,i);else{let t=this.observers.get(e);t&&t.forEach(t=>t.onChange(e,n,i)),t=this.observers.get(g),t&&t.forEach(t=>t.onChange(e,n,i))}}_clean(e){this.removeObserver(g,e)}setIndex(e,t,n){const i=this.rawGet(e);if(!(i instanceof m)||t instanceof Store||!i.merge(t,n)){let n=I(t);n!==i&&(this.rawSet(e,n),this.emitChange(e,n,i))}}}class v extends m{constructor(){super(...arguments),this.data=[]}getType(){return"array"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t=[];for(let n=0;n<this.data.length;n++){let i=this.data[n];t.push(i instanceof m?e?i.getRecursive(e-1):new Store(this,n):i)}return t}rawGet(e){return this.data[e]}rawSet(e,t){if(e!==(0|e)||e<0||e>999999)throw new Error("Invalid array index "+JSON.stringify(e));for(this.data[e]=t;this.data.length>0&&void 0===this.data[this.data.length-1];)this.data.pop()}merge(e,t){if(!(e instanceof Array))return!1;for(let n=0;n<e.length;n++)this.setIndex(n,e[n],t);if(t&&this.data.length>e.length){for(let t=e.length;t<this.data.length;t++){let e=this.data[t];void 0!==e&&this.emitChange(t,void 0,e)}this.data.length=e.length}return!0}iterateIndexes(e){for(let t=0;t<this.data.length;t++)void 0!==this.data[t]&&e.addChild(t)}normalizeIndex(e){if("number"==typeof e)return e;if("string"==typeof e){let t=0|e;if(e.length&&t==e)return e}throw new Error("Invalid array index "+JSON.stringify(e))}getCount(){return this.data.length}}class y extends m{constructor(){super(...arguments),this.data=new Map}getType(){return"map"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t=new Map;return this.data.forEach((n,i)=>{t.set(i,n instanceof m?e?n.getRecursive(e-1):new Store(this,i):n)}),t}rawGet(e){return this.data.get(e)}rawSet(e,t){void 0===t?this.data.delete(e):this.data.set(e,t)}merge(e,t){return e instanceof Map&&(e.forEach((e,n)=>{this.setIndex(n,e,t)}),t&&this.data.forEach((t,n)=>{e.has(n)||this.setIndex(n,void 0,!1)}),!0)}iterateIndexes(e){this.data.forEach((t,n)=>{e.addChild(n)})}normalizeIndex(e){return e}getCount(){return this.data.size}}class w extends y{getType(){return"object"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t={};return this.data.forEach((n,i)=>{t[i]=n instanceof m?e?n.getRecursive(e-1):new Store(this,i):n}),t}merge(e,t){if(!e||e.constructor!==Object)return!1;for(let n in e)this.setIndex(n,e[n],t);return t&&this.data.forEach((t,n)=>{e.hasOwnProperty(n)||this.setIndex(n,void 0,!1)}),!0}normalizeIndex(e){let t=typeof e;if("string"===t)return e;if("number"===t)return""+e;throw new Error("Invalid object index "+JSON.stringify(e))}getCount(){let e=0;for(let t of this.data)e++;return e}}export class Store{constructor(e,t){if(void 0===t)this.collection=new v,this.idx=0,void 0!==e&&this.collection.rawSet(0,I(e));else{if(!(e instanceof m))throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");this.collection=e,this.idx=t}}index(){return this.idx}_clean(e){this.collection.removeObserver(this.idx,e)}get(...e){return this.query({path:e})}peek(...e){return this.query({path:e,peek:!0})}getNumber(...e){return this.query({path:e,type:"number"})}getString(...e){return this.query({path:e,type:"string"})}getBoolean(...e){return this.query({path:e,type:"boolean"})}getFunction(...e){return this.query({path:e,type:"function"})}getArray(...e){return this.query({path:e,type:"array"})}getObject(...e){return this.query({path:e,type:"object"})}getMap(...e){return this.query({path:e,type:"map"})}getOr(e,...t){let n=typeof e;return"object"===n&&(e instanceof Map?n="map":e instanceof Array&&(n="array")),this.query({type:n,defaultValue:e,path:t})}query(e){if(e.peek&&a){let t=a;a=void 0;let n=this.query(e);return a=t,n}let t=(e.path&&e.path.length?this.ref(...e.path):this)._observe();if(e.type&&(void 0!==t||void 0===e.defaultValue)){let n=t instanceof m?t.getType():null===t?"null":typeof t;if(n!==e.type)throw new TypeError(`Expecting ${e.type} but got ${n}`)}return t instanceof m?t.getRecursive(null==e.depth?-1:e.depth-1):void 0===t?e.defaultValue:t}isEmpty(...e){let t=this.ref(...e)._observe();if(t instanceof m){if(a){return!new c(a,t,!1).count}return!t.getCount()}if(void 0===t)return!0;throw new Error("isEmpty() expects a collection or undefined, but got "+JSON.stringify(t))}count(...e){let t=this.ref(...e)._observe();if(t instanceof m){if(a){return new c(a,t,!0).count}return t.getCount()}if(void 0===t)return 0;throw new Error("count() expects a collection or undefined, but got "+JSON.stringify(t))}getType(...e){let t=this.ref(...e)._observe();return t instanceof m?t.getType():null===t?"null":typeof t}set(...e){let t=e.pop(),n=this.makeRef(...e);n.collection.setIndex(n.idx,t,!0)}merge(...e){let t=e.pop(),n=this.makeRef(...e);n.collection.setIndex(n.idx,t,!1)}delete(...e){let t=this.makeRef(...e);t.collection.setIndex(t.idx,void 0,!0)}push(...e){let t=e.pop(),n=this.makeRef(...e),i=n.collection.rawGet(n.idx);if(void 0===i)i=new v,n.collection.setIndex(n.idx,i,!0);else if(!(i instanceof v))throw new Error("push() is only allowed for an array or undefined (which would become an array)");let r=I(t),s=i.data.length;return i.data.push(r),i.emitChange(s,r,void 0),s}modify(e){this.set(e(this.query({peek:!0})))}ref(...e){let t=this;for(let n=0;n<e.length;n++){let i=t._observe();if(!(i instanceof m)){if(void 0!==i)throw new Error(`Value ${JSON.stringify(i)} is not a collection (nor undefined) in step ${n} of $(${JSON.stringify(e)})`);return new x}t=new Store(i,i.normalizeIndex(e[n]))}return t}makeRef(...e){let t=this;for(let n=0;n<e.length;n++){let i=t.collection.rawGet(t.idx);if(!(i instanceof m)){if(void 0!==i)throw new Error(`Value ${JSON.stringify(i)} is not a collection (nor undefined) in step ${n} of $(${JSON.stringify(e)})`);i=new w,t.collection.rawSet(t.idx,i),t.collection.emitChange(t.idx,i,void 0)}t=new Store(i,i.normalizeIndex(e[n]))}return t}_observe(){return a&&this.collection.addObserver(this.idx,a)&&a.cleaners.push(this),this.collection.rawGet(this.idx)}onEach(...e){let t=q,n=e.pop();if("function"!=typeof e[e.length-1]||"function"!=typeof n&&null!=n||(null!=n&&(t=n),n=e.pop()),"function"!=typeof n)throw new Error("onEach() expects a render function as its last argument but got "+JSON.stringify(n));if(!a)throw new k(!1);let i=this.ref(...e)._observe();if(i instanceof m){let e=new f(a.parentElement,a.lastChild||a.precedingSibling,a.queueOrder+1,i,n,t);i.addObserver(g,e),a.cleaners.push(e),a.lastChild=e,e.renderInitial()}else if(void 0!==i)throw new Error("onEach() attempted on a value that is neither a collection nor undefined")}map(e){let t=new Store(new Map);return this.onEach(n=>{let i=e(n);if(void 0!==i){let e=n.index();t.set(e,i),clean(()=>{t.delete(e)})}}),t}multiMap(e){let t=new Store(new Map);return this.onEach(n=>{let i,r=e(n);if(r.constructor===Object){for(let e in r)t.set(e,r[e]);i=Object.keys(r)}else{if(!(r instanceof Map))return;r.forEach((e,n)=>{t.set(n,e)}),i=[...r.keys()]}i.length&&clean(()=>{for(let e of i)t.delete(e)})}),t}isDetached(){return!1}dump(){let e=this.getType();"array"===e||"object"===e||"map"===e?(text("<"+e+">"),node("ul",()=>{this.onEach(e=>{node("li",()=>{text(JSON.stringify(e.index())+": "),e.dump()})})})):text(JSON.stringify(this.get()))}}class x extends Store{isDetached(){return!0}}let b=!1,S=new WeakMap;function E(e,t){e.classList.add(t),setTimeout(()=>e.remove(),2e3)}export function node(e="",...t){if(!a)throw new k(!0);let n;if(e instanceof Element)n=e;else{let t,i=e.indexOf(".");i>=0&&(t=e.substr(i+1),e=e.substr(0,i)),n=document.createElement(e||"div"),t&&(n.className=t.replaceAll("."," "))}a.addNode(n);for(let e of t){let t=typeof e;if("function"===t){let t=new u(n,void 0,a.queueOrder+1,e);b?(b=!1,t.update(),b=!0):t.update(),a.cleaners.push(t)}else if("string"===t||"number"===t)n.textContent=e;else if("object"===t&&e&&e.constructor===Object)for(let t in e)O(n,t,e[t]);else if(e instanceof Store)C(n,e);else if(null!=e)throw new Error("Unexpected argument "+JSON.stringify(e))}}export function html(e){if(!a||!a.parentElement)throw new k(!0);let t=document.createElement(a.parentElement.tagName);for(t.innerHTML=""+e;t.firstChild;)a.addNode(t.firstChild)}function C(e,t){let n,i,r=e.getAttribute("type"),s=t.query({peek:!0});"checkbox"===r?(void 0===s&&t.set(e.checked),n=t=>e.checked=t,i=()=>t.set(e.checked)):"radio"===r?(void 0===s&&e.checked&&t.set(e.value),n=t=>e.checked=t===e.value,i=()=>{e.checked&&t.set(e.value)}):(i=()=>t.set("number"===r||"range"===r?""===e.value?null:+e.value:e.value),void 0===s&&i(),n=t=>{e.value!==t&&(e.value=t)}),observe(()=>{n(t.get())}),e.addEventListener("input",i),clean(()=>{e.removeEventListener("input",i)})}export function text(e){if(!a)throw new k(!0);null!=e&&a.addNode(document.createTextNode(e))}export function prop(e,t){if(!a||!a.parentElement)throw new k(!0);if("object"==typeof e)for(let t in e)O(a.parentElement,t,e[t]);else O(a.parentElement,e,t)}export function getParentElement(){if(!a||!a.parentElement)throw new k(!0);return a.parentElement}export function clean(e){if(!a)throw new k(!1);a.cleaners.push({_clean:e})}export function observe(e){mount(void 0,e)}export function mount(e,t){let n;e||!a?n=new u(e,void 0,0,t):(n=new u(a.parentElement,a.lastChild||a.precedingSibling,a.queueOrder+1,t),a.lastChild=n),n.update(),a&&a.cleaners.push(n)}export function peek(e){let t=a;a=void 0;try{return e()}finally{a=t}}function O(e,t,n){if("create"===t)b&&("function"==typeof n?n(e):(e.classList.add(n),setTimeout((function(){e.classList.remove(n)}),0)));else if("destroy"===t)S.set(e,n);else if("function"==typeof n)e.addEventListener(t,n),clean(()=>e.removeEventListener(t,n));else if("value"===t||"className"===t||"selectedIndex"===t||!0===n||!1===n)e[t]=n;else if("text"===t)e.textContent=n;else if("class"!==t&&"className"!==t||"object"!=typeof n)"style"===t&&"object"==typeof n?Object.assign(e.style,n):e.setAttribute(t,n);else for(let t in n)n[t]?e.classList.add(t):e.classList.remove(t)}function I(e){if("object"==typeof e&&e){if(e instanceof Store)return e._observe();if(e instanceof Map){let t=new y;return e.forEach((e,n)=>{let i=I(e);void 0!==i&&t.rawSet(n,i)}),t}if(e instanceof Array){let t=new v;for(let n=0;n<e.length;n++){let i=I(e[n]);void 0!==i&&t.rawSet(n,i)}return t}if(e.constructor===Object){let t=new w;for(let n in e){let i=I(e[n]);void 0!==i&&t.rawSet(n,i)}return t}return e}return e}function q(e){return e.index()}function N(e){let t=new Error("Aberdeen internal error "+e);setTimeout(()=>{throw t},0)}function P(e){setTimeout(()=>{throw e},0)}class k extends Error{constructor(e){super(`Operation not permitted outside of ${e?"a mount":"an observe"}() scope`)}}function R(e){const t=e.parentElement?getComputedStyle(e.parentElement):{};return"flex"===t.display&&(t.flexDirection||"").startsWith("row")?{marginLeft:`-${e.offsetWidth/2}px`,marginRight:`-${e.offsetWidth/2}px`,transform:"scaleX(0)"}:{marginBottom:`-${e.offsetHeight/2}px`,marginTop:`-${e.offsetHeight/2}px`,transform:"scaleY(0)"}}export function grow(e){scheduleDomReader(()=>{let t=R(e);scheduleDomWriter(()=>{Object.assign(e.style,t),scheduleDomReader(()=>{e.offsetHeight,scheduleDomWriter(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out";for(let n in t)e.style[n]="";setTimeout(()=>{e.style.transition=""},400)})})})})}export function shrink(e){scheduleDomReader(()=>{const t=R(e);scheduleDomWriter(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out",Object.assign(e.style,t),setTimeout(()=>e.remove(),400)})})}function j(e){if(t)throw new Error("already recording a patch");t=new Map;try{e()}catch(e){throw t=void 0,e}const n=t;return t=void 0,n}function M(e,t,n,i,r){let s=e.get(t);null==s&&(s=new Map,e.set(t,s));let o=s.get(n);s.set(n,[i,null==o?r:o[1]])}function T(e){for(let[t,n]of e)for(let[e,[i,r]]of n)t.emitChange(e,i,r)}function D(e,t,n=!1){for(let[i,r]of t)for(let[t,[s,o]]of r)M(e,i,t,n?o:s,n?s:o)}function $(e,t=!1){for(let[n,i]of e)for(let[e,[r,s]]of i){let i=n.rawGet(e);if(i!==s){if(!t)return!1;P(new Error(`Applying invalid patch: data ${i} is unequal to expected old data ${s} for index ${e}`))}}for(let[t,n]of e)for(let[e,[i,r]]of n)t.rawSet(e,i);return!0}const _=[];export function applyPrediction(e){let t=j(e);return _.push(t),T(t),t}export function applyCanon(e,t=[]){let n=new Map;for(let e of _)D(n,e,!0);$(n,!0);for(let e of t){let t=_.indexOf(e);t>=0&&_.splice(t,1)}e&&D(n,j(e));for(let e=0;e<_.length;e++)$(_[e])?D(n,_[e]):(_.splice(e,1),e--);T(n)}String.prototype.replaceAll||(String.prototype.replaceAll=function(e,t){return this.split(e).join(t)});
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aberdeen",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A TypeScript/JavaScript library for quickly building performant declarative user interfaces without the use of a virtual DOM.",
|
|
5
5
|
"main": "dist/aberdeen.js",
|
|
6
6
|
"types": "dist/aberdeen.d.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "$npm_execpath run build-dist && $npm_execpath run build-docs",
|
|
9
|
-
"build-dist": "tsc && terser --module --compress --mangle -- dist/aberdeen.js > dist/aberdeen.min.js",
|
|
9
|
+
"build-dist": "rm -f dist/aberdeen.js && tsc && chmod a-w dist/aberdeen.js && terser --module --compress --mangle -- dist/aberdeen.js > dist/aberdeen.min.js",
|
|
10
10
|
"build-docs": "typedoc --excludePrivate --excludeInternal src/aberdeen.ts",
|
|
11
|
-
"build-test": "[ tests/build/aberdeen.js -nt src/aberdeen.ts ] || tsc -t ES2015 -m commonjs --outdir tests/build src/aberdeen.ts",
|
|
11
|
+
"build-test": "[ tests/build/aberdeen.js -nt src/aberdeen.ts ] || ( rm -f tests/build/aberdeen.js && tsc -t ES2015 -m commonjs --outdir tests/build src/aberdeen.ts && chmod a-w tests/build/aberdeen.js )",
|
|
12
12
|
"coverage": "$npm_execpath run build-test && nyc mocha --file tests/_init.js tests/[^_]*.js",
|
|
13
13
|
"test": "$npm_execpath run build-test && mocha --file tests/_init.js tests/[^_]*.js",
|
|
14
14
|
"prepack": "$npm_execpath run test && $npm_execpath run build"
|