@thomas-siegfried/tapout 0.0.1
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/LICENSE +21 -0
- package/README.md +1206 -0
- package/dist/applyBindings.d.ts +10 -0
- package/dist/applyBindings.d.ts.map +1 -0
- package/dist/applyBindings.js +261 -0
- package/dist/applyBindings.js.map +1 -0
- package/dist/arrayToDomMapping.d.ts +14 -0
- package/dist/arrayToDomMapping.d.ts.map +1 -0
- package/dist/arrayToDomMapping.js +219 -0
- package/dist/arrayToDomMapping.js.map +1 -0
- package/dist/attributeInterpolationMarkup.d.ts +6 -0
- package/dist/attributeInterpolationMarkup.d.ts.map +1 -0
- package/dist/attributeInterpolationMarkup.js +70 -0
- package/dist/attributeInterpolationMarkup.js.map +1 -0
- package/dist/bindingContext.d.ts +38 -0
- package/dist/bindingContext.d.ts.map +1 -0
- package/dist/bindingContext.js +138 -0
- package/dist/bindingContext.js.map +1 -0
- package/dist/bindingEvent.d.ts +33 -0
- package/dist/bindingEvent.d.ts.map +1 -0
- package/dist/bindingEvent.js +144 -0
- package/dist/bindingEvent.js.map +1 -0
- package/dist/bindingProvider.d.ts +38 -0
- package/dist/bindingProvider.d.ts.map +1 -0
- package/dist/bindingProvider.js +145 -0
- package/dist/bindingProvider.js.map +1 -0
- package/dist/bindings.d.ts +2 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +862 -0
- package/dist/bindings.js.map +1 -0
- package/dist/compareArrays.d.ts +13 -0
- package/dist/compareArrays.d.ts.map +1 -0
- package/dist/compareArrays.js +91 -0
- package/dist/compareArrays.js.map +1 -0
- package/dist/componentBinding.d.ts +4 -0
- package/dist/componentBinding.d.ts.map +1 -0
- package/dist/componentBinding.js +240 -0
- package/dist/componentBinding.js.map +1 -0
- package/dist/componentDecorator.d.ts +22 -0
- package/dist/componentDecorator.d.ts.map +1 -0
- package/dist/componentDecorator.js +32 -0
- package/dist/componentDecorator.js.map +1 -0
- package/dist/components.d.ts +34 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +256 -0
- package/dist/components.js.map +1 -0
- package/dist/computed.d.ts +54 -0
- package/dist/computed.d.ts.map +1 -0
- package/dist/computed.js +376 -0
- package/dist/computed.js.map +1 -0
- package/dist/configure.d.ts +4 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +48 -0
- package/dist/configure.js.map +1 -0
- package/dist/controlFlowBindings.d.ts +2 -0
- package/dist/controlFlowBindings.d.ts.map +1 -0
- package/dist/controlFlowBindings.js +151 -0
- package/dist/controlFlowBindings.js.map +1 -0
- package/dist/core.d.ts +29 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +27 -0
- package/dist/core.js.map +1 -0
- package/dist/decorators.d.ts +40 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +276 -0
- package/dist/decorators.js.map +1 -0
- package/dist/dependencyDetection.d.ts +21 -0
- package/dist/dependencyDetection.d.ts.map +1 -0
- package/dist/dependencyDetection.js +53 -0
- package/dist/dependencyDetection.js.map +1 -0
- package/dist/disposable.d.ts +12 -0
- package/dist/disposable.d.ts.map +1 -0
- package/dist/disposable.js +29 -0
- package/dist/disposable.js.map +1 -0
- package/dist/domData.d.ts +6 -0
- package/dist/domData.d.ts.map +1 -0
- package/dist/domData.js +40 -0
- package/dist/domData.js.map +1 -0
- package/dist/domNodeDisposal.d.ts +7 -0
- package/dist/domNodeDisposal.d.ts.map +1 -0
- package/dist/domNodeDisposal.js +78 -0
- package/dist/domNodeDisposal.js.map +1 -0
- package/dist/effects.d.ts +13 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +24 -0
- package/dist/effects.js.map +1 -0
- package/dist/event.d.ts +38 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +138 -0
- package/dist/event.js.map +1 -0
- package/dist/expressionRewriting.d.ts +22 -0
- package/dist/expressionRewriting.d.ts.map +1 -0
- package/dist/expressionRewriting.js +169 -0
- package/dist/expressionRewriting.js.map +1 -0
- package/dist/extenders.d.ts +17 -0
- package/dist/extenders.d.ts.map +1 -0
- package/dist/extenders.js +75 -0
- package/dist/extenders.js.map +1 -0
- package/dist/filters.d.ts +5 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +81 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolationMarkup.d.ts +6 -0
- package/dist/interpolationMarkup.d.ts.map +1 -0
- package/dist/interpolationMarkup.js +110 -0
- package/dist/interpolationMarkup.js.map +1 -0
- package/dist/memoization.d.ts +7 -0
- package/dist/memoization.d.ts.map +1 -0
- package/dist/memoization.js +59 -0
- package/dist/memoization.js.map +1 -0
- package/dist/namespacedBindings.d.ts +7 -0
- package/dist/namespacedBindings.d.ts.map +1 -0
- package/dist/namespacedBindings.js +81 -0
- package/dist/namespacedBindings.js.map +1 -0
- package/dist/observable.d.ts +13 -0
- package/dist/observable.d.ts.map +1 -0
- package/dist/observable.js +39 -0
- package/dist/observable.js.map +1 -0
- package/dist/observableArray.d.ts +58 -0
- package/dist/observableArray.d.ts.map +1 -0
- package/dist/observableArray.js +328 -0
- package/dist/observableArray.js.map +1 -0
- package/dist/options.d.ts +14 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +12 -0
- package/dist/options.js.map +1 -0
- package/dist/selectExtensions.d.ts +4 -0
- package/dist/selectExtensions.d.ts.map +1 -0
- package/dist/selectExtensions.js +67 -0
- package/dist/selectExtensions.js.map +1 -0
- package/dist/slotBinding.d.ts +2 -0
- package/dist/slotBinding.d.ts.map +1 -0
- package/dist/slotBinding.js +63 -0
- package/dist/slotBinding.js.map +1 -0
- package/dist/subscribable.d.ts +57 -0
- package/dist/subscribable.d.ts.map +1 -0
- package/dist/subscribable.js +211 -0
- package/dist/subscribable.js.map +1 -0
- package/dist/tasks.d.ts +8 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +68 -0
- package/dist/tasks.js.map +1 -0
- package/dist/templateEngine.d.ts +37 -0
- package/dist/templateEngine.d.ts.map +1 -0
- package/dist/templateEngine.js +60 -0
- package/dist/templateEngine.js.map +1 -0
- package/dist/templateRendering.d.ts +8 -0
- package/dist/templateRendering.d.ts.map +1 -0
- package/dist/templateRendering.js +322 -0
- package/dist/templateRendering.js.map +1 -0
- package/dist/templateRewriting.d.ts +7 -0
- package/dist/templateRewriting.d.ts.map +1 -0
- package/dist/templateRewriting.js +63 -0
- package/dist/templateRewriting.js.map +1 -0
- package/dist/templateSources.d.ts +19 -0
- package/dist/templateSources.d.ts.map +1 -0
- package/dist/templateSources.js +110 -0
- package/dist/templateSources.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +100 -0
- package/dist/utils.js.map +1 -0
- package/dist/utilsDom.d.ts +8 -0
- package/dist/utilsDom.d.ts.map +1 -0
- package/dist/utilsDom.js +81 -0
- package/dist/utilsDom.js.map +1 -0
- package/dist/virtualElements.d.ts +12 -0
- package/dist/virtualElements.d.ts.map +1 -0
- package/dist/virtualElements.js +141 -0
- package/dist/virtualElements.js.map +1 -0
- package/dist/wireParams.d.ts +6 -0
- package/dist/wireParams.d.ts.map +1 -0
- package/dist/wireParams.js +39 -0
- package/dist/wireParams.js.map +1 -0
- package/package.json +55 -0
- package/src/applyBindings.ts +372 -0
- package/src/arrayToDomMapping.ts +300 -0
- package/src/attributeInterpolationMarkup.ts +91 -0
- package/src/bindingContext.ts +207 -0
- package/src/bindingEvent.ts +198 -0
- package/src/bindingProvider.ts +260 -0
- package/src/bindings.ts +963 -0
- package/src/compareArrays.ts +122 -0
- package/src/componentBinding.ts +318 -0
- package/src/componentDecorator.ts +62 -0
- package/src/components.ts +367 -0
- package/src/computed.ts +439 -0
- package/src/configure.ts +52 -0
- package/src/controlFlowBindings.ts +206 -0
- package/src/core.ts +60 -0
- package/src/decorators.ts +407 -0
- package/src/dependencyDetection.ts +76 -0
- package/src/disposable.ts +36 -0
- package/src/domData.ts +42 -0
- package/src/domNodeDisposal.ts +94 -0
- package/src/effects.ts +29 -0
- package/src/event.ts +173 -0
- package/src/expressionRewriting.ts +219 -0
- package/src/extenders.ts +102 -0
- package/src/filters.ts +91 -0
- package/src/index.ts +150 -0
- package/src/interpolationMarkup.ts +130 -0
- package/src/memoization.ts +71 -0
- package/src/namespacedBindings.ts +132 -0
- package/src/observable.ts +48 -0
- package/src/observableArray.ts +397 -0
- package/src/options.ts +25 -0
- package/src/selectExtensions.ts +68 -0
- package/src/slotBinding.ts +84 -0
- package/src/subscribable.ts +266 -0
- package/src/tasks.ts +79 -0
- package/src/templateEngine.ts +108 -0
- package/src/templateRendering.ts +399 -0
- package/src/templateRewriting.ts +94 -0
- package/src/templateSources.ts +134 -0
- package/src/utils.ts +123 -0
- package/src/utilsDom.ts +87 -0
- package/src/virtualElements.ts +153 -0
- package/src/wireParams.ts +49 -0
|
@@ -0,0 +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
|
+
}
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
|
+
type Constructor = new (...args: any[]) => any;
|
|
3
|
+
export type ViewModelFactory = (ctor: Constructor) => unknown;
|
|
4
|
+
|
|
5
|
+
const defaultViewModelFactory: ViewModelFactory = (ctor) => new ctor();
|
|
6
|
+
|
|
7
|
+
export const options: {
|
|
8
|
+
deferUpdates: boolean;
|
|
9
|
+
onError: ((error: unknown) => void) | null;
|
|
10
|
+
viewModelFactory: ViewModelFactory;
|
|
11
|
+
customElementDisplayContents: boolean;
|
|
12
|
+
interpolation: boolean;
|
|
13
|
+
attributeInterpolation: boolean;
|
|
14
|
+
namespacedBindings: boolean;
|
|
15
|
+
filters: boolean;
|
|
16
|
+
} = {
|
|
17
|
+
deferUpdates: false,
|
|
18
|
+
onError: null,
|
|
19
|
+
viewModelFactory: defaultViewModelFactory,
|
|
20
|
+
customElementDisplayContents: true,
|
|
21
|
+
interpolation: false,
|
|
22
|
+
attributeInterpolation: false,
|
|
23
|
+
namespacedBindings: false,
|
|
24
|
+
filters: false,
|
|
25
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { domDataGet, domDataSet, domDataNextKey } from './domData.js';
|
|
2
|
+
|
|
3
|
+
const optionValueDomDataKey = domDataNextKey();
|
|
4
|
+
const HAS_DOM_DATA = '__tapout_hasDomData';
|
|
5
|
+
|
|
6
|
+
function tagNameLower(element: Node): string {
|
|
7
|
+
return (element as Element).tagName ? (element as Element).tagName.toLowerCase() : '';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function readValue(element: Node): unknown {
|
|
11
|
+
const el = element as HTMLOptionElement & HTMLSelectElement & HTMLInputElement & Record<string, unknown>;
|
|
12
|
+
switch (tagNameLower(element)) {
|
|
13
|
+
case 'option':
|
|
14
|
+
if (el[HAS_DOM_DATA] === true) {
|
|
15
|
+
return domDataGet(element, optionValueDomDataKey);
|
|
16
|
+
}
|
|
17
|
+
return el.value;
|
|
18
|
+
case 'select':
|
|
19
|
+
return el.selectedIndex >= 0
|
|
20
|
+
? readValue((el.options as HTMLOptionsCollection)[el.selectedIndex])
|
|
21
|
+
: undefined;
|
|
22
|
+
default:
|
|
23
|
+
return el.value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function writeValue(element: Node, value: unknown, allowUnset?: boolean): void {
|
|
28
|
+
const el = element as HTMLOptionElement & HTMLSelectElement & HTMLInputElement & Record<string, unknown>;
|
|
29
|
+
switch (tagNameLower(element)) {
|
|
30
|
+
case 'option':
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
domDataSet(element, optionValueDomDataKey, undefined);
|
|
33
|
+
if (HAS_DOM_DATA in el) {
|
|
34
|
+
delete el[HAS_DOM_DATA];
|
|
35
|
+
}
|
|
36
|
+
el.value = value;
|
|
37
|
+
} else {
|
|
38
|
+
domDataSet(element, optionValueDomDataKey, value);
|
|
39
|
+
el[HAS_DOM_DATA] = true;
|
|
40
|
+
el.value = typeof value === 'number' ? String(value) : '';
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
case 'select': {
|
|
44
|
+
if (value === '' || value === null) value = undefined;
|
|
45
|
+
let selection = -1;
|
|
46
|
+
const options = el.options as HTMLOptionsCollection;
|
|
47
|
+
for (let i = 0, n = options.length; i < n; ++i) {
|
|
48
|
+
const optionValue = readValue(options[i]);
|
|
49
|
+
if (optionValue == value || (optionValue === '' && value === undefined)) {
|
|
50
|
+
selection = i;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (allowUnset || selection >= 0 || (value === undefined && el.size > 1)) {
|
|
55
|
+
el.selectedIndex = selection;
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
default:
|
|
60
|
+
if (value === null || value === undefined) value = '';
|
|
61
|
+
el.value = value as string;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getOptionValueDomDataKey(): string {
|
|
67
|
+
return optionValueDomDataKey;
|
|
68
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { BindingHandler } from './bindingProvider.js';
|
|
2
|
+
import { bindingHandlers } from './bindingProvider.js';
|
|
3
|
+
import type { BindingContext } from './bindingContext.js';
|
|
4
|
+
import { applyBindingsToDescendants } from './applyBindings.js';
|
|
5
|
+
import {
|
|
6
|
+
allowedVirtualElementBindings,
|
|
7
|
+
virtualChildNodes,
|
|
8
|
+
virtualSetChildren,
|
|
9
|
+
} from './virtualElements.js';
|
|
10
|
+
import { cloneNodes } from './utilsDom.js';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_SLOT = '';
|
|
13
|
+
|
|
14
|
+
function partitionTemplateNodes(
|
|
15
|
+
templateNodes: Node[],
|
|
16
|
+
): { defaultNodes: Node[]; namedSlots: Map<string, Node[]> } {
|
|
17
|
+
const namedSlots = new Map<string, Node[]>();
|
|
18
|
+
const defaultNodes: Node[] = [];
|
|
19
|
+
|
|
20
|
+
for (const node of templateNodes) {
|
|
21
|
+
const slotAttr = node.nodeType === 1
|
|
22
|
+
? (node as Element).getAttribute('slot')
|
|
23
|
+
: null;
|
|
24
|
+
|
|
25
|
+
if (slotAttr) {
|
|
26
|
+
let bucket = namedSlots.get(slotAttr);
|
|
27
|
+
if (!bucket) {
|
|
28
|
+
bucket = [];
|
|
29
|
+
namedSlots.set(slotAttr, bucket);
|
|
30
|
+
}
|
|
31
|
+
bucket.push(node);
|
|
32
|
+
} else {
|
|
33
|
+
defaultNodes.push(node);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { defaultNodes, namedSlots };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const slotHandler: BindingHandler = {
|
|
41
|
+
init(element, valueAccessor, _allBindings, _viewModel, bindingContext) {
|
|
42
|
+
const slotName = valueAccessor() as string | undefined;
|
|
43
|
+
const name = (typeof slotName === 'string' ? slotName : DEFAULT_SLOT);
|
|
44
|
+
|
|
45
|
+
const templateNodes = (bindingContext as BindingContext & { $componentTemplateNodes?: Node[] })
|
|
46
|
+
.$componentTemplateNodes;
|
|
47
|
+
|
|
48
|
+
if (!templateNodes || templateNodes.length === 0) {
|
|
49
|
+
applyBindingsToDescendants(bindingContext, element);
|
|
50
|
+
return { controlsDescendantBindings: true };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { defaultNodes, namedSlots } = partitionTemplateNodes(templateNodes);
|
|
54
|
+
const matchingNodes = name === DEFAULT_SLOT ? defaultNodes : (namedSlots.get(name) || []);
|
|
55
|
+
|
|
56
|
+
if (matchingNodes.length === 0) {
|
|
57
|
+
applyBindingsToDescendants(bindingContext, element);
|
|
58
|
+
return { controlsDescendantBindings: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const cloned = cloneNodes(matchingNodes);
|
|
62
|
+
virtualSetChildren(element, cloned);
|
|
63
|
+
|
|
64
|
+
const parentContext = findParentOuterContext(bindingContext);
|
|
65
|
+
applyBindingsToDescendants(parentContext, element);
|
|
66
|
+
|
|
67
|
+
return { controlsDescendantBindings: true };
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Walk up the context chain to find the outer (pre-component) context.
|
|
73
|
+
* The component binding creates a child context with $component set,
|
|
74
|
+
* so we look for the context just above that boundary.
|
|
75
|
+
*/
|
|
76
|
+
function findParentOuterContext(bindingContext: BindingContext): BindingContext {
|
|
77
|
+
if (bindingContext.$parentContext && '$component' in bindingContext) {
|
|
78
|
+
return bindingContext.$parentContext;
|
|
79
|
+
}
|
|
80
|
+
return bindingContext;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
bindingHandlers['slot'] = slotHandler;
|
|
84
|
+
allowedVirtualElementBindings['slot'] = true;
|