@thomas-siegfried/tapout 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1207 -1205
  3. package/decorator-config.md +220 -0
  4. package/dist/wireParams.d.ts.map +1 -1
  5. package/dist/wireParams.js +27 -4
  6. package/dist/wireParams.js.map +1 -1
  7. package/llms.txt +360 -0
  8. package/package.json +57 -55
  9. package/src/applyBindings.ts +372 -372
  10. package/src/arrayToDomMapping.ts +300 -300
  11. package/src/attributeInterpolationMarkup.ts +91 -91
  12. package/src/bindingContext.ts +207 -207
  13. package/src/bindingEvent.ts +198 -198
  14. package/src/bindingProvider.ts +260 -260
  15. package/src/bindings.ts +963 -963
  16. package/src/compareArrays.ts +122 -122
  17. package/src/componentBinding.ts +318 -318
  18. package/src/componentDecorator.ts +62 -62
  19. package/src/components.ts +367 -367
  20. package/src/computed.ts +439 -439
  21. package/src/configure.ts +52 -52
  22. package/src/controlFlowBindings.ts +206 -206
  23. package/src/core.ts +60 -60
  24. package/src/decorators.ts +407 -407
  25. package/src/dependencyDetection.ts +76 -76
  26. package/src/disposable.ts +36 -36
  27. package/src/domData.ts +42 -42
  28. package/src/domNodeDisposal.ts +94 -94
  29. package/src/effects.ts +29 -29
  30. package/src/event.ts +173 -173
  31. package/src/expressionRewriting.ts +219 -219
  32. package/src/extenders.ts +102 -102
  33. package/src/filters.ts +91 -91
  34. package/src/index.ts +150 -150
  35. package/src/interpolationMarkup.ts +130 -130
  36. package/src/memoization.ts +71 -71
  37. package/src/namespacedBindings.ts +132 -132
  38. package/src/observable.ts +48 -48
  39. package/src/observableArray.ts +397 -397
  40. package/src/options.ts +25 -25
  41. package/src/selectExtensions.ts +68 -68
  42. package/src/slotBinding.ts +84 -84
  43. package/src/subscribable.ts +266 -266
  44. package/src/tasks.ts +79 -79
  45. package/src/templateEngine.ts +108 -108
  46. package/src/templateRendering.ts +399 -399
  47. package/src/templateRewriting.ts +94 -94
  48. package/src/templateSources.ts +134 -134
  49. package/src/utils.ts +123 -123
  50. package/src/utilsDom.ts +87 -87
  51. package/src/virtualElements.ts +153 -153
  52. package/src/wireParams.ts +69 -49
@@ -1,397 +1,397 @@
1
- import { Observable, isObservable } from './observable.js';
2
- import { Subscription } from './subscribable.js';
3
- import { compareArrays, findMovesInArrayComparison, type ArrayChange, type CompareArraysOptions } from './compareArrays.js';
4
-
5
- const ARRAY_CHANGE_EVENT = 'arrayChange';
6
-
7
- export class ObservableArray<T> extends Observable<T[]> {
8
- private _trackingChanges = false;
9
- private _cachedDiff: ArrayChange<T>[] | null = null;
10
- private _pendingChanges = 0;
11
- private _previousContents: T[] | undefined;
12
- private _changeSubscription: Subscription<T[]> | null = null;
13
- private _spectateSubscription: Subscription<T[]> | null = null;
14
- compareArrayOptions: CompareArraysOptions = { sparse: true };
15
-
16
- constructor(initialValues?: T[]) {
17
- super(initialValues ?? []);
18
- }
19
-
20
- // --- Array change tracking (lifecycle hooks) ---
21
-
22
- protected override beforeSubscriptionAdd(event: string): void {
23
- if (event === ARRAY_CHANGE_EVENT) {
24
- this._trackChanges();
25
- }
26
- }
27
-
28
- protected override afterSubscriptionRemove(event: string): void {
29
- if (event === ARRAY_CHANGE_EVENT && !this.hasSubscriptionsForEvent(ARRAY_CHANGE_EVENT)) {
30
- this._changeSubscription?.dispose();
31
- this._spectateSubscription?.dispose();
32
- this._changeSubscription = null;
33
- this._spectateSubscription = null;
34
- this._trackingChanges = false;
35
- this._previousContents = undefined;
36
- }
37
- }
38
-
39
- private _trackChanges(): void {
40
- if (this._trackingChanges) {
41
- this._notifyArrayChanges();
42
- return;
43
- }
44
-
45
- this._trackingChanges = true;
46
-
47
- this._spectateSubscription = this.subscribe(() => {
48
- ++this._pendingChanges;
49
- }, 'spectate');
50
-
51
- this._previousContents = ([] as T[]).concat(this.peek() || []);
52
- this._cachedDiff = null;
53
-
54
- this._changeSubscription = this.subscribe(() => this._notifyArrayChanges());
55
- }
56
-
57
- private _notifyArrayChanges(): void {
58
- if (this._pendingChanges) {
59
- const currentContents = ([] as T[]).concat(this.peek() || []);
60
- let changes: ArrayChange<T>[] | undefined;
61
-
62
- if (this.hasSubscriptionsForEvent(ARRAY_CHANGE_EVENT)) {
63
- changes = this._getChanges(this._previousContents!, currentContents);
64
- }
65
-
66
- this._previousContents = currentContents;
67
- this._cachedDiff = null;
68
- this._pendingChanges = 0;
69
-
70
- if (changes && changes.length) {
71
- this.notifySubscribers(changes as unknown as T[], ARRAY_CHANGE_EVENT);
72
- }
73
- }
74
- }
75
-
76
- private _getChanges(previousContents: T[], currentContents: T[]): ArrayChange<T>[] {
77
- if (!this._cachedDiff || this._pendingChanges > 1) {
78
- this._cachedDiff = compareArrays(previousContents, currentContents, this.compareArrayOptions);
79
- }
80
- return this._cachedDiff;
81
- }
82
-
83
- /** @internal */
84
- cacheDiffForKnownOperation(rawArray: T[], operationName: string, args: IArguments | T[]): void {
85
- if (!this._trackingChanges || this._pendingChanges) {
86
- return;
87
- }
88
-
89
- const diff: ArrayChange<T>[] = [];
90
- const arrayLength = rawArray.length;
91
- const argsLength = args.length;
92
- let offset = 0;
93
-
94
- function pushDiff(status: 'added' | 'deleted', value: T, index: number): ArrayChange<T> {
95
- const entry: ArrayChange<T> = { status, value, index };
96
- diff.push(entry);
97
- return entry;
98
- }
99
-
100
- switch (operationName) {
101
- case 'push':
102
- offset = arrayLength;
103
- // falls through
104
- case 'unshift':
105
- for (let index = 0; index < argsLength; index++) {
106
- pushDiff('added', args[index] as T, offset + index);
107
- }
108
- break;
109
-
110
- case 'pop':
111
- offset = arrayLength - 1;
112
- // falls through
113
- case 'shift':
114
- if (arrayLength) {
115
- pushDiff('deleted', rawArray[offset], offset);
116
- }
117
- break;
118
-
119
- case 'splice': {
120
- const startIndex = Math.min(Math.max(0, (args[0] as number) < 0 ? arrayLength + (args[0] as number) : (args[0] as number)), arrayLength);
121
- const endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + ((args[1] as number) || 0), arrayLength);
122
- const endAddIndex = startIndex + argsLength - 2;
123
- const endIndex = Math.max(endDeleteIndex, endAddIndex);
124
- const additions: ArrayChange<T>[] = [];
125
- const deletions: ArrayChange<T>[] = [];
126
- for (let index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
127
- if (index < endDeleteIndex)
128
- deletions.push(pushDiff('deleted', rawArray[index], index));
129
- if (index < endAddIndex)
130
- additions.push(pushDiff('added', args[argsIndex] as T, index));
131
- }
132
- findMovesInArrayComparison(deletions, additions);
133
- break;
134
- }
135
-
136
- default:
137
- return;
138
- }
139
-
140
- this._cachedDiff = diff;
141
- }
142
-
143
- // --- Standard array mutators ---
144
-
145
- push(...items: T[]): number {
146
- const underlyingArray = this.peek();
147
- this.valueWillMutate();
148
- this.cacheDiffForKnownOperation(underlyingArray, 'push', items);
149
- const result = underlyingArray.push(...items);
150
- this.valueHasMutated();
151
- return result;
152
- }
153
-
154
- pop(): T | undefined {
155
- const underlyingArray = this.peek();
156
- this.valueWillMutate();
157
- this.cacheDiffForKnownOperation(underlyingArray, 'pop', [] as T[]);
158
- const result = underlyingArray.pop();
159
- this.valueHasMutated();
160
- return result;
161
- }
162
-
163
- shift(): T | undefined {
164
- const underlyingArray = this.peek();
165
- this.valueWillMutate();
166
- this.cacheDiffForKnownOperation(underlyingArray, 'shift', [] as T[]);
167
- const result = underlyingArray.shift();
168
- this.valueHasMutated();
169
- return result;
170
- }
171
-
172
- unshift(...items: T[]): number {
173
- const underlyingArray = this.peek();
174
- this.valueWillMutate();
175
- this.cacheDiffForKnownOperation(underlyingArray, 'unshift', items);
176
- const result = underlyingArray.unshift(...items);
177
- this.valueHasMutated();
178
- return result;
179
- }
180
-
181
- splice(start: number, deleteCount?: number, ...items: T[]): T[] {
182
- const underlyingArray = this.peek();
183
- this.valueWillMutate();
184
- const spliceArgs: unknown[] = [start];
185
- if (deleteCount !== undefined) spliceArgs.push(deleteCount);
186
- spliceArgs.push(...items);
187
- this.cacheDiffForKnownOperation(underlyingArray, 'splice', spliceArgs as T[]);
188
- const result = underlyingArray.splice(start, deleteCount ?? underlyingArray.length, ...items);
189
- this.valueHasMutated();
190
- return result;
191
- }
192
-
193
- sort(compareFunction?: (a: T, b: T) => number): this {
194
- const underlyingArray = this.peek();
195
- this.valueWillMutate();
196
- underlyingArray.sort(compareFunction);
197
- this.valueHasMutated();
198
- return this;
199
- }
200
-
201
- reverse(): this {
202
- const underlyingArray = this.peek();
203
- this.valueWillMutate();
204
- underlyingArray.reverse();
205
- this.valueHasMutated();
206
- return this;
207
- }
208
-
209
- // --- Custom mutators ---
210
-
211
- remove(valueOrPredicate: T | ((item: T) => boolean)): T[] {
212
- const underlyingArray = this.peek();
213
- const removedValues: T[] = [];
214
- const predicate = typeof valueOrPredicate === 'function' && !isObservable(valueOrPredicate)
215
- ? valueOrPredicate as (item: T) => boolean
216
- : (value: T) => value === valueOrPredicate;
217
-
218
- for (let i = 0; i < underlyingArray.length; i++) {
219
- const value = underlyingArray[i];
220
- if (predicate(value)) {
221
- if (removedValues.length === 0) {
222
- this.valueWillMutate();
223
- }
224
- if (underlyingArray[i] !== value) {
225
- throw new Error('Array modified during remove; cannot remove item');
226
- }
227
- removedValues.push(value);
228
- underlyingArray.splice(i, 1);
229
- i--;
230
- }
231
- }
232
-
233
- if (removedValues.length) {
234
- this.valueHasMutated();
235
- }
236
-
237
- return removedValues;
238
- }
239
-
240
- removeAll(arrayOfValues?: T[]): T[] {
241
- if (arrayOfValues === undefined) {
242
- const underlyingArray = this.peek();
243
- const allValues = underlyingArray.slice(0);
244
- this.valueWillMutate();
245
- underlyingArray.splice(0, underlyingArray.length);
246
- this.valueHasMutated();
247
- return allValues;
248
- }
249
-
250
- if (!arrayOfValues) return [];
251
-
252
- return this.remove((value) => arrayOfValues.indexOf(value) >= 0);
253
- }
254
-
255
- // TODO: destroy/destroyAll may be removed in favor of a mapping library approach.
256
- destroy(valueOrPredicate: T | ((item: T) => boolean)): void {
257
- const underlyingArray = this.peek();
258
- const predicate = typeof valueOrPredicate === 'function' && !isObservable(valueOrPredicate)
259
- ? valueOrPredicate as (item: T) => boolean
260
- : (value: T) => value === valueOrPredicate;
261
-
262
- this.valueWillMutate();
263
- for (let i = underlyingArray.length - 1; i >= 0; i--) {
264
- const value = underlyingArray[i];
265
- if (predicate(value)) {
266
- (value as Record<symbol, boolean>)[DESTROY] = true;
267
- }
268
- }
269
- this.valueHasMutated();
270
- }
271
-
272
- destroyAll(arrayOfValues?: T[]): void {
273
- if (arrayOfValues === undefined) {
274
- this.destroy(() => true);
275
- return;
276
- }
277
- if (!arrayOfValues) return;
278
- this.destroy((value) => arrayOfValues.indexOf(value) >= 0);
279
- }
280
-
281
- replace(oldItem: T, newItem: T): void {
282
- const index = this.indexOf(oldItem);
283
- if (index >= 0) {
284
- this.valueWillMutate();
285
- this.peek()[index] = newItem;
286
- this.valueHasMutated();
287
- }
288
- }
289
-
290
- // --- Readers ---
291
-
292
- get length(): number {
293
- return this.get().length;
294
- }
295
-
296
- indexOf(item: T): number {
297
- return this.get().indexOf(item);
298
- }
299
-
300
- slice(start?: number, end?: number): T[] {
301
- return this.get().slice(start, end);
302
- }
303
-
304
- sorted(compareFunction?: (a: T, b: T) => number): T[] {
305
- const copy = this.get().slice(0);
306
- return compareFunction ? copy.sort(compareFunction) : copy.sort();
307
- }
308
-
309
- reversed(): T[] {
310
- return this.get().slice(0).reverse();
311
- }
312
-
313
- map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: unknown): U[] {
314
- return this.get().map(callbackfn, thisArg);
315
- }
316
-
317
- filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): T[] {
318
- return this.get().filter(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
319
- }
320
-
321
- find(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): T | undefined {
322
- return this.get().find(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
323
- }
324
-
325
- findIndex(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): number {
326
- return this.get().findIndex(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
327
- }
328
-
329
- some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): boolean {
330
- return this.get().some(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
331
- }
332
-
333
- every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): boolean {
334
- return this.get().every(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
335
- }
336
-
337
- forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: unknown): void {
338
- this.get().forEach(callbackfn, thisArg);
339
- }
340
-
341
- reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
342
- reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
343
- reduce<U>(callbackfn: (previousValue: U | T, currentValue: T, currentIndex: number, array: T[]) => U | T, ...args: unknown[]): U | T {
344
- const arr = this.get();
345
- if (args.length > 0) {
346
- return arr.reduce(callbackfn as (pv: U | T, cv: T, ci: number, a: T[]) => U | T, args[0] as U | T);
347
- }
348
- return arr.reduce(callbackfn as (pv: T, cv: T, ci: number, a: T[]) => T);
349
- }
350
-
351
- includes(searchElement: T, fromIndex?: number): boolean {
352
- return this.get().includes(searchElement, fromIndex);
353
- }
354
-
355
- at(index: number): T | undefined {
356
- return this.get().at(index);
357
- }
358
-
359
- join(separator?: string): string {
360
- return this.get().join(separator);
361
- }
362
-
363
- flat<D extends number = 1>(depth?: D): FlatArray<T[], D>[] {
364
- return this.get().flat(depth) as FlatArray<T[], D>[];
365
- }
366
-
367
- flatMap<U>(callback: (value: T, index: number, array: T[]) => U | readonly U[], thisArg?: unknown): U[] {
368
- return this.get().flatMap(callback, thisArg);
369
- }
370
-
371
- entries(): ArrayIterator<[number, T]> {
372
- return this.get().entries();
373
- }
374
-
375
- keys(): ArrayIterator<number> {
376
- return this.get().keys();
377
- }
378
-
379
- values(): ArrayIterator<T> {
380
- return this.get().values();
381
- }
382
-
383
- [Symbol.iterator](): ArrayIterator<T> {
384
- return this.get()[Symbol.iterator]();
385
- }
386
- }
387
-
388
- export function isObservableArray(value: unknown): value is ObservableArray<unknown> {
389
- return value instanceof ObservableArray;
390
- }
391
-
392
- // TODO: DESTROY/isDestroyed may be removed in favor of a mapping library approach.
393
- export const DESTROY: unique symbol = Symbol('destroy');
394
-
395
- export function isDestroyed(item: unknown): boolean {
396
- return item != null && (item as Record<symbol, unknown>)[DESTROY] === true;
397
- }
1
+ import { Observable, isObservable } from './observable.js';
2
+ import { Subscription } from './subscribable.js';
3
+ import { compareArrays, findMovesInArrayComparison, type ArrayChange, type CompareArraysOptions } from './compareArrays.js';
4
+
5
+ const ARRAY_CHANGE_EVENT = 'arrayChange';
6
+
7
+ export class ObservableArray<T> extends Observable<T[]> {
8
+ private _trackingChanges = false;
9
+ private _cachedDiff: ArrayChange<T>[] | null = null;
10
+ private _pendingChanges = 0;
11
+ private _previousContents: T[] | undefined;
12
+ private _changeSubscription: Subscription<T[]> | null = null;
13
+ private _spectateSubscription: Subscription<T[]> | null = null;
14
+ compareArrayOptions: CompareArraysOptions = { sparse: true };
15
+
16
+ constructor(initialValues?: T[]) {
17
+ super(initialValues ?? []);
18
+ }
19
+
20
+ // --- Array change tracking (lifecycle hooks) ---
21
+
22
+ protected override beforeSubscriptionAdd(event: string): void {
23
+ if (event === ARRAY_CHANGE_EVENT) {
24
+ this._trackChanges();
25
+ }
26
+ }
27
+
28
+ protected override afterSubscriptionRemove(event: string): void {
29
+ if (event === ARRAY_CHANGE_EVENT && !this.hasSubscriptionsForEvent(ARRAY_CHANGE_EVENT)) {
30
+ this._changeSubscription?.dispose();
31
+ this._spectateSubscription?.dispose();
32
+ this._changeSubscription = null;
33
+ this._spectateSubscription = null;
34
+ this._trackingChanges = false;
35
+ this._previousContents = undefined;
36
+ }
37
+ }
38
+
39
+ private _trackChanges(): void {
40
+ if (this._trackingChanges) {
41
+ this._notifyArrayChanges();
42
+ return;
43
+ }
44
+
45
+ this._trackingChanges = true;
46
+
47
+ this._spectateSubscription = this.subscribe(() => {
48
+ ++this._pendingChanges;
49
+ }, 'spectate');
50
+
51
+ this._previousContents = ([] as T[]).concat(this.peek() || []);
52
+ this._cachedDiff = null;
53
+
54
+ this._changeSubscription = this.subscribe(() => this._notifyArrayChanges());
55
+ }
56
+
57
+ private _notifyArrayChanges(): void {
58
+ if (this._pendingChanges) {
59
+ const currentContents = ([] as T[]).concat(this.peek() || []);
60
+ let changes: ArrayChange<T>[] | undefined;
61
+
62
+ if (this.hasSubscriptionsForEvent(ARRAY_CHANGE_EVENT)) {
63
+ changes = this._getChanges(this._previousContents!, currentContents);
64
+ }
65
+
66
+ this._previousContents = currentContents;
67
+ this._cachedDiff = null;
68
+ this._pendingChanges = 0;
69
+
70
+ if (changes && changes.length) {
71
+ this.notifySubscribers(changes as unknown as T[], ARRAY_CHANGE_EVENT);
72
+ }
73
+ }
74
+ }
75
+
76
+ private _getChanges(previousContents: T[], currentContents: T[]): ArrayChange<T>[] {
77
+ if (!this._cachedDiff || this._pendingChanges > 1) {
78
+ this._cachedDiff = compareArrays(previousContents, currentContents, this.compareArrayOptions);
79
+ }
80
+ return this._cachedDiff;
81
+ }
82
+
83
+ /** @internal */
84
+ cacheDiffForKnownOperation(rawArray: T[], operationName: string, args: IArguments | T[]): void {
85
+ if (!this._trackingChanges || this._pendingChanges) {
86
+ return;
87
+ }
88
+
89
+ const diff: ArrayChange<T>[] = [];
90
+ const arrayLength = rawArray.length;
91
+ const argsLength = args.length;
92
+ let offset = 0;
93
+
94
+ function pushDiff(status: 'added' | 'deleted', value: T, index: number): ArrayChange<T> {
95
+ const entry: ArrayChange<T> = { status, value, index };
96
+ diff.push(entry);
97
+ return entry;
98
+ }
99
+
100
+ switch (operationName) {
101
+ case 'push':
102
+ offset = arrayLength;
103
+ // falls through
104
+ case 'unshift':
105
+ for (let index = 0; index < argsLength; index++) {
106
+ pushDiff('added', args[index] as T, offset + index);
107
+ }
108
+ break;
109
+
110
+ case 'pop':
111
+ offset = arrayLength - 1;
112
+ // falls through
113
+ case 'shift':
114
+ if (arrayLength) {
115
+ pushDiff('deleted', rawArray[offset], offset);
116
+ }
117
+ break;
118
+
119
+ case 'splice': {
120
+ const startIndex = Math.min(Math.max(0, (args[0] as number) < 0 ? arrayLength + (args[0] as number) : (args[0] as number)), arrayLength);
121
+ const endDeleteIndex = argsLength === 1 ? arrayLength : Math.min(startIndex + ((args[1] as number) || 0), arrayLength);
122
+ const endAddIndex = startIndex + argsLength - 2;
123
+ const endIndex = Math.max(endDeleteIndex, endAddIndex);
124
+ const additions: ArrayChange<T>[] = [];
125
+ const deletions: ArrayChange<T>[] = [];
126
+ for (let index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
127
+ if (index < endDeleteIndex)
128
+ deletions.push(pushDiff('deleted', rawArray[index], index));
129
+ if (index < endAddIndex)
130
+ additions.push(pushDiff('added', args[argsIndex] as T, index));
131
+ }
132
+ findMovesInArrayComparison(deletions, additions);
133
+ break;
134
+ }
135
+
136
+ default:
137
+ return;
138
+ }
139
+
140
+ this._cachedDiff = diff;
141
+ }
142
+
143
+ // --- Standard array mutators ---
144
+
145
+ push(...items: T[]): number {
146
+ const underlyingArray = this.peek();
147
+ this.valueWillMutate();
148
+ this.cacheDiffForKnownOperation(underlyingArray, 'push', items);
149
+ const result = underlyingArray.push(...items);
150
+ this.valueHasMutated();
151
+ return result;
152
+ }
153
+
154
+ pop(): T | undefined {
155
+ const underlyingArray = this.peek();
156
+ this.valueWillMutate();
157
+ this.cacheDiffForKnownOperation(underlyingArray, 'pop', [] as T[]);
158
+ const result = underlyingArray.pop();
159
+ this.valueHasMutated();
160
+ return result;
161
+ }
162
+
163
+ shift(): T | undefined {
164
+ const underlyingArray = this.peek();
165
+ this.valueWillMutate();
166
+ this.cacheDiffForKnownOperation(underlyingArray, 'shift', [] as T[]);
167
+ const result = underlyingArray.shift();
168
+ this.valueHasMutated();
169
+ return result;
170
+ }
171
+
172
+ unshift(...items: T[]): number {
173
+ const underlyingArray = this.peek();
174
+ this.valueWillMutate();
175
+ this.cacheDiffForKnownOperation(underlyingArray, 'unshift', items);
176
+ const result = underlyingArray.unshift(...items);
177
+ this.valueHasMutated();
178
+ return result;
179
+ }
180
+
181
+ splice(start: number, deleteCount?: number, ...items: T[]): T[] {
182
+ const underlyingArray = this.peek();
183
+ this.valueWillMutate();
184
+ const spliceArgs: unknown[] = [start];
185
+ if (deleteCount !== undefined) spliceArgs.push(deleteCount);
186
+ spliceArgs.push(...items);
187
+ this.cacheDiffForKnownOperation(underlyingArray, 'splice', spliceArgs as T[]);
188
+ const result = underlyingArray.splice(start, deleteCount ?? underlyingArray.length, ...items);
189
+ this.valueHasMutated();
190
+ return result;
191
+ }
192
+
193
+ sort(compareFunction?: (a: T, b: T) => number): this {
194
+ const underlyingArray = this.peek();
195
+ this.valueWillMutate();
196
+ underlyingArray.sort(compareFunction);
197
+ this.valueHasMutated();
198
+ return this;
199
+ }
200
+
201
+ reverse(): this {
202
+ const underlyingArray = this.peek();
203
+ this.valueWillMutate();
204
+ underlyingArray.reverse();
205
+ this.valueHasMutated();
206
+ return this;
207
+ }
208
+
209
+ // --- Custom mutators ---
210
+
211
+ remove(valueOrPredicate: T | ((item: T) => boolean)): T[] {
212
+ const underlyingArray = this.peek();
213
+ const removedValues: T[] = [];
214
+ const predicate = typeof valueOrPredicate === 'function' && !isObservable(valueOrPredicate)
215
+ ? valueOrPredicate as (item: T) => boolean
216
+ : (value: T) => value === valueOrPredicate;
217
+
218
+ for (let i = 0; i < underlyingArray.length; i++) {
219
+ const value = underlyingArray[i];
220
+ if (predicate(value)) {
221
+ if (removedValues.length === 0) {
222
+ this.valueWillMutate();
223
+ }
224
+ if (underlyingArray[i] !== value) {
225
+ throw new Error('Array modified during remove; cannot remove item');
226
+ }
227
+ removedValues.push(value);
228
+ underlyingArray.splice(i, 1);
229
+ i--;
230
+ }
231
+ }
232
+
233
+ if (removedValues.length) {
234
+ this.valueHasMutated();
235
+ }
236
+
237
+ return removedValues;
238
+ }
239
+
240
+ removeAll(arrayOfValues?: T[]): T[] {
241
+ if (arrayOfValues === undefined) {
242
+ const underlyingArray = this.peek();
243
+ const allValues = underlyingArray.slice(0);
244
+ this.valueWillMutate();
245
+ underlyingArray.splice(0, underlyingArray.length);
246
+ this.valueHasMutated();
247
+ return allValues;
248
+ }
249
+
250
+ if (!arrayOfValues) return [];
251
+
252
+ return this.remove((value) => arrayOfValues.indexOf(value) >= 0);
253
+ }
254
+
255
+ // TODO: destroy/destroyAll may be removed in favor of a mapping library approach.
256
+ destroy(valueOrPredicate: T | ((item: T) => boolean)): void {
257
+ const underlyingArray = this.peek();
258
+ const predicate = typeof valueOrPredicate === 'function' && !isObservable(valueOrPredicate)
259
+ ? valueOrPredicate as (item: T) => boolean
260
+ : (value: T) => value === valueOrPredicate;
261
+
262
+ this.valueWillMutate();
263
+ for (let i = underlyingArray.length - 1; i >= 0; i--) {
264
+ const value = underlyingArray[i];
265
+ if (predicate(value)) {
266
+ (value as Record<symbol, boolean>)[DESTROY] = true;
267
+ }
268
+ }
269
+ this.valueHasMutated();
270
+ }
271
+
272
+ destroyAll(arrayOfValues?: T[]): void {
273
+ if (arrayOfValues === undefined) {
274
+ this.destroy(() => true);
275
+ return;
276
+ }
277
+ if (!arrayOfValues) return;
278
+ this.destroy((value) => arrayOfValues.indexOf(value) >= 0);
279
+ }
280
+
281
+ replace(oldItem: T, newItem: T): void {
282
+ const index = this.indexOf(oldItem);
283
+ if (index >= 0) {
284
+ this.valueWillMutate();
285
+ this.peek()[index] = newItem;
286
+ this.valueHasMutated();
287
+ }
288
+ }
289
+
290
+ // --- Readers ---
291
+
292
+ get length(): number {
293
+ return this.get().length;
294
+ }
295
+
296
+ indexOf(item: T): number {
297
+ return this.get().indexOf(item);
298
+ }
299
+
300
+ slice(start?: number, end?: number): T[] {
301
+ return this.get().slice(start, end);
302
+ }
303
+
304
+ sorted(compareFunction?: (a: T, b: T) => number): T[] {
305
+ const copy = this.get().slice(0);
306
+ return compareFunction ? copy.sort(compareFunction) : copy.sort();
307
+ }
308
+
309
+ reversed(): T[] {
310
+ return this.get().slice(0).reverse();
311
+ }
312
+
313
+ map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: unknown): U[] {
314
+ return this.get().map(callbackfn, thisArg);
315
+ }
316
+
317
+ filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): T[] {
318
+ return this.get().filter(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
319
+ }
320
+
321
+ find(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): T | undefined {
322
+ return this.get().find(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
323
+ }
324
+
325
+ findIndex(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): number {
326
+ return this.get().findIndex(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
327
+ }
328
+
329
+ some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): boolean {
330
+ return this.get().some(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
331
+ }
332
+
333
+ every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: unknown): boolean {
334
+ return this.get().every(predicate as (value: T, index: number, array: T[]) => boolean, thisArg);
335
+ }
336
+
337
+ forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: unknown): void {
338
+ this.get().forEach(callbackfn, thisArg);
339
+ }
340
+
341
+ reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
342
+ reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
343
+ reduce<U>(callbackfn: (previousValue: U | T, currentValue: T, currentIndex: number, array: T[]) => U | T, ...args: unknown[]): U | T {
344
+ const arr = this.get();
345
+ if (args.length > 0) {
346
+ return arr.reduce(callbackfn as (pv: U | T, cv: T, ci: number, a: T[]) => U | T, args[0] as U | T);
347
+ }
348
+ return arr.reduce(callbackfn as (pv: T, cv: T, ci: number, a: T[]) => T);
349
+ }
350
+
351
+ includes(searchElement: T, fromIndex?: number): boolean {
352
+ return this.get().includes(searchElement, fromIndex);
353
+ }
354
+
355
+ at(index: number): T | undefined {
356
+ return this.get().at(index);
357
+ }
358
+
359
+ join(separator?: string): string {
360
+ return this.get().join(separator);
361
+ }
362
+
363
+ flat<D extends number = 1>(depth?: D): FlatArray<T[], D>[] {
364
+ return this.get().flat(depth) as FlatArray<T[], D>[];
365
+ }
366
+
367
+ flatMap<U>(callback: (value: T, index: number, array: T[]) => U | readonly U[], thisArg?: unknown): U[] {
368
+ return this.get().flatMap(callback, thisArg);
369
+ }
370
+
371
+ entries(): ArrayIterator<[number, T]> {
372
+ return this.get().entries();
373
+ }
374
+
375
+ keys(): ArrayIterator<number> {
376
+ return this.get().keys();
377
+ }
378
+
379
+ values(): ArrayIterator<T> {
380
+ return this.get().values();
381
+ }
382
+
383
+ [Symbol.iterator](): ArrayIterator<T> {
384
+ return this.get()[Symbol.iterator]();
385
+ }
386
+ }
387
+
388
+ export function isObservableArray(value: unknown): value is ObservableArray<unknown> {
389
+ return value instanceof ObservableArray;
390
+ }
391
+
392
+ // TODO: DESTROY/isDestroyed may be removed in favor of a mapping library approach.
393
+ export const DESTROY: unique symbol = Symbol('destroy');
394
+
395
+ export function isDestroyed(item: unknown): boolean {
396
+ return item != null && (item as Record<symbol, unknown>)[DESTROY] === true;
397
+ }