@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.
- package/LICENSE +21 -0
- package/README.md +1206 -0
- package/dist/applyBindings.d.ts +10 -0
- package/dist/applyBindings.d.ts.map +1 -0
- package/dist/applyBindings.js +261 -0
- package/dist/applyBindings.js.map +1 -0
- package/dist/arrayToDomMapping.d.ts +14 -0
- package/dist/arrayToDomMapping.d.ts.map +1 -0
- package/dist/arrayToDomMapping.js +219 -0
- package/dist/arrayToDomMapping.js.map +1 -0
- package/dist/attributeInterpolationMarkup.d.ts +6 -0
- package/dist/attributeInterpolationMarkup.d.ts.map +1 -0
- package/dist/attributeInterpolationMarkup.js +70 -0
- package/dist/attributeInterpolationMarkup.js.map +1 -0
- package/dist/bindingContext.d.ts +38 -0
- package/dist/bindingContext.d.ts.map +1 -0
- package/dist/bindingContext.js +138 -0
- package/dist/bindingContext.js.map +1 -0
- package/dist/bindingEvent.d.ts +33 -0
- package/dist/bindingEvent.d.ts.map +1 -0
- package/dist/bindingEvent.js +144 -0
- package/dist/bindingEvent.js.map +1 -0
- package/dist/bindingProvider.d.ts +38 -0
- package/dist/bindingProvider.d.ts.map +1 -0
- package/dist/bindingProvider.js +145 -0
- package/dist/bindingProvider.js.map +1 -0
- package/dist/bindings.d.ts +2 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +862 -0
- package/dist/bindings.js.map +1 -0
- package/dist/compareArrays.d.ts +13 -0
- package/dist/compareArrays.d.ts.map +1 -0
- package/dist/compareArrays.js +91 -0
- package/dist/compareArrays.js.map +1 -0
- package/dist/componentBinding.d.ts +4 -0
- package/dist/componentBinding.d.ts.map +1 -0
- package/dist/componentBinding.js +240 -0
- package/dist/componentBinding.js.map +1 -0
- package/dist/componentDecorator.d.ts +22 -0
- package/dist/componentDecorator.d.ts.map +1 -0
- package/dist/componentDecorator.js +32 -0
- package/dist/componentDecorator.js.map +1 -0
- package/dist/components.d.ts +34 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +256 -0
- package/dist/components.js.map +1 -0
- package/dist/computed.d.ts +54 -0
- package/dist/computed.d.ts.map +1 -0
- package/dist/computed.js +376 -0
- package/dist/computed.js.map +1 -0
- package/dist/configure.d.ts +4 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +48 -0
- package/dist/configure.js.map +1 -0
- package/dist/controlFlowBindings.d.ts +2 -0
- package/dist/controlFlowBindings.d.ts.map +1 -0
- package/dist/controlFlowBindings.js +151 -0
- package/dist/controlFlowBindings.js.map +1 -0
- package/dist/core.d.ts +29 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +27 -0
- package/dist/core.js.map +1 -0
- package/dist/decorators.d.ts +40 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +276 -0
- package/dist/decorators.js.map +1 -0
- package/dist/dependencyDetection.d.ts +21 -0
- package/dist/dependencyDetection.d.ts.map +1 -0
- package/dist/dependencyDetection.js +53 -0
- package/dist/dependencyDetection.js.map +1 -0
- package/dist/disposable.d.ts +12 -0
- package/dist/disposable.d.ts.map +1 -0
- package/dist/disposable.js +29 -0
- package/dist/disposable.js.map +1 -0
- package/dist/domData.d.ts +6 -0
- package/dist/domData.d.ts.map +1 -0
- package/dist/domData.js +40 -0
- package/dist/domData.js.map +1 -0
- package/dist/domNodeDisposal.d.ts +7 -0
- package/dist/domNodeDisposal.d.ts.map +1 -0
- package/dist/domNodeDisposal.js +78 -0
- package/dist/domNodeDisposal.js.map +1 -0
- package/dist/effects.d.ts +13 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +24 -0
- package/dist/effects.js.map +1 -0
- package/dist/event.d.ts +38 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +138 -0
- package/dist/event.js.map +1 -0
- package/dist/expressionRewriting.d.ts +22 -0
- package/dist/expressionRewriting.d.ts.map +1 -0
- package/dist/expressionRewriting.js +169 -0
- package/dist/expressionRewriting.js.map +1 -0
- package/dist/extenders.d.ts +17 -0
- package/dist/extenders.d.ts.map +1 -0
- package/dist/extenders.js +75 -0
- package/dist/extenders.js.map +1 -0
- package/dist/filters.d.ts +5 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +81 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolationMarkup.d.ts +6 -0
- package/dist/interpolationMarkup.d.ts.map +1 -0
- package/dist/interpolationMarkup.js +110 -0
- package/dist/interpolationMarkup.js.map +1 -0
- package/dist/memoization.d.ts +7 -0
- package/dist/memoization.d.ts.map +1 -0
- package/dist/memoization.js +59 -0
- package/dist/memoization.js.map +1 -0
- package/dist/namespacedBindings.d.ts +7 -0
- package/dist/namespacedBindings.d.ts.map +1 -0
- package/dist/namespacedBindings.js +81 -0
- package/dist/namespacedBindings.js.map +1 -0
- package/dist/observable.d.ts +13 -0
- package/dist/observable.d.ts.map +1 -0
- package/dist/observable.js +39 -0
- package/dist/observable.js.map +1 -0
- package/dist/observableArray.d.ts +58 -0
- package/dist/observableArray.d.ts.map +1 -0
- package/dist/observableArray.js +328 -0
- package/dist/observableArray.js.map +1 -0
- package/dist/options.d.ts +14 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +12 -0
- package/dist/options.js.map +1 -0
- package/dist/selectExtensions.d.ts +4 -0
- package/dist/selectExtensions.d.ts.map +1 -0
- package/dist/selectExtensions.js +67 -0
- package/dist/selectExtensions.js.map +1 -0
- package/dist/slotBinding.d.ts +2 -0
- package/dist/slotBinding.d.ts.map +1 -0
- package/dist/slotBinding.js +63 -0
- package/dist/slotBinding.js.map +1 -0
- package/dist/subscribable.d.ts +57 -0
- package/dist/subscribable.d.ts.map +1 -0
- package/dist/subscribable.js +211 -0
- package/dist/subscribable.js.map +1 -0
- package/dist/tasks.d.ts +8 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +68 -0
- package/dist/tasks.js.map +1 -0
- package/dist/templateEngine.d.ts +37 -0
- package/dist/templateEngine.d.ts.map +1 -0
- package/dist/templateEngine.js +60 -0
- package/dist/templateEngine.js.map +1 -0
- package/dist/templateRendering.d.ts +8 -0
- package/dist/templateRendering.d.ts.map +1 -0
- package/dist/templateRendering.js +322 -0
- package/dist/templateRendering.js.map +1 -0
- package/dist/templateRewriting.d.ts +7 -0
- package/dist/templateRewriting.d.ts.map +1 -0
- package/dist/templateRewriting.js +63 -0
- package/dist/templateRewriting.js.map +1 -0
- package/dist/templateSources.d.ts +19 -0
- package/dist/templateSources.d.ts.map +1 -0
- package/dist/templateSources.js +110 -0
- package/dist/templateSources.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +100 -0
- package/dist/utils.js.map +1 -0
- package/dist/utilsDom.d.ts +8 -0
- package/dist/utilsDom.d.ts.map +1 -0
- package/dist/utilsDom.js +81 -0
- package/dist/utilsDom.js.map +1 -0
- package/dist/virtualElements.d.ts +12 -0
- package/dist/virtualElements.d.ts.map +1 -0
- package/dist/virtualElements.js +141 -0
- package/dist/virtualElements.js.map +1 -0
- package/dist/wireParams.d.ts +6 -0
- package/dist/wireParams.d.ts.map +1 -0
- package/dist/wireParams.js +39 -0
- package/dist/wireParams.js.map +1 -0
- package/package.json +55 -0
- package/src/applyBindings.ts +372 -0
- package/src/arrayToDomMapping.ts +300 -0
- package/src/attributeInterpolationMarkup.ts +91 -0
- package/src/bindingContext.ts +207 -0
- package/src/bindingEvent.ts +198 -0
- package/src/bindingProvider.ts +260 -0
- package/src/bindings.ts +963 -0
- package/src/compareArrays.ts +122 -0
- package/src/componentBinding.ts +318 -0
- package/src/componentDecorator.ts +62 -0
- package/src/components.ts +367 -0
- package/src/computed.ts +439 -0
- package/src/configure.ts +52 -0
- package/src/controlFlowBindings.ts +206 -0
- package/src/core.ts +60 -0
- package/src/decorators.ts +407 -0
- package/src/dependencyDetection.ts +76 -0
- package/src/disposable.ts +36 -0
- package/src/domData.ts +42 -0
- package/src/domNodeDisposal.ts +94 -0
- package/src/effects.ts +29 -0
- package/src/event.ts +173 -0
- package/src/expressionRewriting.ts +219 -0
- package/src/extenders.ts +102 -0
- package/src/filters.ts +91 -0
- package/src/index.ts +150 -0
- package/src/interpolationMarkup.ts +130 -0
- package/src/memoization.ts +71 -0
- package/src/namespacedBindings.ts +132 -0
- package/src/observable.ts +48 -0
- package/src/observableArray.ts +397 -0
- package/src/options.ts +25 -0
- package/src/selectExtensions.ts +68 -0
- package/src/slotBinding.ts +84 -0
- package/src/subscribable.ts +266 -0
- package/src/tasks.ts +79 -0
- package/src/templateEngine.ts +108 -0
- package/src/templateRendering.ts +399 -0
- package/src/templateRewriting.ts +94 -0
- package/src/templateSources.ts +134 -0
- package/src/utils.ts +123 -0
- package/src/utilsDom.ts +87 -0
- package/src/virtualElements.ts +153 -0
- package/src/wireParams.ts +49 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { addNodePreprocessor, getBindingHandler } from './bindingProvider.js';
|
|
2
|
+
import type { NodePreprocessFn } from './bindingProvider.js';
|
|
3
|
+
import { options } from './options.js';
|
|
4
|
+
import { parseInterpolationMarkup } from './interpolationMarkup.js';
|
|
5
|
+
|
|
6
|
+
const DATA_BIND_ATTR = 'data-bind';
|
|
7
|
+
|
|
8
|
+
// Determines the binding expression for an attribute.
|
|
9
|
+
// If a binding handler exists for the attribute name, use it directly.
|
|
10
|
+
// Otherwise, fall back to `attr.name` (namespaced binding syntax).
|
|
11
|
+
export function attributeBinding(
|
|
12
|
+
name: string,
|
|
13
|
+
value: string,
|
|
14
|
+
_node: Element,
|
|
15
|
+
): string {
|
|
16
|
+
if (getBindingHandler(name)) {
|
|
17
|
+
return name + ':' + value;
|
|
18
|
+
} else {
|
|
19
|
+
return 'attr.' + name + ':' + value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Customizable hook — override to change how attribute names map to bindings.
|
|
24
|
+
let _attributeBindingFn = attributeBinding;
|
|
25
|
+
|
|
26
|
+
export function setAttributeBinding(
|
|
27
|
+
fn: (name: string, value: string, node: Element) => string,
|
|
28
|
+
): void {
|
|
29
|
+
_attributeBindingFn = fn;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const attributeInterpolationMarkupPreprocessor: NodePreprocessFn = function (node: Node): void {
|
|
33
|
+
if (node.nodeType !== 1 || !(node as Element).attributes?.length) return;
|
|
34
|
+
|
|
35
|
+
const element = node as Element;
|
|
36
|
+
let dataBindAttribute = element.getAttribute(DATA_BIND_ATTR);
|
|
37
|
+
|
|
38
|
+
const attrs: Attr[] = [];
|
|
39
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
40
|
+
attrs.push(element.attributes[i]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const attr of attrs) {
|
|
44
|
+
if (
|
|
45
|
+
attr.specified !== false &&
|
|
46
|
+
attr.name !== DATA_BIND_ATTR &&
|
|
47
|
+
attr.value.indexOf('{{') !== -1
|
|
48
|
+
) {
|
|
49
|
+
const parts: string[] = [];
|
|
50
|
+
let attrValue = '';
|
|
51
|
+
|
|
52
|
+
function addText(text: string): void {
|
|
53
|
+
if (text) {
|
|
54
|
+
parts.push('"' + text.replace(/"/g, '\\"') + '"');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function addExpr(expressionText: string): void {
|
|
59
|
+
if (expressionText) {
|
|
60
|
+
attrValue = expressionText;
|
|
61
|
+
parts.push('$unwrap(' + expressionText + ')');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
parseInterpolationMarkup(attr.value, addText, addExpr);
|
|
66
|
+
|
|
67
|
+
if (parts.length > 1) {
|
|
68
|
+
attrValue = '""+' + parts.join('+');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (attrValue) {
|
|
72
|
+
const attrName = attr.name.toLowerCase();
|
|
73
|
+
const binding = _attributeBindingFn(attrName, attrValue, element);
|
|
74
|
+
|
|
75
|
+
if (!dataBindAttribute) {
|
|
76
|
+
dataBindAttribute = binding;
|
|
77
|
+
} else {
|
|
78
|
+
dataBindAttribute += ',' + binding;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
element.setAttribute(DATA_BIND_ATTR, dataBindAttribute);
|
|
82
|
+
element.removeAttribute(attr.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export function enableAttributeInterpolationMarkup(): void {
|
|
89
|
+
options.attributeInterpolation = true;
|
|
90
|
+
addNodePreprocessor(attributeInterpolationMarkupPreprocessor);
|
|
91
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { PureComputed } from './computed.js';
|
|
2
|
+
import type { Computed } from './computed.js';
|
|
3
|
+
import { isReadableSubscribable } from './subscribable.js';
|
|
4
|
+
import { domDataGet } from './domData.js';
|
|
5
|
+
import { getDependencies } from './dependencyDetection.js';
|
|
6
|
+
|
|
7
|
+
function unwrapObservable(value: unknown): unknown {
|
|
8
|
+
return isReadableSubscribable(value) ? value.get() : value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SUBSCRIBABLE = Symbol('subscribable');
|
|
12
|
+
const ANCESTOR_BINDING_INFO = Symbol('ancestorBindingInfo');
|
|
13
|
+
const DATA_DEPENDENCY = Symbol('dataDependency');
|
|
14
|
+
const INHERIT = Symbol('inheritParentVm');
|
|
15
|
+
|
|
16
|
+
export { SUBSCRIBABLE, ANCESTOR_BINDING_INFO, DATA_DEPENDENCY };
|
|
17
|
+
|
|
18
|
+
export interface BindingContextOptions {
|
|
19
|
+
exportDependencies?: boolean;
|
|
20
|
+
dataDependency?: Computed<unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CreateChildContextOptions {
|
|
24
|
+
as?: string;
|
|
25
|
+
extend?: (context: BindingContext) => void;
|
|
26
|
+
noChildContext?: boolean;
|
|
27
|
+
exportDependencies?: boolean;
|
|
28
|
+
dataDependency?: Computed<unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ExtendCallback = (
|
|
32
|
+
self: BindingContext,
|
|
33
|
+
parentContext: BindingContext | undefined,
|
|
34
|
+
dataItem: unknown,
|
|
35
|
+
) => void;
|
|
36
|
+
|
|
37
|
+
export class BindingContext {
|
|
38
|
+
$data: unknown;
|
|
39
|
+
$rawData: unknown;
|
|
40
|
+
$root: unknown;
|
|
41
|
+
$parent?: unknown;
|
|
42
|
+
$parentContext?: BindingContext;
|
|
43
|
+
$parents: unknown[] = [];
|
|
44
|
+
|
|
45
|
+
[SUBSCRIBABLE]?: PureComputed<unknown>;
|
|
46
|
+
[ANCESTOR_BINDING_INFO]?: unknown;
|
|
47
|
+
[DATA_DEPENDENCY]?: Computed<unknown>;
|
|
48
|
+
|
|
49
|
+
// Allow dynamic alias properties set by createChildContext/extend
|
|
50
|
+
[key: string | symbol]: unknown;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
dataItemOrAccessor: unknown,
|
|
54
|
+
parentContext?: BindingContext,
|
|
55
|
+
dataItemAlias?: string | null,
|
|
56
|
+
extendCallback?: ExtendCallback,
|
|
57
|
+
options?: BindingContextOptions,
|
|
58
|
+
) {
|
|
59
|
+
const self = this;
|
|
60
|
+
const shouldInheritData = dataItemOrAccessor === INHERIT;
|
|
61
|
+
const realDataItemOrAccessor = shouldInheritData ? undefined : dataItemOrAccessor;
|
|
62
|
+
const isFunc = typeof realDataItemOrAccessor === 'function' && !isReadableSubscribable(realDataItemOrAccessor);
|
|
63
|
+
const dataDependency = options?.dataDependency;
|
|
64
|
+
|
|
65
|
+
function updateContext() {
|
|
66
|
+
const dataItemOrObservable = isFunc
|
|
67
|
+
? (realDataItemOrAccessor as () => unknown)()
|
|
68
|
+
: realDataItemOrAccessor;
|
|
69
|
+
const dataItem = unwrapObservable(dataItemOrObservable);
|
|
70
|
+
|
|
71
|
+
if (parentContext) {
|
|
72
|
+
Object.assign(self, parentContext);
|
|
73
|
+
|
|
74
|
+
if (ANCESTOR_BINDING_INFO in parentContext) {
|
|
75
|
+
self[ANCESTOR_BINDING_INFO] = parentContext[ANCESTOR_BINDING_INFO];
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
self.$parents = [];
|
|
79
|
+
self.$root = dataItem;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
self[SUBSCRIBABLE] = subscribable;
|
|
83
|
+
|
|
84
|
+
if (shouldInheritData) {
|
|
85
|
+
// $data is already set from parent via Object.assign
|
|
86
|
+
} else {
|
|
87
|
+
self.$rawData = dataItemOrObservable;
|
|
88
|
+
self.$data = dataItem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (dataItemAlias) {
|
|
92
|
+
self[dataItemAlias] = dataItem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (extendCallback) {
|
|
96
|
+
extendCallback(self, parentContext, dataItem);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Ensure child depends on parent context's data dependencies.
|
|
100
|
+
// Reading the parent subscribable registers it as a dependency of
|
|
101
|
+
// the current PureComputed. Dependency deduplication makes this safe
|
|
102
|
+
// even if updateContext already transitively depends on it.
|
|
103
|
+
if (parentContext?.[SUBSCRIBABLE]) {
|
|
104
|
+
parentContext[SUBSCRIBABLE]!.get();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (dataDependency) {
|
|
108
|
+
self[DATA_DEPENDENCY] = dataDependency;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return self.$data;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let subscribable: PureComputed<unknown> | undefined;
|
|
115
|
+
|
|
116
|
+
if (options?.exportDependencies) {
|
|
117
|
+
updateContext();
|
|
118
|
+
} else {
|
|
119
|
+
subscribable = new PureComputed(updateContext);
|
|
120
|
+
subscribable.peek();
|
|
121
|
+
|
|
122
|
+
if (subscribable.isActive()) {
|
|
123
|
+
subscribable.equalityComparer = undefined;
|
|
124
|
+
} else {
|
|
125
|
+
self[SUBSCRIBABLE] = undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
createChildContext(
|
|
131
|
+
dataItemOrAccessor: unknown,
|
|
132
|
+
aliasOrOptions?: string | CreateChildContextOptions,
|
|
133
|
+
extendCallback?: (context: BindingContext) => void,
|
|
134
|
+
options?: BindingContextOptions,
|
|
135
|
+
): BindingContext {
|
|
136
|
+
let dataItemAlias: string | undefined;
|
|
137
|
+
|
|
138
|
+
if (!options && aliasOrOptions && typeof aliasOrOptions === 'object') {
|
|
139
|
+
options = aliasOrOptions;
|
|
140
|
+
dataItemAlias = aliasOrOptions.as;
|
|
141
|
+
extendCallback = aliasOrOptions.extend;
|
|
142
|
+
} else if (typeof aliasOrOptions === 'string') {
|
|
143
|
+
dataItemAlias = aliasOrOptions;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const childOptions = options as CreateChildContextOptions | undefined;
|
|
147
|
+
|
|
148
|
+
if (dataItemAlias && childOptions?.noChildContext) {
|
|
149
|
+
const isFunc = typeof dataItemOrAccessor === 'function' && !isReadableSubscribable(dataItemOrAccessor);
|
|
150
|
+
return new BindingContext(INHERIT, this, null, (self) => {
|
|
151
|
+
if (extendCallback) {
|
|
152
|
+
extendCallback(self);
|
|
153
|
+
}
|
|
154
|
+
self[dataItemAlias!] = isFunc ? (dataItemOrAccessor as () => unknown)() : dataItemOrAccessor;
|
|
155
|
+
}, options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return new BindingContext(dataItemOrAccessor, this, dataItemAlias, (self, parentCtx) => {
|
|
159
|
+
self.$parentContext = parentCtx;
|
|
160
|
+
self.$parent = parentCtx?.$data;
|
|
161
|
+
self.$parents = (parentCtx?.$parents || []).slice(0);
|
|
162
|
+
self.$parents.unshift(self.$parent);
|
|
163
|
+
if (extendCallback) {
|
|
164
|
+
extendCallback(self);
|
|
165
|
+
}
|
|
166
|
+
}, options);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
extend(
|
|
170
|
+
properties: Record<string, unknown> | ((self: BindingContext) => Record<string, unknown>) | null,
|
|
171
|
+
options?: BindingContextOptions,
|
|
172
|
+
): BindingContext {
|
|
173
|
+
return new BindingContext(INHERIT, this, null, (self) => {
|
|
174
|
+
if (properties) {
|
|
175
|
+
Object.assign(
|
|
176
|
+
self,
|
|
177
|
+
typeof properties === 'function' ? properties(self) : properties,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}, options);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// DOM data key for storing binding info on nodes (shared with applyBindings)
|
|
185
|
+
export const BINDING_INFO_KEY = '__tapout_bindingInfo';
|
|
186
|
+
|
|
187
|
+
interface BindingInfo {
|
|
188
|
+
context?: BindingContext;
|
|
189
|
+
alreadyBound?: boolean;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function storedBindingContextForNode(node: Node): BindingContext | undefined {
|
|
193
|
+
const info = domDataGet(node, BINDING_INFO_KEY) as BindingInfo | undefined;
|
|
194
|
+
return info?.context;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function contextFor(node: Node): BindingContext | undefined {
|
|
198
|
+
if (node && (node.nodeType === 1 || node.nodeType === 8)) {
|
|
199
|
+
return storedBindingContextForNode(node);
|
|
200
|
+
}
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function dataFor(node: Node): unknown {
|
|
205
|
+
const context = contextFor(node);
|
|
206
|
+
return context ? context.$data : undefined;
|
|
207
|
+
}
|
|
@@ -0,0 +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
|
+
}
|