aberdeen 0.4.0 → 1.0.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/dist/aberdeen.js CHANGED
@@ -1,2012 +1,1199 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- let queueArray = []; // When not empty, a runQueue is scheduled or currently running.
11
- let queueIndex = 0; // This first element in queueArray that still needs to be processed.
12
- let queueSet = new Set(); // Contains the subset of queueArray at index >= queueIndex.
13
- let queueOrdered = true; // Set to `false` when `queue()` appends a runner to `queueArray` that should come before the previous last item in the array. Will trigger a sort.
14
- let runQueueDepth = 0; // Incremented when a queue event causes another queue event to be added. Reset when queue is empty. Throw when >= 42 to break (infinite) recursion.
15
- let showCreateTransitions = false; // Set to `true` only when creating top level elements in response to `Store` changes, triggering `create` transitions.
16
- function queue(runner) {
17
- if (queueSet.has(runner))
18
- return;
19
- if (runQueueDepth > 42) {
20
- throw new Error("Too many recursive updates from observes");
21
- }
22
- if (!queueArray.length) {
23
- setTimeout(runQueue, 0);
24
- }
25
- else if (runner._queueOrder < queueArray[queueArray.length - 1]._queueOrder) {
26
- queueOrdered = false;
1
+ // src/helpers/reverseSortedSet.ts
2
+ class ReverseSortedSet {
3
+ keyProp;
4
+ tail;
5
+ symbols;
6
+ constructor(keyProp) {
7
+ this.keyProp = keyProp;
8
+ this.tail = {};
9
+ this.symbols = [Symbol(0)];
10
+ }
11
+ add(item) {
12
+ if (this.symbols[0] in item)
13
+ return false;
14
+ const level = 1 + (Math.clz32(Math.random() * 4294967295) >> 2);
15
+ for (let l = this.symbols.length;l < level; l++)
16
+ this.symbols.push(Symbol(l));
17
+ const keyProp = this.keyProp;
18
+ const key = item[keyProp];
19
+ let prev;
20
+ let current = this.tail;
21
+ for (let l = this.symbols.length - 1;l >= 0; l--) {
22
+ const symbol = this.symbols[l];
23
+ while ((prev = current[symbol]) && prev[keyProp] > key)
24
+ current = prev;
25
+ if (l < level) {
26
+ item[symbol] = current[symbol];
27
+ current[symbol] = item;
28
+ }
27
29
  }
28
- queueArray.push(runner);
29
- queueSet.add(runner);
30
+ return true;
31
+ }
32
+ has(item) {
33
+ return this.symbols[0] in item;
34
+ }
35
+ fetchLast() {
36
+ let item = this.tail[this.symbols[0]];
37
+ if (item) {
38
+ this.remove(item);
39
+ return item;
40
+ }
41
+ }
42
+ isEmpty() {
43
+ return this.tail[this.symbols[0]] === undefined;
44
+ }
45
+ get(indexValue) {
46
+ const keyProp = this.keyProp;
47
+ let current = this.tail;
48
+ let prev;
49
+ for (let l = this.symbols.length - 1;l >= 0; l--) {
50
+ const symbol = this.symbols[l];
51
+ while ((prev = current[symbol]) && prev[keyProp] > indexValue)
52
+ current = prev;
53
+ }
54
+ return current[this.symbols[0]]?.[keyProp] === indexValue ? current[this.symbols[0]] : undefined;
55
+ }
56
+ *[Symbol.iterator]() {
57
+ let symbol = this.symbols[0];
58
+ let node = this.tail[symbol];
59
+ while (node) {
60
+ yield node;
61
+ node = node[symbol];
62
+ }
63
+ }
64
+ prev(item) {
65
+ return item[this.symbols[0]];
66
+ }
67
+ remove(item) {
68
+ if (!(this.symbols[0] in item))
69
+ return false;
70
+ const keyProp = this.keyProp;
71
+ const prop = item[keyProp];
72
+ let prev;
73
+ let current = this.tail;
74
+ for (let l = this.symbols.length - 1;l >= 0; l--) {
75
+ const symbol = this.symbols[l];
76
+ while ((prev = current[symbol]) && prev[keyProp] >= prop && prev !== item)
77
+ current = prev;
78
+ if (prev === item) {
79
+ current[symbol] = prev[symbol];
80
+ delete prev[symbol];
81
+ }
82
+ }
83
+ return prev === item;
84
+ }
85
+ clear() {
86
+ const symbol = this.symbols[0];
87
+ let current = this.tail;
88
+ while (current) {
89
+ const prev = current[symbol];
90
+ for (const symbol2 of this.symbols) {
91
+ if (!(symbol2 in current))
92
+ break;
93
+ delete current[symbol2];
94
+ }
95
+ current = prev;
96
+ }
97
+ this.tail = {};
98
+ }
30
99
  }
31
- /**
32
- * Normally, changes to `Store`s are reacted to asynchronously, in an (optimized)
33
- * batch, after a timeout of 0s. Calling `runQueue()` will do so immediately
34
- * and synchronously. Doing so may be helpful in cases where you need some DOM
35
- * modification to be done synchronously.
36
- *
37
- * This function is re-entrant, meaning it is safe to call `runQueue` from a
38
- * function that is called due to another (automatic) invocation of `runQueue`.
39
- */
40
- export function runQueue() {
41
- showCreateTransitions = true;
42
- for (; queueIndex < queueArray.length;) {
43
- // Sort queue if new unordered items have been added since last time.
44
- if (!queueOrdered) {
45
- queueArray.splice(0, queueIndex);
46
- queueIndex = 0;
47
- // Order queued observers by depth, lowest first.
48
- queueArray.sort((a, b) => a._queueOrder - b._queueOrder);
49
- queueOrdered = true;
50
- }
51
- // Process the rest of what's currently in the queue.
52
- let batchEndIndex = queueArray.length;
53
- while (queueIndex < batchEndIndex && queueOrdered) {
54
- let runner = queueArray[queueIndex++];
55
- queueSet.delete(runner);
56
- runner._queueRun();
57
- }
58
- // If new items have been added to the queue while processing the previous
59
- // batch, we'll need to run this loop again.
60
- runQueueDepth++;
61
- }
62
- queueIndex = 0;
63
- queueArray.length = 0;
64
- runQueueDepth = 0;
65
- showCreateTransitions = false;
100
+
101
+ // src/aberdeen.ts
102
+ var sortedQueue;
103
+ var runQueueDepth = 0;
104
+ var topRedrawScope;
105
+ function queue(runner) {
106
+ if (!sortedQueue) {
107
+ sortedQueue = new ReverseSortedSet("prio");
108
+ setTimeout(runQueue, 0);
109
+ } else if (!(runQueueDepth & 1)) {
110
+ runQueueDepth++;
111
+ if (runQueueDepth > 98) {
112
+ throw new Error("Too many recursive updates from observes");
113
+ }
114
+ }
115
+ sortedQueue.add(runner);
66
116
  }
67
- let domWaiters = [];
68
- let domInReadPhase = false;
69
- /**
70
- * A promise-like object that you can `await`. It will resolve *after* the current batch
71
- * of DOM-write operations has completed. This is the best time to retrieve DOM properties
72
- * that dependent on a layout being completed, such as `offsetHeight`.
73
- *
74
- * By batching DOM reads separately from DOM writes, this prevents the browser from
75
- * interleaving layout reads and writes, which can force additional layout recalculations.
76
- * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
77
- * intermediate DOM states during updates.
78
- *
79
- * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
80
- * operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
81
- *
82
- * See `transitions.js` for some examples.
83
- */
84
- export const DOM_READ_PHASE = {
85
- then: function (fulfilled) {
86
- if (domInReadPhase)
87
- fulfilled();
88
- else {
89
- if (!domWaiters.length)
90
- queue(DOM_PHASE_RUNNER);
91
- domWaiters.push(fulfilled);
92
- }
93
- return this;
94
- }
95
- };
96
- /**
97
- * A promise-like object that you can `await`. It will resolve *after* the current
98
- * DOM_READ_PHASE has completed (if any) and after any DOM triggered by Aberdeen
99
- * have completed. This is a good time to do little manual DOM tweaks that depend
100
- * on a *read phase* first, like triggering transitions.
101
- *
102
- * By batching DOM writes separately from DOM reads, this prevents the browser from
103
- * interleaving layout reads and writes, which can force additional layout recalculations.
104
- * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
105
- * intermediate DOM states during updates.
106
- *
107
- * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
108
- * operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
109
- *
110
- * See `transitions.js` for some examples.
111
- */
112
- export const DOM_WRITE_PHASE = {
113
- then: function (fulfilled) {
114
- if (!domInReadPhase)
115
- fulfilled();
116
- else {
117
- if (!domWaiters.length)
118
- queue(DOM_PHASE_RUNNER);
119
- domWaiters.push(fulfilled);
120
- }
121
- return this;
122
- }
123
- };
124
- const DOM_PHASE_RUNNER = {
125
- _queueOrder: 99999,
126
- _queueRun: function () {
127
- let waiters = domWaiters;
128
- domWaiters = [];
129
- domInReadPhase = !domInReadPhase;
130
- for (let waiter of waiters) {
131
- try {
132
- waiter();
133
- }
134
- catch (e) {
135
- console.error(e);
136
- }
137
- }
138
- }
139
- };
140
- /**
141
- * Given an integer number, a string or an array of these, this function returns a string that can be used
142
- * to compare items in a natural sorting order. So `[3, 'ab']` should be smaller than `[3, 'ac']`.
143
- * The resulting string is guaranteed to never be empty.
144
- */
145
- function sortKeyToString(key) {
146
- if (key instanceof Array) {
147
- return key.map(partToStr).join('');
148
- }
149
- else {
150
- return partToStr(key);
151
- }
117
+ function runQueue() {
118
+ let time = Date.now();
119
+ while (sortedQueue) {
120
+ const runner = sortedQueue.fetchLast();
121
+ if (!runner)
122
+ break;
123
+ if (runQueueDepth & 1)
124
+ runQueueDepth++;
125
+ runner.queueRun();
126
+ }
127
+ sortedQueue = undefined;
128
+ runQueueDepth = 0;
129
+ time = Date.now() - time;
130
+ if (time > 1)
131
+ console.debug(`Aberdeen queue took ${time}ms`);
152
132
  }
153
133
  function partToStr(part) {
154
- if (typeof part === 'string') {
155
- return part + '\x01';
156
- }
157
- else {
158
- let result = numToString(Math.abs(Math.round(part)), part < 0);
159
- // Prefix the number of digits, counting down from 128 for negative and up for positive
160
- return String.fromCharCode(128 + (part > 0 ? result.length : -result.length)) + result;
161
- }
134
+ if (typeof part === "string") {
135
+ return part + "\x01";
136
+ }
137
+ let result = "";
138
+ let num = Math.abs(Math.round(part));
139
+ const negative = part < 0;
140
+ while (num > 0) {
141
+ result += String.fromCharCode(negative ? 65534 - num % 65533 : 2 + num % 65533);
142
+ num = Math.floor(num / 65533);
143
+ }
144
+ return String.fromCharCode(128 + (negative ? -result.length : result.length)) + result;
162
145
  }
163
- function numToString(num, neg) {
164
- let result = '';
165
- while (num > 0) {
166
- /*
167
- * We're reserving a few character codes:
168
- * 0 - for compatibility
169
- * 1 - separator between array items
170
- * 65535 - for compatibility
171
- */
172
- result += String.fromCharCode(neg ? 65535 - (num % 65533) : 2 + (num % 65533));
173
- num = Math.floor(num / 65533);
174
- }
175
- return result;
146
+ function invertString(input) {
147
+ let result = "";
148
+ for (let i = 0;i < input.length; i++) {
149
+ result += String.fromCodePoint(65535 - input.charCodeAt(i));
150
+ }
151
+ return result;
176
152
  }
177
- /*
178
- * Scope
179
- * @internal
180
- *
181
- * A `Scope` is created with a `render` function that is run initially,
182
- * and again when any of the `Store`s that this function reads are changed. Any
183
- * DOM elements that is given a `render` function for its contents has its own scope.
184
- * The `Scope` manages the position in the DOM tree elements created by `render`
185
- * are inserted at. Before a rerender, all previously created elements are removed
186
- * and the `clean` functions for the scope and all sub-scopes are called.
187
- */
153
+ var lastPrio = 0;
154
+
188
155
  class Scope {
189
- constructor(_parentElement,
190
- // The node or scope right before this scope that has the same `parentElement`
191
- _precedingSibling,
192
- // How deep is this scope nested in other scopes; we use this to make sure events
193
- // at lower depths are handled before events at higher depths.
194
- _queueOrder) {
195
- this._parentElement = _parentElement;
196
- this._precedingSibling = _precedingSibling;
197
- this._queueOrder = _queueOrder;
198
- // The list of clean functions to be called when this scope is cleaned. These can
199
- // be for child scopes, subscriptions as well as `clean(..)` hooks.
200
- this._cleaners = [];
201
- // Set to true after the scope has been cleaned, causing any spurious reruns to
202
- // be ignored.
203
- this._isDead = false;
204
- }
205
- // Get a reference to the last Node preceding this Scope, or undefined if there is none
206
- _findPrecedingNode(stopAt = undefined) {
207
- let cur = this;
208
- let pre;
209
- while ((pre = cur._precedingSibling) && pre !== stopAt) {
210
- if (pre instanceof Node)
211
- return pre;
212
- let node = pre._findLastNode();
213
- if (node)
214
- return node;
215
- cur = pre;
216
- }
217
- }
218
- // Get a reference to the last Node within this scope and parentElement
219
- _findLastNode() {
220
- if (this._lastChild) {
221
- if (this._lastChild instanceof Node)
222
- return this._lastChild;
223
- else
224
- return this._lastChild._findLastNode() || this._lastChild._findPrecedingNode(this._precedingSibling);
225
- }
226
- }
227
- _addNode(node) {
228
- let prevNode = this._findLastNode() || this._findPrecedingNode();
229
- this._parentElement.insertBefore(node, prevNode ? prevNode.nextSibling : this._parentElement.firstChild);
230
- this._lastChild = node;
231
- }
232
- _remove() {
233
- if (this._parentElement) {
234
- let lastNode = this._findLastNode();
235
- if (lastNode) {
236
- // at least one DOM node to be removed
237
- let nextNode = this._findPrecedingNode();
238
- nextNode = (nextNode ? nextNode.nextSibling : this._parentElement.firstChild);
239
- this._lastChild = undefined;
240
- // Keep removing DOM nodes starting at our first node, until we encounter the last node
241
- while (true) {
242
- /* c8 ignore next */
243
- if (!nextNode)
244
- return internalError(1);
245
- const node = nextNode;
246
- nextNode = node.nextSibling || undefined;
247
- let onDestroy = onDestroyMap.get(node);
248
- if (onDestroy && node instanceof Element) {
249
- if (onDestroy !== true) {
250
- if (typeof onDestroy === 'function') {
251
- onDestroy(node);
252
- }
253
- else {
254
- destroyWithClass(node, onDestroy);
255
- }
256
- // This causes the element to be ignored from this function from now on:
257
- onDestroyMap.set(node, true);
258
- }
259
- // Ignore the deleting element
260
- }
261
- else {
262
- this._parentElement.removeChild(node);
263
- }
264
- if (node === lastNode)
265
- break;
266
- }
267
- }
268
- }
269
- // run cleaners
270
- this._clean();
271
- }
272
- _clean() {
273
- this._isDead = true;
274
- for (let cleaner of this._cleaners) {
275
- cleaner._clean(this);
276
- }
277
- this._cleaners.length = 0;
278
- }
279
- _onChange(index, newData, oldData) {
280
- queue(this);
281
- }
156
+ prio = --lastPrio;
157
+ remove() {
158
+ const lastNode = this.getLastNode();
159
+ if (lastNode)
160
+ removeNodes(lastNode, this.getPrecedingNode());
161
+ this.delete();
162
+ }
282
163
  }
283
- class SimpleScope extends Scope {
284
- constructor(parentElement, precedingSibling, queueOrder, renderer) {
285
- super(parentElement, precedingSibling, queueOrder);
286
- if (renderer)
287
- this._renderer = renderer;
288
- }
289
- /* c8 ignore start */
290
- _renderer() {
291
- // Should be overriden by a subclass or the constructor
292
- internalError(14);
293
- }
294
- /* c8 ignore stop */
295
- _queueRun() {
296
- /* c8 ignore next */
297
- if (currentScope)
298
- internalError(2);
299
- if (this._isDead)
300
- return;
301
- this._remove();
302
- this._isDead = false;
303
- this._update();
304
- }
305
- _update() {
306
- let savedScope = currentScope;
307
- currentScope = this;
308
- try {
309
- this._renderer();
310
- }
311
- catch (e) {
312
- // Throw the error async, so the rest of the rendering can continue
313
- handleError(e, true);
314
- }
315
- currentScope = savedScope;
316
- }
317
- _install() {
318
- if (showCreateTransitions) {
319
- showCreateTransitions = false;
320
- this._update();
321
- showCreateTransitions = true;
322
- }
323
- else {
324
- this._update();
325
- }
326
- // Add it to our list of cleaners. Even if `childScope` currently has
327
- // no cleaners, it may get them in a future refresh.
328
- currentScope._cleaners.push(this);
329
- }
164
+
165
+ class ContentScope extends Scope {
166
+ cleaners;
167
+ constructor(cleaners = []) {
168
+ super();
169
+ this.cleaners = cleaners;
170
+ }
171
+ lastChild;
172
+ redraw() {}
173
+ getLastNode() {
174
+ return findLastNodeInPrevSiblings(this.lastChild);
175
+ }
176
+ delete() {
177
+ for (let cleaner of this.cleaners) {
178
+ if (typeof cleaner === "function")
179
+ cleaner();
180
+ else
181
+ cleaner.delete(this);
182
+ }
183
+ this.cleaners.length = 0;
184
+ sortedQueue?.remove(this);
185
+ this.lastChild = undefined;
186
+ }
187
+ queueRun() {
188
+ this.remove();
189
+ topRedrawScope = this;
190
+ this.redraw();
191
+ topRedrawScope = undefined;
192
+ }
193
+ getInsertAfterNode() {
194
+ return this.getLastNode() || this.getPrecedingNode();
195
+ }
196
+ onChange(index, newData, oldData) {
197
+ queue(this);
198
+ }
199
+ getChildPrevSibling() {
200
+ return this.lastChild;
201
+ }
330
202
  }
331
- /**
332
- * This could have been done with a SimpleScope, but then we'd have to draw along an instance of
333
- * that as well as a renderer function that closes over quite a few variables, which probably
334
- * wouldn't be great for the performance of this common feature.
335
- */
336
- class SetArgScope extends SimpleScope {
337
- constructor(parentElement, precedingSibling, queueOrder, _key, _value) {
338
- super(parentElement, precedingSibling, queueOrder);
339
- this._key = _key;
340
- this._value = _value;
341
- }
342
- _renderer() {
343
- applyArg(this._parentElement, this._key, this._value.get());
344
- }
203
+
204
+ class ChainedScope extends ContentScope {
205
+ parentElement;
206
+ prevSibling;
207
+ constructor(parentElement, useParentCleaners = false) {
208
+ super(useParentCleaners ? currentScope.cleaners : []);
209
+ this.parentElement = parentElement;
210
+ if (parentElement === currentScope.parentElement) {
211
+ this.prevSibling = currentScope.getChildPrevSibling();
212
+ currentScope.lastChild = this;
213
+ }
214
+ if (!useParentCleaners)
215
+ currentScope.cleaners.push(this);
216
+ }
217
+ getPrecedingNode() {
218
+ return findLastNodeInPrevSiblings(this.prevSibling);
219
+ }
220
+ getChildPrevSibling() {
221
+ return this.lastChild || this.prevSibling;
222
+ }
345
223
  }
346
- let immediateQueue = new Set();
347
- class ImmediateScope extends SimpleScope {
348
- _onChange(index, newData, oldData) {
349
- immediateQueue.add(this);
224
+
225
+ class RegularScope extends ChainedScope {
226
+ renderer;
227
+ constructor(parentElement, renderer) {
228
+ super(parentElement);
229
+ this.renderer = renderer;
230
+ this.redraw();
231
+ }
232
+ redraw() {
233
+ let savedScope = currentScope;
234
+ currentScope = this;
235
+ try {
236
+ this.renderer();
237
+ } catch (e) {
238
+ handleError(e, true);
350
239
  }
240
+ currentScope = savedScope;
241
+ }
351
242
  }
352
- let immediateQueuerRunning = false;
353
- function runImmediateQueue() {
354
- if (immediateQueuerRunning)
355
- return;
356
- for (let count = 0; immediateQueue.size; count++) {
357
- if (count > 42) {
358
- immediateQueue.clear();
359
- throw new Error("Too many recursive updates from immediate-mode observes");
360
- }
361
- immediateQueuerRunning = true;
362
- let copy = immediateQueue;
363
- immediateQueue = new Set();
364
- let savedScope = currentScope;
365
- currentScope = undefined;
366
- try {
367
- for (const scope of copy) {
368
- scope._queueRun();
369
- }
370
- }
371
- finally {
372
- currentScope = savedScope;
373
- immediateQueuerRunning = false;
374
- }
375
- }
243
+
244
+ class RootScope extends ContentScope {
245
+ parentElement = document.body;
246
+ getPrecedingNode() {
247
+ return;
248
+ }
376
249
  }
377
- class IsEmptyObserver {
378
- constructor(scope, collection, triggerCount) {
379
- this.scope = scope;
380
- this.collection = collection;
381
- this.triggerCount = triggerCount;
382
- this.count = collection._getCount();
383
- collection._addObserver(ANY_INDEX, this);
384
- scope._cleaners.push(this);
385
- }
386
- _onChange(index, newData, oldData) {
387
- if (newData === undefined) {
388
- // oldData is guaranteed not to be undefined
389
- if (this.triggerCount || !--this.count)
390
- queue(this.scope);
391
- }
392
- else if (oldData === undefined) {
393
- if (this.triggerCount || !this.count++)
394
- queue(this.scope);
395
- }
396
- }
397
- _clean() {
398
- this.collection._removeObserver(ANY_INDEX, this);
399
- }
250
+
251
+ class MountScope extends ContentScope {
252
+ parentElement;
253
+ renderer;
254
+ constructor(parentElement, renderer) {
255
+ super();
256
+ this.parentElement = parentElement;
257
+ this.renderer = renderer;
258
+ this.redraw();
259
+ currentScope.cleaners.push(this);
260
+ }
261
+ redraw() {
262
+ RegularScope.prototype.redraw.call(this);
263
+ }
264
+ getPrecedingNode() {
265
+ return;
266
+ }
267
+ delete() {
268
+ removeNodes(this.getLastNode(), this.getPrecedingNode());
269
+ super.delete();
270
+ }
271
+ remove() {
272
+ this.delete();
273
+ }
400
274
  }
401
- /** @internal */
402
- class OnEachScope extends Scope {
403
- constructor(parentElement, precedingSibling, queueOrder, collection, renderer, makeSortKey) {
404
- super(parentElement, precedingSibling, queueOrder);
405
- /** The ordered list of currently item scopes */
406
- this._byPosition = [];
407
- /** The item scopes in a Map by index */
408
- this._byIndex = new Map();
409
- /** Indexes that have been created/removed and need to be handled in the next `queueRun` */
410
- this._newIndexes = new Set();
411
- this._removedIndexes = new Set();
412
- this._collection = collection;
413
- this._renderer = renderer;
414
- this._makeSortKey = makeSortKey;
415
- }
416
- // toString(): string {
417
- // return `OnEachScope(collection=${this.collection})`
418
- // }
419
- _onChange(index, newData, oldData) {
420
- if (oldData === undefined) {
421
- if (this._removedIndexes.has(index)) {
422
- this._removedIndexes.delete(index);
423
- }
424
- else {
425
- this._newIndexes.add(index);
426
- queue(this);
427
- }
428
- }
429
- else if (newData === undefined) {
430
- if (this._newIndexes.has(index)) {
431
- this._newIndexes.delete(index);
432
- }
433
- else {
434
- this._removedIndexes.add(index);
435
- queue(this);
436
- }
437
- }
438
- }
439
- _queueRun() {
440
- if (this._isDead)
441
- return;
442
- let indexes = this._removedIndexes;
443
- this._removedIndexes = new Set();
444
- indexes.forEach(index => {
445
- this._removeChild(index);
446
- });
447
- indexes = this._newIndexes;
448
- this._newIndexes = new Set();
449
- indexes.forEach(index => {
450
- this._addChild(index);
451
- });
452
- }
453
- _clean() {
454
- super._clean();
455
- this._collection._observers.delete(this);
456
- for (const [index, scope] of this._byIndex) {
457
- scope._clean();
458
- }
459
- // Help garbage collection:
460
- this._byPosition.length = 0;
461
- this._byIndex.clear();
462
- }
463
- _renderInitial() {
464
- /* c8 ignore next */
465
- if (!currentScope)
466
- return internalError(3);
467
- let parentScope = currentScope;
468
- this._collection._iterateIndexes(this);
469
- currentScope = parentScope;
470
- }
471
- _addChild(itemIndex) {
472
- let scope = new OnEachItemScope(this._parentElement, undefined, this._queueOrder + 1, this, itemIndex);
473
- this._byIndex.set(itemIndex, scope);
474
- scope._update();
475
- // We're not adding a cleaner here, as we'll be calling them from our _clean function
476
- }
477
- _removeChild(itemIndex) {
478
- let scope = this._byIndex.get(itemIndex);
479
- /* c8 ignore next */
480
- if (!scope)
481
- return internalError(6);
482
- scope._remove();
483
- this._byIndex.delete(itemIndex);
484
- this._removeFromPosition(scope);
485
- }
486
- _findPosition(sortStr) {
487
- // In case of duplicate `sortStr`s, this will return the first match.
488
- let items = this._byPosition;
489
- let min = 0, max = items.length;
490
- // Fast-path for elements that are already ordered (as is the case when working with arrays ordered by index)
491
- if (!max || sortStr > items[max - 1]._sortStr)
492
- return max;
493
- // Binary search for the insert position
494
- while (min < max) {
495
- let mid = (min + max) >> 1;
496
- if (items[mid]._sortStr < sortStr) {
497
- min = mid + 1;
498
- }
499
- else {
500
- max = mid;
501
- }
502
- }
503
- return min;
504
- }
505
- _insertAtPosition(child) {
506
- let pos = this._findPosition(child._sortStr);
507
- this._byPosition.splice(pos, 0, child);
508
- // Based on the position in the list, set the precedingSibling for the new Scope
509
- // and for the next sibling.
510
- let nextSibling = this._byPosition[pos + 1];
511
- if (nextSibling) {
512
- child._precedingSibling = nextSibling._precedingSibling;
513
- nextSibling._precedingSibling = child;
514
- }
515
- else {
516
- child._precedingSibling = this._lastChild || this._precedingSibling;
517
- this._lastChild = child;
518
- }
519
- }
520
- _removeFromPosition(child) {
521
- if (child._sortStr === '')
522
- return;
523
- let pos = this._findPosition(child._sortStr);
524
- while (true) {
525
- if (this._byPosition[pos] === child) {
526
- // Yep, this is the right scope
527
- this._byPosition.splice(pos, 1);
528
- if (pos < this._byPosition.length) {
529
- let nextSibling = this._byPosition[pos];
530
- /* c8 ignore next */
531
- if (!nextSibling)
532
- return internalError(8);
533
- /* c8 ignore next */
534
- if (nextSibling._precedingSibling !== child)
535
- return internalError(13);
536
- nextSibling._precedingSibling = child._precedingSibling;
537
- }
538
- else {
539
- /* c8 ignore next */
540
- if (child !== this._lastChild)
541
- return internalError(12);
542
- this._lastChild = child._precedingSibling === this._precedingSibling ? undefined : child._precedingSibling;
543
- }
544
- return;
545
- }
546
- // There may be another Scope with the same sortStr
547
- /* c8 ignore next */
548
- if (++pos >= this._byPosition.length || this._byPosition[pos]._sortStr !== child._sortStr)
549
- return internalError(5);
550
- }
551
- }
275
+ function removeNodes(node, preNode) {
276
+ while (node && node !== preNode) {
277
+ const prevNode = node.previousSibling;
278
+ let onDestroy = onDestroyMap.get(node);
279
+ if (onDestroy && node instanceof Element) {
280
+ if (onDestroy !== true) {
281
+ if (typeof onDestroy === "function") {
282
+ onDestroy(node);
283
+ } else {
284
+ destroyWithClass(node, onDestroy);
285
+ }
286
+ onDestroyMap.set(node, true);
287
+ }
288
+ } else {
289
+ node.remove();
290
+ }
291
+ node = prevNode;
292
+ }
552
293
  }
553
- /** @internal */
554
- class OnEachItemScope extends Scope {
555
- constructor(parentElement, precedingSibling, queueOrder, parent, itemIndex) {
556
- super(parentElement, precedingSibling, queueOrder);
557
- this._sortStr = "";
558
- this._parent = parent;
559
- this._itemIndex = itemIndex;
560
- }
561
- // toString(): string {
562
- // return `OnEachItemScope(itemIndex=${this.itemIndex} parentElement=${this.parentElement} parent=${this.parent} precedingSibling=${this.precedingSibling} lastChild=${this.lastChild})`
563
- // }
564
- _queueRun() {
565
- /* c8 ignore next */
566
- if (currentScope)
567
- internalError(4);
568
- if (this._isDead)
569
- return;
570
- this._remove();
571
- this._isDead = false;
572
- this._update();
573
- }
574
- _update() {
575
- // Have the makeSortKey function return an ordering int/string/array.
576
- // Since makeSortKey may get() the Store, we'll need to set currentScope first.
577
- let savedScope = currentScope;
578
- currentScope = this;
579
- let itemStore = new Store(this._parent._collection, this._itemIndex);
580
- let sortKey;
581
- try {
582
- sortKey = this._parent._makeSortKey(itemStore);
583
- }
584
- catch (e) {
585
- handleError(e, false);
586
- }
587
- let oldSortStr = this._sortStr;
588
- let newSortStr = sortKey == null ? '' : sortKeyToString(sortKey);
589
- if (oldSortStr !== '' && oldSortStr !== newSortStr) {
590
- this._parent._removeFromPosition(this);
591
- }
592
- this._sortStr = newSortStr;
593
- if (newSortStr !== '') {
594
- if (newSortStr !== oldSortStr) {
595
- this._parent._insertAtPosition(this);
596
- }
597
- try {
598
- this._parent._renderer(itemStore);
599
- }
600
- catch (e) {
601
- handleError(e, true);
602
- }
603
- }
604
- currentScope = savedScope;
605
- }
294
+ function findLastNodeInPrevSiblings(sibling) {
295
+ if (!sibling || sibling instanceof Node)
296
+ return sibling;
297
+ return sibling.getLastNode() || sibling.getPrecedingNode();
606
298
  }
607
- /**
608
- * This global is set during the execution of a `Scope.render`. It is used by
609
- * functions like `$` and `clean`.
610
- */
611
- let currentScope;
612
- /**
613
- * A special Node observer index to subscribe to any value in the map changing.
614
- */
615
- const ANY_INDEX = {};
616
- /** @internal */
617
- export class ObsCollection {
618
- constructor() {
619
- this._observers = new Map();
620
- }
621
- // toString(): string {
622
- // return JSON.stringify(peek(() => this.getRecursive(3)))
623
- // }
624
- _addObserver(index, observer) {
625
- observer = observer;
626
- let obsSet = this._observers.get(index);
627
- if (obsSet) {
628
- if (obsSet.has(observer))
629
- return false;
630
- obsSet.add(observer);
631
- }
632
- else {
633
- this._observers.set(index, new Set([observer]));
634
- }
635
- return true;
636
- }
637
- _removeObserver(index, observer) {
638
- let obsSet = this._observers.get(index);
639
- obsSet.delete(observer);
640
- }
641
- emitChange(index, newData, oldData) {
642
- let obsSet = this._observers.get(index);
643
- if (obsSet)
644
- obsSet.forEach(observer => observer._onChange(index, newData, oldData));
645
- obsSet = this._observers.get(ANY_INDEX);
646
- if (obsSet)
647
- obsSet.forEach(observer => observer._onChange(index, newData, oldData));
648
- }
649
- _clean(observer) {
650
- this._removeObserver(ANY_INDEX, observer);
651
- }
652
- _setIndex(index, newValue, deleteMissing) {
653
- const curData = this.rawGet(index);
654
- if (!(curData instanceof ObsCollection) || newValue instanceof Store || !curData._merge(newValue, deleteMissing)) {
655
- let newData = valueToData(newValue);
656
- if (newData !== curData) {
657
- this.rawSet(index, newData);
658
- this.emitChange(index, newData, curData);
659
- }
660
- }
299
+
300
+ class ResultScope extends ChainedScope {
301
+ renderer;
302
+ result = optProxy({ value: undefined });
303
+ constructor(parentElement, renderer) {
304
+ super(parentElement);
305
+ this.renderer = renderer;
306
+ this.redraw();
307
+ }
308
+ redraw() {
309
+ let savedScope = currentScope;
310
+ currentScope = this;
311
+ try {
312
+ this.result.value = this.renderer();
313
+ } catch (e) {
314
+ handleError(e, true);
661
315
  }
316
+ currentScope = savedScope;
317
+ }
662
318
  }
663
- /** @internal */
664
- class ObsArray extends ObsCollection {
665
- constructor() {
666
- super(...arguments);
667
- this._data = [];
668
- }
669
- _getType() {
670
- return "array";
671
- }
672
- _getRecursive(depth) {
673
- if (currentScope) {
674
- if (this._addObserver(ANY_INDEX, currentScope)) {
675
- currentScope._cleaners.push(this);
676
- }
677
- }
678
- let result = [];
679
- for (let i = 0; i < this._data.length; i++) {
680
- let v = this._data[i];
681
- result.push(v instanceof ObsCollection ? (depth ? v._getRecursive(depth - 1) : new Store(this, i)) : v);
682
- }
683
- return result;
684
- }
685
- rawGet(index) {
686
- return this._data[index];
687
- }
688
- rawSet(index, newData) {
689
- if (index !== (0 | index) || index < 0 || index > 999999) {
690
- throw new Error(`Invalid array index ${JSON.stringify(index)}`);
691
- }
692
- this._data[index] = newData;
693
- // Remove trailing `undefined`s
694
- while (this._data.length > 0 && this._data[this._data.length - 1] === undefined) {
695
- this._data.pop();
696
- }
697
- }
698
- _merge(newValue, deleteMissing) {
699
- if (!(newValue instanceof Array)) {
700
- return false;
701
- }
702
- // newValue is an array
703
- for (let i = 0; i < newValue.length; i++) {
704
- this._setIndex(i, newValue[i], deleteMissing);
705
- }
706
- // Overwriting just the first elements of an array and leaving the rest of
707
- // the old data in place is just weird and unexpected, so we'll always use
708
- // 'replace' behavior for arrays.
709
- if ( /*deleteMissing &&*/this._data.length > newValue.length) {
710
- for (let i = newValue.length; i < this._data.length; i++) {
711
- let old = this._data[i];
712
- if (old !== undefined) {
713
- this.emitChange(i, undefined, old);
714
- }
715
- }
716
- this._data.length = newValue.length;
717
- }
718
- return true;
719
- }
720
- _iterateIndexes(scope) {
721
- for (let i = 0; i < this._data.length; i++) {
722
- if (this._data[i] !== undefined) {
723
- scope._addChild(i);
724
- }
725
- }
726
- }
727
- _normalizeIndex(index) {
728
- if (typeof index === 'number')
729
- return index;
730
- if (typeof index === 'string') {
731
- // Convert to int
732
- let num = 0 | index;
733
- // Check if the number is still the same after conversion
734
- if (index.length && num == index)
735
- return index;
736
- }
737
- throw new Error(`Invalid array index ${JSON.stringify(index)}`);
738
- }
739
- _getCount() {
740
- return this._data.length;
741
- }
319
+
320
+ class SetArgScope extends ChainedScope {
321
+ key;
322
+ target;
323
+ constructor(parentElement, key, target) {
324
+ super(parentElement);
325
+ this.key = key;
326
+ this.target = target;
327
+ this.redraw();
328
+ }
329
+ redraw() {
330
+ let savedScope = currentScope;
331
+ currentScope = this;
332
+ applyArg(this.key, this.target.value);
333
+ currentScope = savedScope;
334
+ }
742
335
  }
743
- /** @internal */
744
- class ObsMap extends ObsCollection {
745
- constructor() {
746
- super(...arguments);
747
- this.data = new Map();
748
- }
749
- _getType() {
750
- return "map";
751
- }
752
- _getRecursive(depth) {
753
- if (currentScope) {
754
- if (this._addObserver(ANY_INDEX, currentScope)) {
755
- currentScope._cleaners.push(this);
756
- }
757
- }
758
- let result = new Map();
759
- this.data.forEach((v, k) => {
760
- result.set(k, (v instanceof ObsCollection) ? (depth ? v._getRecursive(depth - 1) : new Store(this, k)) : v);
761
- });
762
- return result;
763
- }
764
- rawGet(index) {
765
- return this.data.get(index);
766
- }
767
- rawSet(index, newData) {
768
- if (newData === undefined) {
769
- this.data.delete(index);
770
- }
771
- else {
772
- this.data.set(index, newData);
773
- }
774
- }
775
- _merge(newValue, deleteMissing) {
776
- if (!(newValue instanceof Map)) {
777
- return false;
778
- }
779
- // Walk the pairs of the new value map
780
- newValue.forEach((v, k) => {
781
- this._setIndex(k, v, deleteMissing);
782
- });
783
- if (deleteMissing) {
784
- this.data.forEach((v, k) => {
785
- if (!newValue.has(k))
786
- this._setIndex(k, undefined, false);
787
- });
788
- }
789
- return true;
790
- }
791
- _iterateIndexes(scope) {
792
- this.data.forEach((_, itemIndex) => {
793
- scope._addChild(itemIndex);
794
- });
795
- }
796
- _normalizeIndex(index) {
797
- return index;
798
- }
799
- _getCount() {
800
- return this.data.size;
801
- }
336
+ var immediateQueue = new ReverseSortedSet("prio");
337
+
338
+ class ImmediateScope extends RegularScope {
339
+ onChange(index, newData, oldData) {
340
+ immediateQueue.add(this);
341
+ }
802
342
  }
803
- /** @internal */
804
- class ObsObject extends ObsMap {
805
- _getType() {
806
- return "object";
807
- }
808
- _getRecursive(depth) {
809
- if (currentScope) {
810
- if (this._addObserver(ANY_INDEX, currentScope)) {
811
- currentScope._cleaners.push(this);
812
- }
813
- }
814
- let result = {};
815
- this.data.forEach((v, k) => {
816
- result[k] = (v instanceof ObsCollection) ? (depth ? v._getRecursive(depth - 1) : new Store(this, k)) : v;
817
- });
818
- return result;
819
- }
820
- _merge(newValue, deleteMissing) {
821
- if (!newValue || newValue.constructor !== Object) {
822
- return false;
823
- }
824
- // Walk the pairs of the new value object
825
- for (let k in newValue) {
826
- this._setIndex(k, newValue[k], deleteMissing);
827
- }
828
- if (deleteMissing) {
829
- this.data.forEach((v, k) => {
830
- if (!newValue.hasOwnProperty(k))
831
- this._setIndex(k, undefined, false);
832
- });
833
- }
834
- return true;
835
- }
836
- _normalizeIndex(index) {
837
- let type = typeof index;
838
- if (type === 'string')
839
- return index;
840
- if (type === 'number')
841
- return '' + index;
842
- throw new Error(`Invalid object index ${JSON.stringify(index)}`);
843
- }
844
- _getCount() {
845
- let cnt = 0;
846
- for (let key of this.data)
847
- cnt++;
848
- return cnt;
343
+ var immediateQueueRunning = false;
344
+ function runImmediateQueue() {
345
+ for (let count = 0;!immediateQueue.isEmpty() && !immediateQueueRunning; count++) {
346
+ if (count > 42) {
347
+ immediateQueue.clear();
348
+ throw new Error("Too many immediate-mode recursive updates");
349
+ }
350
+ immediateQueueRunning = true;
351
+ let copy = immediateQueue;
352
+ immediateQueue = new ReverseSortedSet("prio");
353
+ try {
354
+ for (const scope of copy) {
355
+ scope.queueRun();
356
+ }
357
+ } finally {
358
+ immediateQueueRunning = false;
849
359
  }
360
+ }
850
361
  }
851
- const DETACHED_KEY = {};
852
- export class Store {
853
- /** @internal */
854
- constructor(value = undefined, index = undefined) {
855
- /**
856
- * Create and return a new `Store` that represents the subtree at `path` of
857
- * the current `Store`.
858
- *
859
- * The `path` is only actually resolved when this new `Store` is first used,
860
- * and how this is done depends on whether a read or a write operation is
861
- * performed. Read operations will just use an `undefined` value when a
862
- * subtree that we're diving into does not exist. Also, they'll subscribe
863
- * to changes at each level of the tree indexed by `path`.
864
- *
865
- * Write operations will create any missing subtrees as objects. They don't
866
- * subscribe to changes (as they are the ones causing the changes).
867
- *
868
- * Both read and write operations will throw an error if, while resolving
869
- * `path`, they encounters a non-collection data type (such as a number)
870
- */
871
- const ref = function (...path) {
872
- const result = new Store(ref._collection, ref._idx);
873
- if (path.length || ref._virtual) {
874
- result._virtual = ref._virtual ? ref._virtual.concat(path) : path;
875
- }
876
- return result;
877
- };
878
- Object.setPrototypeOf(ref, Store.prototype);
879
- if (index === undefined) {
880
- ref._collection = new ObsArray();
881
- ref._idx = 0;
882
- if (value !== undefined) {
883
- ref._collection.rawSet(0, valueToData(value));
884
- }
885
- }
886
- else {
887
- if (!(value instanceof ObsCollection)) {
888
- throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");
889
- }
890
- ref._collection = value;
891
- ref._idx = index;
892
- }
893
- // @ts-ignore
894
- return ref;
895
- }
896
- /**
897
- * @returns The index for this Store within its parent collection. This will be a `number`
898
- * when the parent collection is an array, a `string` when it's an object, or any data type
899
- * when it's a `Map`.
900
- *
901
- * @example
902
- * ```
903
- * let store = new Store({x: 123})
904
- * let subStore = store.ref('x')
905
- * subStore.get() // 123
906
- * subStore.index() // 'x'
907
- * ```
908
- */
909
- index() {
910
- return this._idx;
911
- }
912
- /** @internal */
913
- _clean(scope) {
914
- this._collection._removeObserver(this._idx, scope);
915
- }
916
- /**
917
- * Retrieve the value for store, subscribing the observe scope to changes.
918
- *
919
- * @param depth Limit the depth of the retrieved data structure to this positive integer.
920
- * When `depth` is `1`, only a single level of the value at `path` is unpacked. This
921
- * makes no difference for primitive values (like strings), but for objects, maps and
922
- * arrays, it means that each *value* in the resulting data structure will be a
923
- * reference to the `Store` for that value.
924
- *
925
- * @returns The resulting value (or `undefined` if the `Store` does not exist).
926
- */
927
- get(depth = 0) {
928
- let value = this._observe();
929
- return value instanceof ObsCollection ? value._getRecursive(depth - 1) : value;
930
- }
931
- /**
932
- * Exactly like {@link Store.get}, except that when executed from an observe scope,
933
- * we will not subscribe to changes in the data retrieved data.
934
- */
935
- peek(depth = 0) {
936
- let savedScope = currentScope;
937
- currentScope = undefined;
938
- let result = this.get(depth);
939
- currentScope = savedScope;
940
- return result;
941
- }
942
- /**
943
- * Like {@link Store.get}, but with return type checking.
944
- *
945
- * @param expectType A string specifying what type the.get is expected to return. Options are:
946
- * "undefined", "null", "boolean", "number", "string", "function", "array", "map"
947
- * and "object". If the store holds a different type of value, a `TypeError`
948
- * exception is thrown.
949
- * @returns
950
- */
951
- getTyped(expectType, depth = 0) {
952
- let value = this._observe();
953
- let type = (value instanceof ObsCollection) ? value._getType() : (value === null ? "null" : typeof value);
954
- if (type !== expectType)
955
- throw new TypeError(`Expecting ${expectType} but got ${type}`);
956
- return value instanceof ObsCollection ? value._getRecursive(depth - 1) : value;
957
- }
958
- /**
959
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
960
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
961
- */
962
- getNumber() { return this.getTyped('number'); }
963
- /**
964
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
965
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
966
- */
967
- getString() { return this.getTyped('string'); }
968
- /**
969
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
970
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
971
- */
972
- getBoolean() { return this.getTyped('boolean'); }
973
- /**
974
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
975
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
976
- */
977
- getFunction() { return this.getTyped('function'); }
978
- /**
979
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
980
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
981
- */
982
- getArray(depth = 0) { return this.getTyped('array', depth); }
983
- /**
984
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
985
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
986
- */
987
- getObject(depth = 0) { return this.getTyped('object', depth); }
988
- /**
989
- * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
990
- * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
991
- */
992
- getMap(depth = 0) { return this.getTyped('map', depth); }
993
- /**
994
- * Like {@link Store.get}, but with a default value (returned when the Store
995
- * contains `undefined`). This default value is also used to determine the expected type,
996
- * and to throw otherwise.
997
- *
998
- * @example
999
- * ```
1000
- * let store = new Store({x: 42})
1001
- * store('x').getOr(99) // 42
1002
- * store('y').getOr(99) // 99
1003
- * store('x').getOr('hello') // throws TypeError (because 42 is not a string)
1004
- * ```
1005
- */
1006
- getOr(defaultValue) {
1007
- let value = this._observe();
1008
- if (value === undefined)
1009
- return defaultValue;
1010
- let expectType = typeof defaultValue;
1011
- if (expectType === 'object') {
1012
- if (defaultValue instanceof Map)
1013
- expectType = 'map';
1014
- else if (defaultValue instanceof Array)
1015
- expectType = 'array';
1016
- else if (defaultValue === null)
1017
- expectType = 'null';
1018
- }
1019
- let type = (value instanceof ObsCollection) ? value._getType() : (value === null ? "null" : typeof value);
1020
- if (type !== expectType)
1021
- throw new TypeError(`Expecting ${expectType} but got ${type}`);
1022
- return (value instanceof ObsCollection ? value._getRecursive(-1) : value);
1023
- }
1024
- /**
1025
- * Checks if the collection held in `Store` is empty, and subscribes the current scope to changes of the emptiness of this collection.
1026
- *
1027
- * @returns When the collection is not empty `true` is returned. If it is empty or if the value is undefined, `false` is returned.
1028
- * @throws When the value is not a collection and not undefined, an Error will be thrown.
1029
- */
1030
- isEmpty() {
1031
- let value = this._observe();
1032
- if (value instanceof ObsCollection) {
1033
- if (currentScope) {
1034
- let observer = new IsEmptyObserver(currentScope, value, false);
1035
- return !observer.count;
1036
- }
1037
- else {
1038
- return !value._getCount();
1039
- }
1040
- }
1041
- else if (value === undefined) {
1042
- return true;
1043
- }
1044
- else {
1045
- throw new Error(`isEmpty() expects a collection or undefined, but got ${JSON.stringify(value)}`);
1046
- }
1047
- }
1048
- /**
1049
- * Returns the number of items in the collection held in Store, and subscribes the current scope to changes in this count.
1050
- *
1051
- * @returns The number of items contained in the collection, or 0 if the value is undefined.
1052
- * @throws When the value is not a collection and not undefined, an Error will be thrown.
1053
- */
1054
- count() {
1055
- let value = this._observe();
1056
- if (value instanceof ObsCollection) {
1057
- if (currentScope) {
1058
- let observer = new IsEmptyObserver(currentScope, value, true);
1059
- return observer.count;
1060
- }
1061
- else {
1062
- return value._getCount();
1063
- }
1064
- }
1065
- else if (value === undefined) {
1066
- return 0;
1067
- }
1068
- else {
1069
- throw new Error(`count() expects a collection or undefined, but got ${JSON.stringify(value)}`);
1070
- }
1071
- }
1072
- /**
1073
- * Returns a strings describing the type of the `Store` value, subscribing to changes of this type.
1074
- * Note: this currently also subscribes to changes of primitive values, so changing a value from 3 to 4
1075
- * would cause the scope to be rerun. This is not great, and may change in the future. This caveat does
1076
- * not apply to changes made *inside* an object, `Array` or `Map`.
1077
- *
1078
- * @returns Possible options: "undefined", "null", "boolean", "number", "string", "function", "array", "map" or "object".
1079
- */
1080
- getType() {
1081
- let value = this._observe();
1082
- return (value instanceof ObsCollection) ? value._getType() : (value === null ? "null" : typeof value);
1083
- }
1084
- /**
1085
- * Returns a new `Store` that will always hold either the value of `whenTrue` or the value
1086
- * of `whenFalse` depending on whether the original `Store` is truthy or not.
1087
- *
1088
- * @param whenTrue The value set to the return-`Store` while `this` is truthy. This can be
1089
- * any type of value. If it's a `Store`, the return-`Store` will reference the same
1090
- * data (so *no* deep copy will be made).
1091
- * @param whenFalse Like `whenTrue`, but for falsy values (false, undefined, null, 0, "").
1092
- * @returns A store holding the result value. The value will keep getting updated while
1093
- * the observe context from which `if()` was called remains active.
1094
- */
1095
- if(whenTrue, whenFalse) {
1096
- const result = new Store();
1097
- observe(() => {
1098
- const value = this.get() ? whenTrue : whenFalse;
1099
- result.set(value);
1100
- });
1101
- return result;
1102
- }
1103
- /**
1104
- * Sets the `Store` value to the given argument.
1105
- *
1106
- * When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
1107
- * case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
1108
- * be created, so that changes made through one `Store` will be reflected through the other. Be
1109
- * carefull not to create loops in your `Store` tree that way, as that would cause any future
1110
- * call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
1111
- *
1112
- * If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
1113
- *
1114
- * @returns The `Store` itself, for chaining other methods.
1115
- *
1116
- * @example
1117
- * ```
1118
- * let store = new Store() // Value is `undefined`
1119
- *
1120
- * store.set(6)
1121
- * store.get() // 6
1122
- *
1123
- * store.set({}) // Change value to an empty object
1124
- * store('a', 'b', 'c').set('d') // Create parent path as objects
1125
- * store.get() // {x: 6, a: {b: {c: 'd'}}}
1126
- *
1127
- * store.set(42) // Overwrites all of the above
1128
- * store.get() // 42
1129
- *
1130
- * store('x').set(6) // Throw Error (42 is not a collection)
1131
- * ```
1132
- */
1133
- set(newValue) {
1134
- this._materialize(true);
1135
- this._collection._setIndex(this._idx, newValue, true);
1136
- runImmediateQueue();
1137
- return this;
1138
- }
1139
- /** @internal */
1140
- _materialize(forWriting) {
1141
- if (!this._virtual)
1142
- return true;
1143
- let collection = this._collection;
1144
- let idx = this._idx;
1145
- for (let i = 0; i < this._virtual.length; i++) {
1146
- if (!forWriting && currentScope) {
1147
- if (collection._addObserver(idx, currentScope)) {
1148
- currentScope._cleaners.push(this);
1149
- }
1150
- }
1151
- let value = collection.rawGet(idx);
1152
- if (!(value instanceof ObsCollection)) {
1153
- // Throw an error if trying to index a primitive type
1154
- if (value !== undefined)
1155
- throw new Error(`While resolving ${JSON.stringify(this._virtual)}, found ${JSON.stringify(value)} at index ${i} instead of a collection.`);
1156
- // For reads, we'll just give up. We might reactively get another shot at this.
1157
- if (!forWriting)
1158
- return false;
1159
- // For writes, create a new collection.
1160
- value = new ObsObject();
1161
- collection.rawSet(idx, value);
1162
- collection.emitChange(idx, value, undefined);
1163
- }
1164
- collection = value;
1165
- const prop = this._virtual[i];
1166
- idx = collection._normalizeIndex(prop);
1167
- }
1168
- this._collection = collection;
1169
- this._idx = idx;
1170
- delete this._virtual;
1171
- return true;
1172
- }
1173
- /**
1174
- * Sets the `Store` to the given `mergeValue`, but without deleting any pre-existing
1175
- * items when a collection overwrites a similarly typed collection. This results in
1176
- * a deep merge.
1177
- *
1178
- * @returns The `Store` itself, for chaining other methods.
1179
- *
1180
- * @example
1181
- * ```
1182
- * let store = new Store({a: {x: 1}})
1183
- * store.merge({a: {y: 2}, b: 3})
1184
- * store.get() // {a: {x: 1, y: 2}, b: 3}
1185
- * ```
1186
- */
1187
- merge(mergeValue) {
1188
- this._materialize(true);
1189
- this._collection._setIndex(this._idx, mergeValue, false);
1190
- runImmediateQueue();
1191
- return this;
1192
- }
1193
- /**
1194
- * 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)
1195
- *
1196
- * @returns The `Store` itself, for chaining other methods.
1197
- *
1198
- * @example
1199
- * ```
1200
- * let store = new Store({a: 1, b: 2})
1201
- * store('a').delete()
1202
- * store.get() // {b: 2}
1203
- *
1204
- * store = new Store(['a','b','c'])
1205
- * store(1).delete()
1206
- * store.get() // ['a', undefined, 'c']
1207
- * store(2).delete()
1208
- * store.get() // ['a']
1209
- * store.delete()
1210
- * store.get() // undefined
1211
- * ```
1212
- */
1213
- delete() {
1214
- this._materialize(true);
1215
- this._collection._setIndex(this._idx, undefined, true);
1216
- runImmediateQueue();
1217
- return this;
1218
- }
1219
- /**
1220
- * Pushes a value to the end of the Array that is at the specified path in the store.
1221
- * If that store path is `undefined`, an Array is created first.
1222
- * The last argument is the value to be added, any earlier arguments indicate the path.
1223
- *
1224
- * @returns The index at which the item was appended.
1225
- * @throws TypeError when the store contains a primitive data type.
1226
- *
1227
- * @example
1228
- * ```
1229
- * let store = new Store()
1230
- * store.push(3) // Creates the array
1231
- * store.push(6)
1232
- * store.get() // [3,6]
1233
- *
1234
- * store = new Store({myArray: [1,2]})
1235
- * store('myArray').push(3)
1236
- * store.get() // {myArray: [1,2,3]}
1237
- * ```
1238
- */
1239
- push(newValue) {
1240
- this._materialize(true);
1241
- let obsArray = this._collection.rawGet(this._idx);
1242
- if (obsArray === undefined) {
1243
- obsArray = new ObsArray();
1244
- this._collection._setIndex(this._idx, obsArray, true);
1245
- }
1246
- else if (!(obsArray instanceof ObsArray)) {
1247
- throw new TypeError(`push() is only allowed for an array or undefined (which would become an array)`);
1248
- }
1249
- let newData = valueToData(newValue);
1250
- let pos = obsArray._data.length;
1251
- obsArray._data.push(newData);
1252
- obsArray.emitChange(pos, newData, undefined);
1253
- runImmediateQueue();
1254
- return pos;
1255
- }
1256
- /**
1257
- * {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
1258
- * value.
1259
- * @param func The function transforming the value.
1260
- * @returns The `Store` itself, for chaining other methods.
1261
- */
1262
- modify(func) {
1263
- this.set(func(this.peek()));
1264
- return this;
1265
- }
1266
- /** @internal */
1267
- _observe() {
1268
- if (!this._materialize(false))
1269
- return undefined;
1270
- if (currentScope) {
1271
- if (this._collection._addObserver(this._idx, currentScope)) {
1272
- currentScope._cleaners.push(this);
1273
- }
1274
- }
1275
- return this._collection.rawGet(this._idx);
1276
- }
1277
- /**
1278
- * Iterate the specified collection (Array, Map or object), running the given code block for each item.
1279
- * When items are added to the collection at some later point, the code block will be ran for them as well.
1280
- * When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
1281
- *
1282
- * @param renderer The function to be called for each item. It receives the item's `Store` object as its only argument.
1283
- * @param makeSortKey An optional function that, given an items `Store` object, returns a value to be sorted on.
1284
- * This value can be a number, a string, or an array containing a combination of both. When undefined is returned,
1285
- * the item is *not* rendered. If `makeSortKey` is not specified, the output will be sorted by `index()`.
1286
- */
1287
- onEach(renderer, makeSortKey = defaultMakeSortKey) {
1288
- if (!currentScope) { // Do this in a new top-level scope
1289
- _mount(undefined, () => this.onEach(renderer, makeSortKey), SimpleScope);
1290
- return;
1291
- }
1292
- let val = this._observe();
1293
- if (val instanceof ObsCollection) {
1294
- // Subscribe to changes using the specialized OnEachScope
1295
- let onEachScope = new OnEachScope(currentScope._parentElement, currentScope._lastChild || currentScope._precedingSibling, currentScope._queueOrder + 1, val, renderer, makeSortKey);
1296
- val._addObserver(ANY_INDEX, onEachScope);
1297
- currentScope._cleaners.push(onEachScope);
1298
- currentScope._lastChild = onEachScope;
1299
- onEachScope._renderInitial();
1300
- }
1301
- else if (val !== undefined) {
1302
- throw new Error(`onEach() attempted on a value that is neither a collection nor undefined`);
1303
- }
1304
- }
1305
- /**
1306
- * Derive a new `Store` from this `Store`, by reactively passing its value
1307
- * through the specified function.
1308
- * @param func Your function. It should accept a the input store's value, and return
1309
- * a result to be reactively set to the output store.
1310
- * @returns The output `Store`.
1311
- * @example
1312
- * ```javascript
1313
- * const store = new Store(21)
1314
- * const double = store.derive(v => v*2)
1315
- * double.get() // 42
1316
- *
1317
- * store.set(100)
1318
- * runQueue() // Or after a setTimeout 0, due to batching
1319
- * double.get() // 200
1320
- * ```
1321
- */
1322
- derive(func) {
1323
- let out = new Store();
1324
- observe(() => {
1325
- out.set(func(this.get()));
1326
- });
1327
- return out;
1328
- }
1329
- /**
1330
- * Applies a filter/map function on each item within the `Store`'s collection,
1331
- * and reactively manages the returned `Map` `Store` to hold any results.
1332
- *
1333
- * @param func - Function that transform the given store into an output value or
1334
- * `undefined` in case this value should be skipped:
1335
- *
1336
- * @returns - A array/map/object `Store` with the values returned by `func` and the
1337
- * corresponding keys from the original map, array or object `Store`.
1338
- *
1339
- * When items disappear from the `Store` or are changed in a way that `func` depends
1340
- * upon, the resulting items are removed from the output `Store` as well. When multiple
1341
- * input items produce the same output keys, this may lead to unexpected results.
1342
- */
1343
- map(func) {
1344
- let out = new Store();
1345
- observe(() => {
1346
- let t = this.getType();
1347
- out.set(t === 'array' ? [] : (t === 'object' ? {} : new Map()));
1348
- this.onEach((item) => {
1349
- let value = func(item);
1350
- if (value !== undefined) {
1351
- let key = item.index();
1352
- const ref = out(key);
1353
- ref.set(value);
1354
- clean(() => {
1355
- ref.delete();
1356
- });
1357
- }
1358
- });
1359
- });
1360
- return out;
1361
- }
1362
- /**
1363
- * Applies a filter/map function on each item within the `Store`'s collection,
1364
- * each of which can deliver any number of key/value pairs, and reactively manages the
1365
- * returned map `Store` to hold any results.
1366
- *
1367
- * @param func - Function that transform the given store into output values
1368
- * that can take one of the following forms:
1369
- * - an `Object` or a `Map`: Each key/value pair will be added to the output `Store`.
1370
- * - anything else: No key/value pairs are added to the output `Store`.
1371
- *
1372
- * @returns - A map `Store` with the key/value pairs returned by all `func` invocations.
1373
- *
1374
- * When items disappear from the `Store` or are changed in a way that `func` depends
1375
- * upon, the resulting items are removed from the output `Store` as well. When multiple
1376
- * input items produce the same output keys, this may lead to unexpected results.
1377
- */
1378
- multiMap(func) {
1379
- let out = new Store(new Map());
1380
- this.onEach((item) => {
1381
- let result = func(item);
1382
- let refs = [];
1383
- if (result.constructor === Object) {
1384
- for (let key in result) {
1385
- const ref = out(key);
1386
- ref.set(result[key]);
1387
- refs.push(ref);
1388
- }
1389
- }
1390
- else if (result instanceof Map) {
1391
- result.forEach((value, key) => {
1392
- const ref = out(key);
1393
- ref.set(value);
1394
- refs.push(ref);
1395
- });
1396
- }
1397
- else {
1398
- return;
1399
- }
1400
- if (refs.length) {
1401
- clean(() => {
1402
- for (let ref of refs) {
1403
- ref.delete();
1404
- }
1405
- });
1406
- }
1407
- });
1408
- return out;
1409
- }
1410
- /**
1411
- * Dump a live view of the `Store` tree as HTML text, `ul` and `li` nodes at
1412
- * the current mount position. Meant for debugging purposes.
1413
- * @returns The `Store` itself, for chaining other methods.
1414
- */
1415
- dump() {
1416
- let type = this.getType();
1417
- if (type === 'array' || type === 'object' || type === 'map') {
1418
- $({ text: `<${type}>` });
1419
- $('ul', () => {
1420
- this.onEach((sub) => {
1421
- $('li:' + JSON.stringify(sub.index()) + ": ", () => {
1422
- sub.dump();
1423
- });
1424
- });
1425
- });
1426
- }
1427
- else {
1428
- $({ text: JSON.stringify(this.get()) });
1429
- }
1430
- return this;
1431
- }
362
+
363
+ class OnEachScope extends Scope {
364
+ renderer;
365
+ makeSortKey;
366
+ parentElement = currentScope.parentElement;
367
+ prevSibling;
368
+ target;
369
+ byIndex = new Map;
370
+ sortedSet = new ReverseSortedSet("sortKey");
371
+ changedIndexes = new Set;
372
+ constructor(proxy, renderer, makeSortKey) {
373
+ super();
374
+ this.renderer = renderer;
375
+ this.makeSortKey = makeSortKey;
376
+ const target = this.target = proxy[TARGET_SYMBOL] || proxy;
377
+ subscribe(target, ANY_SYMBOL, this);
378
+ this.prevSibling = currentScope.getChildPrevSibling();
379
+ currentScope.lastChild = this;
380
+ currentScope.cleaners.push(this);
381
+ if (target instanceof Array) {
382
+ for (let i = 0;i < target.length; i++) {
383
+ if (target[i] !== undefined) {
384
+ new OnEachItemScope(this, i, false);
385
+ }
386
+ }
387
+ } else {
388
+ for (const key in target) {
389
+ if (target[key] !== undefined) {
390
+ new OnEachItemScope(this, key, false);
391
+ }
392
+ }
393
+ }
394
+ }
395
+ getPrecedingNode() {
396
+ return findLastNodeInPrevSiblings(this.prevSibling);
397
+ }
398
+ onChange(index, newData, oldData) {
399
+ if (!(this.target instanceof Array) || typeof index === "number")
400
+ this.changedIndexes.add(index);
401
+ queue(this);
402
+ }
403
+ queueRun() {
404
+ let indexes = this.changedIndexes;
405
+ this.changedIndexes = new Set;
406
+ for (let index of indexes) {
407
+ const oldScope = this.byIndex.get(index);
408
+ if (oldScope)
409
+ oldScope.remove();
410
+ if (this.target[index] === undefined) {
411
+ this.byIndex.delete(index);
412
+ } else {
413
+ new OnEachItemScope(this, index, true);
414
+ }
415
+ }
416
+ topRedrawScope = undefined;
417
+ }
418
+ delete() {
419
+ for (const scope of this.byIndex.values()) {
420
+ scope.delete();
421
+ }
422
+ this.byIndex.clear();
423
+ setTimeout(() => {
424
+ this.sortedSet.clear();
425
+ }, 1);
426
+ }
427
+ getLastNode() {
428
+ for (let scope of this.sortedSet) {
429
+ const node = scope.getActualLastNode();
430
+ if (node)
431
+ return node;
432
+ }
433
+ }
1432
434
  }
1433
- let onDestroyMap = new WeakMap();
1434
- function destroyWithClass(element, cls) {
1435
- element.classList.add(cls);
1436
- setTimeout(() => element.remove(), 2000);
435
+
436
+ class OnEachItemScope extends ContentScope {
437
+ parent;
438
+ itemIndex;
439
+ sortKey;
440
+ parentElement;
441
+ constructor(parent, itemIndex, topRedraw) {
442
+ super();
443
+ this.parent = parent;
444
+ this.itemIndex = itemIndex;
445
+ this.parentElement = parent.parentElement;
446
+ this.parent.byIndex.set(this.itemIndex, this);
447
+ this.lastChild = this;
448
+ if (topRedraw)
449
+ topRedrawScope = this;
450
+ this.redraw();
451
+ }
452
+ getPrecedingNode() {
453
+ this.parent.sortedSet.add(this);
454
+ const preScope = this.parent.sortedSet.prev(this);
455
+ if (preScope)
456
+ return findLastNodeInPrevSiblings(preScope.lastChild);
457
+ return this.parent.getPrecedingNode();
458
+ }
459
+ getLastNode() {
460
+ return this.getPrecedingNode();
461
+ }
462
+ getActualLastNode() {
463
+ let child = this.lastChild;
464
+ while (child && child !== this) {
465
+ if (child instanceof Node)
466
+ return child;
467
+ const node = child.getLastNode();
468
+ if (node)
469
+ return node;
470
+ child = child.getPrecedingNode();
471
+ }
472
+ }
473
+ queueRun() {
474
+ if (currentScope !== ROOT_SCOPE)
475
+ internalError(4);
476
+ if (this.sortKey !== undefined) {
477
+ const lastNode = this.getActualLastNode();
478
+ if (lastNode)
479
+ removeNodes(lastNode, this.getPrecedingNode());
480
+ }
481
+ this.delete();
482
+ this.lastChild = this;
483
+ topRedrawScope = this;
484
+ this.redraw();
485
+ topRedrawScope = undefined;
486
+ }
487
+ redraw() {
488
+ const value = optProxy(this.parent.target[this.itemIndex]);
489
+ let savedScope = currentScope;
490
+ currentScope = this;
491
+ let sortKey;
492
+ try {
493
+ if (this.parent.makeSortKey) {
494
+ let rawSortKey = this.parent.makeSortKey(value, this.itemIndex);
495
+ if (rawSortKey != null)
496
+ sortKey = rawSortKey instanceof Array ? rawSortKey.map(partToStr).join("") : rawSortKey;
497
+ } else {
498
+ sortKey = this.itemIndex;
499
+ }
500
+ if (typeof sortKey === "number")
501
+ sortKey = partToStr(sortKey);
502
+ if (this.sortKey !== sortKey) {
503
+ this.parent.sortedSet.remove(this);
504
+ this.sortKey = sortKey;
505
+ }
506
+ if (sortKey != null)
507
+ this.parent.renderer(value, this.itemIndex);
508
+ } catch (e) {
509
+ handleError(e, sortKey != null);
510
+ }
511
+ currentScope = savedScope;
512
+ }
513
+ getInsertAfterNode() {
514
+ if (this.sortKey == null)
515
+ internalError(1);
516
+ return findLastNodeInPrevSiblings(this.lastChild);
517
+ }
518
+ remove() {
519
+ if (this.sortKey !== undefined) {
520
+ const lastNode = this.getActualLastNode();
521
+ if (lastNode)
522
+ removeNodes(lastNode, this.getPrecedingNode());
523
+ this.parent.sortedSet.remove(this);
524
+ this.sortKey = undefined;
525
+ }
526
+ this.delete();
527
+ }
1437
528
  }
1438
- function addLeafNode(deepEl, node) {
1439
- if (deepEl === currentScope._parentElement) {
1440
- currentScope._addNode(node);
1441
- }
1442
- else {
1443
- deepEl.appendChild(node);
1444
- }
529
+ function addNode(node) {
530
+ const parentEl = currentScope.parentElement;
531
+ const prevNode = currentScope.getInsertAfterNode();
532
+ parentEl.insertBefore(node, prevNode ? prevNode.nextSibling : parentEl.firstChild);
533
+ currentScope.lastChild = node;
1445
534
  }
1446
- function applyBinding(_el, _key, store) {
1447
- if (store == null)
1448
- return;
1449
- if (!(store instanceof Store))
1450
- throw new Error(`Unexpect bind-argument: ${JSON.parse(store)}`);
1451
- const el = _el;
1452
- let onStoreChange;
1453
- let onInputChange;
1454
- let type = el.getAttribute('type');
1455
- let value = store.peek();
1456
- if (type === 'checkbox') {
1457
- if (value === undefined)
1458
- store.set(el.checked);
1459
- onStoreChange = value => el.checked = value;
1460
- onInputChange = () => store.set(el.checked);
1461
- }
1462
- else if (type === 'radio') {
1463
- if (value === undefined && el.checked)
1464
- store.set(el.value);
1465
- onStoreChange = value => el.checked = (value === el.value);
1466
- onInputChange = () => {
1467
- if (el.checked)
1468
- store.set(el.value);
1469
- };
1470
- }
1471
- else {
1472
- onInputChange = () => store.set(type === 'number' || type === 'range' ? (el.value === '' ? null : +el.value) : el.value);
1473
- if (value === undefined)
1474
- onInputChange();
1475
- onStoreChange = value => {
1476
- if (el.value !== value)
1477
- el.value = value;
1478
- };
1479
- }
1480
- observe(() => {
1481
- onStoreChange(store.get());
535
+ var ROOT_SCOPE = new RootScope;
536
+ var currentScope = ROOT_SCOPE;
537
+ var ANY_SYMBOL = Symbol("any");
538
+ var TARGET_SYMBOL = Symbol("target");
539
+ var subscribers = new WeakMap;
540
+ var peeking = 0;
541
+ function subscribe(target, index, observer = currentScope) {
542
+ if (observer === ROOT_SCOPE || peeking)
543
+ return;
544
+ let byTarget = subscribers.get(target);
545
+ if (!byTarget)
546
+ subscribers.set(target, byTarget = new Map);
547
+ if (index !== ANY_SYMBOL && byTarget.get(ANY_SYMBOL)?.has(observer))
548
+ return;
549
+ let byIndex = byTarget.get(index);
550
+ if (!byIndex)
551
+ byTarget.set(index, byIndex = new Set);
552
+ if (byIndex.has(observer))
553
+ return;
554
+ byIndex.add(observer);
555
+ if (observer === currentScope) {
556
+ currentScope.cleaners.push(byIndex);
557
+ } else {
558
+ currentScope.cleaners.push(function() {
559
+ byIndex.delete(observer);
560
+ });
561
+ }
562
+ }
563
+ function onEach(target, render, makeKey) {
564
+ if (!target || typeof target !== "object")
565
+ throw new Error("onEach requires an object");
566
+ target = target[TARGET_SYMBOL] || target;
567
+ new OnEachScope(target, render, makeKey);
568
+ }
569
+ function isObjEmpty(obj) {
570
+ for (let k in obj)
571
+ return false;
572
+ return true;
573
+ }
574
+ function isEmpty(proxied) {
575
+ const target = proxied[TARGET_SYMBOL] || proxied;
576
+ const scope = currentScope;
577
+ if (target instanceof Array) {
578
+ subscribe(target, "length", function(index, newData, oldData) {
579
+ if (!newData !== !oldData)
580
+ queue(scope);
1482
581
  });
1483
- el.addEventListener('input', onInputChange);
1484
- clean(() => {
1485
- el.removeEventListener('input', onInputChange);
582
+ return !target.length;
583
+ } else {
584
+ const result = isObjEmpty(target);
585
+ subscribe(target, ANY_SYMBOL, function(index, newData, oldData) {
586
+ if (result ? oldData === undefined : newData === undefined)
587
+ queue(scope);
1486
588
  });
589
+ return result;
590
+ }
1487
591
  }
1488
- const SPECIAL_PROPS = {
1489
- create: function (el, value) {
1490
- if (!showCreateTransitions)
1491
- return;
1492
- if (typeof value === 'function') {
1493
- value(el);
1494
- }
1495
- else {
1496
- el.classList.add(value);
1497
- (function () {
1498
- return __awaiter(this, void 0, void 0, function* () {
1499
- yield DOM_READ_PHASE;
1500
- el.offsetHeight;
1501
- yield DOM_WRITE_PHASE;
1502
- el.classList.remove(value);
1503
- });
1504
- })();
1505
- }
1506
- },
1507
- destroy: function (deepEl, value) {
1508
- onDestroyMap.set(deepEl, value);
1509
- },
1510
- html: function (deepEl, value) {
1511
- if (!value)
1512
- return;
1513
- let tmpParent = document.createElement(deepEl.tagName);
1514
- tmpParent.innerHTML = '' + value;
1515
- while (tmpParent.firstChild)
1516
- addLeafNode(deepEl, tmpParent.firstChild);
1517
- },
1518
- text: function (deepEl, value) {
1519
- if (value != null)
1520
- addLeafNode(deepEl, document.createTextNode(value));
1521
- },
1522
- element: function (deepEl, value) {
1523
- if (value == null)
1524
- return;
1525
- if (!(value instanceof Node))
1526
- throw new Error(`Unexpect element-argument: ${JSON.parse(value)}`);
1527
- addLeafNode(deepEl, value);
1528
- },
1529
- };
1530
- /**
1531
- * Modifies the *parent* DOM element in the current reactive scope, or adds
1532
- * new DOM elements to it.
1533
- *
1534
- * @param args - Arguments that define how to modify/create elements.
1535
- *
1536
- * ### String arguments
1537
- * Create new elements with optional classes and text content:
1538
- * ```js
1539
- * $('div.myClass') // <div class="myClass"></div>
1540
- * $('span.c1.c2:Hello') // <span class="c1 c2">Hello</span>
1541
- * $('p:Some text') // <p>Some text</p>
1542
- * $('.my-thing') // <div class="my-thing"></div>
1543
- * $('div', 'span', 'p.cls') // <div><span<p class="cls"></p></span></div>
1544
- * $(':Just some text!') // Just some text! (No new element, just a text node)
1545
- * ```
1546
- *
1547
- * ### Object arguments
1548
- * Set properties, attributes, events and special features:
1549
- * ```js
1550
- * // Classes (dot prefix)
1551
- * $('div', {'.active': true}) // Add class
1552
- * $('div', {'.hidden': false}) // Remove (or don't add) class
1553
- * $('div', {'.selected': myStore}) // Reactively add/remove class
1554
- *
1555
- * // Styles (dollar prefixed and camel-cased CSS properties)
1556
- * $('div', {$color: 'red'}) // style.color = 'red'
1557
- * $('div', {$marginTop: '10px'}) // style.marginTop = '10px'
1558
- * $('div', {$color: myColorStore}) // Reactively change color
1559
- *
1560
- * // Events (function values)
1561
- * $('button', {click: () => alert()}) // Add click handler
1562
- *
1563
- * // Properties (boolean values, `selectedIndex`, `value`)
1564
- * $('input', {disabled: true}) // el.disabled = true
1565
- * $('input', {value: 'test'}) // el.value = 'test'
1566
- * $('select', {selectedIndex: 2}) // el.selectedIndex = 2
1567
- *
1568
- * // Transitions
1569
- * $('div', {create: 'fade-in'}) // Add class on create
1570
- * $('div', {create: el => {...}}) // Run function on create
1571
- * $('div', {destroy: 'fade-out'}) // Add class before remove
1572
- * $('div', {destroy: el => {...}}) // Run function before remove
1573
- *
1574
- * // Content
1575
- * $('div', {html: '<b>Bold</b>'}) // Set innerHTML
1576
- * $('div', {text: 'Plain text'}) // Add text node
1577
- * const myElement = document.createElement('video')
1578
- * $('div', {element: myElement}) // Add existing DOM element
1579
- *
1580
- * // Regular attributes (everything else)
1581
- * $('div', {title: 'Info'}) // el.setAttribute('title', 'info')
1582
- * ```
1583
- *
1584
- * When a `Store` is passed as a value, a seperate observe-scope will
1585
- * be created for it, such that when the `Store` changes, only *that*
1586
- * UI property will need to be updated.
1587
- * So in the following example, when `colorStore` changes, only the
1588
- * `color` CSS property will be updated.
1589
- * ```js
1590
- * $('div', {
1591
- * '.active': activeStore, // Reactive class
1592
- * $color: colorStore, // Reactive style
1593
- * text: textStore // Reactive text
1594
- * })
1595
- * ```
1596
- *
1597
- * ### Two-way input binding
1598
- * Set the initial value of an <input> <textarea> or <select> to that
1599
- * of a `Store`, and then start reflecting user changes to the former
1600
- * in the latter.
1601
- * ```js
1602
- * $('input', {bind: myStore}) // Binds input.value
1603
- * ```
1604
- * This is a special case, as changes to the `Store` will *not* be
1605
- * reflected in the UI.
1606
- *
1607
- * ### Function arguments
1608
- * Create child scopes that re-run on observed `Store` changes:
1609
- * ```js
1610
- * $('div', () => {
1611
- * $(myStore.get() ? 'span' : 'p') // Reactive element type
1612
- * })
1613
- * ```
1614
- * When *only* a function is given, `$` behaves exactly like {@link Store.observe},
1615
- * except that it will only work when we're inside a `mount`.
1616
- *
1617
- * @throws {ScopeError} If called outside an observable scope.
1618
- * @throws {Error} If invalid arguments are provided.
1619
- */
1620
- export function $(...args) {
1621
- if (!currentScope || !currentScope._parentElement)
1622
- throw new ScopeError(true);
1623
- let deepEl = currentScope._parentElement;
1624
- for (let arg of args) {
1625
- if (arg == null || arg === false)
1626
- continue;
1627
- if (typeof arg === 'string') {
1628
- let text, classes;
1629
- const textPos = arg.indexOf(':');
1630
- if (textPos >= 0) {
1631
- text = arg.substring(textPos + 1);
1632
- if (textPos === 0) { // Just a string to add as text, no new node
1633
- addLeafNode(deepEl, document.createTextNode(text));
1634
- continue;
1635
- }
1636
- arg = arg.substring(0, textPos);
1637
- }
1638
- const classPos = arg.indexOf('.');
1639
- if (classPos >= 0) {
1640
- classes = arg.substring(classPos + 1).replaceAll('.', ' ');
1641
- arg = arg.substring(0, classPos);
1642
- }
1643
- if (arg.indexOf(' ') >= 0)
1644
- throw new Error(`Tag '${arg}' cannot contain space`);
1645
- const el = document.createElement(arg || 'div');
1646
- if (classes)
1647
- el.className = classes;
1648
- if (text)
1649
- el.textContent = text;
1650
- addLeafNode(deepEl, el);
1651
- deepEl = el;
1652
- }
1653
- else if (typeof arg === 'object') {
1654
- if (arg.constructor !== Object)
1655
- throw new Error(`Unexpected argument: ${arg}`);
1656
- for (const key in arg) {
1657
- const val = arg[key];
1658
- if (key === 'bind') { // Special case, as for this prop we *don't* want to resolve the Store to a value first.
1659
- applyBinding(deepEl, key, val);
1660
- }
1661
- else if (val instanceof Store) {
1662
- let childScope = new SetArgScope(deepEl, deepEl.lastChild, currentScope._queueOrder + 1, key, val);
1663
- childScope._install();
1664
- }
1665
- else {
1666
- applyArg(deepEl, key, val);
1667
- }
1668
- }
1669
- }
1670
- else if (typeof arg === 'function') {
1671
- if (deepEl === currentScope._parentElement) { // do what observe does
1672
- _mount(undefined, args[0], SimpleScope);
1673
- }
1674
- else { // new scope for a new node without any scope attached yet
1675
- let childScope = new SimpleScope(deepEl, deepEl.lastChild, currentScope._queueOrder + 1, arg);
1676
- childScope._install();
1677
- }
1678
- }
1679
- else {
1680
- throw new Error(`Unexpected argument: ${JSON.stringify(arg)}`);
1681
- }
1682
- }
592
+ function count(proxied) {
593
+ if (proxied instanceof Array)
594
+ return ref(proxied, "length");
595
+ const target = proxied[TARGET_SYMBOL] || proxied;
596
+ let cnt = 0;
597
+ for (let k in target)
598
+ if (target[k] !== undefined)
599
+ cnt++;
600
+ const result = proxy(cnt);
601
+ subscribe(target, ANY_SYMBOL, function(index, newData, oldData) {
602
+ if (oldData === newData) {} else if (oldData === undefined)
603
+ result.value = ++cnt;
604
+ else if (newData === undefined)
605
+ result.value = --cnt;
606
+ });
607
+ return result;
1683
608
  }
1684
- function applyArg(deepEl, key, value) {
1685
- if (key[0] === '.') { // CSS class(es)
1686
- const classes = key.substring(1).split('.');
1687
- if (value)
1688
- deepEl.classList.add(...classes);
609
+ function defaultEmitHandler(target, index, newData, oldData) {
610
+ if (newData === oldData && newData !== undefined)
611
+ return;
612
+ const byTarget = subscribers.get(target);
613
+ if (byTarget === undefined)
614
+ return;
615
+ for (const what of [index, ANY_SYMBOL]) {
616
+ let byIndex = byTarget.get(what);
617
+ if (byIndex) {
618
+ for (let observer of byIndex) {
619
+ if (typeof observer === "function")
620
+ observer(index, newData, oldData);
1689
621
  else
1690
- deepEl.classList.remove(...classes);
622
+ observer.onChange(index, newData, oldData);
623
+ }
1691
624
  }
1692
- else if (key[0] === '$') { // Style
1693
- const name = key.substring(1);
1694
- if (value == null || value === false)
1695
- deepEl.style[name] = '';
1696
- else
1697
- deepEl.style[name] = '' + value;
1698
- }
1699
- else if (key in SPECIAL_PROPS) { // Special property
1700
- SPECIAL_PROPS[key](deepEl, value);
1701
- }
1702
- else if (typeof value === 'function') { // Event listener
1703
- deepEl.addEventListener(key, value);
1704
- clean(() => deepEl.removeEventListener(key, value));
1705
- }
1706
- else if (value === true || value === false || key === 'value' || key === 'selectedIndex') { // DOM property
1707
- deepEl[key] = value;
1708
- }
1709
- else { // HTML attribute
1710
- deepEl.setAttribute(key, value);
625
+ }
626
+ }
627
+ var emit = defaultEmitHandler;
628
+ var objectHandler = {
629
+ get(target, prop) {
630
+ if (prop === TARGET_SYMBOL)
631
+ return target;
632
+ subscribe(target, prop);
633
+ return optProxy(target[prop]);
634
+ },
635
+ set(target, prop, newData) {
636
+ if (typeof newData === "object" && newData)
637
+ newData = newData[TARGET_SYMBOL] || newData;
638
+ const oldData = target[prop];
639
+ if (newData !== oldData) {
640
+ target[prop] = newData;
641
+ emit(target, prop, newData, oldData);
642
+ runImmediateQueue();
1711
643
  }
644
+ return true;
645
+ },
646
+ deleteProperty(target, prop) {
647
+ const old = target[prop];
648
+ delete target[prop];
649
+ emit(target, prop, undefined, old);
650
+ runImmediateQueue();
651
+ return true;
652
+ },
653
+ has(target, prop) {
654
+ const result = prop in target;
655
+ subscribe(target, prop);
656
+ return result;
657
+ },
658
+ ownKeys(target) {
659
+ subscribe(target, ANY_SYMBOL);
660
+ return Reflect.ownKeys(target);
661
+ }
662
+ };
663
+ function arraySet(target, prop, newData) {
664
+ if (typeof newData === "object" && newData)
665
+ newData = newData[TARGET_SYMBOL] || newData;
666
+ const oldData = target[prop];
667
+ if (newData !== oldData) {
668
+ let oldLength = target.length;
669
+ if (prop === "length") {
670
+ target.length = newData;
671
+ for (let i = newData;i < oldLength; i++) {
672
+ emit(target, i, undefined, target[i]);
673
+ }
674
+ } else {
675
+ const intProp = parseInt(prop);
676
+ if (intProp.toString() === prop)
677
+ prop = intProp;
678
+ target[prop] = newData;
679
+ emit(target, prop, newData, oldData);
680
+ }
681
+ if (target.length !== oldLength) {
682
+ emit(target, "length", target.length, oldLength);
683
+ }
684
+ runImmediateQueue();
685
+ }
686
+ return true;
687
+ }
688
+ var arrayHandler = {
689
+ get(target, prop) {
690
+ if (prop === TARGET_SYMBOL)
691
+ return target;
692
+ let subProp = prop;
693
+ if (typeof prop !== "symbol") {
694
+ const intProp = parseInt(prop);
695
+ if (intProp.toString() === prop)
696
+ subProp = intProp;
697
+ }
698
+ subscribe(target, subProp);
699
+ return optProxy(target[prop]);
700
+ },
701
+ set: arraySet,
702
+ deleteProperty(target, prop) {
703
+ return arraySet(target, prop, undefined);
704
+ }
705
+ };
706
+ var proxyMap = new WeakMap;
707
+ function optProxy(value) {
708
+ if (typeof value !== "object" || !value || value[TARGET_SYMBOL] !== undefined) {
709
+ return value;
710
+ }
711
+ let proxied = proxyMap.get(value);
712
+ if (proxied)
713
+ return proxied;
714
+ proxied = new Proxy(value, value instanceof Array ? arrayHandler : objectHandler);
715
+ proxyMap.set(value, proxied);
716
+ return proxied;
717
+ }
718
+ function proxy(target) {
719
+ return optProxy(typeof target === "object" && target !== null ? target : { value: target });
720
+ }
721
+ function unproxy(target) {
722
+ return target ? target[TARGET_SYMBOL] || target : target;
723
+ }
724
+ var onDestroyMap = new WeakMap;
725
+ function destroyWithClass(element, cls) {
726
+ const classes = cls.split(".").filter((c) => c);
727
+ element.classList.add(...classes);
728
+ setTimeout(() => element.remove(), 2000);
729
+ }
730
+ function copy(dst, src, flags = 0) {
731
+ copyRecurse(dst, src, flags);
732
+ runImmediateQueue();
733
+ }
734
+ var MERGE = 1;
735
+ var SHALLOW = 2;
736
+ var COPY_SUBSCRIBE = 32;
737
+ var COPY_EMIT = 64;
738
+ function clone(src, flags = 0) {
739
+ const dst = Object.create(Object.getPrototypeOf(src));
740
+ copyRecurse(dst, src, flags);
741
+ return dst;
742
+ }
743
+ function copyRecurse(dst, src, flags) {
744
+ let unproxied = dst[TARGET_SYMBOL];
745
+ if (unproxied) {
746
+ dst = unproxied;
747
+ flags |= COPY_EMIT;
748
+ }
749
+ unproxied = src[TARGET_SYMBOL];
750
+ if (unproxied) {
751
+ src = unproxied;
752
+ if (currentScope !== ROOT_SCOPE && !peeking)
753
+ flags |= COPY_SUBSCRIBE;
754
+ }
755
+ if (flags & COPY_SUBSCRIBE)
756
+ subscribe(src, ANY_SYMBOL);
757
+ if (src instanceof Array) {
758
+ if (!(dst instanceof Array))
759
+ throw new Error("Cannot copy array into object");
760
+ const dstLen = dst.length;
761
+ const srcLen = src.length;
762
+ for (let i = 0;i < srcLen; i++) {
763
+ copyValue(dst, src, i, flags);
764
+ }
765
+ if (srcLen !== dstLen) {
766
+ if (flags & COPY_EMIT) {
767
+ for (let i = srcLen;i < dstLen; i++) {
768
+ const old = dst[i];
769
+ dst[i] = undefined;
770
+ emit(dst, i, undefined, old);
771
+ }
772
+ dst.length = srcLen;
773
+ emit(dst, "length", srcLen, dstLen);
774
+ } else {
775
+ dst.length = srcLen;
776
+ }
777
+ }
778
+ } else {
779
+ for (let k in src) {
780
+ copyValue(dst, src, k, flags);
781
+ }
782
+ if (!(flags & MERGE)) {
783
+ for (let k in dst) {
784
+ if (!(k in src)) {
785
+ const old = dst[k];
786
+ delete dst[k];
787
+ if (flags & COPY_EMIT && old !== undefined) {
788
+ emit(dst, k, undefined, old);
789
+ }
790
+ }
791
+ }
792
+ }
793
+ }
794
+ }
795
+ function copyValue(dst, src, index, flags) {
796
+ let dstValue = dst[index];
797
+ let srcValue = src[index];
798
+ if (srcValue !== dstValue) {
799
+ if (srcValue && dstValue && typeof srcValue === "object" && typeof dstValue === "object" && (srcValue.constructor === dstValue.constructor || flags & MERGE && dstValue instanceof Array)) {
800
+ copyRecurse(dstValue, srcValue, flags);
801
+ return;
802
+ }
803
+ if (!(flags & SHALLOW) && srcValue && typeof srcValue === "object") {
804
+ let copy2 = Object.create(Object.getPrototypeOf(srcValue));
805
+ copyRecurse(copy2, srcValue, 0);
806
+ srcValue = copy2;
807
+ }
808
+ const old = dst[index];
809
+ if (flags & MERGE && srcValue == null)
810
+ delete dst[index];
811
+ else
812
+ dst[index] = srcValue;
813
+ if (flags & COPY_EMIT)
814
+ emit(dst, index, srcValue, old);
815
+ }
816
+ }
817
+ var refHandler = {
818
+ get(target, prop) {
819
+ if (prop === TARGET_SYMBOL) {
820
+ return ref(unproxy(target.proxy), target.index);
821
+ }
822
+ if (prop === "value") {
823
+ return target.proxy[target.index];
824
+ }
825
+ },
826
+ set(target, prop, value) {
827
+ if (prop === "value") {
828
+ target.proxy[target.index] = value;
829
+ return true;
830
+ }
831
+ return false;
832
+ }
833
+ };
834
+ function ref(target, index) {
835
+ return new Proxy({ proxy: target, index }, refHandler);
836
+ }
837
+ function applyBind(_el, target) {
838
+ const el = _el;
839
+ let onProxyChange;
840
+ let onInputChange;
841
+ let type = el.getAttribute("type");
842
+ let value = unproxy(target).value;
843
+ if (type === "checkbox") {
844
+ if (value === undefined)
845
+ target.value = el.checked;
846
+ onProxyChange = () => el.checked = target.value;
847
+ onInputChange = () => target.value = el.checked;
848
+ } else if (type === "radio") {
849
+ if (value === undefined && el.checked)
850
+ target.value = el.value;
851
+ onProxyChange = () => el.checked = target.value === el.value;
852
+ onInputChange = () => {
853
+ if (el.checked)
854
+ target.value = el.value;
855
+ };
856
+ } else {
857
+ onInputChange = () => target.value = type === "number" || type === "range" ? el.value === "" ? null : +el.value : el.value;
858
+ if (value === undefined)
859
+ onInputChange();
860
+ onProxyChange = () => el.value = target.value;
861
+ }
862
+ observe(onProxyChange);
863
+ el.addEventListener("input", onInputChange);
864
+ clean(() => {
865
+ el.removeEventListener("input", onInputChange);
866
+ });
867
+ }
868
+ var SPECIAL_PROPS = {
869
+ create: function(value) {
870
+ const el = currentScope.parentElement;
871
+ if (currentScope !== topRedrawScope)
872
+ return;
873
+ if (typeof value === "function") {
874
+ value(el);
875
+ } else {
876
+ const classes = value.split(".").filter((c) => c);
877
+ el.classList.add(...classes);
878
+ (async function() {
879
+ el.offsetHeight;
880
+ el.classList.remove(...classes);
881
+ })();
882
+ }
883
+ },
884
+ destroy: function(value) {
885
+ const el = currentScope.parentElement;
886
+ onDestroyMap.set(el, value);
887
+ },
888
+ html: function(value) {
889
+ let tmpParent = document.createElement(currentScope.parentElement.tagName);
890
+ tmpParent.innerHTML = "" + value;
891
+ while (tmpParent.firstChild)
892
+ addNode(tmpParent.firstChild);
893
+ },
894
+ text: function(value) {
895
+ addNode(document.createTextNode(value));
896
+ },
897
+ element: function(value) {
898
+ if (!(value instanceof Node))
899
+ throw new Error(`Unexpected element-argument: ${JSON.parse(value)}`);
900
+ addNode(value);
901
+ }
902
+ };
903
+ function $(...args) {
904
+ let savedCurrentScope;
905
+ let err;
906
+ for (let arg of args) {
907
+ if (arg == null || arg === false)
908
+ continue;
909
+ if (typeof arg === "string") {
910
+ let text, classes;
911
+ const textPos = arg.indexOf(":");
912
+ if (textPos >= 0) {
913
+ text = arg.substring(textPos + 1);
914
+ arg = arg.substring(0, textPos);
915
+ }
916
+ const classPos = arg.indexOf(".");
917
+ if (classPos >= 0) {
918
+ classes = arg.substring(classPos + 1);
919
+ arg = arg.substring(0, classPos);
920
+ }
921
+ if (arg === "") {
922
+ if (text)
923
+ addNode(document.createTextNode(text));
924
+ if (classes) {
925
+ const el = currentScope.parentElement;
926
+ el.classList.add(...classes.split("."));
927
+ if (!savedCurrentScope) {
928
+ clean(() => el.classList.remove(...classes.split(".")));
929
+ }
930
+ }
931
+ } else if (arg.indexOf(" ") >= 0) {
932
+ err = `Tag '${arg}' cannot contain space`;
933
+ break;
934
+ } else {
935
+ const el = document.createElement(arg);
936
+ if (classes)
937
+ el.className = classes.replaceAll(".", " ");
938
+ if (text)
939
+ el.textContent = text;
940
+ addNode(el);
941
+ if (!savedCurrentScope) {
942
+ savedCurrentScope = currentScope;
943
+ }
944
+ let newScope = new ChainedScope(el, true);
945
+ newScope.lastChild = el.lastChild || undefined;
946
+ if (topRedrawScope === currentScope)
947
+ topRedrawScope = newScope;
948
+ currentScope = newScope;
949
+ }
950
+ } else if (typeof arg === "object") {
951
+ if (arg.constructor !== Object) {
952
+ err = `Unexpected argument: ${arg}`;
953
+ break;
954
+ }
955
+ for (const key in arg) {
956
+ const val = arg[key];
957
+ applyArg(key, val);
958
+ }
959
+ } else if (typeof arg === "function") {
960
+ new RegularScope(currentScope.parentElement, arg);
961
+ } else {
962
+ err = `Unexpected argument: ${arg}`;
963
+ break;
964
+ }
965
+ }
966
+ if (savedCurrentScope) {
967
+ currentScope = savedCurrentScope;
968
+ }
969
+ if (err)
970
+ throw new Error(err);
971
+ }
972
+ var cssCount = 0;
973
+ function insertCss(style, global = false) {
974
+ const prefix = global ? "" : ".AbdStl" + ++cssCount;
975
+ let css = styleToCss(style, prefix);
976
+ if (css)
977
+ $("style:" + css);
978
+ return prefix;
979
+ }
980
+ function styleToCss(style, prefix) {
981
+ let props = "";
982
+ let rules = "";
983
+ for (const kOr in style) {
984
+ const v = style[kOr];
985
+ for (const k of kOr.split(/, ?/g)) {
986
+ if (v && typeof v === "object") {
987
+ if (k.startsWith("@")) {
988
+ rules += k + `{
989
+ ` + styleToCss(v, prefix) + `}
990
+ `;
991
+ } else {
992
+ rules += styleToCss(v, k.includes("&") ? k.replace(/&/g, prefix) : prefix + " " + k);
993
+ }
994
+ } else {
995
+ props += k.replace(/[A-Z]/g, (letter) => "-" + letter.toLowerCase()) + ":" + v + ";";
996
+ }
997
+ }
998
+ }
999
+ if (props)
1000
+ rules = (prefix.trimStart() || "*") + "{" + props + `}
1001
+ ` + rules;
1002
+ return rules;
1003
+ }
1004
+ function applyArg(key, value) {
1005
+ const el = currentScope.parentElement;
1006
+ if (typeof value === "object" && value !== null && value[TARGET_SYMBOL]) {
1007
+ if (key === "bind") {
1008
+ applyBind(el, value);
1009
+ } else {
1010
+ new SetArgScope(el, key, value);
1011
+ }
1012
+ } else if (key[0] === ".") {
1013
+ const classes = key.substring(1).split(".");
1014
+ if (value)
1015
+ el.classList.add(...classes);
1016
+ else
1017
+ el.classList.remove(...classes);
1018
+ } else if (key[0] === "$") {
1019
+ const name = key.substring(1);
1020
+ if (value == null || value === false)
1021
+ el.style[name] = "";
1022
+ else
1023
+ el.style[name] = "" + value;
1024
+ } else if (value == null) {} else if (key in SPECIAL_PROPS) {
1025
+ SPECIAL_PROPS[key](value);
1026
+ } else if (typeof value === "function") {
1027
+ el.addEventListener(key, value);
1028
+ clean(() => el.removeEventListener(key, value));
1029
+ } else if (value === true || value === false || key === "value" || key === "selectedIndex") {
1030
+ el[key] = value;
1031
+ } else {
1032
+ el.setAttribute(key, value);
1033
+ }
1712
1034
  }
1713
1035
  function defaultOnError(error) {
1714
- console.error('Error while in Aberdeen render:', error);
1715
- return true;
1036
+ console.error("Error while in Aberdeen render:", error);
1037
+ return true;
1716
1038
  }
1717
- let onError = defaultOnError;
1718
- /**
1719
- * Set a custome error handling function, thast is called when an error occurs during rendering
1720
- * while in a reactive scope. The default implementation logs the error to the console, and then
1721
- * just returns `true`, which causes an 'Error' message to be displayed in the UI. When this function
1722
- * returns `false`, the error is suppressed. This mechanism exists because rendering errors can occur
1723
- * at any time, not just synchronous when making a call to Aberdeen, thus normal exception handling
1724
- * is not always possible.
1725
- *
1726
- * @param handler The handler function, getting an `Error` as its argument, and returning `false`
1727
- * if it does *not* want an error message to be added to the DOM.
1728
- * When `handler is `undefined`, the default error handling will be reinstated.
1729
- *
1730
- * @example
1731
- * ```javascript
1732
- * //
1733
- * setErrorHandler(error => {
1734
- * // Tell our developers about the problem.
1735
- * fancyErrorLogger(error)
1736
- * // Add custom error message to the DOM.
1737
- * try {
1738
- * $('.error:Sorry, something went wrong!')
1739
- * } catch() {} // In case there is no parent element.
1740
- * // Don't add default error message to the DOM.
1741
- * return false
1742
- * })
1743
- * ```
1744
- */
1745
- export function setErrorHandler(handler) {
1746
- onError = handler || defaultOnError;
1039
+ var onError = defaultOnError;
1040
+ function setErrorHandler(handler) {
1041
+ onError = handler || defaultOnError;
1747
1042
  }
1748
- /**
1749
- * Return the browser Element that nodes would be rendered to at this point.
1750
- * NOTE: Manually changing the DOM is not recommended in most cases. There is
1751
- * usually a better, declarative way. Although there are no hard guarantees on
1752
- * how your changes interact with Aberdeen, in most cases results will not be
1753
- * terribly surprising. Be careful within the parent element of onEach() though.
1754
- */
1755
- export function getParentElement() {
1756
- if (!currentScope || !currentScope._parentElement)
1757
- throw new ScopeError(true);
1758
- return currentScope._parentElement;
1043
+ function getParentElement() {
1044
+ return currentScope.parentElement;
1759
1045
  }
1760
- /**
1761
- * Register a function that is to be executed right before the current reactive scope
1762
- * disappears or redraws.
1763
- * @param clean - The function to be executed.
1764
- */
1765
- export function clean(clean) {
1766
- if (!currentScope)
1767
- throw new ScopeError(false);
1768
- currentScope._cleaners.push({ _clean: clean });
1046
+ function clean(cleaner) {
1047
+ currentScope.cleaners.push(cleaner);
1769
1048
  }
1770
- /**
1771
- * Reactively run a function, meaning the function will rerun when any `Store` that was read
1772
- * during its execution is updated.
1773
- * Calls to `observe` can be nested, such that changes to `Store`s read by the inner function do
1774
- * no cause the outer function to rerun.
1775
- *
1776
- * @param func - The function to be (repeatedly) executed.
1777
- * @returns The mount id (usable for `unmount`) if this is a top-level observe.
1778
- * @example
1779
- * ```
1780
- * let number = new Store(0)
1781
- * let doubled = new Store()
1782
- * setInterval(() => number.set(0|Math.random()*100)), 1000)
1783
- *
1784
- * observe(() => {
1785
- * doubled.set(number.get() * 2)
1786
- * })
1787
- *
1788
- * observe(() => {
1789
- * console.log(doubled.get())
1790
- * })
1791
- */
1792
- export function observe(func) {
1793
- return _mount(undefined, func, SimpleScope);
1049
+ function observe(func) {
1050
+ return new ResultScope(currentScope.parentElement, func).result;
1794
1051
  }
1795
- /**
1796
- * Like `observe`, but instead of deferring running the observer function until
1797
- * a setTimeout 0, run it immediately and synchronously when a change to one of
1798
- * the observed `Store`s is made. Use this sparingly, as this prevents Aberdeen
1799
- * from doing the usual batching and smart ordering of observers, leading to
1800
- * performance problems and observing of 'weird' partial states.
1801
- * @param func The function to be (repeatedly) executed.
1802
- * @returns The mount id (usable for `unmount`) if this is a top-level observe.
1803
- */
1804
- export function immediateObserve(func) {
1805
- return _mount(undefined, func, ImmediateScope);
1052
+ function immediateObserve(func) {
1053
+ new ImmediateScope(currentScope.parentElement, func);
1806
1054
  }
1807
- /**
1808
- * Reactively run the function, adding any DOM-elements created using {@link $} to the given parent element.
1809
-
1810
- * @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
1811
- * @param parentElement - A DOM element that will be used as the parent element for calls to `$`.
1812
- * @returns The mount id (usable for `unmount`) if this is a top-level mount.
1813
- *
1814
- * @example
1815
- * ```
1816
- * let store = new Store(0)
1817
- * setInterval(() => store.modify(v => v+1), 1000)
1818
- *
1819
- * mount(document.body, () => {
1820
- * $(`h2:${store.get()} seconds have passed`)
1821
- * })
1822
- * ```
1823
- *
1824
- * An example nesting {@link Store.observe} within `mount`:
1825
- * ```
1826
- * let selected = new Store(0)
1827
- * let colors = new Store(new Map())
1828
- *
1829
- * mount(document.body, () => {
1830
- * // This function will never rerun (as it does not read any `Store`s)
1831
- * $('button:<<', {click: () => selected.modify(n => n-1)})
1832
- * $('button:>>', {click: () => selected.modify(n => n+1)})
1833
- *
1834
- * observe(() => {
1835
- * // This will rerun whenever `selected` changes, recreating the <h2> and <input>.
1836
- * $('h2', {text: '#' + selected.get()})
1837
- * $('input', {type: 'color', value: '#ffffff' bind: colors(selected.get())})
1838
- * })
1839
- *
1840
- * observe(() => {
1841
- * // This function will rerun when `selected` or the selected color changes.
1842
- * // It will change the <body> background-color.
1843
- * $({$backgroundColor: colors.get(selected.get()) || 'white'})
1844
- * })
1845
- * })
1846
- * ```
1847
- */
1848
- export function mount(parentElement, func) {
1849
- for (let scope of topScopes.values()) {
1850
- if (parentElement === scope._parentElement) {
1851
- throw new Error("Only a single mount per parent element");
1852
- }
1853
- }
1854
- return _mount(parentElement, func, SimpleScope);
1055
+ function mount(parentElement, func) {
1056
+ new MountScope(parentElement, func);
1855
1057
  }
1856
- let maxTopScopeId = 0;
1857
- const topScopes = new Map();
1858
- function _mount(parentElement, func, MountScope) {
1859
- let scope;
1860
- if (parentElement || !currentScope) {
1861
- scope = new MountScope(parentElement, undefined, 0, func);
1862
- }
1863
- else {
1864
- scope = new MountScope(currentScope._parentElement, currentScope._lastChild || currentScope._precedingSibling, currentScope._queueOrder + 1, func);
1865
- currentScope._lastChild = scope;
1866
- }
1867
- // Do the initial run
1868
- scope._update();
1869
- // Add it to our list of cleaners. Even if `scope` currently has
1870
- // no cleaners, it may get them in a future refresh.
1871
- if (currentScope) {
1872
- currentScope._cleaners.push(scope);
1873
- }
1874
- else {
1875
- topScopes.set(++maxTopScopeId, scope);
1876
- return maxTopScopeId;
1877
- }
1058
+ function unmountAll() {
1059
+ ROOT_SCOPE.remove();
1060
+ cssCount = 0;
1878
1061
  }
1879
- /**
1880
- * Unmount one specific or all top-level mounts or observes, meaning those that were created outside of the scope
1881
- * of any other mount or observe.
1882
- * @param id Optional mount number (as returned by `mount`, `observe` or `immediateObserve`). If `undefined`, unmount all.
1883
- */
1884
- export function unmount(id) {
1885
- if (id == null) {
1886
- for (let scope of topScopes.values())
1887
- scope._remove();
1888
- topScopes.clear();
1889
- }
1890
- else {
1891
- let scope = topScopes.get(id);
1892
- if (!scope)
1893
- throw new Error("No such mount " + id);
1894
- topScopes.delete(id);
1895
- scope._remove();
1896
- }
1062
+ function peek(func) {
1063
+ peeking++;
1064
+ try {
1065
+ return func();
1066
+ } finally {
1067
+ peeking--;
1068
+ }
1897
1069
  }
1898
- /** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
1899
- *
1900
- * @param func Function to be executed immediately.
1901
- * @returns Whatever `func()` returns.
1902
- * @example
1903
- * ```
1904
- * import {Store, peek, text} from aberdeen
1905
- *
1906
- * let store = new Store(['a', 'b', 'c'])
1907
- *
1908
- * mount(document.body, () => {
1909
- * // Prevent rerender when store changes
1910
- * let msg = peek(() => `Store has ${store.count()} elements, and the first is ${store.get(0)}`))
1911
- * text(msg)
1912
- * })
1913
- * ```
1914
- *
1915
- * In the above example `store.get(0)` could be replaced with `store.peek(0)` to achieve the
1916
- * same result without `peek()` wrapping everything. There is no non-subscribing equivalent
1917
- * for `count()` however.
1918
- */
1919
- export function peek(func) {
1920
- let savedScope = currentScope;
1921
- currentScope = undefined;
1922
- try {
1923
- return func();
1924
- }
1925
- finally {
1926
- currentScope = savedScope;
1927
- }
1070
+ function map(source, func) {
1071
+ let out = optProxy(source instanceof Array ? [] : {});
1072
+ onEach(source, (item, key) => {
1073
+ let value = func(item, key);
1074
+ if (value !== undefined) {
1075
+ out[key] = value;
1076
+ clean(() => {
1077
+ delete out[key];
1078
+ });
1079
+ }
1080
+ });
1081
+ return out;
1928
1082
  }
1929
- /*
1930
- * Helper functions
1931
- */
1932
- function valueToData(value) {
1933
- if (value instanceof Store) {
1934
- // When a Store is passed pointing at a collection, a reference
1935
- // is made to that collection.
1936
- return value._observe();
1937
- }
1938
- else if (typeof value !== "object" || !value) {
1939
- // Simple data types
1940
- return value;
1941
- }
1942
- else if (value instanceof Map) {
1943
- let result = new ObsMap();
1944
- value.forEach((v, k) => {
1945
- let d = valueToData(v);
1946
- if (d !== undefined)
1947
- result.rawSet(k, d);
1083
+ function multiMap(source, func) {
1084
+ let out = optProxy({});
1085
+ onEach(source, (item, key) => {
1086
+ let pairs = func(item, key);
1087
+ if (pairs) {
1088
+ for (let key2 in pairs)
1089
+ out[key2] = pairs[key2];
1090
+ clean(() => {
1091
+ for (let key2 in pairs)
1092
+ delete out[key2];
1093
+ });
1094
+ }
1095
+ });
1096
+ return out;
1097
+ }
1098
+ function partition(source, func) {
1099
+ const unproxiedOut = {};
1100
+ const out = proxy(unproxiedOut);
1101
+ onEach(source, (item, key) => {
1102
+ let rsp = func(item, key);
1103
+ if (rsp != null) {
1104
+ const buckets = rsp instanceof Array ? rsp : [rsp];
1105
+ if (buckets.length) {
1106
+ for (let bucket of buckets) {
1107
+ if (unproxiedOut[bucket])
1108
+ out[bucket][key] = item;
1109
+ else
1110
+ out[bucket] = { [key]: item };
1111
+ }
1112
+ clean(() => {
1113
+ for (let bucket of buckets) {
1114
+ delete out[bucket][key];
1115
+ if (isObjEmpty(unproxiedOut[bucket]))
1116
+ delete out[bucket];
1117
+ }
1948
1118
  });
1949
- return result;
1950
- }
1951
- else if (value instanceof Array) {
1952
- let result = new ObsArray();
1953
- for (let i = 0; i < value.length; i++) {
1954
- let d = valueToData(value[i]);
1955
- if (d !== undefined)
1956
- result.rawSet(i, d);
1957
- }
1958
- return result;
1959
- }
1960
- else if (value.constructor === Object) {
1961
- // A plain (literal) object
1962
- let result = new ObsObject();
1963
- for (let k in value) {
1964
- let d = valueToData(value[k]);
1965
- if (d !== undefined)
1966
- result.rawSet(k, d);
1967
- }
1968
- return result;
1969
- }
1970
- else {
1971
- // Any other type of object (including ObsCollection)
1972
- return value;
1119
+ }
1973
1120
  }
1121
+ });
1122
+ return out;
1974
1123
  }
1975
- function defaultMakeSortKey(store) {
1976
- return store.index();
1124
+ function dump(data) {
1125
+ if (data && typeof data === "object") {
1126
+ $({ text: data instanceof Array ? "<array>" : "<object>" });
1127
+ $("ul", () => {
1128
+ onEach(data, (value, key) => {
1129
+ $("li:" + JSON.stringify(key) + ": ", () => {
1130
+ dump(value);
1131
+ });
1132
+ });
1133
+ });
1134
+ } else {
1135
+ $({ text: JSON.stringify(data) });
1136
+ }
1137
+ return data;
1977
1138
  }
1978
- /* c8 ignore start */
1979
1139
  function internalError(code) {
1980
- throw new Error("Aberdeen internal error " + code);
1140
+ throw new Error("Aberdeen internal error " + code);
1981
1141
  }
1982
- /* c8 ignore end */
1983
1142
  function handleError(e, showMessage) {
1984
- try {
1985
- if (onError(e) === false)
1986
- showMessage = false;
1987
- }
1988
- catch (_a) { }
1989
- if (showMessage && (currentScope === null || currentScope === void 0 ? void 0 : currentScope._parentElement))
1990
- $('.aberdeen-error:Error');
1991
- }
1992
- class ScopeError extends Error {
1993
- constructor(mount) {
1994
- super(`Operation not permitted outside of ${mount ? "a mount" : "an observe"}() scope`);
1995
- }
1143
+ try {
1144
+ if (onError(e) === false)
1145
+ showMessage = false;
1146
+ } catch (e2) {
1147
+ console.error(e2);
1148
+ }
1149
+ try {
1150
+ if (showMessage)
1151
+ $("div.aberdeen-error:Error");
1152
+ } catch {}
1996
1153
  }
1997
- /** @internal */
1998
- export function withEmitHandler(handler, func) {
1999
- const oldEmitHandler = ObsCollection.prototype.emitChange;
2000
- ObsCollection.prototype.emitChange = handler;
2001
- try {
2002
- func();
2003
- }
2004
- finally {
2005
- ObsCollection.prototype.emitChange = oldEmitHandler;
2006
- }
1154
+ function withEmitHandler(handler, func) {
1155
+ const oldEmitHandler = emit;
1156
+ emit = handler;
1157
+ try {
1158
+ func();
1159
+ } finally {
1160
+ emit = oldEmitHandler;
1161
+ }
2007
1162
  }
2008
- // @ts-ignore
2009
- // c8 ignore next
2010
1163
  if (!String.prototype.replaceAll)
2011
- String.prototype.replaceAll = function (from, to) { return this.split(from).join(to); };
2012
- //# sourceMappingURL=aberdeen.js.map
1164
+ String.prototype.replaceAll = function(from, to) {
1165
+ return this.split(from).join(to);
1166
+ };
1167
+ export {
1168
+ withEmitHandler,
1169
+ unproxy,
1170
+ unmountAll,
1171
+ setErrorHandler,
1172
+ runQueue,
1173
+ ref,
1174
+ proxy,
1175
+ peek,
1176
+ partition,
1177
+ onEach,
1178
+ observe,
1179
+ multiMap,
1180
+ mount,
1181
+ map,
1182
+ isEmpty,
1183
+ invertString,
1184
+ insertCss,
1185
+ immediateObserve,
1186
+ getParentElement,
1187
+ dump,
1188
+ defaultEmitHandler,
1189
+ count,
1190
+ copy,
1191
+ clone,
1192
+ clean,
1193
+ SHALLOW,
1194
+ MERGE,
1195
+ $
1196
+ };
1197
+
1198
+ //# debugId=DE860A8F16A3286C64756E2164756E21
1199
+ //# sourceMappingURL=aberdeen.js.map