@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.
Files changed (47) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1205 -1205
  3. package/package.json +55 -55
  4. package/src/applyBindings.ts +372 -372
  5. package/src/arrayToDomMapping.ts +300 -300
  6. package/src/attributeInterpolationMarkup.ts +91 -91
  7. package/src/bindingContext.ts +207 -207
  8. package/src/bindingEvent.ts +198 -198
  9. package/src/bindingProvider.ts +260 -260
  10. package/src/bindings.ts +963 -963
  11. package/src/compareArrays.ts +122 -122
  12. package/src/componentBinding.ts +318 -318
  13. package/src/componentDecorator.ts +62 -62
  14. package/src/components.ts +367 -367
  15. package/src/computed.ts +439 -439
  16. package/src/configure.ts +52 -52
  17. package/src/controlFlowBindings.ts +206 -206
  18. package/src/core.ts +60 -60
  19. package/src/decorators.ts +407 -407
  20. package/src/dependencyDetection.ts +76 -76
  21. package/src/disposable.ts +36 -36
  22. package/src/domData.ts +42 -42
  23. package/src/domNodeDisposal.ts +94 -94
  24. package/src/effects.ts +29 -29
  25. package/src/event.ts +173 -173
  26. package/src/expressionRewriting.ts +219 -219
  27. package/src/extenders.ts +102 -102
  28. package/src/filters.ts +91 -91
  29. package/src/index.ts +150 -150
  30. package/src/interpolationMarkup.ts +130 -130
  31. package/src/memoization.ts +71 -71
  32. package/src/namespacedBindings.ts +132 -132
  33. package/src/observable.ts +48 -48
  34. package/src/observableArray.ts +397 -397
  35. package/src/options.ts +25 -25
  36. package/src/selectExtensions.ts +68 -68
  37. package/src/slotBinding.ts +84 -84
  38. package/src/subscribable.ts +266 -266
  39. package/src/tasks.ts +79 -79
  40. package/src/templateEngine.ts +108 -108
  41. package/src/templateRendering.ts +399 -399
  42. package/src/templateRewriting.ts +94 -94
  43. package/src/templateSources.ts +134 -134
  44. package/src/utils.ts +123 -123
  45. package/src/utilsDom.ts +87 -87
  46. package/src/virtualElements.ts +153 -153
  47. package/src/wireParams.ts +49 -49
@@ -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
+ }