@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/bindingProvider.ts
CHANGED
|
@@ -1,260 +1,260 @@
|
|
|
1
|
-
import type { BindingContext } from './bindingContext.js';
|
|
2
|
-
import type { AllBindingsAccessor } from './expressionRewriting.js';
|
|
3
|
-
export type { AllBindingsAccessor } from './expressionRewriting.js';
|
|
4
|
-
import { preProcessBindings } from './expressionRewriting.js';
|
|
5
|
-
import { hasBindingValue, virtualNodeBindingValue } from './virtualElements.js';
|
|
6
|
-
import { unwrapObservable } from './utils.js';
|
|
7
|
-
|
|
8
|
-
const DATA_BIND_ATTR = 'data-bind';
|
|
9
|
-
|
|
10
|
-
// ---- Binding Handler Interface ----
|
|
11
|
-
|
|
12
|
-
export interface BindingHandler {
|
|
13
|
-
init?(
|
|
14
|
-
node: Node,
|
|
15
|
-
valueAccessor: () => unknown,
|
|
16
|
-
allBindings: AllBindingsAccessor,
|
|
17
|
-
viewModel: unknown,
|
|
18
|
-
bindingContext: BindingContext,
|
|
19
|
-
): void | { controlsDescendantBindings: boolean };
|
|
20
|
-
|
|
21
|
-
update?(
|
|
22
|
-
node: Node,
|
|
23
|
-
valueAccessor: () => unknown,
|
|
24
|
-
allBindings: AllBindingsAccessor,
|
|
25
|
-
viewModel: unknown,
|
|
26
|
-
bindingContext: BindingContext,
|
|
27
|
-
): void;
|
|
28
|
-
|
|
29
|
-
preprocess?(
|
|
30
|
-
value: string,
|
|
31
|
-
key: string,
|
|
32
|
-
addBinding: (key: string, val: string) => void,
|
|
33
|
-
): string | void;
|
|
34
|
-
|
|
35
|
-
after?: string[];
|
|
36
|
-
|
|
37
|
-
getNamespacedHandler?(name: string, namespace: string, namespacedName: string): BindingHandler;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ---- Binding Handler Registry ----
|
|
41
|
-
|
|
42
|
-
export const bindingHandlers: Record<string, BindingHandler> = {};
|
|
43
|
-
|
|
44
|
-
let _getBindingHandlerImpl = (key: string): BindingHandler | undefined => bindingHandlers[key];
|
|
45
|
-
|
|
46
|
-
export function getBindingHandler(key: string): BindingHandler | undefined {
|
|
47
|
-
return _getBindingHandlerImpl(key);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---- Preprocessor Infrastructure ----
|
|
51
|
-
|
|
52
|
-
export type PreprocessFn = (
|
|
53
|
-
value: string,
|
|
54
|
-
key: string,
|
|
55
|
-
addBinding: (key: string, val: string) => void,
|
|
56
|
-
) => string | void;
|
|
57
|
-
|
|
58
|
-
export type NodePreprocessFn = (node: Node) => Node[] | void;
|
|
59
|
-
|
|
60
|
-
function getOrCreateHandler(bindingKeyOrHandler: string | BindingHandler): BindingHandler {
|
|
61
|
-
if (typeof bindingKeyOrHandler === 'object') return bindingKeyOrHandler;
|
|
62
|
-
return getBindingHandler(bindingKeyOrHandler) || (bindingHandlers[bindingKeyOrHandler] = {});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function chainPreprocessor(
|
|
66
|
-
handler: BindingHandler,
|
|
67
|
-
fn: PreprocessFn,
|
|
68
|
-
): BindingHandler {
|
|
69
|
-
const previous = handler.preprocess;
|
|
70
|
-
if (previous) {
|
|
71
|
-
handler.preprocess = function (value, key, addBinding) {
|
|
72
|
-
const result = previous(value, key, addBinding);
|
|
73
|
-
if (result !== undefined) {
|
|
74
|
-
return fn(result, key, addBinding);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
} else {
|
|
78
|
-
handler.preprocess = fn;
|
|
79
|
-
}
|
|
80
|
-
return handler;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function addBindingPreprocessor(
|
|
84
|
-
bindingKeyOrHandler: string | BindingHandler,
|
|
85
|
-
preprocessFn: PreprocessFn,
|
|
86
|
-
): BindingHandler {
|
|
87
|
-
return chainPreprocessor(getOrCreateHandler(bindingKeyOrHandler), preprocessFn);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function addNodePreprocessor(preprocessFn: NodePreprocessFn): void {
|
|
91
|
-
const provider = instance;
|
|
92
|
-
const previous = provider.preprocessNode;
|
|
93
|
-
if (previous) {
|
|
94
|
-
provider.preprocessNode = function (node: Node): Node[] | void {
|
|
95
|
-
const newNodes = previous.call(this, node);
|
|
96
|
-
if (!newNodes) {
|
|
97
|
-
return preprocessFn.call(this, node);
|
|
98
|
-
}
|
|
99
|
-
return newNodes;
|
|
100
|
-
};
|
|
101
|
-
} else {
|
|
102
|
-
provider.preprocessNode = preprocessFn;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function addBindingHandlerCreator(
|
|
107
|
-
matchRegex: RegExp,
|
|
108
|
-
callbackFn: (match: RegExpMatchArray, bindingKey: string) => BindingHandler | undefined,
|
|
109
|
-
): void {
|
|
110
|
-
const previousImpl = _getBindingHandlerImpl;
|
|
111
|
-
_getBindingHandlerImpl = (key) => {
|
|
112
|
-
const existing = previousImpl(key);
|
|
113
|
-
if (existing) return existing;
|
|
114
|
-
const match = key.match(matchRegex);
|
|
115
|
-
return match ? callbackFn(match, key) : undefined;
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ---- Filters Registry (injected by filters module) ----
|
|
120
|
-
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
122
|
-
let _filters: Record<string, Function> = {};
|
|
123
|
-
|
|
124
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
125
|
-
export function setFiltersRegistry(filters: Record<string, Function>): void {
|
|
126
|
-
_filters = filters;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ---- Compiled Binding Function Cache ----
|
|
130
|
-
|
|
131
|
-
type BindingEvaluator = (context: BindingContext, element: Node) => Record<string, unknown> | null;
|
|
132
|
-
|
|
133
|
-
function createBindingsStringEvaluator(
|
|
134
|
-
bindingsString: string,
|
|
135
|
-
options?: { valueAccessors?: boolean },
|
|
136
|
-
): BindingEvaluator {
|
|
137
|
-
const rewrittenBindings = preProcessBindings(bindingsString, {
|
|
138
|
-
valueAccessors: options?.valueAccessors,
|
|
139
|
-
getBindingHandler,
|
|
140
|
-
});
|
|
141
|
-
const functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
|
|
142
|
-
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
143
|
-
const innerFn = new Function("$context", "$element", "$unwrap", "$filters", functionBody) as
|
|
144
|
-
(context: BindingContext, element: Node, unwrap: typeof unwrapObservable, filters: Record<string, Function>) => Record<string, unknown> | null;
|
|
145
|
-
return (context, element) => innerFn(context, element, unwrapObservable, _filters);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function createBindingsStringEvaluatorViaCache(
|
|
149
|
-
bindingsString: string,
|
|
150
|
-
cache: Record<string, BindingEvaluator>,
|
|
151
|
-
options?: { valueAccessors?: boolean },
|
|
152
|
-
): BindingEvaluator {
|
|
153
|
-
const cacheKey = bindingsString + (options?.valueAccessors ? 'true' : '');
|
|
154
|
-
return cache[cacheKey] || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// ---- Custom Element Hook (set by componentBinding.ts to avoid circular imports) ----
|
|
158
|
-
|
|
159
|
-
export type GetComponentNameForNodeFn = (node: Node) => string | undefined;
|
|
160
|
-
export type AddBindingsForCustomElementFn = (
|
|
161
|
-
allBindings: Record<string, unknown> | null,
|
|
162
|
-
node: Node,
|
|
163
|
-
bindingContext: BindingContext,
|
|
164
|
-
valueAccessors?: boolean,
|
|
165
|
-
) => Record<string, unknown> | null;
|
|
166
|
-
|
|
167
|
-
let _getComponentNameForNode: GetComponentNameForNodeFn | undefined;
|
|
168
|
-
let _addBindingsForCustomElement: AddBindingsForCustomElementFn | undefined;
|
|
169
|
-
|
|
170
|
-
export function setCustomElementHooks(
|
|
171
|
-
getComponentNameForNode: GetComponentNameForNodeFn,
|
|
172
|
-
addBindingsForCustomElement: AddBindingsForCustomElementFn,
|
|
173
|
-
): void {
|
|
174
|
-
_getComponentNameForNode = getComponentNameForNode;
|
|
175
|
-
_addBindingsForCustomElement = addBindingsForCustomElement;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ---- Binding Provider ----
|
|
179
|
-
|
|
180
|
-
export class BindingProvider {
|
|
181
|
-
bindingCache: Record<string, BindingEvaluator> = {};
|
|
182
|
-
preprocessNode?: NodePreprocessFn;
|
|
183
|
-
|
|
184
|
-
nodeHasBindings(node: Node): boolean {
|
|
185
|
-
switch (node.nodeType) {
|
|
186
|
-
case 1: // Element
|
|
187
|
-
return (node as Element).getAttribute(DATA_BIND_ATTR) != null
|
|
188
|
-
|| !!(_getComponentNameForNode && _getComponentNameForNode(node));
|
|
189
|
-
case 8: // Comment
|
|
190
|
-
return hasBindingValue(node);
|
|
191
|
-
default:
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
getBindingsString(node: Node): string | null {
|
|
197
|
-
switch (node.nodeType) {
|
|
198
|
-
case 1: // Element
|
|
199
|
-
return (node as Element).getAttribute(DATA_BIND_ATTR);
|
|
200
|
-
case 8: // Comment
|
|
201
|
-
return virtualNodeBindingValue(node);
|
|
202
|
-
default:
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
getBindingAccessors(
|
|
208
|
-
node: Node,
|
|
209
|
-
bindingContext: BindingContext,
|
|
210
|
-
): Record<string, () => unknown> | null {
|
|
211
|
-
const bindingsString = this.getBindingsString(node);
|
|
212
|
-
let result = bindingsString
|
|
213
|
-
? this.parseBindingsString(bindingsString, bindingContext, node, { valueAccessors: true }) as Record<string, () => unknown> | null
|
|
214
|
-
: null;
|
|
215
|
-
|
|
216
|
-
if (_addBindingsForCustomElement) {
|
|
217
|
-
result = _addBindingsForCustomElement(result, node, bindingContext, true) as typeof result;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return result;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
getBindings(
|
|
224
|
-
node: Node,
|
|
225
|
-
bindingContext: BindingContext,
|
|
226
|
-
): Record<string, unknown> | null {
|
|
227
|
-
const bindingsString = this.getBindingsString(node);
|
|
228
|
-
let result = bindingsString
|
|
229
|
-
? this.parseBindingsString(bindingsString, bindingContext, node)
|
|
230
|
-
: null;
|
|
231
|
-
|
|
232
|
-
if (_addBindingsForCustomElement) {
|
|
233
|
-
result = _addBindingsForCustomElement(result, node, bindingContext, false) as typeof result;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return result;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
parseBindingsString(
|
|
240
|
-
bindingsString: string,
|
|
241
|
-
bindingContext: BindingContext,
|
|
242
|
-
node: Node,
|
|
243
|
-
options?: { valueAccessors?: boolean },
|
|
244
|
-
): Record<string, unknown> | null {
|
|
245
|
-
try {
|
|
246
|
-
const bindingFunction = createBindingsStringEvaluatorViaCache(
|
|
247
|
-
bindingsString,
|
|
248
|
-
this.bindingCache,
|
|
249
|
-
options,
|
|
250
|
-
);
|
|
251
|
-
return bindingFunction(bindingContext, node);
|
|
252
|
-
} catch (ex) {
|
|
253
|
-
const err = ex as Error;
|
|
254
|
-
err.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + err.message;
|
|
255
|
-
throw err;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export const instance = new BindingProvider();
|
|
1
|
+
import type { BindingContext } from './bindingContext.js';
|
|
2
|
+
import type { AllBindingsAccessor } from './expressionRewriting.js';
|
|
3
|
+
export type { AllBindingsAccessor } from './expressionRewriting.js';
|
|
4
|
+
import { preProcessBindings } from './expressionRewriting.js';
|
|
5
|
+
import { hasBindingValue, virtualNodeBindingValue } from './virtualElements.js';
|
|
6
|
+
import { unwrapObservable } from './utils.js';
|
|
7
|
+
|
|
8
|
+
const DATA_BIND_ATTR = 'data-bind';
|
|
9
|
+
|
|
10
|
+
// ---- Binding Handler Interface ----
|
|
11
|
+
|
|
12
|
+
export interface BindingHandler {
|
|
13
|
+
init?(
|
|
14
|
+
node: Node,
|
|
15
|
+
valueAccessor: () => unknown,
|
|
16
|
+
allBindings: AllBindingsAccessor,
|
|
17
|
+
viewModel: unknown,
|
|
18
|
+
bindingContext: BindingContext,
|
|
19
|
+
): void | { controlsDescendantBindings: boolean };
|
|
20
|
+
|
|
21
|
+
update?(
|
|
22
|
+
node: Node,
|
|
23
|
+
valueAccessor: () => unknown,
|
|
24
|
+
allBindings: AllBindingsAccessor,
|
|
25
|
+
viewModel: unknown,
|
|
26
|
+
bindingContext: BindingContext,
|
|
27
|
+
): void;
|
|
28
|
+
|
|
29
|
+
preprocess?(
|
|
30
|
+
value: string,
|
|
31
|
+
key: string,
|
|
32
|
+
addBinding: (key: string, val: string) => void,
|
|
33
|
+
): string | void;
|
|
34
|
+
|
|
35
|
+
after?: string[];
|
|
36
|
+
|
|
37
|
+
getNamespacedHandler?(name: string, namespace: string, namespacedName: string): BindingHandler;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---- Binding Handler Registry ----
|
|
41
|
+
|
|
42
|
+
export const bindingHandlers: Record<string, BindingHandler> = {};
|
|
43
|
+
|
|
44
|
+
let _getBindingHandlerImpl = (key: string): BindingHandler | undefined => bindingHandlers[key];
|
|
45
|
+
|
|
46
|
+
export function getBindingHandler(key: string): BindingHandler | undefined {
|
|
47
|
+
return _getBindingHandlerImpl(key);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---- Preprocessor Infrastructure ----
|
|
51
|
+
|
|
52
|
+
export type PreprocessFn = (
|
|
53
|
+
value: string,
|
|
54
|
+
key: string,
|
|
55
|
+
addBinding: (key: string, val: string) => void,
|
|
56
|
+
) => string | void;
|
|
57
|
+
|
|
58
|
+
export type NodePreprocessFn = (node: Node) => Node[] | void;
|
|
59
|
+
|
|
60
|
+
function getOrCreateHandler(bindingKeyOrHandler: string | BindingHandler): BindingHandler {
|
|
61
|
+
if (typeof bindingKeyOrHandler === 'object') return bindingKeyOrHandler;
|
|
62
|
+
return getBindingHandler(bindingKeyOrHandler) || (bindingHandlers[bindingKeyOrHandler] = {});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function chainPreprocessor(
|
|
66
|
+
handler: BindingHandler,
|
|
67
|
+
fn: PreprocessFn,
|
|
68
|
+
): BindingHandler {
|
|
69
|
+
const previous = handler.preprocess;
|
|
70
|
+
if (previous) {
|
|
71
|
+
handler.preprocess = function (value, key, addBinding) {
|
|
72
|
+
const result = previous(value, key, addBinding);
|
|
73
|
+
if (result !== undefined) {
|
|
74
|
+
return fn(result, key, addBinding);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
} else {
|
|
78
|
+
handler.preprocess = fn;
|
|
79
|
+
}
|
|
80
|
+
return handler;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function addBindingPreprocessor(
|
|
84
|
+
bindingKeyOrHandler: string | BindingHandler,
|
|
85
|
+
preprocessFn: PreprocessFn,
|
|
86
|
+
): BindingHandler {
|
|
87
|
+
return chainPreprocessor(getOrCreateHandler(bindingKeyOrHandler), preprocessFn);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function addNodePreprocessor(preprocessFn: NodePreprocessFn): void {
|
|
91
|
+
const provider = instance;
|
|
92
|
+
const previous = provider.preprocessNode;
|
|
93
|
+
if (previous) {
|
|
94
|
+
provider.preprocessNode = function (node: Node): Node[] | void {
|
|
95
|
+
const newNodes = previous.call(this, node);
|
|
96
|
+
if (!newNodes) {
|
|
97
|
+
return preprocessFn.call(this, node);
|
|
98
|
+
}
|
|
99
|
+
return newNodes;
|
|
100
|
+
};
|
|
101
|
+
} else {
|
|
102
|
+
provider.preprocessNode = preprocessFn;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function addBindingHandlerCreator(
|
|
107
|
+
matchRegex: RegExp,
|
|
108
|
+
callbackFn: (match: RegExpMatchArray, bindingKey: string) => BindingHandler | undefined,
|
|
109
|
+
): void {
|
|
110
|
+
const previousImpl = _getBindingHandlerImpl;
|
|
111
|
+
_getBindingHandlerImpl = (key) => {
|
|
112
|
+
const existing = previousImpl(key);
|
|
113
|
+
if (existing) return existing;
|
|
114
|
+
const match = key.match(matchRegex);
|
|
115
|
+
return match ? callbackFn(match, key) : undefined;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- Filters Registry (injected by filters module) ----
|
|
120
|
+
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
122
|
+
let _filters: Record<string, Function> = {};
|
|
123
|
+
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
125
|
+
export function setFiltersRegistry(filters: Record<string, Function>): void {
|
|
126
|
+
_filters = filters;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---- Compiled Binding Function Cache ----
|
|
130
|
+
|
|
131
|
+
type BindingEvaluator = (context: BindingContext, element: Node) => Record<string, unknown> | null;
|
|
132
|
+
|
|
133
|
+
function createBindingsStringEvaluator(
|
|
134
|
+
bindingsString: string,
|
|
135
|
+
options?: { valueAccessors?: boolean },
|
|
136
|
+
): BindingEvaluator {
|
|
137
|
+
const rewrittenBindings = preProcessBindings(bindingsString, {
|
|
138
|
+
valueAccessors: options?.valueAccessors,
|
|
139
|
+
getBindingHandler,
|
|
140
|
+
});
|
|
141
|
+
const functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
143
|
+
const innerFn = new Function("$context", "$element", "$unwrap", "$filters", functionBody) as
|
|
144
|
+
(context: BindingContext, element: Node, unwrap: typeof unwrapObservable, filters: Record<string, Function>) => Record<string, unknown> | null;
|
|
145
|
+
return (context, element) => innerFn(context, element, unwrapObservable, _filters);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function createBindingsStringEvaluatorViaCache(
|
|
149
|
+
bindingsString: string,
|
|
150
|
+
cache: Record<string, BindingEvaluator>,
|
|
151
|
+
options?: { valueAccessors?: boolean },
|
|
152
|
+
): BindingEvaluator {
|
|
153
|
+
const cacheKey = bindingsString + (options?.valueAccessors ? 'true' : '');
|
|
154
|
+
return cache[cacheKey] || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---- Custom Element Hook (set by componentBinding.ts to avoid circular imports) ----
|
|
158
|
+
|
|
159
|
+
export type GetComponentNameForNodeFn = (node: Node) => string | undefined;
|
|
160
|
+
export type AddBindingsForCustomElementFn = (
|
|
161
|
+
allBindings: Record<string, unknown> | null,
|
|
162
|
+
node: Node,
|
|
163
|
+
bindingContext: BindingContext,
|
|
164
|
+
valueAccessors?: boolean,
|
|
165
|
+
) => Record<string, unknown> | null;
|
|
166
|
+
|
|
167
|
+
let _getComponentNameForNode: GetComponentNameForNodeFn | undefined;
|
|
168
|
+
let _addBindingsForCustomElement: AddBindingsForCustomElementFn | undefined;
|
|
169
|
+
|
|
170
|
+
export function setCustomElementHooks(
|
|
171
|
+
getComponentNameForNode: GetComponentNameForNodeFn,
|
|
172
|
+
addBindingsForCustomElement: AddBindingsForCustomElementFn,
|
|
173
|
+
): void {
|
|
174
|
+
_getComponentNameForNode = getComponentNameForNode;
|
|
175
|
+
_addBindingsForCustomElement = addBindingsForCustomElement;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---- Binding Provider ----
|
|
179
|
+
|
|
180
|
+
export class BindingProvider {
|
|
181
|
+
bindingCache: Record<string, BindingEvaluator> = {};
|
|
182
|
+
preprocessNode?: NodePreprocessFn;
|
|
183
|
+
|
|
184
|
+
nodeHasBindings(node: Node): boolean {
|
|
185
|
+
switch (node.nodeType) {
|
|
186
|
+
case 1: // Element
|
|
187
|
+
return (node as Element).getAttribute(DATA_BIND_ATTR) != null
|
|
188
|
+
|| !!(_getComponentNameForNode && _getComponentNameForNode(node));
|
|
189
|
+
case 8: // Comment
|
|
190
|
+
return hasBindingValue(node);
|
|
191
|
+
default:
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getBindingsString(node: Node): string | null {
|
|
197
|
+
switch (node.nodeType) {
|
|
198
|
+
case 1: // Element
|
|
199
|
+
return (node as Element).getAttribute(DATA_BIND_ATTR);
|
|
200
|
+
case 8: // Comment
|
|
201
|
+
return virtualNodeBindingValue(node);
|
|
202
|
+
default:
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getBindingAccessors(
|
|
208
|
+
node: Node,
|
|
209
|
+
bindingContext: BindingContext,
|
|
210
|
+
): Record<string, () => unknown> | null {
|
|
211
|
+
const bindingsString = this.getBindingsString(node);
|
|
212
|
+
let result = bindingsString
|
|
213
|
+
? this.parseBindingsString(bindingsString, bindingContext, node, { valueAccessors: true }) as Record<string, () => unknown> | null
|
|
214
|
+
: null;
|
|
215
|
+
|
|
216
|
+
if (_addBindingsForCustomElement) {
|
|
217
|
+
result = _addBindingsForCustomElement(result, node, bindingContext, true) as typeof result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getBindings(
|
|
224
|
+
node: Node,
|
|
225
|
+
bindingContext: BindingContext,
|
|
226
|
+
): Record<string, unknown> | null {
|
|
227
|
+
const bindingsString = this.getBindingsString(node);
|
|
228
|
+
let result = bindingsString
|
|
229
|
+
? this.parseBindingsString(bindingsString, bindingContext, node)
|
|
230
|
+
: null;
|
|
231
|
+
|
|
232
|
+
if (_addBindingsForCustomElement) {
|
|
233
|
+
result = _addBindingsForCustomElement(result, node, bindingContext, false) as typeof result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
parseBindingsString(
|
|
240
|
+
bindingsString: string,
|
|
241
|
+
bindingContext: BindingContext,
|
|
242
|
+
node: Node,
|
|
243
|
+
options?: { valueAccessors?: boolean },
|
|
244
|
+
): Record<string, unknown> | null {
|
|
245
|
+
try {
|
|
246
|
+
const bindingFunction = createBindingsStringEvaluatorViaCache(
|
|
247
|
+
bindingsString,
|
|
248
|
+
this.bindingCache,
|
|
249
|
+
options,
|
|
250
|
+
);
|
|
251
|
+
return bindingFunction(bindingContext, node);
|
|
252
|
+
} catch (ex) {
|
|
253
|
+
const err = ex as Error;
|
|
254
|
+
err.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + err.message;
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const instance = new BindingProvider();
|