@thomas-siegfried/tapout 0.0.1 → 0.0.2
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 -21
- package/README.md +1205 -1205
- package/package.json +55 -55
- package/src/applyBindings.ts +372 -372
- package/src/arrayToDomMapping.ts +300 -300
- package/src/attributeInterpolationMarkup.ts +91 -91
- package/src/bindingContext.ts +207 -207
- package/src/bindingEvent.ts +198 -198
- package/src/bindingProvider.ts +260 -260
- package/src/bindings.ts +963 -963
- package/src/compareArrays.ts +122 -122
- package/src/componentBinding.ts +318 -318
- package/src/componentDecorator.ts +62 -62
- package/src/components.ts +367 -367
- package/src/computed.ts +439 -439
- package/src/configure.ts +52 -52
- package/src/controlFlowBindings.ts +206 -206
- package/src/core.ts +60 -60
- package/src/decorators.ts +407 -407
- package/src/dependencyDetection.ts +76 -76
- package/src/disposable.ts +36 -36
- package/src/domData.ts +42 -42
- package/src/domNodeDisposal.ts +94 -94
- package/src/effects.ts +29 -29
- package/src/event.ts +173 -173
- package/src/expressionRewriting.ts +219 -219
- package/src/extenders.ts +102 -102
- package/src/filters.ts +91 -91
- package/src/index.ts +150 -150
- package/src/interpolationMarkup.ts +130 -130
- package/src/memoization.ts +71 -71
- package/src/namespacedBindings.ts +132 -132
- package/src/observable.ts +48 -48
- package/src/observableArray.ts +397 -397
- package/src/options.ts +25 -25
- package/src/selectExtensions.ts +68 -68
- package/src/slotBinding.ts +84 -84
- package/src/subscribable.ts +266 -266
- package/src/tasks.ts +79 -79
- package/src/templateEngine.ts +108 -108
- package/src/templateRendering.ts +399 -399
- package/src/templateRewriting.ts +94 -94
- package/src/templateSources.ts +134 -134
- package/src/utils.ts +123 -123
- package/src/utilsDom.ts +87 -87
- package/src/virtualElements.ts +153 -153
- package/src/wireParams.ts +49 -49
package/src/applyBindings.ts
CHANGED
|
@@ -1,372 +1,372 @@
|
|
|
1
|
-
import { BindingContext, BINDING_INFO_KEY, SUBSCRIBABLE, DATA_DEPENDENCY } from './bindingContext.js';
|
|
2
|
-
import type { AllBindingsAccessor } from './expressionRewriting.js';
|
|
3
|
-
import { instance as providerInstance, getBindingHandler } from './bindingProvider.js';
|
|
4
|
-
import type { BindingHandler } from './bindingProvider.js';
|
|
5
|
-
import { Computed } from './computed.js';
|
|
6
|
-
import { ignore } from './dependencyDetection.js';
|
|
7
|
-
import { domDataGetOrSet } from './domData.js';
|
|
8
|
-
import { addDisposeCallback } from './domNodeDisposal.js';
|
|
9
|
-
import {
|
|
10
|
-
virtualFirstChild,
|
|
11
|
-
virtualNextSibling,
|
|
12
|
-
allowedVirtualElementBindings,
|
|
13
|
-
} from './virtualElements.js';
|
|
14
|
-
import { bindingEvent, subscribeToBindingEvent } from './bindingEvent.js';
|
|
15
|
-
import type { BindingInfo } from './bindingEvent.js';
|
|
16
|
-
import { ensureConfigured } from './configure.js';
|
|
17
|
-
|
|
18
|
-
interface SortedBinding {
|
|
19
|
-
key: string;
|
|
20
|
-
handler: BindingHandler;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Elements whose descendants should not be recursively bound
|
|
24
|
-
const NO_RECURSE: Record<string, boolean> = {
|
|
25
|
-
script: true,
|
|
26
|
-
textarea: true,
|
|
27
|
-
template: true,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function tagNameLower(node: Node): string {
|
|
31
|
-
return (node as Element).tagName ? (node as Element).tagName.toLowerCase() : '';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ---- Helpers ----
|
|
35
|
-
|
|
36
|
-
function evaluateValueAccessor(valueAccessor: unknown): unknown {
|
|
37
|
-
return typeof valueAccessor === 'function' ? (valueAccessor as () => unknown)() : valueAccessor;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function makeValueAccessor(value: unknown): () => unknown {
|
|
41
|
-
return () => value;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ---- Topological Sort ----
|
|
45
|
-
|
|
46
|
-
function topologicalSortBindings(
|
|
47
|
-
bindings: Record<string, unknown>,
|
|
48
|
-
): SortedBinding[] {
|
|
49
|
-
const result: SortedBinding[] = [];
|
|
50
|
-
const considered: Record<string, boolean> = {};
|
|
51
|
-
const cyclicStack: string[] = [];
|
|
52
|
-
|
|
53
|
-
function pushBinding(bindingKey: string) {
|
|
54
|
-
if (considered[bindingKey]) return;
|
|
55
|
-
|
|
56
|
-
const handler = getBindingHandler(bindingKey);
|
|
57
|
-
if (handler) {
|
|
58
|
-
if (handler.after) {
|
|
59
|
-
cyclicStack.push(bindingKey);
|
|
60
|
-
for (const depKey of handler.after) {
|
|
61
|
-
if (depKey in bindings) {
|
|
62
|
-
if (cyclicStack.indexOf(depKey) !== -1) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
'Cannot combine the following bindings, because they have a cyclic dependency: ' +
|
|
65
|
-
cyclicStack.join(', '),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
pushBinding(depKey);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
cyclicStack.pop();
|
|
72
|
-
}
|
|
73
|
-
result.push({ key: bindingKey, handler });
|
|
74
|
-
}
|
|
75
|
-
considered[bindingKey] = true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
for (const key of Object.keys(bindings)) {
|
|
79
|
-
pushBinding(key);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ---- Virtual Element Validation ----
|
|
86
|
-
|
|
87
|
-
function validateVirtualElementBinding(bindingName: string): void {
|
|
88
|
-
if (!allowedVirtualElementBindings[bindingName]) {
|
|
89
|
-
throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ---- Core: Apply Bindings to a Single Node ----
|
|
94
|
-
|
|
95
|
-
function applyBindingsToNodeInternal(
|
|
96
|
-
node: Node,
|
|
97
|
-
sourceBindings: Record<string, () => unknown> | ((ctx: BindingContext, node: Node) => Record<string, () => unknown>) | null,
|
|
98
|
-
bindingContext: BindingContext,
|
|
99
|
-
): { shouldBindDescendants: boolean; bindingContextForDescendants: BindingContext | false } {
|
|
100
|
-
const bindingInfo = domDataGetOrSet(node, BINDING_INFO_KEY, {}) as BindingInfo;
|
|
101
|
-
|
|
102
|
-
const alreadyBound = bindingInfo.alreadyBound;
|
|
103
|
-
if (!sourceBindings) {
|
|
104
|
-
if (alreadyBound) {
|
|
105
|
-
throw new Error('You cannot apply bindings multiple times to the same element.');
|
|
106
|
-
}
|
|
107
|
-
bindingInfo.alreadyBound = true;
|
|
108
|
-
}
|
|
109
|
-
if (!alreadyBound) {
|
|
110
|
-
bindingInfo.context = bindingContext;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
|
-
let bindings: Record<string, any> | null = null;
|
|
115
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
-
let bindingsUpdater: Computed<any> | null = null;
|
|
117
|
-
|
|
118
|
-
if (sourceBindings && typeof sourceBindings !== 'function') {
|
|
119
|
-
bindings = sourceBindings;
|
|
120
|
-
} else {
|
|
121
|
-
const provider = providerInstance;
|
|
122
|
-
const getBindingsFn = provider.getBindingAccessors.bind(provider);
|
|
123
|
-
|
|
124
|
-
const updater = new Computed(() => {
|
|
125
|
-
const resolved = sourceBindings
|
|
126
|
-
? (sourceBindings as (ctx: BindingContext, n: Node) => Record<string, () => unknown>)(bindingContext, node)
|
|
127
|
-
: getBindingsFn(node, bindingContext);
|
|
128
|
-
|
|
129
|
-
if (resolved) {
|
|
130
|
-
const sub = bindingContext[SUBSCRIBABLE];
|
|
131
|
-
if (sub) sub.get();
|
|
132
|
-
const dep = bindingContext[DATA_DEPENDENCY] as Computed<unknown> | undefined;
|
|
133
|
-
if (dep) dep.get();
|
|
134
|
-
}
|
|
135
|
-
bindings = resolved;
|
|
136
|
-
return resolved;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
bindings = updater.peek() as typeof bindings;
|
|
140
|
-
|
|
141
|
-
if (!bindings || !updater.isActive()) {
|
|
142
|
-
if (updater.isActive()) {
|
|
143
|
-
addDisposeCallback(node, () => updater.dispose());
|
|
144
|
-
}
|
|
145
|
-
bindingsUpdater = null;
|
|
146
|
-
} else {
|
|
147
|
-
bindingsUpdater = updater;
|
|
148
|
-
addDisposeCallback(node, () => updater.dispose());
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
let contextToExtend = bindingContext;
|
|
153
|
-
let bindingHandlerThatControlsDescendants: string | undefined;
|
|
154
|
-
|
|
155
|
-
if (bindings) {
|
|
156
|
-
contextToExtend = subscribeToBindingEvent(node, bindings, bindingContext, evaluateValueAccessor);
|
|
157
|
-
|
|
158
|
-
const getValueAccessor: (key: string) => () => unknown = bindingsUpdater
|
|
159
|
-
? (bindingKey) => () => {
|
|
160
|
-
const current = bindingsUpdater!.get() as Record<string, unknown> | null;
|
|
161
|
-
return evaluateValueAccessor(current?.[bindingKey]);
|
|
162
|
-
}
|
|
163
|
-
: (bindingKey) => bindings![bindingKey] as () => unknown;
|
|
164
|
-
|
|
165
|
-
const allBindings: AllBindingsAccessor = {
|
|
166
|
-
get(key: string): unknown {
|
|
167
|
-
return bindings![key] ? evaluateValueAccessor(getValueAccessor(key)) : undefined;
|
|
168
|
-
},
|
|
169
|
-
has(key: string): boolean {
|
|
170
|
-
return key in bindings!;
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const orderedBindings = topologicalSortBindings(bindings);
|
|
175
|
-
|
|
176
|
-
for (const { key: bindingKey, handler } of orderedBindings) {
|
|
177
|
-
if (node.nodeType === 8) {
|
|
178
|
-
validateVirtualElementBinding(bindingKey);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
if (typeof handler.init === 'function') {
|
|
183
|
-
const initFn = handler.init;
|
|
184
|
-
ignore(() => {
|
|
185
|
-
const initResult = initFn(
|
|
186
|
-
node,
|
|
187
|
-
getValueAccessor(bindingKey),
|
|
188
|
-
allBindings,
|
|
189
|
-
contextToExtend.$data,
|
|
190
|
-
contextToExtend,
|
|
191
|
-
);
|
|
192
|
-
if (initResult && initResult.controlsDescendantBindings) {
|
|
193
|
-
if (bindingHandlerThatControlsDescendants !== undefined) {
|
|
194
|
-
throw new Error(
|
|
195
|
-
'Multiple bindings (' + bindingHandlerThatControlsDescendants + ' and ' + bindingKey +
|
|
196
|
-
') are trying to control descendant bindings of the same element.',
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
bindingHandlerThatControlsDescendants = bindingKey;
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (typeof handler.update === 'function') {
|
|
205
|
-
const updateFn = handler.update;
|
|
206
|
-
const updateComputed = new Computed(() => {
|
|
207
|
-
updateFn(
|
|
208
|
-
node,
|
|
209
|
-
getValueAccessor(bindingKey),
|
|
210
|
-
allBindings,
|
|
211
|
-
contextToExtend.$data,
|
|
212
|
-
contextToExtend,
|
|
213
|
-
);
|
|
214
|
-
});
|
|
215
|
-
if (updateComputed.isActive()) {
|
|
216
|
-
addDisposeCallback(node, () => updateComputed.dispose());
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} catch (ex) {
|
|
220
|
-
const err = ex as Error;
|
|
221
|
-
err.message =
|
|
222
|
-
'Unable to process binding "' + bindingKey + ': ' + bindings[bindingKey] +
|
|
223
|
-
'"\nMessage: ' + err.message;
|
|
224
|
-
throw err;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const shouldBindDescendants = bindingHandlerThatControlsDescendants === undefined;
|
|
230
|
-
return {
|
|
231
|
-
shouldBindDescendants,
|
|
232
|
-
bindingContextForDescendants: shouldBindDescendants && contextToExtend,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ---- Tree Walk ----
|
|
237
|
-
|
|
238
|
-
function applyBindingsToDescendantsInternal(
|
|
239
|
-
bindingContext: BindingContext,
|
|
240
|
-
elementOrVirtualElement: Node,
|
|
241
|
-
): void {
|
|
242
|
-
let nextInQueue = virtualFirstChild(elementOrVirtualElement);
|
|
243
|
-
|
|
244
|
-
if (nextInQueue) {
|
|
245
|
-
const provider = providerInstance;
|
|
246
|
-
const preprocessNode = provider.preprocessNode;
|
|
247
|
-
|
|
248
|
-
if (preprocessNode) {
|
|
249
|
-
let currentChild: Node | null = nextInQueue;
|
|
250
|
-
while (currentChild) {
|
|
251
|
-
const next = virtualNextSibling(currentChild);
|
|
252
|
-
preprocessNode.call(provider, currentChild);
|
|
253
|
-
currentChild = next;
|
|
254
|
-
}
|
|
255
|
-
nextInQueue = virtualFirstChild(elementOrVirtualElement);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
let currentChild: Node | null = nextInQueue;
|
|
259
|
-
while (currentChild) {
|
|
260
|
-
const next = virtualNextSibling(currentChild);
|
|
261
|
-
applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild);
|
|
262
|
-
currentChild = next;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
bindingEvent.notify(elementOrVirtualElement, bindingEvent.childrenComplete);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function applyBindingsToNodeAndDescendantsInternal(
|
|
270
|
-
bindingContext: BindingContext,
|
|
271
|
-
nodeVerified: Node,
|
|
272
|
-
): void {
|
|
273
|
-
let bindingContextForDescendants: BindingContext | false = bindingContext;
|
|
274
|
-
|
|
275
|
-
const isElement = nodeVerified.nodeType === 1;
|
|
276
|
-
|
|
277
|
-
const shouldApplyBindings = isElement || providerInstance.nodeHasBindings(nodeVerified);
|
|
278
|
-
if (shouldApplyBindings) {
|
|
279
|
-
const result = applyBindingsToNodeInternal(nodeVerified, null, bindingContext);
|
|
280
|
-
bindingContextForDescendants = result.bindingContextForDescendants;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (bindingContextForDescendants && !NO_RECURSE[tagNameLower(nodeVerified)]) {
|
|
284
|
-
applyBindingsToDescendantsInternal(bindingContextForDescendants, nodeVerified);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// ---- Utility: Wrap raw bindings as accessors ----
|
|
289
|
-
|
|
290
|
-
function makeBindingAccessors(
|
|
291
|
-
bindings: Record<string, unknown> | ((ctx: BindingContext, node: Node) => Record<string, unknown>),
|
|
292
|
-
context: BindingContext,
|
|
293
|
-
node: Node,
|
|
294
|
-
): Record<string, () => unknown> {
|
|
295
|
-
if (typeof bindings === 'function') {
|
|
296
|
-
const boundFn = (bindings as (ctx: BindingContext, node: Node) => Record<string, unknown>).bind(null, context, node);
|
|
297
|
-
const initial = ignore(boundFn);
|
|
298
|
-
const result: Record<string, () => unknown> = {};
|
|
299
|
-
for (const key of Object.keys(initial)) {
|
|
300
|
-
result[key] = () => boundFn()[key];
|
|
301
|
-
}
|
|
302
|
-
return result;
|
|
303
|
-
}
|
|
304
|
-
const result: Record<string, () => unknown> = {};
|
|
305
|
-
for (const key of Object.keys(bindings)) {
|
|
306
|
-
result[key] = makeValueAccessor(bindings[key]);
|
|
307
|
-
}
|
|
308
|
-
return result;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// ---- Public Helpers ----
|
|
312
|
-
|
|
313
|
-
function getBindingContext(
|
|
314
|
-
viewModelOrBindingContext: unknown,
|
|
315
|
-
extendContextCallback?: (self: BindingContext, parentContext: BindingContext | undefined, dataItem: unknown) => void,
|
|
316
|
-
): BindingContext {
|
|
317
|
-
if (viewModelOrBindingContext instanceof BindingContext) {
|
|
318
|
-
return viewModelOrBindingContext;
|
|
319
|
-
}
|
|
320
|
-
return new BindingContext(viewModelOrBindingContext, undefined, undefined, extendContextCallback);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// ---- Public API ----
|
|
324
|
-
|
|
325
|
-
export function applyBindings(
|
|
326
|
-
viewModelOrBindingContext: unknown,
|
|
327
|
-
rootNode: Node,
|
|
328
|
-
extendContextCallback?: (self: BindingContext, parentContext: BindingContext | undefined, dataItem: unknown) => void,
|
|
329
|
-
): void {
|
|
330
|
-
ensureConfigured();
|
|
331
|
-
if (!rootNode || (rootNode.nodeType !== 1 && rootNode.nodeType !== 8)) {
|
|
332
|
-
throw new Error('applyBindings: second parameter should be a DOM element or comment node');
|
|
333
|
-
}
|
|
334
|
-
applyBindingsToNodeAndDescendantsInternal(
|
|
335
|
-
getBindingContext(viewModelOrBindingContext, extendContextCallback),
|
|
336
|
-
rootNode,
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export function applyBindingsToDescendants(
|
|
341
|
-
viewModelOrBindingContext: unknown,
|
|
342
|
-
rootNode: Node,
|
|
343
|
-
): void {
|
|
344
|
-
ensureConfigured();
|
|
345
|
-
if (rootNode.nodeType === 1 || rootNode.nodeType === 8) {
|
|
346
|
-
applyBindingsToDescendantsInternal(
|
|
347
|
-
getBindingContext(viewModelOrBindingContext),
|
|
348
|
-
rootNode,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export function applyBindingsToNode(
|
|
354
|
-
node: Node,
|
|
355
|
-
bindings: Record<string, unknown>,
|
|
356
|
-
viewModelOrBindingContext?: unknown,
|
|
357
|
-
): { shouldBindDescendants: boolean } {
|
|
358
|
-
ensureConfigured();
|
|
359
|
-
const context = getBindingContext(viewModelOrBindingContext);
|
|
360
|
-
const accessors = makeBindingAccessors(bindings, context, node);
|
|
361
|
-
return applyBindingsToNodeInternal(node, accessors, context);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export function applyBindingAccessorsToNode(
|
|
365
|
-
node: Node,
|
|
366
|
-
bindings: Record<string, () => unknown> | ((ctx: BindingContext, node: Node) => Record<string, () => unknown>),
|
|
367
|
-
viewModelOrBindingContext?: unknown,
|
|
368
|
-
): { shouldBindDescendants: boolean } {
|
|
369
|
-
ensureConfigured();
|
|
370
|
-
const context = getBindingContext(viewModelOrBindingContext);
|
|
371
|
-
return applyBindingsToNodeInternal(node, bindings, context);
|
|
372
|
-
}
|
|
1
|
+
import { BindingContext, BINDING_INFO_KEY, SUBSCRIBABLE, DATA_DEPENDENCY } from './bindingContext.js';
|
|
2
|
+
import type { AllBindingsAccessor } from './expressionRewriting.js';
|
|
3
|
+
import { instance as providerInstance, getBindingHandler } from './bindingProvider.js';
|
|
4
|
+
import type { BindingHandler } from './bindingProvider.js';
|
|
5
|
+
import { Computed } from './computed.js';
|
|
6
|
+
import { ignore } from './dependencyDetection.js';
|
|
7
|
+
import { domDataGetOrSet } from './domData.js';
|
|
8
|
+
import { addDisposeCallback } from './domNodeDisposal.js';
|
|
9
|
+
import {
|
|
10
|
+
virtualFirstChild,
|
|
11
|
+
virtualNextSibling,
|
|
12
|
+
allowedVirtualElementBindings,
|
|
13
|
+
} from './virtualElements.js';
|
|
14
|
+
import { bindingEvent, subscribeToBindingEvent } from './bindingEvent.js';
|
|
15
|
+
import type { BindingInfo } from './bindingEvent.js';
|
|
16
|
+
import { ensureConfigured } from './configure.js';
|
|
17
|
+
|
|
18
|
+
interface SortedBinding {
|
|
19
|
+
key: string;
|
|
20
|
+
handler: BindingHandler;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Elements whose descendants should not be recursively bound
|
|
24
|
+
const NO_RECURSE: Record<string, boolean> = {
|
|
25
|
+
script: true,
|
|
26
|
+
textarea: true,
|
|
27
|
+
template: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function tagNameLower(node: Node): string {
|
|
31
|
+
return (node as Element).tagName ? (node as Element).tagName.toLowerCase() : '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---- Helpers ----
|
|
35
|
+
|
|
36
|
+
function evaluateValueAccessor(valueAccessor: unknown): unknown {
|
|
37
|
+
return typeof valueAccessor === 'function' ? (valueAccessor as () => unknown)() : valueAccessor;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makeValueAccessor(value: unknown): () => unknown {
|
|
41
|
+
return () => value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---- Topological Sort ----
|
|
45
|
+
|
|
46
|
+
function topologicalSortBindings(
|
|
47
|
+
bindings: Record<string, unknown>,
|
|
48
|
+
): SortedBinding[] {
|
|
49
|
+
const result: SortedBinding[] = [];
|
|
50
|
+
const considered: Record<string, boolean> = {};
|
|
51
|
+
const cyclicStack: string[] = [];
|
|
52
|
+
|
|
53
|
+
function pushBinding(bindingKey: string) {
|
|
54
|
+
if (considered[bindingKey]) return;
|
|
55
|
+
|
|
56
|
+
const handler = getBindingHandler(bindingKey);
|
|
57
|
+
if (handler) {
|
|
58
|
+
if (handler.after) {
|
|
59
|
+
cyclicStack.push(bindingKey);
|
|
60
|
+
for (const depKey of handler.after) {
|
|
61
|
+
if (depKey in bindings) {
|
|
62
|
+
if (cyclicStack.indexOf(depKey) !== -1) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'Cannot combine the following bindings, because they have a cyclic dependency: ' +
|
|
65
|
+
cyclicStack.join(', '),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
pushBinding(depKey);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
cyclicStack.pop();
|
|
72
|
+
}
|
|
73
|
+
result.push({ key: bindingKey, handler });
|
|
74
|
+
}
|
|
75
|
+
considered[bindingKey] = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const key of Object.keys(bindings)) {
|
|
79
|
+
pushBinding(key);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---- Virtual Element Validation ----
|
|
86
|
+
|
|
87
|
+
function validateVirtualElementBinding(bindingName: string): void {
|
|
88
|
+
if (!allowedVirtualElementBindings[bindingName]) {
|
|
89
|
+
throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---- Core: Apply Bindings to a Single Node ----
|
|
94
|
+
|
|
95
|
+
function applyBindingsToNodeInternal(
|
|
96
|
+
node: Node,
|
|
97
|
+
sourceBindings: Record<string, () => unknown> | ((ctx: BindingContext, node: Node) => Record<string, () => unknown>) | null,
|
|
98
|
+
bindingContext: BindingContext,
|
|
99
|
+
): { shouldBindDescendants: boolean; bindingContextForDescendants: BindingContext | false } {
|
|
100
|
+
const bindingInfo = domDataGetOrSet(node, BINDING_INFO_KEY, {}) as BindingInfo;
|
|
101
|
+
|
|
102
|
+
const alreadyBound = bindingInfo.alreadyBound;
|
|
103
|
+
if (!sourceBindings) {
|
|
104
|
+
if (alreadyBound) {
|
|
105
|
+
throw new Error('You cannot apply bindings multiple times to the same element.');
|
|
106
|
+
}
|
|
107
|
+
bindingInfo.alreadyBound = true;
|
|
108
|
+
}
|
|
109
|
+
if (!alreadyBound) {
|
|
110
|
+
bindingInfo.context = bindingContext;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
|
+
let bindings: Record<string, any> | null = null;
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
let bindingsUpdater: Computed<any> | null = null;
|
|
117
|
+
|
|
118
|
+
if (sourceBindings && typeof sourceBindings !== 'function') {
|
|
119
|
+
bindings = sourceBindings;
|
|
120
|
+
} else {
|
|
121
|
+
const provider = providerInstance;
|
|
122
|
+
const getBindingsFn = provider.getBindingAccessors.bind(provider);
|
|
123
|
+
|
|
124
|
+
const updater = new Computed(() => {
|
|
125
|
+
const resolved = sourceBindings
|
|
126
|
+
? (sourceBindings as (ctx: BindingContext, n: Node) => Record<string, () => unknown>)(bindingContext, node)
|
|
127
|
+
: getBindingsFn(node, bindingContext);
|
|
128
|
+
|
|
129
|
+
if (resolved) {
|
|
130
|
+
const sub = bindingContext[SUBSCRIBABLE];
|
|
131
|
+
if (sub) sub.get();
|
|
132
|
+
const dep = bindingContext[DATA_DEPENDENCY] as Computed<unknown> | undefined;
|
|
133
|
+
if (dep) dep.get();
|
|
134
|
+
}
|
|
135
|
+
bindings = resolved;
|
|
136
|
+
return resolved;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
bindings = updater.peek() as typeof bindings;
|
|
140
|
+
|
|
141
|
+
if (!bindings || !updater.isActive()) {
|
|
142
|
+
if (updater.isActive()) {
|
|
143
|
+
addDisposeCallback(node, () => updater.dispose());
|
|
144
|
+
}
|
|
145
|
+
bindingsUpdater = null;
|
|
146
|
+
} else {
|
|
147
|
+
bindingsUpdater = updater;
|
|
148
|
+
addDisposeCallback(node, () => updater.dispose());
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let contextToExtend = bindingContext;
|
|
153
|
+
let bindingHandlerThatControlsDescendants: string | undefined;
|
|
154
|
+
|
|
155
|
+
if (bindings) {
|
|
156
|
+
contextToExtend = subscribeToBindingEvent(node, bindings, bindingContext, evaluateValueAccessor);
|
|
157
|
+
|
|
158
|
+
const getValueAccessor: (key: string) => () => unknown = bindingsUpdater
|
|
159
|
+
? (bindingKey) => () => {
|
|
160
|
+
const current = bindingsUpdater!.get() as Record<string, unknown> | null;
|
|
161
|
+
return evaluateValueAccessor(current?.[bindingKey]);
|
|
162
|
+
}
|
|
163
|
+
: (bindingKey) => bindings![bindingKey] as () => unknown;
|
|
164
|
+
|
|
165
|
+
const allBindings: AllBindingsAccessor = {
|
|
166
|
+
get(key: string): unknown {
|
|
167
|
+
return bindings![key] ? evaluateValueAccessor(getValueAccessor(key)) : undefined;
|
|
168
|
+
},
|
|
169
|
+
has(key: string): boolean {
|
|
170
|
+
return key in bindings!;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const orderedBindings = topologicalSortBindings(bindings);
|
|
175
|
+
|
|
176
|
+
for (const { key: bindingKey, handler } of orderedBindings) {
|
|
177
|
+
if (node.nodeType === 8) {
|
|
178
|
+
validateVirtualElementBinding(bindingKey);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
if (typeof handler.init === 'function') {
|
|
183
|
+
const initFn = handler.init;
|
|
184
|
+
ignore(() => {
|
|
185
|
+
const initResult = initFn(
|
|
186
|
+
node,
|
|
187
|
+
getValueAccessor(bindingKey),
|
|
188
|
+
allBindings,
|
|
189
|
+
contextToExtend.$data,
|
|
190
|
+
contextToExtend,
|
|
191
|
+
);
|
|
192
|
+
if (initResult && initResult.controlsDescendantBindings) {
|
|
193
|
+
if (bindingHandlerThatControlsDescendants !== undefined) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
'Multiple bindings (' + bindingHandlerThatControlsDescendants + ' and ' + bindingKey +
|
|
196
|
+
') are trying to control descendant bindings of the same element.',
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
bindingHandlerThatControlsDescendants = bindingKey;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (typeof handler.update === 'function') {
|
|
205
|
+
const updateFn = handler.update;
|
|
206
|
+
const updateComputed = new Computed(() => {
|
|
207
|
+
updateFn(
|
|
208
|
+
node,
|
|
209
|
+
getValueAccessor(bindingKey),
|
|
210
|
+
allBindings,
|
|
211
|
+
contextToExtend.$data,
|
|
212
|
+
contextToExtend,
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
if (updateComputed.isActive()) {
|
|
216
|
+
addDisposeCallback(node, () => updateComputed.dispose());
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch (ex) {
|
|
220
|
+
const err = ex as Error;
|
|
221
|
+
err.message =
|
|
222
|
+
'Unable to process binding "' + bindingKey + ': ' + bindings[bindingKey] +
|
|
223
|
+
'"\nMessage: ' + err.message;
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const shouldBindDescendants = bindingHandlerThatControlsDescendants === undefined;
|
|
230
|
+
return {
|
|
231
|
+
shouldBindDescendants,
|
|
232
|
+
bindingContextForDescendants: shouldBindDescendants && contextToExtend,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---- Tree Walk ----
|
|
237
|
+
|
|
238
|
+
function applyBindingsToDescendantsInternal(
|
|
239
|
+
bindingContext: BindingContext,
|
|
240
|
+
elementOrVirtualElement: Node,
|
|
241
|
+
): void {
|
|
242
|
+
let nextInQueue = virtualFirstChild(elementOrVirtualElement);
|
|
243
|
+
|
|
244
|
+
if (nextInQueue) {
|
|
245
|
+
const provider = providerInstance;
|
|
246
|
+
const preprocessNode = provider.preprocessNode;
|
|
247
|
+
|
|
248
|
+
if (preprocessNode) {
|
|
249
|
+
let currentChild: Node | null = nextInQueue;
|
|
250
|
+
while (currentChild) {
|
|
251
|
+
const next = virtualNextSibling(currentChild);
|
|
252
|
+
preprocessNode.call(provider, currentChild);
|
|
253
|
+
currentChild = next;
|
|
254
|
+
}
|
|
255
|
+
nextInQueue = virtualFirstChild(elementOrVirtualElement);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let currentChild: Node | null = nextInQueue;
|
|
259
|
+
while (currentChild) {
|
|
260
|
+
const next = virtualNextSibling(currentChild);
|
|
261
|
+
applyBindingsToNodeAndDescendantsInternal(bindingContext, currentChild);
|
|
262
|
+
currentChild = next;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
bindingEvent.notify(elementOrVirtualElement, bindingEvent.childrenComplete);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function applyBindingsToNodeAndDescendantsInternal(
|
|
270
|
+
bindingContext: BindingContext,
|
|
271
|
+
nodeVerified: Node,
|
|
272
|
+
): void {
|
|
273
|
+
let bindingContextForDescendants: BindingContext | false = bindingContext;
|
|
274
|
+
|
|
275
|
+
const isElement = nodeVerified.nodeType === 1;
|
|
276
|
+
|
|
277
|
+
const shouldApplyBindings = isElement || providerInstance.nodeHasBindings(nodeVerified);
|
|
278
|
+
if (shouldApplyBindings) {
|
|
279
|
+
const result = applyBindingsToNodeInternal(nodeVerified, null, bindingContext);
|
|
280
|
+
bindingContextForDescendants = result.bindingContextForDescendants;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (bindingContextForDescendants && !NO_RECURSE[tagNameLower(nodeVerified)]) {
|
|
284
|
+
applyBindingsToDescendantsInternal(bindingContextForDescendants, nodeVerified);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ---- Utility: Wrap raw bindings as accessors ----
|
|
289
|
+
|
|
290
|
+
function makeBindingAccessors(
|
|
291
|
+
bindings: Record<string, unknown> | ((ctx: BindingContext, node: Node) => Record<string, unknown>),
|
|
292
|
+
context: BindingContext,
|
|
293
|
+
node: Node,
|
|
294
|
+
): Record<string, () => unknown> {
|
|
295
|
+
if (typeof bindings === 'function') {
|
|
296
|
+
const boundFn = (bindings as (ctx: BindingContext, node: Node) => Record<string, unknown>).bind(null, context, node);
|
|
297
|
+
const initial = ignore(boundFn);
|
|
298
|
+
const result: Record<string, () => unknown> = {};
|
|
299
|
+
for (const key of Object.keys(initial)) {
|
|
300
|
+
result[key] = () => boundFn()[key];
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
const result: Record<string, () => unknown> = {};
|
|
305
|
+
for (const key of Object.keys(bindings)) {
|
|
306
|
+
result[key] = makeValueAccessor(bindings[key]);
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ---- Public Helpers ----
|
|
312
|
+
|
|
313
|
+
function getBindingContext(
|
|
314
|
+
viewModelOrBindingContext: unknown,
|
|
315
|
+
extendContextCallback?: (self: BindingContext, parentContext: BindingContext | undefined, dataItem: unknown) => void,
|
|
316
|
+
): BindingContext {
|
|
317
|
+
if (viewModelOrBindingContext instanceof BindingContext) {
|
|
318
|
+
return viewModelOrBindingContext;
|
|
319
|
+
}
|
|
320
|
+
return new BindingContext(viewModelOrBindingContext, undefined, undefined, extendContextCallback);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---- Public API ----
|
|
324
|
+
|
|
325
|
+
export function applyBindings(
|
|
326
|
+
viewModelOrBindingContext: unknown,
|
|
327
|
+
rootNode: Node,
|
|
328
|
+
extendContextCallback?: (self: BindingContext, parentContext: BindingContext | undefined, dataItem: unknown) => void,
|
|
329
|
+
): void {
|
|
330
|
+
ensureConfigured();
|
|
331
|
+
if (!rootNode || (rootNode.nodeType !== 1 && rootNode.nodeType !== 8)) {
|
|
332
|
+
throw new Error('applyBindings: second parameter should be a DOM element or comment node');
|
|
333
|
+
}
|
|
334
|
+
applyBindingsToNodeAndDescendantsInternal(
|
|
335
|
+
getBindingContext(viewModelOrBindingContext, extendContextCallback),
|
|
336
|
+
rootNode,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function applyBindingsToDescendants(
|
|
341
|
+
viewModelOrBindingContext: unknown,
|
|
342
|
+
rootNode: Node,
|
|
343
|
+
): void {
|
|
344
|
+
ensureConfigured();
|
|
345
|
+
if (rootNode.nodeType === 1 || rootNode.nodeType === 8) {
|
|
346
|
+
applyBindingsToDescendantsInternal(
|
|
347
|
+
getBindingContext(viewModelOrBindingContext),
|
|
348
|
+
rootNode,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function applyBindingsToNode(
|
|
354
|
+
node: Node,
|
|
355
|
+
bindings: Record<string, unknown>,
|
|
356
|
+
viewModelOrBindingContext?: unknown,
|
|
357
|
+
): { shouldBindDescendants: boolean } {
|
|
358
|
+
ensureConfigured();
|
|
359
|
+
const context = getBindingContext(viewModelOrBindingContext);
|
|
360
|
+
const accessors = makeBindingAccessors(bindings, context, node);
|
|
361
|
+
return applyBindingsToNodeInternal(node, accessors, context);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function applyBindingAccessorsToNode(
|
|
365
|
+
node: Node,
|
|
366
|
+
bindings: Record<string, () => unknown> | ((ctx: BindingContext, node: Node) => Record<string, () => unknown>),
|
|
367
|
+
viewModelOrBindingContext?: unknown,
|
|
368
|
+
): { shouldBindDescendants: boolean } {
|
|
369
|
+
ensureConfigured();
|
|
370
|
+
const context = getBindingContext(viewModelOrBindingContext);
|
|
371
|
+
return applyBindingsToNodeInternal(node, bindings, context);
|
|
372
|
+
}
|