aberdeen 0.0.12 → 0.0.14
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 +7 -0
- package/dist/aberdeen.d.ts +185 -52
- package/dist/aberdeen.js +189 -89
- package/dist/aberdeen.min.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,3 +5,10 @@ A TypeScript/JavaScript library for quickly building performant declarative user
|
|
|
5
5
|
|
|
6
6
|
The key insight is the use of many small anonymous functions, that will automatically rerun when the underlying data changes. In order to trigger updates, that data should be encapsulated in any number of `Store` objects. They can hold anything, from simple values to deeply nested data structures, in which case user-interface functions can (automatically) subscribe to just the parts they depend upon.
|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
## Why use Aberdeen?
|
|
10
|
+
|
|
11
|
+
- It provides a flexible and simple to understand model for reactive user-interface building.
|
|
12
|
+
- It allows you to express user-interfaces in plain JavaScript (or TypeScript) in an easy to read form, without (JSX-like) compilation steps.
|
|
13
|
+
- It's fast, as it doesn't use a *virtual DOM* and only reruns small pieces of code in response to updated data.
|
|
14
|
+
- It's lightweight, at less than 15kb when minimized.
|
package/dist/aberdeen.d.ts
CHANGED
|
@@ -96,9 +96,29 @@ declare class ObsMap extends ObsCollection {
|
|
|
96
96
|
normalizeIndex(index: any): any;
|
|
97
97
|
getCount(): number;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* A data store that automatically subscribes the current scope to updates
|
|
101
|
+
* whenever data is read from it.
|
|
102
|
+
*
|
|
103
|
+
* Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
|
|
104
|
+
* `Array`, `object` and `Map`. The latter three will always have `Store` objects as
|
|
105
|
+
* values, creating a tree of `Store`s.
|
|
106
|
+
*/
|
|
99
107
|
export declare class Store {
|
|
100
108
|
private collection;
|
|
101
109
|
private idx;
|
|
110
|
+
/**
|
|
111
|
+
* Create a new store with the given `value` as its value. Defaults to `undefined` if no value is given.
|
|
112
|
+
* 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 [[`get`]] on the store will recreate the original data strucure, though.)
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```
|
|
117
|
+
* let emptyStore = new Store()
|
|
118
|
+
* let numStore = new Store(42)
|
|
119
|
+
* let objStore = new Store({x: {alice: 1, bob: 2}, y: [9,7,5,3,1]})
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
102
122
|
constructor();
|
|
103
123
|
constructor(value: any);
|
|
104
124
|
/** @internal */
|
|
@@ -127,56 +147,67 @@ export declare class Store {
|
|
|
127
147
|
* @example
|
|
128
148
|
* ```
|
|
129
149
|
* let store = new Store({a: {b: {c: {d: 42}}}})
|
|
150
|
+
* assert('a' in store.get())
|
|
130
151
|
* assert(store.get('a', 'b') === {c: {d: 42}})
|
|
131
152
|
* ```
|
|
132
153
|
*/
|
|
133
|
-
get(...path: any): any;
|
|
154
|
+
get(...path: any[]): any;
|
|
134
155
|
/**
|
|
135
|
-
*
|
|
156
|
+
* Like [[`get`]], but doesn't subscribe to changes.
|
|
136
157
|
*/
|
|
137
|
-
peek(...path: any): any;
|
|
158
|
+
peek(...path: any[]): any;
|
|
138
159
|
/**
|
|
139
160
|
* @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `number`.
|
|
140
161
|
* Using this instead of just [[`get`]] is especially useful from within TypeScript.
|
|
141
162
|
*/
|
|
142
|
-
getNumber(...path: any): number;
|
|
163
|
+
getNumber(...path: any[]): number;
|
|
143
164
|
/**
|
|
144
165
|
* @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `string`.
|
|
145
166
|
* Using this instead of just [[`get`]] is especially useful from within TypeScript.
|
|
146
167
|
*/
|
|
147
|
-
getString(...path: any): string;
|
|
168
|
+
getString(...path: any[]): string;
|
|
148
169
|
/**
|
|
149
170
|
* @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `boolean`.
|
|
150
171
|
* Using this instead of just [[`get`]] is especially useful from within TypeScript.
|
|
151
172
|
*/
|
|
152
|
-
getBoolean(...path: any): boolean;
|
|
173
|
+
getBoolean(...path: any[]): boolean;
|
|
153
174
|
/**
|
|
154
175
|
* @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `function`.
|
|
155
176
|
* Using this instead of just [[`get`]] is especially useful from within TypeScript.
|
|
156
177
|
*/
|
|
157
|
-
getFunction(...path: any): (Function);
|
|
178
|
+
getFunction(...path: any[]): (Function);
|
|
158
179
|
/**
|
|
159
180
|
* @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `array`.
|
|
160
181
|
* Using this instead of just [[`get`]] is especially useful from within TypeScript.
|
|
161
182
|
*/
|
|
162
|
-
getArray(...path: any): any[];
|
|
183
|
+
getArray(...path: any[]): any[];
|
|
163
184
|
/**
|
|
164
185
|
* @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `object`.
|
|
165
186
|
* Using this instead of just [[`get`]] is especially useful from within TypeScript.
|
|
166
187
|
*/
|
|
167
|
-
getObject(...path: any): object;
|
|
188
|
+
getObject(...path: any[]): object;
|
|
168
189
|
/**
|
|
169
190
|
* @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `map`.
|
|
170
191
|
* Using this instead of just [[`get`]] is especially useful from within TypeScript.
|
|
171
192
|
*/
|
|
172
|
-
getMap(...path: any): Map<any, any>;
|
|
193
|
+
getMap(...path: any[]): Map<any, any>;
|
|
173
194
|
/**
|
|
174
|
-
*
|
|
195
|
+
* Like [[`get`]], but the first parameter is the default value (returned when the Store
|
|
175
196
|
* contains `undefined`). This default value is also used to determine the expected type,
|
|
176
197
|
* and to throw otherwise.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```
|
|
201
|
+
* let store = {x: 42}
|
|
202
|
+
* assert(getOr(99, 'x') == 42)
|
|
203
|
+
* assert(getOr(99, 'y') == 99)
|
|
204
|
+
* getOr('hello', x') # throws TypeError (because 42 is not a string)
|
|
205
|
+
* ```
|
|
177
206
|
*/
|
|
178
|
-
getOr<T>(defaultValue: T, ...path: any): T;
|
|
179
|
-
/** Retrieve a value. This is a more flexible
|
|
207
|
+
getOr<T>(defaultValue: T, ...path: any[]): T;
|
|
208
|
+
/** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
|
|
209
|
+
* form of the [[`get`]] and [[`peek`]] methods.
|
|
210
|
+
*
|
|
180
211
|
* @returns The resulting value, or `undefined` if the `path` does not exist.
|
|
181
212
|
*/
|
|
182
213
|
query(opts: {
|
|
@@ -189,6 +220,12 @@ export declare class Store {
|
|
|
189
220
|
* is done.
|
|
190
221
|
*/
|
|
191
222
|
type?: string;
|
|
223
|
+
/** Limit the depth of the retrieved data structure to this positive integer.
|
|
224
|
+
* When `depth` is `1`, only a single level of the value at `path` is unpacked. This
|
|
225
|
+
* makes no difference for primitive values (like strings), but for objects, maps and
|
|
226
|
+
* arrays, it means that each *value* in the resulting data structure will be a
|
|
227
|
+
* reference to the `Store` for that value.
|
|
228
|
+
*/
|
|
192
229
|
depth?: number;
|
|
193
230
|
/** Return this value when the `path` does not exist. Defaults to `undefined`. */
|
|
194
231
|
defaultValue?: any;
|
|
@@ -198,33 +235,110 @@ export declare class Store {
|
|
|
198
235
|
*/
|
|
199
236
|
peek?: boolean;
|
|
200
237
|
}): any;
|
|
201
|
-
isEmpty(...path: any): boolean;
|
|
202
|
-
count(...path: any): number;
|
|
203
238
|
/**
|
|
204
|
-
*
|
|
239
|
+
* Checks if the specified collection is empty, and subscribes the current scope to changes of the emptiness of this collection.
|
|
240
|
+
*
|
|
241
|
+
* @param path Any path terms to resolve before retrieving the value.
|
|
242
|
+
* @returns When the specified collection is not empty `true` is returned. If it is empty or if the value is undefined, `false` is returned.
|
|
243
|
+
* @throws When the value is not a collection and not undefined, an Error will be thrown.
|
|
244
|
+
*/
|
|
245
|
+
isEmpty(...path: any[]): boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Returns the number of items in the specified collection, and subscribes the current scope to changes in this count.
|
|
248
|
+
*
|
|
249
|
+
* @param path Any path terms to resolve before retrieving the value.
|
|
250
|
+
* @returns The number of items contained in the collection, or 0 if the value is undefined.
|
|
251
|
+
* @throws When the value is not a collection and not undefined, an Error will be thrown.
|
|
252
|
+
*/
|
|
253
|
+
count(...path: any[]): number;
|
|
254
|
+
/**
|
|
255
|
+
* Returns a strings describing the type of the store value, subscribing to changes of this type.
|
|
256
|
+
* Note: this currently also subscribes to changes of primitive values, so changing a value from 3 to 4
|
|
257
|
+
* would cause the scope to be rerun. This is not great, and may change in the future. This caveat does
|
|
258
|
+
* not apply to changes made *inside* an object, `Array` or `Map`.
|
|
259
|
+
*
|
|
260
|
+
* @param path Any path terms to resolve before retrieving the value.
|
|
261
|
+
* @returns Possible options: "undefined", "null", "boolean", "number", "string", "function", "array", "map" or "object".
|
|
205
262
|
*/
|
|
206
|
-
getType(...path: any): string;
|
|
263
|
+
getType(...path: any[]): string;
|
|
207
264
|
/**
|
|
208
|
-
* Sets the
|
|
209
|
-
* resolved/created using `makeRef
|
|
265
|
+
* Sets the value to the last given argument. Any earlier argument are a Store-path that is first
|
|
266
|
+
* resolved/created using [[`makeRef`]].
|
|
267
|
+
*
|
|
268
|
+
* When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
|
|
269
|
+
* case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
|
|
270
|
+
* be created, so that changes made through one `Store` will be reflected through the other. Be
|
|
271
|
+
* carefull not to create loops in your `Store` tree that way, as that would cause any future
|
|
272
|
+
* call to [[`get`]] to throw a `RangeError` (Maximum call stack size exceeded.)
|
|
273
|
+
*
|
|
274
|
+
* If you intent to make a copy instead of a reference, call [[`get`]] on the origin `Store`.
|
|
275
|
+
*
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```
|
|
279
|
+
* let store = new Store() // Value is `undefined`
|
|
280
|
+
*
|
|
281
|
+
* store.set('x', 6) // Causes the store to become an object
|
|
282
|
+
* assert(store.get() == {x: 6})
|
|
283
|
+
*
|
|
284
|
+
* store.set('a', 'b', 'c', 'd') // Create parent path as objects
|
|
285
|
+
* assert(store.get() == {x: 6, a: {b: {c: 'd'}}})
|
|
286
|
+
*
|
|
287
|
+
* store.set(42) // Overwrites all of the above
|
|
288
|
+
* assert(store.get() == 42)
|
|
289
|
+
*
|
|
290
|
+
* store.set('x', 6) // Throw Error (42 is not a collection)
|
|
291
|
+
* ```
|
|
210
292
|
*/
|
|
211
|
-
set(...pathAndValue: any): void;
|
|
293
|
+
set(...pathAndValue: any[]): void;
|
|
212
294
|
/**
|
|
213
295
|
* Sets the `Store` to the given `mergeValue`, but without deleting any pre-existing
|
|
214
296
|
* items when a collection overwrites a similarly typed collection. This results in
|
|
215
297
|
* a deep merge.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```
|
|
301
|
+
* let store = new Store({a: {x: 1}})
|
|
302
|
+
* store.merge({a: {y: 2}, b: 3})
|
|
303
|
+
* assert(store.get() == {a: {x: 1, y: 2}, b: 3})
|
|
304
|
+
* ```
|
|
216
305
|
*/
|
|
217
|
-
merge(
|
|
306
|
+
merge(...pathAndValue: any): void;
|
|
218
307
|
/**
|
|
219
308
|
* Sets the value for the store to `undefined`, which causes it to be omitted from the map (or array, if it's at the end)
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```
|
|
312
|
+
* let store = new Store({a: 1, b: 2})
|
|
313
|
+
* store.delete('a')
|
|
314
|
+
* assert(store.get() == {b: 2})
|
|
315
|
+
*
|
|
316
|
+
* store = new Store(['a','b','c'])
|
|
317
|
+
* store.delete(1)
|
|
318
|
+
* assert(store.get() == ['a', undefined, 'c'])
|
|
319
|
+
* store.delete(2)
|
|
320
|
+
* assert(store.get() == ['a'])
|
|
321
|
+
* ```
|
|
220
322
|
*/
|
|
221
323
|
delete(...path: any): void;
|
|
222
324
|
/**
|
|
223
325
|
* Pushes a value to the end of the Array that is at the specified path in the store.
|
|
224
|
-
* If that
|
|
326
|
+
* If that store path is `undefined`, an Array is created first.
|
|
225
327
|
* The last argument is the value to be added, any earlier arguments indicate the path.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```
|
|
331
|
+
* let store = new Store()
|
|
332
|
+
* store.push(3) // Creates the array
|
|
333
|
+
* store.push(6)
|
|
334
|
+
* assert(store.get() == [3,6])
|
|
335
|
+
*
|
|
336
|
+
* store = new Store({myArray: [1,2]})
|
|
337
|
+
* store.push('myArray', 3)
|
|
338
|
+
* assert(store.get() == {myArray: [1,2,3]})
|
|
339
|
+
* ```
|
|
226
340
|
*/
|
|
227
|
-
push(
|
|
341
|
+
push(...pathAndValue: any[]): number;
|
|
228
342
|
/**
|
|
229
343
|
* [[`peek`]] the current value, pass it through `func`, and [[`set`]] the resulting
|
|
230
344
|
* value.
|
|
@@ -246,10 +360,32 @@ export declare class Store {
|
|
|
246
360
|
* type.
|
|
247
361
|
* Unlike `ref`, `makeRef` does *not* subscribe to the path levels, as it is intended to be
|
|
248
362
|
* a write-only operation.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```
|
|
366
|
+
* let store = new Store() // Value is `undefined`
|
|
367
|
+
*
|
|
368
|
+
* let ref = store.makeRef('a', 'b', 'c')
|
|
369
|
+
* assert(store.get() == {a: {b: {}}}
|
|
370
|
+
*
|
|
371
|
+
* ref.set(42)
|
|
372
|
+
* assert(store.get() == {a: {b: {c: 42}}}
|
|
373
|
+
*
|
|
374
|
+
* ref.makeRef('d') // Throw Error (42 is not a collection)
|
|
375
|
+
* ```
|
|
249
376
|
*/
|
|
250
377
|
makeRef(...path: any[]): Store;
|
|
251
378
|
/** @Internal */
|
|
252
379
|
_observe(): DatumType;
|
|
380
|
+
/**
|
|
381
|
+
* Iterate the specified collection (Array, Map or object), running the given code block for each item.
|
|
382
|
+
* 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 [[`clean`]] handlers left by its code block are executed.
|
|
384
|
+
*
|
|
385
|
+
*
|
|
386
|
+
*
|
|
387
|
+
* @param pathAndFuncs
|
|
388
|
+
*/
|
|
253
389
|
onEach(...pathAndFuncs: any): void;
|
|
254
390
|
/**
|
|
255
391
|
* Applies a filter/map function on each item within the `Store`'s collection,
|
|
@@ -266,6 +402,22 @@ export declare class Store {
|
|
|
266
402
|
* input items produce the same output keys, this may lead to unexpected results.
|
|
267
403
|
*/
|
|
268
404
|
map(func: (store: Store) => any): Store;
|
|
405
|
+
/**
|
|
406
|
+
* Applies a filter/map function on each item within the `Store`'s collection,
|
|
407
|
+
* each of which can deliver any number of key/value pairs, and reactively manages the
|
|
408
|
+
* returned map `Store` to hold any results.
|
|
409
|
+
*
|
|
410
|
+
* @param func - Function that transform the given store into output values
|
|
411
|
+
* that can take one of the following forms:
|
|
412
|
+
* - an `Object` or a `Map`: Each key/value pair will be added to the output `Store`.
|
|
413
|
+
* - anything else: No key/value pairs are added to the output `Store`.
|
|
414
|
+
*
|
|
415
|
+
* @returns - A map `Store` with the key/value pairs returned by all `func` invocations.
|
|
416
|
+
*
|
|
417
|
+
* When items disappear from the `Store` or are changed in a way that `func` depends
|
|
418
|
+
* upon, the resulting items are removed from the output `Store` as well. When multiple
|
|
419
|
+
* input items produce the same output keys, this may lead to unexpected results.
|
|
420
|
+
*/
|
|
269
421
|
multiMap(func: (store: Store) => any): Store;
|
|
270
422
|
/**
|
|
271
423
|
* @returns Returns `true` when the `Store` was created by [[`ref`]]ing a path that
|
|
@@ -275,13 +427,13 @@ export declare class Store {
|
|
|
275
427
|
dump(): void;
|
|
276
428
|
}
|
|
277
429
|
/**
|
|
278
|
-
* Create a new DOM element.
|
|
430
|
+
* Create a new DOM element, and insert it into the DOM at the position held by the current scope.
|
|
279
431
|
* @param tag - The tag of the element to be created and optionally dot-separated class names. For example: `h1` or `p.intro.has_avatar`.
|
|
280
432
|
* @param rest - The other arguments are flexible and interpreted based on their types:
|
|
281
433
|
* - `string`: Used as textContent for the element.
|
|
282
|
-
* - `object`: Used as attributes
|
|
434
|
+
* - `object`: Used as attributes, properties or event listeners for the element. See [[`prop`]] on how the distinction is made.
|
|
283
435
|
* - `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.
|
|
284
|
-
* - `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`. After that, the `Store` will be updated when the input changes.
|
|
436
|
+
* - `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.
|
|
285
437
|
* @example
|
|
286
438
|
* node('aside.editorial', 'Yada yada yada....', () => {
|
|
287
439
|
* node('a', {href: '/bio'}, () => {
|
|
@@ -290,6 +442,11 @@ export declare class Store {
|
|
|
290
442
|
* })
|
|
291
443
|
*/
|
|
292
444
|
export declare function node(tag?: string | Element, ...rest: any[]): void;
|
|
445
|
+
/**
|
|
446
|
+
* Convert an HTML string to one or more DOM elements, and add them to the current DOM scope.
|
|
447
|
+
* @param html - The HTML string. For example `"<section><h2>Test</h2><p>Info..</p></section>"`.
|
|
448
|
+
*/
|
|
449
|
+
export declare function html(html: string): void;
|
|
293
450
|
/**
|
|
294
451
|
* Add a text node at the current Scope position.
|
|
295
452
|
*/
|
|
@@ -317,29 +474,6 @@ export declare function getParentElement(): Element;
|
|
|
317
474
|
* @param clean - The function to be executed.
|
|
318
475
|
*/
|
|
319
476
|
export declare function clean(clean: (scope: Scope) => void): void;
|
|
320
|
-
/**
|
|
321
|
-
* Create a new reactive scope and execute the `func` within that scope. When
|
|
322
|
-
* `Store`s that the `func` reads are updated, only this scope will need to be refreshed,
|
|
323
|
-
* leaving the parent scope untouched.
|
|
324
|
-
*
|
|
325
|
-
* In case this function is called outside of a an existing scope, it will create a new
|
|
326
|
-
* top-level scope (a [[`Mount`]]) without a `parentElement`, meaning that aberdeen operations
|
|
327
|
-
* that create/modify DOM elements are not permitted.
|
|
328
|
-
* @param func - The function to be (repeatedly) executed within the newly created scope.
|
|
329
|
-
* @returns The newly created `Mount` object in case this is a top-level reactive scope.
|
|
330
|
-
* @example
|
|
331
|
-
* ```
|
|
332
|
-
* let store = new Store('John Doe')
|
|
333
|
-
* mount(document.body, () => {
|
|
334
|
-
* node('div.card', () => {
|
|
335
|
-
* node('input', {placeholder: 'Name'}, store)
|
|
336
|
-
* observe(() => {
|
|
337
|
-
* prop('class', {correct: store.get().length > 5})
|
|
338
|
-
* })
|
|
339
|
-
* })
|
|
340
|
-
* })
|
|
341
|
-
* ```
|
|
342
|
-
*/
|
|
343
477
|
/**
|
|
344
478
|
* Reactively run a function, meaning the function will rerun when any `Store` that was read
|
|
345
479
|
* during its execution is updated.
|
|
@@ -415,9 +549,8 @@ export declare function mount(parentElement: Element | undefined, func: () => vo
|
|
|
415
549
|
*
|
|
416
550
|
* mount(document.body, () => {
|
|
417
551
|
* // Prevent rerender when store changes
|
|
418
|
-
* peek(() => {
|
|
419
|
-
*
|
|
420
|
-
* })
|
|
552
|
+
* let msg = peek(() => `Store has ${store.count()} elements, and the first is ${store.get(0)}`))
|
|
553
|
+
* text(msg)
|
|
421
554
|
* })
|
|
422
555
|
* ```
|
|
423
556
|
*
|
package/dist/aberdeen.js
CHANGED
|
@@ -89,6 +89,8 @@ class Scope {
|
|
|
89
89
|
// The list of clean functions to be called when this scope is cleaned. These can
|
|
90
90
|
// be for child scopes, subscriptions as well as `clean(..)` hooks.
|
|
91
91
|
this.cleaners = [];
|
|
92
|
+
// Set to true after the scope has been cleaned, causing any spurious reruns to
|
|
93
|
+
// be ignored.
|
|
92
94
|
this.isDead = false;
|
|
93
95
|
this.parentElement = parentElement;
|
|
94
96
|
this.precedingSibling = precedingSibling;
|
|
@@ -631,16 +633,14 @@ class ObsObject extends ObsMap {
|
|
|
631
633
|
return cnt;
|
|
632
634
|
}
|
|
633
635
|
}
|
|
634
|
-
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
|
|
642
|
-
* Map values become separate `Store`s themselves.
|
|
643
|
-
*/
|
|
636
|
+
/**
|
|
637
|
+
* A data store that automatically subscribes the current scope to updates
|
|
638
|
+
* whenever data is read from it.
|
|
639
|
+
*
|
|
640
|
+
* Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
|
|
641
|
+
* `Array`, `object` and `Map`. The latter three will always have `Store` objects as
|
|
642
|
+
* values, creating a tree of `Store`s.
|
|
643
|
+
*/
|
|
644
644
|
export class Store {
|
|
645
645
|
constructor(value = undefined, index = undefined) {
|
|
646
646
|
if (index === undefined) {
|
|
@@ -686,6 +686,7 @@ export class Store {
|
|
|
686
686
|
* @example
|
|
687
687
|
* ```
|
|
688
688
|
* let store = new Store({a: {b: {c: {d: 42}}}})
|
|
689
|
+
* assert('a' in store.get())
|
|
689
690
|
* assert(store.get('a', 'b') === {c: {d: 42}})
|
|
690
691
|
* ```
|
|
691
692
|
*/
|
|
@@ -693,7 +694,7 @@ export class Store {
|
|
|
693
694
|
return this.query({ path });
|
|
694
695
|
}
|
|
695
696
|
/**
|
|
696
|
-
*
|
|
697
|
+
* Like [[`get`]], but doesn't subscribe to changes.
|
|
697
698
|
*/
|
|
698
699
|
peek(...path) {
|
|
699
700
|
return this.query({ path, peek: true });
|
|
@@ -734,9 +735,17 @@ export class Store {
|
|
|
734
735
|
*/
|
|
735
736
|
getMap(...path) { return this.query({ path, type: 'map' }); }
|
|
736
737
|
/**
|
|
737
|
-
*
|
|
738
|
+
* Like [[`get`]], but the first parameter is the default value (returned when the Store
|
|
738
739
|
* contains `undefined`). This default value is also used to determine the expected type,
|
|
739
740
|
* and to throw otherwise.
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* ```
|
|
744
|
+
* let store = {x: 42}
|
|
745
|
+
* assert(getOr(99, 'x') == 42)
|
|
746
|
+
* assert(getOr(99, 'y') == 99)
|
|
747
|
+
* getOr('hello', x') # throws TypeError (because 42 is not a string)
|
|
748
|
+
* ```
|
|
740
749
|
*/
|
|
741
750
|
getOr(defaultValue, ...path) {
|
|
742
751
|
let type = typeof defaultValue;
|
|
@@ -748,7 +757,9 @@ export class Store {
|
|
|
748
757
|
}
|
|
749
758
|
return this.query({ type, defaultValue, path });
|
|
750
759
|
}
|
|
751
|
-
/** Retrieve a value. This is a more flexible
|
|
760
|
+
/** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
|
|
761
|
+
* form of the [[`get`]] and [[`peek`]] methods.
|
|
762
|
+
*
|
|
752
763
|
* @returns The resulting value, or `undefined` if the `path` does not exist.
|
|
753
764
|
*/
|
|
754
765
|
query(opts) {
|
|
@@ -771,6 +782,13 @@ export class Store {
|
|
|
771
782
|
}
|
|
772
783
|
return value === undefined ? opts.defaultValue : value;
|
|
773
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* Checks if the specified collection is empty, and subscribes the current scope to changes of the emptiness of this collection.
|
|
787
|
+
*
|
|
788
|
+
* @param path Any path terms to resolve before retrieving the value.
|
|
789
|
+
* @returns When the specified collection is not empty `true` is returned. If it is empty or if the value is undefined, `false` is returned.
|
|
790
|
+
* @throws When the value is not a collection and not undefined, an Error will be thrown.
|
|
791
|
+
*/
|
|
774
792
|
isEmpty(...path) {
|
|
775
793
|
let store = this.ref(...path);
|
|
776
794
|
let value = store._observe();
|
|
@@ -790,6 +808,13 @@ export class Store {
|
|
|
790
808
|
throw new Error(`isEmpty() expects a collection or undefined, but got ${JSON.stringify(value)}`);
|
|
791
809
|
}
|
|
792
810
|
}
|
|
811
|
+
/**
|
|
812
|
+
* Returns the number of items in the specified collection, and subscribes the current scope to changes in this count.
|
|
813
|
+
*
|
|
814
|
+
* @param path Any path terms to resolve before retrieving the value.
|
|
815
|
+
* @returns The number of items contained in the collection, or 0 if the value is undefined.
|
|
816
|
+
* @throws When the value is not a collection and not undefined, an Error will be thrown.
|
|
817
|
+
*/
|
|
793
818
|
count(...path) {
|
|
794
819
|
let store = this.ref(...path);
|
|
795
820
|
let value = store._observe();
|
|
@@ -810,7 +835,13 @@ export class Store {
|
|
|
810
835
|
}
|
|
811
836
|
}
|
|
812
837
|
/**
|
|
813
|
-
* Returns
|
|
838
|
+
* Returns a strings describing the type of the store value, subscribing to changes of this type.
|
|
839
|
+
* Note: this currently also subscribes to changes of primitive values, so changing a value from 3 to 4
|
|
840
|
+
* would cause the scope to be rerun. This is not great, and may change in the future. This caveat does
|
|
841
|
+
* not apply to changes made *inside* an object, `Array` or `Map`.
|
|
842
|
+
*
|
|
843
|
+
* @param path Any path terms to resolve before retrieving the value.
|
|
844
|
+
* @returns Possible options: "undefined", "null", "boolean", "number", "string", "function", "array", "map" or "object".
|
|
814
845
|
*/
|
|
815
846
|
getType(...path) {
|
|
816
847
|
let store = this.ref(...path);
|
|
@@ -818,8 +849,33 @@ export class Store {
|
|
|
818
849
|
return (value instanceof ObsCollection) ? value.getType() : (value === null ? "null" : typeof value);
|
|
819
850
|
}
|
|
820
851
|
/**
|
|
821
|
-
* Sets the
|
|
822
|
-
* resolved/created using `makeRef
|
|
852
|
+
* Sets the value to the last given argument. Any earlier argument are a Store-path that is first
|
|
853
|
+
* resolved/created using [[`makeRef`]].
|
|
854
|
+
*
|
|
855
|
+
* When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
|
|
856
|
+
* case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
|
|
857
|
+
* be created, so that changes made through one `Store` will be reflected through the other. Be
|
|
858
|
+
* carefull not to create loops in your `Store` tree that way, as that would cause any future
|
|
859
|
+
* call to [[`get`]] to throw a `RangeError` (Maximum call stack size exceeded.)
|
|
860
|
+
*
|
|
861
|
+
* If you intent to make a copy instead of a reference, call [[`get`]] on the origin `Store`.
|
|
862
|
+
*
|
|
863
|
+
*
|
|
864
|
+
* @example
|
|
865
|
+
* ```
|
|
866
|
+
* let store = new Store() // Value is `undefined`
|
|
867
|
+
*
|
|
868
|
+
* store.set('x', 6) // Causes the store to become an object
|
|
869
|
+
* assert(store.get() == {x: 6})
|
|
870
|
+
*
|
|
871
|
+
* store.set('a', 'b', 'c', 'd') // Create parent path as objects
|
|
872
|
+
* assert(store.get() == {x: 6, a: {b: {c: 'd'}}})
|
|
873
|
+
*
|
|
874
|
+
* store.set(42) // Overwrites all of the above
|
|
875
|
+
* assert(store.get() == 42)
|
|
876
|
+
*
|
|
877
|
+
* store.set('x', 6) // Throw Error (42 is not a collection)
|
|
878
|
+
* ```
|
|
823
879
|
*/
|
|
824
880
|
set(...pathAndValue) {
|
|
825
881
|
let newValue = pathAndValue.pop();
|
|
@@ -830,12 +886,34 @@ export class Store {
|
|
|
830
886
|
* Sets the `Store` to the given `mergeValue`, but without deleting any pre-existing
|
|
831
887
|
* items when a collection overwrites a similarly typed collection. This results in
|
|
832
888
|
* a deep merge.
|
|
889
|
+
*
|
|
890
|
+
* @example
|
|
891
|
+
* ```
|
|
892
|
+
* let store = new Store({a: {x: 1}})
|
|
893
|
+
* store.merge({a: {y: 2}, b: 3})
|
|
894
|
+
* assert(store.get() == {a: {x: 1, y: 2}, b: 3})
|
|
895
|
+
* ```
|
|
833
896
|
*/
|
|
834
|
-
merge(
|
|
835
|
-
|
|
897
|
+
merge(...pathAndValue) {
|
|
898
|
+
let mergeValue = pathAndValue.pop();
|
|
899
|
+
let store = this.makeRef(...pathAndValue);
|
|
900
|
+
store.collection.setIndex(store.idx, mergeValue, false);
|
|
836
901
|
}
|
|
837
902
|
/**
|
|
838
903
|
* Sets the value for the store to `undefined`, which causes it to be omitted from the map (or array, if it's at the end)
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* ```
|
|
907
|
+
* let store = new Store({a: 1, b: 2})
|
|
908
|
+
* store.delete('a')
|
|
909
|
+
* assert(store.get() == {b: 2})
|
|
910
|
+
*
|
|
911
|
+
* store = new Store(['a','b','c'])
|
|
912
|
+
* store.delete(1)
|
|
913
|
+
* assert(store.get() == ['a', undefined, 'c'])
|
|
914
|
+
* store.delete(2)
|
|
915
|
+
* assert(store.get() == ['a'])
|
|
916
|
+
* ```
|
|
839
917
|
*/
|
|
840
918
|
delete(...path) {
|
|
841
919
|
let store = this.makeRef(...path);
|
|
@@ -843,14 +921,28 @@ export class Store {
|
|
|
843
921
|
}
|
|
844
922
|
/**
|
|
845
923
|
* Pushes a value to the end of the Array that is at the specified path in the store.
|
|
846
|
-
* If that
|
|
924
|
+
* If that store path is `undefined`, an Array is created first.
|
|
847
925
|
* The last argument is the value to be added, any earlier arguments indicate the path.
|
|
926
|
+
*
|
|
927
|
+
* @example
|
|
928
|
+
* ```
|
|
929
|
+
* let store = new Store()
|
|
930
|
+
* store.push(3) // Creates the array
|
|
931
|
+
* store.push(6)
|
|
932
|
+
* assert(store.get() == [3,6])
|
|
933
|
+
*
|
|
934
|
+
* store = new Store({myArray: [1,2]})
|
|
935
|
+
* store.push('myArray', 3)
|
|
936
|
+
* assert(store.get() == {myArray: [1,2,3]})
|
|
937
|
+
* ```
|
|
848
938
|
*/
|
|
849
|
-
push(
|
|
850
|
-
let
|
|
939
|
+
push(...pathAndValue) {
|
|
940
|
+
let newValue = pathAndValue.pop();
|
|
941
|
+
let store = this.makeRef(...pathAndValue);
|
|
942
|
+
let obsArray = store.collection.rawGet(store.idx);
|
|
851
943
|
if (obsArray === undefined) {
|
|
852
944
|
obsArray = new ObsArray();
|
|
853
|
-
|
|
945
|
+
store.collection.setIndex(store.idx, obsArray, true);
|
|
854
946
|
}
|
|
855
947
|
else if (!(obsArray instanceof ObsArray)) {
|
|
856
948
|
throw new Error(`push() is only allowed for an array or undefined (which would become an array)`);
|
|
@@ -898,6 +990,19 @@ export class Store {
|
|
|
898
990
|
* type.
|
|
899
991
|
* Unlike `ref`, `makeRef` does *not* subscribe to the path levels, as it is intended to be
|
|
900
992
|
* a write-only operation.
|
|
993
|
+
*
|
|
994
|
+
* @example
|
|
995
|
+
* ```
|
|
996
|
+
* let store = new Store() // Value is `undefined`
|
|
997
|
+
*
|
|
998
|
+
* let ref = store.makeRef('a', 'b', 'c')
|
|
999
|
+
* assert(store.get() == {a: {b: {}}}
|
|
1000
|
+
*
|
|
1001
|
+
* ref.set(42)
|
|
1002
|
+
* assert(store.get() == {a: {b: {c: 42}}}
|
|
1003
|
+
*
|
|
1004
|
+
* ref.makeRef('d') // Throw Error (42 is not a collection)
|
|
1005
|
+
* ```
|
|
901
1006
|
*/
|
|
902
1007
|
makeRef(...path) {
|
|
903
1008
|
let store = this;
|
|
@@ -923,6 +1028,15 @@ export class Store {
|
|
|
923
1028
|
}
|
|
924
1029
|
return this.collection.rawGet(this.idx);
|
|
925
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Iterate the specified collection (Array, Map or object), running the given code block for each item.
|
|
1033
|
+
* 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 [[`clean`]] handlers left by its code block are executed.
|
|
1035
|
+
*
|
|
1036
|
+
*
|
|
1037
|
+
*
|
|
1038
|
+
* @param pathAndFuncs
|
|
1039
|
+
*/
|
|
926
1040
|
onEach(...pathAndFuncs) {
|
|
927
1041
|
let makeSortKey = defaultMakeSortKey;
|
|
928
1042
|
let renderer = pathAndFuncs.pop();
|
|
@@ -977,22 +1091,22 @@ export class Store {
|
|
|
977
1091
|
});
|
|
978
1092
|
return out;
|
|
979
1093
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1094
|
+
/**
|
|
1095
|
+
* Applies a filter/map function on each item within the `Store`'s collection,
|
|
1096
|
+
* each of which can deliver any number of key/value pairs, and reactively manages the
|
|
1097
|
+
* returned map `Store` to hold any results.
|
|
1098
|
+
*
|
|
1099
|
+
* @param func - Function that transform the given store into output values
|
|
1100
|
+
* that can take one of the following forms:
|
|
1101
|
+
* - an `Object` or a `Map`: Each key/value pair will be added to the output `Store`.
|
|
1102
|
+
* - anything else: No key/value pairs are added to the output `Store`.
|
|
1103
|
+
*
|
|
1104
|
+
* @returns - A map `Store` with the key/value pairs returned by all `func` invocations.
|
|
1105
|
+
*
|
|
1106
|
+
* When items disappear from the `Store` or are changed in a way that `func` depends
|
|
1107
|
+
* upon, the resulting items are removed from the output `Store` as well. When multiple
|
|
1108
|
+
* input items produce the same output keys, this may lead to unexpected results.
|
|
1109
|
+
*/
|
|
996
1110
|
multiMap(func) {
|
|
997
1111
|
let out = new Store(new Map());
|
|
998
1112
|
this.onEach((item) => {
|
|
@@ -1054,13 +1168,13 @@ class DetachedStore extends Store {
|
|
|
1054
1168
|
isDetached() { return true; }
|
|
1055
1169
|
}
|
|
1056
1170
|
/**
|
|
1057
|
-
* Create a new DOM element.
|
|
1171
|
+
* Create a new DOM element, and insert it into the DOM at the position held by the current scope.
|
|
1058
1172
|
* @param tag - The tag of the element to be created and optionally dot-separated class names. For example: `h1` or `p.intro.has_avatar`.
|
|
1059
1173
|
* @param rest - The other arguments are flexible and interpreted based on their types:
|
|
1060
1174
|
* - `string`: Used as textContent for the element.
|
|
1061
|
-
* - `object`: Used as attributes
|
|
1175
|
+
* - `object`: Used as attributes, properties or event listeners for the element. See [[`prop`]] on how the distinction is made.
|
|
1062
1176
|
* - `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.
|
|
1063
|
-
* - `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`. After that, the `Store` will be updated when the input changes.
|
|
1177
|
+
* - `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.
|
|
1064
1178
|
* @example
|
|
1065
1179
|
* node('aside.editorial', 'Yada yada yada....', () => {
|
|
1066
1180
|
* node('a', {href: '/bio'}, () => {
|
|
@@ -1114,25 +1228,35 @@ export function node(tag = "", ...rest) {
|
|
|
1114
1228
|
}
|
|
1115
1229
|
}
|
|
1116
1230
|
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Convert an HTML string to one or more DOM elements, and add them to the current DOM scope.
|
|
1233
|
+
* @param html - The HTML string. For example `"<section><h2>Test</h2><p>Info..</p></section>"`.
|
|
1234
|
+
*/
|
|
1235
|
+
export function html(html) {
|
|
1236
|
+
if (!currentScope || !currentScope.parentElement)
|
|
1237
|
+
throw new ScopeError(true);
|
|
1238
|
+
let tmpParent = document.createElement(currentScope.parentElement.tagName);
|
|
1239
|
+
tmpParent.innerHTML = '' + html;
|
|
1240
|
+
while (tmpParent.firstChild) {
|
|
1241
|
+
currentScope.addNode(tmpParent.firstChild);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1117
1244
|
function bindInput(el, store) {
|
|
1118
|
-
let
|
|
1245
|
+
let onStoreChange;
|
|
1246
|
+
let onInputChange;
|
|
1119
1247
|
let type = el.getAttribute('type');
|
|
1120
1248
|
let value = store.query({ peek: true });
|
|
1121
1249
|
if (type === 'checkbox') {
|
|
1122
1250
|
if (value === undefined)
|
|
1123
1251
|
store.set(el.checked);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
updater = () => store.set(el.checked);
|
|
1252
|
+
onStoreChange = value => el.checked = value;
|
|
1253
|
+
onInputChange = () => store.set(el.checked);
|
|
1127
1254
|
}
|
|
1128
1255
|
else if (type === 'radio') {
|
|
1129
|
-
if (value === undefined)
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
else
|
|
1134
|
-
el.checked = value === el.value;
|
|
1135
|
-
updater = () => {
|
|
1256
|
+
if (value === undefined && el.checked)
|
|
1257
|
+
store.set(el.value);
|
|
1258
|
+
onStoreChange = value => el.checked = (value === el.value);
|
|
1259
|
+
onInputChange = () => {
|
|
1136
1260
|
if (el.checked)
|
|
1137
1261
|
store.set(el.value);
|
|
1138
1262
|
};
|
|
@@ -1140,13 +1264,18 @@ function bindInput(el, store) {
|
|
|
1140
1264
|
else {
|
|
1141
1265
|
if (value === undefined)
|
|
1142
1266
|
store.set(el.value);
|
|
1143
|
-
|
|
1144
|
-
el.value
|
|
1145
|
-
|
|
1267
|
+
onStoreChange = value => {
|
|
1268
|
+
if (el.value !== value)
|
|
1269
|
+
el.value = value;
|
|
1270
|
+
};
|
|
1271
|
+
onInputChange = () => store.set(el.value);
|
|
1146
1272
|
}
|
|
1147
|
-
|
|
1273
|
+
observe(() => {
|
|
1274
|
+
onStoreChange(store.get());
|
|
1275
|
+
});
|
|
1276
|
+
el.addEventListener('input', onInputChange);
|
|
1148
1277
|
clean(() => {
|
|
1149
|
-
el.removeEventListener('input',
|
|
1278
|
+
el.removeEventListener('input', onInputChange);
|
|
1150
1279
|
});
|
|
1151
1280
|
}
|
|
1152
1281
|
/**
|
|
@@ -1193,29 +1322,6 @@ export function clean(clean) {
|
|
|
1193
1322
|
throw new ScopeError(false);
|
|
1194
1323
|
currentScope.cleaners.push({ _clean: clean });
|
|
1195
1324
|
}
|
|
1196
|
-
/**
|
|
1197
|
-
* Create a new reactive scope and execute the `func` within that scope. When
|
|
1198
|
-
* `Store`s that the `func` reads are updated, only this scope will need to be refreshed,
|
|
1199
|
-
* leaving the parent scope untouched.
|
|
1200
|
-
*
|
|
1201
|
-
* In case this function is called outside of a an existing scope, it will create a new
|
|
1202
|
-
* top-level scope (a [[`Mount`]]) without a `parentElement`, meaning that aberdeen operations
|
|
1203
|
-
* that create/modify DOM elements are not permitted.
|
|
1204
|
-
* @param func - The function to be (repeatedly) executed within the newly created scope.
|
|
1205
|
-
* @returns The newly created `Mount` object in case this is a top-level reactive scope.
|
|
1206
|
-
* @example
|
|
1207
|
-
* ```
|
|
1208
|
-
* let store = new Store('John Doe')
|
|
1209
|
-
* mount(document.body, () => {
|
|
1210
|
-
* node('div.card', () => {
|
|
1211
|
-
* node('input', {placeholder: 'Name'}, store)
|
|
1212
|
-
* observe(() => {
|
|
1213
|
-
* prop('class', {correct: store.get().length > 5})
|
|
1214
|
-
* })
|
|
1215
|
-
* })
|
|
1216
|
-
* })
|
|
1217
|
-
* ```
|
|
1218
|
-
*/
|
|
1219
1325
|
/**
|
|
1220
1326
|
* Reactively run a function, meaning the function will rerun when any `Store` that was read
|
|
1221
1327
|
* during its execution is updated.
|
|
@@ -1309,9 +1415,8 @@ export function mount(parentElement, func) {
|
|
|
1309
1415
|
*
|
|
1310
1416
|
* mount(document.body, () => {
|
|
1311
1417
|
* // Prevent rerender when store changes
|
|
1312
|
-
* peek(() => {
|
|
1313
|
-
*
|
|
1314
|
-
* })
|
|
1418
|
+
* let msg = peek(() => `Store has ${store.count()} elements, and the first is ${store.get(0)}`))
|
|
1419
|
+
* text(msg)
|
|
1315
1420
|
* })
|
|
1316
1421
|
* ```
|
|
1317
1422
|
*
|
|
@@ -1413,7 +1518,7 @@ function defaultMakeSortKey(store) {
|
|
|
1413
1518
|
}
|
|
1414
1519
|
/* istanbul ignore next */
|
|
1415
1520
|
function internalError(code) {
|
|
1416
|
-
let error = new Error("internal error " + code);
|
|
1521
|
+
let error = new Error("Aberdeen internal error " + code);
|
|
1417
1522
|
setTimeout(() => { throw error; }, 0);
|
|
1418
1523
|
}
|
|
1419
1524
|
function handleError(e) {
|
|
@@ -1425,11 +1530,6 @@ class ScopeError extends Error {
|
|
|
1425
1530
|
super(`Operation not permitted outside of ${mount ? "a mount" : "an observe"}() scope`);
|
|
1426
1531
|
}
|
|
1427
1532
|
}
|
|
1428
|
-
let arrayFromSet = Array.from || /* istanbul ignore next */ ((set) => {
|
|
1429
|
-
let array = [];
|
|
1430
|
-
set.forEach(item => array.push(item));
|
|
1431
|
-
return array;
|
|
1432
|
-
});
|
|
1433
1533
|
// @ts-ignore
|
|
1434
1534
|
// istanbul ignore next
|
|
1435
1535
|
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 h{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 h?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 l extends h{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 h{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 h{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){this.collection.setIndex(this.idx,e,!1)}delete(...e){let t=this.makeRef(...e);t.collection.setIndex(t.idx,void 0,!0)}push(e){let t=this.collection.rawGet(this.idx);if(void 0===t)t=new g,this.collection.setIndex(this.idx,t,!0);else if(!(t instanceof g))throw new Error("push() is only allowed for an array or undefined (which would become an array)");let i=x(e),n=t.data.length;return t.data.push(i),t.emitChange(n,i,void 0),n}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 l(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))}}function w(e,t){let i,n=e.getAttribute("type"),r=t.query({peek:!0});"checkbox"===n?(void 0===r?t.set(e.checked):e.checked=r,i=()=>t.set(e.checked)):"radio"===n?(void 0===r?e.checked&&t.set(e.value):e.checked=r===e.value,i=()=>{e.checked&&t.set(e.value)}):(void 0===r?t.set(e.value):e.value=r,i=()=>t.set(e.value)),e.addEventListener("input",i),clean(()=>{e.removeEventListener("input",i)})}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 l(t,void 0,0,i):(n=new l(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("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`)}}Array.from;String.prototype.replaceAll||(String.prototype.replaceAll=function(e,t){return this.split(e).join(t)});
|
|
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)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aberdeen",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"nyc": "^15.0.1",
|
|
21
21
|
"terser": "^4.6.13",
|
|
22
22
|
"typedoc": "^0.22.10",
|
|
23
|
-
"typescript": "^4.5
|
|
23
|
+
"typescript": "^4.9.5",
|
|
24
24
|
"fast-equals": "^2.0.4"
|
|
25
25
|
},
|
|
26
26
|
"nyc": {
|