@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.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1206 -0
  3. package/dist/applyBindings.d.ts +10 -0
  4. package/dist/applyBindings.d.ts.map +1 -0
  5. package/dist/applyBindings.js +261 -0
  6. package/dist/applyBindings.js.map +1 -0
  7. package/dist/arrayToDomMapping.d.ts +14 -0
  8. package/dist/arrayToDomMapping.d.ts.map +1 -0
  9. package/dist/arrayToDomMapping.js +219 -0
  10. package/dist/arrayToDomMapping.js.map +1 -0
  11. package/dist/attributeInterpolationMarkup.d.ts +6 -0
  12. package/dist/attributeInterpolationMarkup.d.ts.map +1 -0
  13. package/dist/attributeInterpolationMarkup.js +70 -0
  14. package/dist/attributeInterpolationMarkup.js.map +1 -0
  15. package/dist/bindingContext.d.ts +38 -0
  16. package/dist/bindingContext.d.ts.map +1 -0
  17. package/dist/bindingContext.js +138 -0
  18. package/dist/bindingContext.js.map +1 -0
  19. package/dist/bindingEvent.d.ts +33 -0
  20. package/dist/bindingEvent.d.ts.map +1 -0
  21. package/dist/bindingEvent.js +144 -0
  22. package/dist/bindingEvent.js.map +1 -0
  23. package/dist/bindingProvider.d.ts +38 -0
  24. package/dist/bindingProvider.d.ts.map +1 -0
  25. package/dist/bindingProvider.js +145 -0
  26. package/dist/bindingProvider.js.map +1 -0
  27. package/dist/bindings.d.ts +2 -0
  28. package/dist/bindings.d.ts.map +1 -0
  29. package/dist/bindings.js +862 -0
  30. package/dist/bindings.js.map +1 -0
  31. package/dist/compareArrays.d.ts +13 -0
  32. package/dist/compareArrays.d.ts.map +1 -0
  33. package/dist/compareArrays.js +91 -0
  34. package/dist/compareArrays.js.map +1 -0
  35. package/dist/componentBinding.d.ts +4 -0
  36. package/dist/componentBinding.d.ts.map +1 -0
  37. package/dist/componentBinding.js +240 -0
  38. package/dist/componentBinding.js.map +1 -0
  39. package/dist/componentDecorator.d.ts +22 -0
  40. package/dist/componentDecorator.d.ts.map +1 -0
  41. package/dist/componentDecorator.js +32 -0
  42. package/dist/componentDecorator.js.map +1 -0
  43. package/dist/components.d.ts +34 -0
  44. package/dist/components.d.ts.map +1 -0
  45. package/dist/components.js +256 -0
  46. package/dist/components.js.map +1 -0
  47. package/dist/computed.d.ts +54 -0
  48. package/dist/computed.d.ts.map +1 -0
  49. package/dist/computed.js +376 -0
  50. package/dist/computed.js.map +1 -0
  51. package/dist/configure.d.ts +4 -0
  52. package/dist/configure.d.ts.map +1 -0
  53. package/dist/configure.js +48 -0
  54. package/dist/configure.js.map +1 -0
  55. package/dist/controlFlowBindings.d.ts +2 -0
  56. package/dist/controlFlowBindings.d.ts.map +1 -0
  57. package/dist/controlFlowBindings.js +151 -0
  58. package/dist/controlFlowBindings.js.map +1 -0
  59. package/dist/core.d.ts +29 -0
  60. package/dist/core.d.ts.map +1 -0
  61. package/dist/core.js +27 -0
  62. package/dist/core.js.map +1 -0
  63. package/dist/decorators.d.ts +40 -0
  64. package/dist/decorators.d.ts.map +1 -0
  65. package/dist/decorators.js +276 -0
  66. package/dist/decorators.js.map +1 -0
  67. package/dist/dependencyDetection.d.ts +21 -0
  68. package/dist/dependencyDetection.d.ts.map +1 -0
  69. package/dist/dependencyDetection.js +53 -0
  70. package/dist/dependencyDetection.js.map +1 -0
  71. package/dist/disposable.d.ts +12 -0
  72. package/dist/disposable.d.ts.map +1 -0
  73. package/dist/disposable.js +29 -0
  74. package/dist/disposable.js.map +1 -0
  75. package/dist/domData.d.ts +6 -0
  76. package/dist/domData.d.ts.map +1 -0
  77. package/dist/domData.js +40 -0
  78. package/dist/domData.js.map +1 -0
  79. package/dist/domNodeDisposal.d.ts +7 -0
  80. package/dist/domNodeDisposal.d.ts.map +1 -0
  81. package/dist/domNodeDisposal.js +78 -0
  82. package/dist/domNodeDisposal.js.map +1 -0
  83. package/dist/effects.d.ts +13 -0
  84. package/dist/effects.d.ts.map +1 -0
  85. package/dist/effects.js +24 -0
  86. package/dist/effects.js.map +1 -0
  87. package/dist/event.d.ts +38 -0
  88. package/dist/event.d.ts.map +1 -0
  89. package/dist/event.js +138 -0
  90. package/dist/event.js.map +1 -0
  91. package/dist/expressionRewriting.d.ts +22 -0
  92. package/dist/expressionRewriting.d.ts.map +1 -0
  93. package/dist/expressionRewriting.js +169 -0
  94. package/dist/expressionRewriting.js.map +1 -0
  95. package/dist/extenders.d.ts +17 -0
  96. package/dist/extenders.d.ts.map +1 -0
  97. package/dist/extenders.js +75 -0
  98. package/dist/extenders.js.map +1 -0
  99. package/dist/filters.d.ts +5 -0
  100. package/dist/filters.d.ts.map +1 -0
  101. package/dist/filters.js +81 -0
  102. package/dist/filters.js.map +1 -0
  103. package/dist/index.d.ts +45 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +35 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/interpolationMarkup.d.ts +6 -0
  108. package/dist/interpolationMarkup.d.ts.map +1 -0
  109. package/dist/interpolationMarkup.js +110 -0
  110. package/dist/interpolationMarkup.js.map +1 -0
  111. package/dist/memoization.d.ts +7 -0
  112. package/dist/memoization.d.ts.map +1 -0
  113. package/dist/memoization.js +59 -0
  114. package/dist/memoization.js.map +1 -0
  115. package/dist/namespacedBindings.d.ts +7 -0
  116. package/dist/namespacedBindings.d.ts.map +1 -0
  117. package/dist/namespacedBindings.js +81 -0
  118. package/dist/namespacedBindings.js.map +1 -0
  119. package/dist/observable.d.ts +13 -0
  120. package/dist/observable.d.ts.map +1 -0
  121. package/dist/observable.js +39 -0
  122. package/dist/observable.js.map +1 -0
  123. package/dist/observableArray.d.ts +58 -0
  124. package/dist/observableArray.d.ts.map +1 -0
  125. package/dist/observableArray.js +328 -0
  126. package/dist/observableArray.js.map +1 -0
  127. package/dist/options.d.ts +14 -0
  128. package/dist/options.d.ts.map +1 -0
  129. package/dist/options.js +12 -0
  130. package/dist/options.js.map +1 -0
  131. package/dist/selectExtensions.d.ts +4 -0
  132. package/dist/selectExtensions.d.ts.map +1 -0
  133. package/dist/selectExtensions.js +67 -0
  134. package/dist/selectExtensions.js.map +1 -0
  135. package/dist/slotBinding.d.ts +2 -0
  136. package/dist/slotBinding.d.ts.map +1 -0
  137. package/dist/slotBinding.js +63 -0
  138. package/dist/slotBinding.js.map +1 -0
  139. package/dist/subscribable.d.ts +57 -0
  140. package/dist/subscribable.d.ts.map +1 -0
  141. package/dist/subscribable.js +211 -0
  142. package/dist/subscribable.js.map +1 -0
  143. package/dist/tasks.d.ts +8 -0
  144. package/dist/tasks.d.ts.map +1 -0
  145. package/dist/tasks.js +68 -0
  146. package/dist/tasks.js.map +1 -0
  147. package/dist/templateEngine.d.ts +37 -0
  148. package/dist/templateEngine.d.ts.map +1 -0
  149. package/dist/templateEngine.js +60 -0
  150. package/dist/templateEngine.js.map +1 -0
  151. package/dist/templateRendering.d.ts +8 -0
  152. package/dist/templateRendering.d.ts.map +1 -0
  153. package/dist/templateRendering.js +322 -0
  154. package/dist/templateRendering.js.map +1 -0
  155. package/dist/templateRewriting.d.ts +7 -0
  156. package/dist/templateRewriting.d.ts.map +1 -0
  157. package/dist/templateRewriting.js +63 -0
  158. package/dist/templateRewriting.js.map +1 -0
  159. package/dist/templateSources.d.ts +19 -0
  160. package/dist/templateSources.d.ts.map +1 -0
  161. package/dist/templateSources.js +110 -0
  162. package/dist/templateSources.js.map +1 -0
  163. package/dist/utils.d.ts +8 -0
  164. package/dist/utils.d.ts.map +1 -0
  165. package/dist/utils.js +100 -0
  166. package/dist/utils.js.map +1 -0
  167. package/dist/utilsDom.d.ts +8 -0
  168. package/dist/utilsDom.d.ts.map +1 -0
  169. package/dist/utilsDom.js +81 -0
  170. package/dist/utilsDom.js.map +1 -0
  171. package/dist/virtualElements.d.ts +12 -0
  172. package/dist/virtualElements.d.ts.map +1 -0
  173. package/dist/virtualElements.js +141 -0
  174. package/dist/virtualElements.js.map +1 -0
  175. package/dist/wireParams.d.ts +6 -0
  176. package/dist/wireParams.d.ts.map +1 -0
  177. package/dist/wireParams.js +39 -0
  178. package/dist/wireParams.js.map +1 -0
  179. package/package.json +55 -0
  180. package/src/applyBindings.ts +372 -0
  181. package/src/arrayToDomMapping.ts +300 -0
  182. package/src/attributeInterpolationMarkup.ts +91 -0
  183. package/src/bindingContext.ts +207 -0
  184. package/src/bindingEvent.ts +198 -0
  185. package/src/bindingProvider.ts +260 -0
  186. package/src/bindings.ts +963 -0
  187. package/src/compareArrays.ts +122 -0
  188. package/src/componentBinding.ts +318 -0
  189. package/src/componentDecorator.ts +62 -0
  190. package/src/components.ts +367 -0
  191. package/src/computed.ts +439 -0
  192. package/src/configure.ts +52 -0
  193. package/src/controlFlowBindings.ts +206 -0
  194. package/src/core.ts +60 -0
  195. package/src/decorators.ts +407 -0
  196. package/src/dependencyDetection.ts +76 -0
  197. package/src/disposable.ts +36 -0
  198. package/src/domData.ts +42 -0
  199. package/src/domNodeDisposal.ts +94 -0
  200. package/src/effects.ts +29 -0
  201. package/src/event.ts +173 -0
  202. package/src/expressionRewriting.ts +219 -0
  203. package/src/extenders.ts +102 -0
  204. package/src/filters.ts +91 -0
  205. package/src/index.ts +150 -0
  206. package/src/interpolationMarkup.ts +130 -0
  207. package/src/memoization.ts +71 -0
  208. package/src/namespacedBindings.ts +132 -0
  209. package/src/observable.ts +48 -0
  210. package/src/observableArray.ts +397 -0
  211. package/src/options.ts +25 -0
  212. package/src/selectExtensions.ts +68 -0
  213. package/src/slotBinding.ts +84 -0
  214. package/src/subscribable.ts +266 -0
  215. package/src/tasks.ts +79 -0
  216. package/src/templateEngine.ts +108 -0
  217. package/src/templateRendering.ts +399 -0
  218. package/src/templateRewriting.ts +94 -0
  219. package/src/templateSources.ts +134 -0
  220. package/src/utils.ts +123 -0
  221. package/src/utilsDom.ts +87 -0
  222. package/src/virtualElements.ts +153 -0
  223. package/src/wireParams.ts +49 -0
@@ -0,0 +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
+ }
@@ -0,0 +1,300 @@
1
+ import { Observable } from './observable.js';
2
+ import { Computed } from './computed.js';
3
+ import { ignore } from './dependencyDetection.js';
4
+ import { domDataGet, domDataSet, domDataNextKey } from './domData.js';
5
+ import { addDisposeCallback, cleanNode, removeNode } from './domNodeDisposal.js';
6
+ import { compareArrays } from './compareArrays.js';
7
+ import type { ArrayChange } from './compareArrays.js';
8
+ import { virtualInsertAfter } from './virtualElements.js';
9
+ import {
10
+ fixUpContinuousNodeArray,
11
+ replaceDomNodes,
12
+ anyDomNodeIsAttachedToDocument,
13
+ } from './utilsDom.js';
14
+
15
+ interface MappingResult<T> {
16
+ arrayEntry: T;
17
+ indexObservable: Observable<number>;
18
+ mappedNodes?: Node[];
19
+ dependentObservable?: Computed<void>;
20
+ initialized?: boolean;
21
+ _countWaitingForRemove?: number;
22
+ }
23
+
24
+ export interface ArrayMappingOptions {
25
+ dontLimitMoves?: boolean;
26
+ beforeRemove?: (node: Node, index: number, item: unknown) => void;
27
+ afterAdd?: (node: Node, index: number, item: unknown) => void;
28
+ beforeMove?: (node: Node, index: number, item: unknown) => void;
29
+ afterMove?: (node: Node, index: number, item: unknown) => void;
30
+ }
31
+
32
+ type MappingFunction<T> = (
33
+ value: T,
34
+ index: Observable<number>,
35
+ existingNodes: Node[],
36
+ ) => Node[];
37
+
38
+ type CallbackAfterAddingNodes<T> = (
39
+ value: T,
40
+ addedNodes: Node[],
41
+ index: Observable<number>,
42
+ ) => void;
43
+
44
+ const lastMappingResultDomDataKey = domDataNextKey();
45
+ const deletedItemDummyValue = domDataNextKey();
46
+
47
+ function mapNodeAndRefreshWhenChanged<T>(
48
+ containerNode: Node,
49
+ mapping: MappingFunction<T>,
50
+ valueToMap: T,
51
+ callbackAfterAddingNodes: CallbackAfterAddingNodes<T> | undefined,
52
+ index: Observable<number>,
53
+ ): Pick<MappingResult<T>, 'mappedNodes' | 'dependentObservable'> {
54
+ let mappedNodes: Node[] = [];
55
+ const dependentObservable = new Computed<void>(() => {
56
+ const newMappedNodes = mapping(valueToMap, index, fixUpContinuousNodeArray(mappedNodes, containerNode)) || [];
57
+
58
+ if (mappedNodes.length > 0) {
59
+ replaceDomNodes(mappedNodes, newMappedNodes);
60
+ if (callbackAfterAddingNodes) {
61
+ ignore(() => callbackAfterAddingNodes(valueToMap, newMappedNodes, index));
62
+ }
63
+ }
64
+
65
+ mappedNodes.length = 0;
66
+ mappedNodes.push(...newMappedNodes);
67
+ });
68
+
69
+ addDisposeCallback(containerNode, () => dependentObservable.dispose());
70
+
71
+ const disposeWhenDetached = () => {
72
+ if (!anyDomNodeIsAttachedToDocument(mappedNodes)) {
73
+ dependentObservable.dispose();
74
+ }
75
+ };
76
+ if (mappedNodes.length > 0) {
77
+ addDisposeCallback(mappedNodes[0], disposeWhenDetached);
78
+ }
79
+
80
+ return {
81
+ mappedNodes,
82
+ dependentObservable: dependentObservable.isActive() ? dependentObservable : undefined,
83
+ };
84
+ }
85
+
86
+ function callCallback<T>(
87
+ callback: ((node: Node, index: number, item: T) => void) | undefined,
88
+ items: (MappingResult<T> | undefined)[],
89
+ ): void {
90
+ if (callback) {
91
+ for (let i = 0; i < items.length; i++) {
92
+ const item = items[i];
93
+ if (item) {
94
+ const nodes = item.mappedNodes!;
95
+ for (let j = 0; j < nodes.length; j++) {
96
+ callback(nodes[j], i, item.arrayEntry);
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ export function setDomNodeChildrenFromArrayMapping<T>(
104
+ domNode: Node,
105
+ array: T[],
106
+ mapping: MappingFunction<T>,
107
+ options: ArrayMappingOptions | undefined,
108
+ callbackAfterAddingNodes: CallbackAfterAddingNodes<T> | undefined,
109
+ editScript?: ArrayChange<T>[],
110
+ ): void {
111
+ array = array || ([] as T[]);
112
+ options = options || {};
113
+
114
+ const lastMappingResult = domDataGet(domNode, lastMappingResultDomDataKey) as MappingResult<T>[] | undefined;
115
+ const isFirstExecution = !lastMappingResult;
116
+
117
+ const newMappingResult: MappingResult<T>[] = [];
118
+ let lastMappingResultIndex = 0;
119
+ let currentArrayIndex = 0;
120
+
121
+ const nodesToDelete: Node[] = [];
122
+ const itemsToMoveFirstIndexes: number[] = [];
123
+ const itemsForBeforeRemoveCallbacks: (MappingResult<T> | undefined)[] = [];
124
+ const itemsForBeforeMoveCallbacks: (MappingResult<T> | undefined)[] = [];
125
+ const itemsForAfterMoveCallbacks: (MappingResult<T> | undefined)[] = [];
126
+ const itemsForAfterAddCallbacks: (MappingResult<T> | undefined)[] = [];
127
+ let mapData: MappingResult<T>;
128
+ let countWaitingForRemove = 0;
129
+
130
+ function itemAdded(value: T): void {
131
+ mapData = { arrayEntry: value, indexObservable: new Observable(currentArrayIndex++) };
132
+ newMappingResult.push(mapData);
133
+ if (!isFirstExecution) {
134
+ itemsForAfterAddCallbacks[currentArrayIndex - 1] = mapData;
135
+ }
136
+ }
137
+
138
+ function itemMovedOrRetained(oldPosition: number): void {
139
+ mapData = lastMappingResult![oldPosition];
140
+ if (currentArrayIndex !== mapData.indexObservable.peek()) {
141
+ itemsForBeforeMoveCallbacks[mapData.indexObservable.peek() as number] = mapData;
142
+ itemsForAfterMoveCallbacks[currentArrayIndex] = mapData;
143
+ }
144
+ mapData.indexObservable.set(currentArrayIndex++);
145
+ fixUpContinuousNodeArray(mapData.mappedNodes!, domNode);
146
+ newMappingResult.push(mapData);
147
+ }
148
+
149
+ if (isFirstExecution) {
150
+ for (let i = 0; i < array.length; i++) itemAdded(array[i]);
151
+ } else {
152
+ if (!editScript || (lastMappingResult as MappingResult<T>[] & { _countWaitingForRemove?: number })._countWaitingForRemove) {
153
+ const lastArray = lastMappingResult!.map(x => x.arrayEntry);
154
+ const compareOptions = {
155
+ dontLimitMoves: options.dontLimitMoves,
156
+ sparse: true,
157
+ };
158
+ editScript = compareArrays(lastArray, array, compareOptions);
159
+ }
160
+
161
+ for (let i = 0; i < editScript.length; i++) {
162
+ const editScriptItem = editScript[i];
163
+ const movedIndex = editScriptItem.moved;
164
+ const itemIndex = editScriptItem.index;
165
+
166
+ switch (editScriptItem.status) {
167
+ case 'deleted':
168
+ while (lastMappingResultIndex < itemIndex) {
169
+ itemMovedOrRetained(lastMappingResultIndex++);
170
+ }
171
+ if (movedIndex === undefined) {
172
+ mapData = lastMappingResult![lastMappingResultIndex];
173
+
174
+ if (mapData.dependentObservable) {
175
+ mapData.dependentObservable.dispose();
176
+ mapData.dependentObservable = undefined;
177
+ }
178
+
179
+ if (fixUpContinuousNodeArray(mapData.mappedNodes!, domNode).length) {
180
+ if (options.beforeRemove) {
181
+ newMappingResult.push(mapData);
182
+ countWaitingForRemove++;
183
+ if (mapData.arrayEntry === deletedItemDummyValue as unknown) {
184
+ mapData = null!;
185
+ } else {
186
+ itemsForBeforeRemoveCallbacks[mapData.indexObservable.peek() as number] = mapData;
187
+ }
188
+ }
189
+ if (mapData) {
190
+ nodesToDelete.push(...mapData.mappedNodes!);
191
+ }
192
+ }
193
+ }
194
+ lastMappingResultIndex++;
195
+ break;
196
+
197
+ case 'added':
198
+ while (currentArrayIndex < itemIndex) {
199
+ itemMovedOrRetained(lastMappingResultIndex++);
200
+ }
201
+ if (movedIndex !== undefined) {
202
+ itemsToMoveFirstIndexes.push(newMappingResult.length);
203
+ itemMovedOrRetained(movedIndex);
204
+ } else {
205
+ itemAdded(editScriptItem.value);
206
+ }
207
+ break;
208
+ }
209
+ }
210
+
211
+ while (currentArrayIndex < array.length) {
212
+ itemMovedOrRetained(lastMappingResultIndex++);
213
+ }
214
+
215
+ (newMappingResult as MappingResult<T>[] & { _countWaitingForRemove?: number })._countWaitingForRemove = countWaitingForRemove;
216
+ }
217
+
218
+ domDataSet(domNode, lastMappingResultDomDataKey, newMappingResult);
219
+
220
+ callCallback(options.beforeMove, itemsForBeforeMoveCallbacks);
221
+
222
+ for (const node of nodesToDelete) {
223
+ if (options.beforeRemove) {
224
+ cleanNode(node);
225
+ } else {
226
+ removeNode(node);
227
+ }
228
+ }
229
+
230
+ let lastNode: Node | undefined;
231
+ let activeElement: Element | null = null;
232
+
233
+ try {
234
+ activeElement = domNode.ownerDocument!.activeElement;
235
+ } catch (_e) { /* ignore */ }
236
+
237
+ // Process moves first to reduce overall DOM operations
238
+ if (itemsToMoveFirstIndexes.length) {
239
+ const indexes = itemsToMoveFirstIndexes.slice();
240
+ let idx: number | undefined;
241
+ while ((idx = indexes.shift()) !== undefined) {
242
+ mapData = newMappingResult[idx];
243
+ lastNode = undefined;
244
+ for (let scanIdx = idx; scanIdx > 0;) {
245
+ const mappedNodes = newMappingResult[--scanIdx].mappedNodes;
246
+ if (mappedNodes && mappedNodes.length) {
247
+ lastNode = mappedNodes[mappedNodes.length - 1];
248
+ break;
249
+ }
250
+ }
251
+ for (let j = 0; j < mapData.mappedNodes!.length; j++) {
252
+ const nodeToInsert = mapData.mappedNodes![j];
253
+ virtualInsertAfter(domNode, nodeToInsert, lastNode || null);
254
+ lastNode = nodeToInsert;
255
+ }
256
+ }
257
+ }
258
+
259
+ // Add/reorder remaining items
260
+ for (let i = 0; i < newMappingResult.length; i++) {
261
+ mapData = newMappingResult[i];
262
+
263
+ if (!mapData.mappedNodes) {
264
+ const result = mapNodeAndRefreshWhenChanged(
265
+ domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable,
266
+ );
267
+ mapData.mappedNodes = result.mappedNodes;
268
+ mapData.dependentObservable = result.dependentObservable;
269
+ }
270
+
271
+ for (let j = 0; j < mapData.mappedNodes!.length; j++) {
272
+ const nodeToInsert = mapData.mappedNodes![j];
273
+ virtualInsertAfter(domNode, nodeToInsert, lastNode || null);
274
+ lastNode = nodeToInsert;
275
+ }
276
+
277
+ if (!mapData.initialized && callbackAfterAddingNodes) {
278
+ callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes!, mapData.indexObservable);
279
+ mapData.initialized = true;
280
+ lastNode = mapData.mappedNodes![mapData.mappedNodes!.length - 1];
281
+ }
282
+ }
283
+
284
+ // Restore focus
285
+ if (activeElement && domNode.ownerDocument!.activeElement !== activeElement) {
286
+ (activeElement as HTMLElement).focus?.();
287
+ }
288
+
289
+ callCallback(options.beforeRemove, itemsForBeforeRemoveCallbacks);
290
+
291
+ // Mark removed items so they won't match in future diffs
292
+ for (let i = 0; i < itemsForBeforeRemoveCallbacks.length; i++) {
293
+ if (itemsForBeforeRemoveCallbacks[i]) {
294
+ itemsForBeforeRemoveCallbacks[i]!.arrayEntry = deletedItemDummyValue as unknown as T;
295
+ }
296
+ }
297
+
298
+ callCallback(options.afterMove, itemsForAfterMoveCallbacks);
299
+ callCallback(options.afterAdd, itemsForAfterAddCallbacks);
300
+ }