@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,198 +1,198 @@
1
- import { Subscribable, Subscription } from './subscribable.js';
2
- import { BindingContext, ANCESTOR_BINDING_INFO, BINDING_INFO_KEY } from './bindingContext.js';
3
- import { domDataGet, domDataGetOrSet } from './domData.js';
4
- import { addDisposeCallback, removeDisposeCallback } from './domNodeDisposal.js';
5
- import { ignore } from './dependencyDetection.js';
6
- import { virtualChildNodes, virtualFirstChild } from './virtualElements.js';
7
-
8
- // ---- Binding Info (shared shape stored per-node via domData) ----
9
-
10
- export interface BindingInfo {
11
- alreadyBound?: boolean;
12
- context?: BindingContext;
13
- eventSubscribable?: Subscribable;
14
- notifiedEvents?: Record<string, boolean>;
15
- asyncContext?: AsyncCompleteContext | null;
16
- }
17
-
18
- export function getBindingInfoForNode(node: Node): BindingInfo {
19
- return domDataGetOrSet(node, BINDING_INFO_KEY, {}) as BindingInfo;
20
- }
21
-
22
- // ---- AsyncCompleteContext ----
23
-
24
- function asyncContextDispose(node: Node): void {
25
- const info = domDataGet(node, BINDING_INFO_KEY) as BindingInfo | undefined;
26
- const asyncContext = info?.asyncContext;
27
- if (asyncContext) {
28
- info!.asyncContext = null;
29
- asyncContext.notifyAncestor();
30
- }
31
- }
32
-
33
- class AsyncCompleteContext {
34
- node: Node;
35
- bindingInfo: BindingInfo;
36
- asyncDescendants: Node[] = [];
37
- childrenComplete = false;
38
- ancestorBindingInfo?: BindingInfo;
39
-
40
- constructor(node: Node, bindingInfo: BindingInfo, ancestorBindingInfo?: BindingInfo) {
41
- this.node = node;
42
- this.bindingInfo = bindingInfo;
43
-
44
- if (!bindingInfo.asyncContext) {
45
- addDisposeCallback(node, asyncContextDispose);
46
- }
47
-
48
- if (ancestorBindingInfo?.asyncContext) {
49
- ancestorBindingInfo.asyncContext.asyncDescendants.push(node);
50
- this.ancestorBindingInfo = ancestorBindingInfo;
51
- }
52
- }
53
-
54
- notifyAncestor(): void {
55
- if (this.ancestorBindingInfo?.asyncContext) {
56
- this.ancestorBindingInfo.asyncContext.descendantComplete(this.node);
57
- }
58
- }
59
-
60
- descendantComplete(node: Node): void {
61
- const idx = this.asyncDescendants.indexOf(node);
62
- if (idx !== -1) {
63
- this.asyncDescendants.splice(idx, 1);
64
- }
65
- if (this.asyncDescendants.length === 0 && this.childrenComplete) {
66
- this.completeChildren();
67
- }
68
- }
69
-
70
- completeChildren(): void {
71
- this.childrenComplete = true;
72
- if (this.bindingInfo.asyncContext && this.asyncDescendants.length === 0) {
73
- this.bindingInfo.asyncContext = null;
74
- removeDisposeCallback(this.node, asyncContextDispose);
75
- bindingEvent.notify(this.node, bindingEvent.descendantsComplete);
76
- this.notifyAncestor();
77
- }
78
- }
79
- }
80
-
81
- // ---- Binding Event API ----
82
-
83
- export const bindingEvent = {
84
- childrenComplete: 'childrenComplete' as const,
85
- descendantsComplete: 'descendantsComplete' as const,
86
-
87
- subscribe(
88
- node: Node,
89
- event: string,
90
- callback: (value: unknown) => void,
91
- context?: unknown,
92
- options?: { notifyImmediately?: boolean },
93
- ): Subscription<unknown> {
94
- const bindingInfo = getBindingInfoForNode(node);
95
- if (!bindingInfo.eventSubscribable) {
96
- bindingInfo.eventSubscribable = new Subscribable();
97
- }
98
-
99
- if (options?.notifyImmediately && bindingInfo.notifiedEvents?.[event]) {
100
- ignore(() => callback.call(context, node));
101
- }
102
-
103
- const boundCallback = context ? (v: unknown) => callback.call(context, v) : callback;
104
- return bindingInfo.eventSubscribable.subscribe(boundCallback, event);
105
- },
106
-
107
- notify(node: Node, event: string): void {
108
- const bindingInfo = domDataGet(node, BINDING_INFO_KEY) as BindingInfo | undefined;
109
- if (bindingInfo) {
110
- if (!bindingInfo.notifiedEvents) {
111
- bindingInfo.notifiedEvents = {};
112
- }
113
- bindingInfo.notifiedEvents[event] = true;
114
-
115
- if (bindingInfo.eventSubscribable) {
116
- bindingInfo.eventSubscribable.notifySubscribers(node as unknown, event);
117
- }
118
-
119
- if (event === bindingEvent.childrenComplete) {
120
- if (bindingInfo.asyncContext) {
121
- bindingInfo.asyncContext.completeChildren();
122
- } else if (
123
- bindingInfo.asyncContext === undefined &&
124
- bindingInfo.eventSubscribable?.hasSubscriptionsForEvent(bindingEvent.descendantsComplete)
125
- ) {
126
- throw new Error('descendantsComplete event not supported for bindings on this node');
127
- }
128
- }
129
- }
130
- },
131
-
132
- startPossiblyAsyncContentBinding(node: Node, bindingContext: BindingContext): BindingContext {
133
- const bindingInfo = getBindingInfoForNode(node);
134
-
135
- if (!bindingInfo.asyncContext) {
136
- bindingInfo.asyncContext = new AsyncCompleteContext(
137
- node,
138
- bindingInfo,
139
- bindingContext[ANCESTOR_BINDING_INFO] as BindingInfo | undefined,
140
- );
141
- }
142
-
143
- if (bindingContext[ANCESTOR_BINDING_INFO] === bindingInfo) {
144
- return bindingContext;
145
- }
146
-
147
- return bindingContext.extend((ctx) => {
148
- ctx[ANCESTOR_BINDING_INFO] = bindingInfo;
149
- return {};
150
- });
151
- },
152
- };
153
-
154
- // ---- Helpers for applyBindings integration ----
155
-
156
- export function subscribeToBindingEvent(
157
- node: Node,
158
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
- bindings: Record<string, any>,
160
- bindingContext: BindingContext,
161
- evaluateValueAccessor: (v: unknown) => unknown,
162
- ): BindingContext {
163
- let contextToExtend = bindingContext;
164
-
165
- if (bindingEvent.childrenComplete in bindings) {
166
- bindingEvent.subscribe(node, bindingEvent.childrenComplete, () => {
167
- const callback = evaluateValueAccessor(bindings[bindingEvent.childrenComplete]);
168
- if (callback) {
169
- const nodes = virtualChildNodes(node);
170
- if (nodes.length) {
171
- (callback as (nodes: Node[] | NodeListOf<ChildNode>, data: unknown) => void)(
172
- nodes,
173
- dataForFirstChild(node),
174
- );
175
- }
176
- }
177
- });
178
- }
179
-
180
- if (bindingEvent.descendantsComplete in bindings) {
181
- contextToExtend = bindingEvent.startPossiblyAsyncContentBinding(node, bindingContext);
182
- bindingEvent.subscribe(node, bindingEvent.descendantsComplete, () => {
183
- const callback = evaluateValueAccessor(bindings[bindingEvent.descendantsComplete]);
184
- if (callback && virtualFirstChild(node)) {
185
- (callback as (node: Node) => void)(node);
186
- }
187
- });
188
- }
189
-
190
- return contextToExtend;
191
- }
192
-
193
- function dataForFirstChild(node: Node): unknown {
194
- const first = virtualFirstChild(node);
195
- if (!first) return undefined;
196
- const info = domDataGet(first, BINDING_INFO_KEY) as BindingInfo | undefined;
197
- return info?.context?.$data;
198
- }
1
+ import { Subscribable, Subscription } from './subscribable.js';
2
+ import { BindingContext, ANCESTOR_BINDING_INFO, BINDING_INFO_KEY } from './bindingContext.js';
3
+ import { domDataGet, domDataGetOrSet } from './domData.js';
4
+ import { addDisposeCallback, removeDisposeCallback } from './domNodeDisposal.js';
5
+ import { ignore } from './dependencyDetection.js';
6
+ import { virtualChildNodes, virtualFirstChild } from './virtualElements.js';
7
+
8
+ // ---- Binding Info (shared shape stored per-node via domData) ----
9
+
10
+ export interface BindingInfo {
11
+ alreadyBound?: boolean;
12
+ context?: BindingContext;
13
+ eventSubscribable?: Subscribable;
14
+ notifiedEvents?: Record<string, boolean>;
15
+ asyncContext?: AsyncCompleteContext | null;
16
+ }
17
+
18
+ export function getBindingInfoForNode(node: Node): BindingInfo {
19
+ return domDataGetOrSet(node, BINDING_INFO_KEY, {}) as BindingInfo;
20
+ }
21
+
22
+ // ---- AsyncCompleteContext ----
23
+
24
+ function asyncContextDispose(node: Node): void {
25
+ const info = domDataGet(node, BINDING_INFO_KEY) as BindingInfo | undefined;
26
+ const asyncContext = info?.asyncContext;
27
+ if (asyncContext) {
28
+ info!.asyncContext = null;
29
+ asyncContext.notifyAncestor();
30
+ }
31
+ }
32
+
33
+ class AsyncCompleteContext {
34
+ node: Node;
35
+ bindingInfo: BindingInfo;
36
+ asyncDescendants: Node[] = [];
37
+ childrenComplete = false;
38
+ ancestorBindingInfo?: BindingInfo;
39
+
40
+ constructor(node: Node, bindingInfo: BindingInfo, ancestorBindingInfo?: BindingInfo) {
41
+ this.node = node;
42
+ this.bindingInfo = bindingInfo;
43
+
44
+ if (!bindingInfo.asyncContext) {
45
+ addDisposeCallback(node, asyncContextDispose);
46
+ }
47
+
48
+ if (ancestorBindingInfo?.asyncContext) {
49
+ ancestorBindingInfo.asyncContext.asyncDescendants.push(node);
50
+ this.ancestorBindingInfo = ancestorBindingInfo;
51
+ }
52
+ }
53
+
54
+ notifyAncestor(): void {
55
+ if (this.ancestorBindingInfo?.asyncContext) {
56
+ this.ancestorBindingInfo.asyncContext.descendantComplete(this.node);
57
+ }
58
+ }
59
+
60
+ descendantComplete(node: Node): void {
61
+ const idx = this.asyncDescendants.indexOf(node);
62
+ if (idx !== -1) {
63
+ this.asyncDescendants.splice(idx, 1);
64
+ }
65
+ if (this.asyncDescendants.length === 0 && this.childrenComplete) {
66
+ this.completeChildren();
67
+ }
68
+ }
69
+
70
+ completeChildren(): void {
71
+ this.childrenComplete = true;
72
+ if (this.bindingInfo.asyncContext && this.asyncDescendants.length === 0) {
73
+ this.bindingInfo.asyncContext = null;
74
+ removeDisposeCallback(this.node, asyncContextDispose);
75
+ bindingEvent.notify(this.node, bindingEvent.descendantsComplete);
76
+ this.notifyAncestor();
77
+ }
78
+ }
79
+ }
80
+
81
+ // ---- Binding Event API ----
82
+
83
+ export const bindingEvent = {
84
+ childrenComplete: 'childrenComplete' as const,
85
+ descendantsComplete: 'descendantsComplete' as const,
86
+
87
+ subscribe(
88
+ node: Node,
89
+ event: string,
90
+ callback: (value: unknown) => void,
91
+ context?: unknown,
92
+ options?: { notifyImmediately?: boolean },
93
+ ): Subscription<unknown> {
94
+ const bindingInfo = getBindingInfoForNode(node);
95
+ if (!bindingInfo.eventSubscribable) {
96
+ bindingInfo.eventSubscribable = new Subscribable();
97
+ }
98
+
99
+ if (options?.notifyImmediately && bindingInfo.notifiedEvents?.[event]) {
100
+ ignore(() => callback.call(context, node));
101
+ }
102
+
103
+ const boundCallback = context ? (v: unknown) => callback.call(context, v) : callback;
104
+ return bindingInfo.eventSubscribable.subscribe(boundCallback, event);
105
+ },
106
+
107
+ notify(node: Node, event: string): void {
108
+ const bindingInfo = domDataGet(node, BINDING_INFO_KEY) as BindingInfo | undefined;
109
+ if (bindingInfo) {
110
+ if (!bindingInfo.notifiedEvents) {
111
+ bindingInfo.notifiedEvents = {};
112
+ }
113
+ bindingInfo.notifiedEvents[event] = true;
114
+
115
+ if (bindingInfo.eventSubscribable) {
116
+ bindingInfo.eventSubscribable.notifySubscribers(node as unknown, event);
117
+ }
118
+
119
+ if (event === bindingEvent.childrenComplete) {
120
+ if (bindingInfo.asyncContext) {
121
+ bindingInfo.asyncContext.completeChildren();
122
+ } else if (
123
+ bindingInfo.asyncContext === undefined &&
124
+ bindingInfo.eventSubscribable?.hasSubscriptionsForEvent(bindingEvent.descendantsComplete)
125
+ ) {
126
+ throw new Error('descendantsComplete event not supported for bindings on this node');
127
+ }
128
+ }
129
+ }
130
+ },
131
+
132
+ startPossiblyAsyncContentBinding(node: Node, bindingContext: BindingContext): BindingContext {
133
+ const bindingInfo = getBindingInfoForNode(node);
134
+
135
+ if (!bindingInfo.asyncContext) {
136
+ bindingInfo.asyncContext = new AsyncCompleteContext(
137
+ node,
138
+ bindingInfo,
139
+ bindingContext[ANCESTOR_BINDING_INFO] as BindingInfo | undefined,
140
+ );
141
+ }
142
+
143
+ if (bindingContext[ANCESTOR_BINDING_INFO] === bindingInfo) {
144
+ return bindingContext;
145
+ }
146
+
147
+ return bindingContext.extend((ctx) => {
148
+ ctx[ANCESTOR_BINDING_INFO] = bindingInfo;
149
+ return {};
150
+ });
151
+ },
152
+ };
153
+
154
+ // ---- Helpers for applyBindings integration ----
155
+
156
+ export function subscribeToBindingEvent(
157
+ node: Node,
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
+ bindings: Record<string, any>,
160
+ bindingContext: BindingContext,
161
+ evaluateValueAccessor: (v: unknown) => unknown,
162
+ ): BindingContext {
163
+ let contextToExtend = bindingContext;
164
+
165
+ if (bindingEvent.childrenComplete in bindings) {
166
+ bindingEvent.subscribe(node, bindingEvent.childrenComplete, () => {
167
+ const callback = evaluateValueAccessor(bindings[bindingEvent.childrenComplete]);
168
+ if (callback) {
169
+ const nodes = virtualChildNodes(node);
170
+ if (nodes.length) {
171
+ (callback as (nodes: Node[] | NodeListOf<ChildNode>, data: unknown) => void)(
172
+ nodes,
173
+ dataForFirstChild(node),
174
+ );
175
+ }
176
+ }
177
+ });
178
+ }
179
+
180
+ if (bindingEvent.descendantsComplete in bindings) {
181
+ contextToExtend = bindingEvent.startPossiblyAsyncContentBinding(node, bindingContext);
182
+ bindingEvent.subscribe(node, bindingEvent.descendantsComplete, () => {
183
+ const callback = evaluateValueAccessor(bindings[bindingEvent.descendantsComplete]);
184
+ if (callback && virtualFirstChild(node)) {
185
+ (callback as (node: Node) => void)(node);
186
+ }
187
+ });
188
+ }
189
+
190
+ return contextToExtend;
191
+ }
192
+
193
+ function dataForFirstChild(node: Node): unknown {
194
+ const first = virtualFirstChild(node);
195
+ if (!first) return undefined;
196
+ const info = domDataGet(first, BINDING_INFO_KEY) as BindingInfo | undefined;
197
+ return info?.context?.$data;
198
+ }