@gogocat/data-bind 1.12.0 → 2.0.0
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/.editorconfig +14 -14
- package/.vscode/launch.json +12 -12
- package/CONFIGURATION.md +294 -0
- package/REACTIVE_MODE.md +553 -0
- package/README.md +266 -829
- package/babel.config.json +30 -0
- package/dist/js/_escape.d.ts +14 -0
- package/dist/js/_escape.d.ts.map +1 -0
- package/dist/js/applyBinding.d.ts +11 -0
- package/dist/js/applyBinding.d.ts.map +1 -0
- package/dist/js/attrBinding.d.ts +12 -0
- package/dist/js/attrBinding.d.ts.map +1 -0
- package/dist/js/binder.d.ts +67 -0
- package/dist/js/binder.d.ts.map +1 -0
- package/dist/js/changeBinding.d.ts +19 -0
- package/dist/js/changeBinding.d.ts.map +1 -0
- package/dist/js/commentWrapper.d.ts +39 -0
- package/dist/js/commentWrapper.d.ts.map +1 -0
- package/dist/js/config.d.ts +55 -0
- package/dist/js/config.d.ts.map +1 -0
- package/dist/js/createBindingOption.d.ts +32 -0
- package/dist/js/createBindingOption.d.ts.map +1 -0
- package/dist/js/createEventBinding.d.ts +10 -0
- package/dist/js/createEventBinding.d.ts.map +1 -0
- package/dist/js/cssBinding.d.ts +15 -0
- package/dist/js/cssBinding.d.ts.map +1 -0
- package/dist/js/dataBind.js +2756 -2530
- package/dist/js/dataBind.min.js +8 -1
- package/dist/js/dataBind.min.js.map +1 -1
- package/dist/js/domWalker.d.ts +9 -0
- package/dist/js/domWalker.d.ts.map +1 -0
- package/dist/js/forOfBinding.d.ts +12 -0
- package/dist/js/forOfBinding.d.ts.map +1 -0
- package/dist/js/hoverBinding.d.ts +13 -0
- package/dist/js/hoverBinding.d.ts.map +1 -0
- package/dist/js/ifBinding.d.ts +12 -0
- package/dist/js/ifBinding.d.ts.map +1 -0
- package/dist/js/index.d.ts +10 -0
- package/dist/js/index.d.ts.map +1 -0
- package/dist/js/modelBinding.d.ts +12 -0
- package/dist/js/modelBinding.d.ts.map +1 -0
- package/dist/js/postProcess.d.ts +3 -0
- package/dist/js/postProcess.d.ts.map +1 -0
- package/dist/js/pubSub.d.ts +11 -0
- package/dist/js/pubSub.d.ts.map +1 -0
- package/dist/js/reactiveProxy.d.ts +28 -0
- package/dist/js/reactiveProxy.d.ts.map +1 -0
- package/dist/js/renderForOfBinding.d.ts +8 -0
- package/dist/js/renderForOfBinding.d.ts.map +1 -0
- package/dist/js/renderIfBinding.d.ts +22 -0
- package/dist/js/renderIfBinding.d.ts.map +1 -0
- package/dist/js/renderIteration.d.ts +16 -0
- package/dist/js/renderIteration.d.ts.map +1 -0
- package/dist/js/renderTemplate.d.ts +14 -0
- package/dist/js/renderTemplate.d.ts.map +1 -0
- package/dist/js/renderTemplatesBinding.d.ts +19 -0
- package/dist/js/renderTemplatesBinding.d.ts.map +1 -0
- package/dist/js/showBinding.d.ts +13 -0
- package/dist/js/showBinding.d.ts.map +1 -0
- package/dist/js/switchBinding.d.ts +13 -0
- package/dist/js/switchBinding.d.ts.map +1 -0
- package/dist/js/textBinding.d.ts +13 -0
- package/dist/js/textBinding.d.ts.map +1 -0
- package/dist/js/types/_escape.d.ts +14 -0
- package/dist/js/types/_escape.d.ts.map +1 -0
- package/dist/js/types/applyBinding.d.ts +11 -0
- package/dist/js/types/applyBinding.d.ts.map +1 -0
- package/dist/js/types/attrBinding.d.ts +12 -0
- package/dist/js/types/attrBinding.d.ts.map +1 -0
- package/dist/js/types/binder.d.ts +67 -0
- package/dist/js/types/binder.d.ts.map +1 -0
- package/dist/js/types/changeBinding.d.ts +19 -0
- package/dist/js/types/changeBinding.d.ts.map +1 -0
- package/dist/js/types/commentWrapper.d.ts +39 -0
- package/dist/js/types/commentWrapper.d.ts.map +1 -0
- package/dist/js/types/config.d.ts +55 -0
- package/dist/js/types/config.d.ts.map +1 -0
- package/dist/js/types/createBindingOption.d.ts +32 -0
- package/dist/js/types/createBindingOption.d.ts.map +1 -0
- package/dist/js/types/createEventBinding.d.ts +10 -0
- package/dist/js/types/createEventBinding.d.ts.map +1 -0
- package/dist/js/types/cssBinding.d.ts +15 -0
- package/dist/js/types/cssBinding.d.ts.map +1 -0
- package/dist/js/types/domWalker.d.ts +9 -0
- package/dist/js/types/domWalker.d.ts.map +1 -0
- package/dist/js/types/forOfBinding.d.ts +12 -0
- package/dist/js/types/forOfBinding.d.ts.map +1 -0
- package/dist/js/types/hoverBinding.d.ts +13 -0
- package/dist/js/types/hoverBinding.d.ts.map +1 -0
- package/dist/js/types/ifBinding.d.ts +12 -0
- package/dist/js/types/ifBinding.d.ts.map +1 -0
- package/dist/js/types/index.d.ts +10 -0
- package/dist/js/types/index.d.ts.map +1 -0
- package/dist/js/types/modelBinding.d.ts +12 -0
- package/dist/js/types/modelBinding.d.ts.map +1 -0
- package/dist/js/types/postProcess.d.ts +3 -0
- package/dist/js/types/postProcess.d.ts.map +1 -0
- package/dist/js/types/pubSub.d.ts +11 -0
- package/dist/js/types/pubSub.d.ts.map +1 -0
- package/dist/js/types/reactiveProxy.d.ts +28 -0
- package/dist/js/types/reactiveProxy.d.ts.map +1 -0
- package/dist/js/types/renderForOfBinding.d.ts +8 -0
- package/dist/js/types/renderForOfBinding.d.ts.map +1 -0
- package/dist/js/types/renderIfBinding.d.ts +22 -0
- package/dist/js/types/renderIfBinding.d.ts.map +1 -0
- package/dist/js/types/renderIteration.d.ts +16 -0
- package/dist/js/types/renderIteration.d.ts.map +1 -0
- package/dist/js/types/renderTemplate.d.ts +14 -0
- package/dist/js/types/renderTemplate.d.ts.map +1 -0
- package/dist/js/types/renderTemplatesBinding.d.ts +19 -0
- package/dist/js/types/renderTemplatesBinding.d.ts.map +1 -0
- package/dist/js/types/showBinding.d.ts +13 -0
- package/dist/js/types/showBinding.d.ts.map +1 -0
- package/dist/js/types/switchBinding.d.ts +13 -0
- package/dist/js/types/switchBinding.d.ts.map +1 -0
- package/dist/js/types/textBinding.d.ts +13 -0
- package/dist/js/types/textBinding.d.ts.map +1 -0
- package/dist/js/types/types.d.ts +111 -0
- package/dist/js/types/types.d.ts.map +1 -0
- package/dist/js/types/util.d.ts +119 -0
- package/dist/js/types/util.d.ts.map +1 -0
- package/dist/js/types.d.ts +111 -0
- package/dist/js/types.d.ts.map +1 -0
- package/dist/js/util.d.ts +119 -0
- package/dist/js/util.d.ts.map +1 -0
- package/eslint.config.js +124 -0
- package/examples/DBMONSTER_COMPARISON.md +123 -0
- package/examples/afterRenderDemo.html +119 -0
- package/examples/bootstrap/css/animate.css +1579 -1579
- package/examples/bootstrap/css/bootstrap.min.css +6 -6
- package/examples/bootstrap/css/homeservices.css +378 -390
- package/examples/bootstrap/css/open-iconic.css +511 -511
- package/examples/bootstrap/fonts/open-iconic.svg +543 -543
- package/examples/bootstrap/js/compMessageDialog.js +20 -19
- package/examples/bootstrap/js/compSearchBar.js +12 -19
- package/examples/bootstrap/js/compSearchResults.js +50 -46
- package/examples/bootstrap/js/featureAdsResult.json +65 -65
- package/examples/bootstrap/js/searchResult.json +57 -57
- package/examples/bootstrap.html +343 -332
- package/examples/css/baseTodo.css +141 -141
- package/examples/css/dbMonsterStyles.css +27 -27
- package/examples/css/indexTodo.css +374 -374
- package/examples/dbmonsterForOfReactive.html +40 -0
- package/examples/dbmonsterReact.html +19 -0
- package/examples/forOfBindingSimpleDebug.html +45 -0
- package/examples/globalConfig.html +131 -0
- package/examples/js/afterRenderDemo.js +190 -0
- package/examples/js/appTodo.js +46 -46
- package/examples/js/attrBindingDemo.js +2 -2
- package/examples/js/dbMonApp.js +24 -26
- package/examples/js/dbMonAppReact.jsx +79 -0
- package/examples/js/dbMonAppReactive.js +28 -0
- package/examples/js/fiberDemo.js +4 -4
- package/examples/js/filtersDemo.js +8 -8
- package/examples/js/forOfDemo.js +7 -9
- package/examples/js/forOfDemoComplex.js +44 -17
- package/examples/js/form.js +14 -14
- package/examples/js/globalConfig.js +117 -0
- package/examples/js/ifBindingDemo.js +16 -16
- package/examples/js/reactiveDemo.js +119 -0
- package/examples/js/switchBindingDemo.js +8 -8
- package/examples/react-dbmonster/dist/bundle.js +43 -0
- package/examples/react-dbmonster/package-lock.json +537 -0
- package/examples/react-dbmonster/package.json +16 -0
- package/examples/react-dbmonster/src/index.jsx +80 -0
- package/examples/reactiveDemo.html +127 -0
- package/examples/refreshRateTest.html +75 -75
- package/index.html +841 -0
- package/package.json +31 -34
- package/rollup.config.js +79 -36
- package/src/{_escape.js → _escape.ts} +19 -17
- package/src/{applyBinding.js → applyBinding.ts} +27 -18
- package/src/{attrBinding.js → attrBinding.ts} +14 -13
- package/src/{binder.js → binder.ts} +289 -181
- package/src/changeBinding.ts +93 -0
- package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
- package/src/config.ts +107 -0
- package/src/{createBindingOption.js → createBindingOption.ts} +39 -15
- package/src/createEventBinding.ts +88 -0
- package/src/{cssBinding.js → cssBinding.ts} +13 -11
- package/src/{domWalker.js → domWalker.ts} +44 -30
- package/src/{forOfBinding.js → forOfBinding.ts} +4 -3
- package/src/hoverBinding.ts +84 -0
- package/src/{ifBinding.js → ifBinding.ts} +14 -12
- package/src/index.ts +53 -0
- package/src/{modelBinding.js → modelBinding.ts} +11 -9
- package/src/{postProcess.js → postProcess.ts} +6 -4
- package/src/{pubSub.js → pubSub.ts} +24 -21
- package/src/reactiveProxy.ts +285 -0
- package/src/{renderForOfBinding.js → renderForOfBinding.ts} +54 -32
- package/src/{renderIfBinding.js → renderIfBinding.ts} +41 -19
- package/src/{renderIteration.js → renderIteration.ts} +24 -8
- package/src/renderTemplate.ts +165 -0
- package/src/renderTemplatesBinding.ts +73 -0
- package/src/{showBinding.js → showBinding.ts} +4 -3
- package/src/{switchBinding.js → switchBinding.ts} +18 -15
- package/src/{textBinding.js → textBinding.ts} +5 -4
- package/src/types.ts +124 -0
- package/src/util.ts +810 -0
- package/test/css/reporter.css +9 -9
- package/test/globals.d.ts +19 -0
- package/test/helpers/testHelper.js +46 -11
- package/test/mocks/featureAdsResult.json +65 -65
- package/test/mocks/searchResult.json +57 -57
- package/test/specs/{attrBinding.spec.js → attrBinding.spec.ts} +103 -106
- package/test/specs/{binder.spec.js → binder.spec.ts} +29 -27
- package/test/specs/blurBinding.spec.ts +60 -0
- package/test/specs/chainableUse.spec.ts +125 -0
- package/test/specs/clickBinding.spec.ts +194 -0
- package/test/specs/{cssBinding.spec.js → cssBinding.spec.ts} +72 -79
- package/test/specs/{dataBindBootstrap.spec.js → dataBindBootstrap.spec.ts} +332 -313
- package/test/specs/{filter.spec.js → filter.spec.ts} +75 -76
- package/test/specs/{forOfBinding.spec.js → forOfBinding.spec.ts} +208 -219
- package/test/specs/formBinding.spec.ts +272 -0
- package/test/specs/ifBinding.spec.ts +165 -0
- package/test/specs/{nestedComponent.spec.js → nestedComponent.spec.ts} +88 -88
- package/test/specs/reactiveProxy.spec.ts +465 -0
- package/test/specs/{showBinding.spec.js → showBinding.spec.ts} +148 -149
- package/test/specs/{switchBinding.spec.js → switchBinding.spec.ts} +172 -173
- package/test/specs/templateBinding.spec.ts +273 -0
- package/test/specs/{textBinding.spec.js → textBinding.spec.ts} +47 -48
- package/test/tsconfig.json +31 -0
- package/test-output.txt +200 -0
- package/test-reactive.html +224 -0
- package/tsconfig.json +28 -0
- package/vendors/lodash.custom.js +4577 -4577
- package/vendors/lodash.custom.min.js +45 -45
- package/vitest.config.js +27 -0
- package/.eslintrc.js +0 -1
- package/.grunt/grunt-contrib-jasmine/boot.js +0 -161
- package/.grunt/grunt-contrib-jasmine/dist/js/dataBind.js +0 -9
- package/.grunt/grunt-contrib-jasmine/grunt-template-jasmine-istanbul/reporter.js +0 -23
- package/.grunt/grunt-contrib-jasmine/jasmine-html.js +0 -853
- package/.grunt/grunt-contrib-jasmine/jasmine.css +0 -271
- package/.grunt/grunt-contrib-jasmine/jasmine.js +0 -9761
- package/.grunt/grunt-contrib-jasmine/jasmine_favicon.png +0 -0
- package/.grunt/grunt-contrib-jasmine/json2.js +0 -489
- package/.grunt/grunt-contrib-jasmine/reporter.js +0 -107
- package/coverage/coverage.json +0 -1
- package/coverage/lcov/lcov-report/base.css +0 -213
- package/coverage/lcov/lcov-report/index.html +0 -93
- package/coverage/lcov/lcov-report/js/dataBind.js.html +0 -6596
- package/coverage/lcov/lcov-report/js/index.html +0 -93
- package/coverage/lcov/lcov-report/prettify.css +0 -1
- package/coverage/lcov/lcov-report/prettify.js +0 -1
- package/coverage/lcov/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov/lcov-report/sorter.js +0 -158
- package/coverage/lcov/lcov.info +0 -1991
- package/eslintrc.json +0 -40
- package/examples/bootstrap/js/bootstrap.min.js +0 -6
- package/examples/bootstrap/js/popper.min.js +0 -5
- package/examples/bootstrap/js/searchSuggestion.js +0 -58
- package/examples/bootstrap/js/typeahead.jquery.js +0 -1538
- package/gruntfile.js +0 -92
- package/gulpfile.js +0 -32
- package/src/applyBindingExport.js +0 -5
- package/src/changeBinding.js +0 -63
- package/src/config.js +0 -66
- package/src/createEventBinding.js +0 -46
- package/src/eventSystem.js +0 -46
- package/src/hoverBinding.js +0 -57
- package/src/index.js +0 -26
- package/src/renderTemplate.js +0 -128
- package/src/renderTemplatesBinding.js +0 -44
- package/src/util.js +0 -648
- package/test/specs/blurBinding.spec.js +0 -57
- package/test/specs/formBinding.spec.js +0 -316
- package/test/specs/ifBinding.spec.js +0 -169
- package/test/specs/templateBinding.spec.js +0 -117
- package/vendors/jasmine-jquery.js +0 -841
- package/vendors/jquery-3.2.1.min.js +0 -4
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type {ViewModel} from './types';
|
|
2
|
+
|
|
3
|
+
interface ReactiveOptions {
|
|
4
|
+
onChange: () => void;
|
|
5
|
+
deep?: boolean;
|
|
6
|
+
trackChanges?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ChangeTracker {
|
|
10
|
+
changedPaths: Set<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// WeakMap to store proxy metadata
|
|
14
|
+
const PROXY_MARKER = Symbol('isReactiveProxy');
|
|
15
|
+
const ORIGINAL_TARGET = Symbol('originalTarget');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if an object is already a reactive proxy
|
|
19
|
+
*/
|
|
20
|
+
function isReactiveProxy(obj: unknown): boolean {
|
|
21
|
+
return obj !== null && typeof obj === 'object' && (obj as Record<symbol, unknown>)[PROXY_MARKER] === true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the original target from a proxy
|
|
26
|
+
*/
|
|
27
|
+
function getOriginalTarget<T>(obj: T): T {
|
|
28
|
+
if (isReactiveProxy(obj)) {
|
|
29
|
+
return (obj as Record<symbol, unknown>)[ORIGINAL_TARGET] as T;
|
|
30
|
+
}
|
|
31
|
+
return obj;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a reactive proxy that automatically triggers onChange when properties are modified
|
|
36
|
+
* Supports deep proxying for nested objects and arrays
|
|
37
|
+
*/
|
|
38
|
+
export function createReactiveProxy<T extends ViewModel>(
|
|
39
|
+
target: T,
|
|
40
|
+
options: ReactiveOptions,
|
|
41
|
+
path: string = '',
|
|
42
|
+
tracker?: ChangeTracker,
|
|
43
|
+
): T {
|
|
44
|
+
const {onChange, deep = true, trackChanges = false} = options;
|
|
45
|
+
|
|
46
|
+
// Don't proxy non-objects
|
|
47
|
+
if (target === null || typeof target !== 'object') {
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Don't re-proxy already proxied objects
|
|
52
|
+
if (isReactiveProxy(target)) {
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Skip proxying special properties to avoid circular issues
|
|
57
|
+
const skipProps = ['APP', '$root', '__proto__', 'constructor'];
|
|
58
|
+
if (skipProps.includes(path)) {
|
|
59
|
+
return target;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Track changes if enabled
|
|
63
|
+
const changeTracker = tracker || (trackChanges ? {changedPaths: new Set<string>()} : undefined);
|
|
64
|
+
|
|
65
|
+
// Store proxied nested objects to reuse same proxy
|
|
66
|
+
const proxiedChildren = new Map<string | symbol, unknown>();
|
|
67
|
+
|
|
68
|
+
const handler: ProxyHandler<T> = {
|
|
69
|
+
set(obj, prop, value) {
|
|
70
|
+
// Skip internal properties
|
|
71
|
+
if (typeof prop === 'symbol') {
|
|
72
|
+
(obj as Record<symbol, unknown>)[prop] = value;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const oldValue = obj[prop as keyof T];
|
|
77
|
+
|
|
78
|
+
// Only trigger if value actually changed
|
|
79
|
+
if (oldValue === value) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Set the new value
|
|
84
|
+
obj[prop as keyof T] = value;
|
|
85
|
+
|
|
86
|
+
// Clear cached proxy for this property since value changed
|
|
87
|
+
proxiedChildren.delete(prop);
|
|
88
|
+
|
|
89
|
+
// Track the changed path
|
|
90
|
+
if (changeTracker) {
|
|
91
|
+
const fullPath = path ? `${path}.${String(prop)}` : String(prop);
|
|
92
|
+
changeTracker.changedPaths.add(fullPath);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Trigger onChange callback (debounced render)
|
|
96
|
+
onChange();
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
get(obj, prop) {
|
|
102
|
+
// Return proxy markers
|
|
103
|
+
if (prop === PROXY_MARKER) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (prop === ORIGINAL_TARGET) {
|
|
107
|
+
return obj;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const value = obj[prop as keyof T];
|
|
111
|
+
|
|
112
|
+
// Don't proxy functions, symbols, or special properties
|
|
113
|
+
if (
|
|
114
|
+
typeof value === 'function' ||
|
|
115
|
+
typeof prop === 'symbol' ||
|
|
116
|
+
skipProps.includes(String(prop))
|
|
117
|
+
) {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// If deep proxying is enabled and value is an object, wrap it in proxy
|
|
122
|
+
if (deep && value !== null && typeof value === 'object') {
|
|
123
|
+
// Return cached proxy if exists
|
|
124
|
+
if (proxiedChildren.has(prop)) {
|
|
125
|
+
return proxiedChildren.get(prop);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const fullPath = path ? `${path}.${String(prop)}` : String(prop);
|
|
129
|
+
const proxied = Array.isArray(value)
|
|
130
|
+
? createReactiveArray(value as unknown[], onChange, options, fullPath, changeTracker)
|
|
131
|
+
: createReactiveProxy(value as ViewModel, options, fullPath, changeTracker);
|
|
132
|
+
|
|
133
|
+
// Cache the proxy
|
|
134
|
+
proxiedChildren.set(prop, proxied);
|
|
135
|
+
|
|
136
|
+
return proxied;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return value;
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
deleteProperty(obj, prop) {
|
|
143
|
+
if (typeof prop === 'symbol') {
|
|
144
|
+
delete (obj as Record<symbol, unknown>)[prop];
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
delete obj[prop as keyof T];
|
|
149
|
+
|
|
150
|
+
// Clear cached proxy
|
|
151
|
+
proxiedChildren.delete(prop);
|
|
152
|
+
|
|
153
|
+
// Track deletion
|
|
154
|
+
if (changeTracker) {
|
|
155
|
+
const fullPath = path ? `${path}.${String(prop)}` : String(prop);
|
|
156
|
+
changeTracker.changedPaths.add(fullPath);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
onChange();
|
|
160
|
+
return true;
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return new Proxy(target, handler);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Special handling for arrays to intercept mutating methods
|
|
169
|
+
*/
|
|
170
|
+
export function createReactiveArray<T extends unknown[]>(
|
|
171
|
+
target: T,
|
|
172
|
+
onChange: () => void,
|
|
173
|
+
options: ReactiveOptions,
|
|
174
|
+
path: string = '',
|
|
175
|
+
tracker?: ChangeTracker,
|
|
176
|
+
): T {
|
|
177
|
+
const {deep = true} = options;
|
|
178
|
+
|
|
179
|
+
// Don't re-proxy already proxied arrays
|
|
180
|
+
if (isReactiveProxy(target)) {
|
|
181
|
+
return target;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const handler: ProxyHandler<T> = {
|
|
185
|
+
set(obj, prop, value) {
|
|
186
|
+
// Handle symbol properties
|
|
187
|
+
if (typeof prop === 'symbol') {
|
|
188
|
+
(obj as Record<symbol, unknown>)[prop] = value;
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const oldValue = obj[prop as keyof T];
|
|
193
|
+
|
|
194
|
+
// Only trigger if value actually changed
|
|
195
|
+
if (oldValue === value) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
obj[prop as keyof T] = value;
|
|
200
|
+
|
|
201
|
+
// Track changes
|
|
202
|
+
if (tracker) {
|
|
203
|
+
const fullPath = path ? `${path}[${String(prop)}]` : `[${String(prop)}]`;
|
|
204
|
+
tracker.changedPaths.add(fullPath);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
onChange();
|
|
208
|
+
return true;
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
get(obj, prop) {
|
|
212
|
+
// Return proxy markers
|
|
213
|
+
if (prop === PROXY_MARKER) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
if (prop === ORIGINAL_TARGET) {
|
|
217
|
+
return obj;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const value = obj[prop as keyof T];
|
|
221
|
+
|
|
222
|
+
// Intercept array mutating methods
|
|
223
|
+
if (typeof value === 'function') {
|
|
224
|
+
const mutatingMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', 'fill'];
|
|
225
|
+
if (mutatingMethods.includes(String(prop))) {
|
|
226
|
+
return function (this: T, ...args: unknown[]) {
|
|
227
|
+
const result = (value as Function).apply(this, args);
|
|
228
|
+
|
|
229
|
+
// Track change
|
|
230
|
+
if (tracker) {
|
|
231
|
+
tracker.changedPaths.add(path || 'array');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
onChange();
|
|
235
|
+
return result;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Deep proxy array elements if they are objects
|
|
241
|
+
if (deep && value !== null && typeof value === 'object' && typeof prop !== 'symbol') {
|
|
242
|
+
const fullPath = path ? `${path}[${String(prop)}]` : `[${String(prop)}]`;
|
|
243
|
+
if (Array.isArray(value)) {
|
|
244
|
+
return createReactiveArray(value as unknown[], onChange, options, fullPath, tracker);
|
|
245
|
+
}
|
|
246
|
+
return createReactiveProxy(value as ViewModel, options, fullPath, tracker);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return value;
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
deleteProperty(obj, prop) {
|
|
253
|
+
if (typeof prop === 'symbol') {
|
|
254
|
+
delete (obj as Record<symbol, unknown>)[prop];
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
delete obj[prop as keyof T];
|
|
259
|
+
|
|
260
|
+
if (tracker) {
|
|
261
|
+
const fullPath = path ? `${path}[${String(prop)}]` : `[${String(prop)}]`;
|
|
262
|
+
tracker.changedPaths.add(fullPath);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
onChange();
|
|
266
|
+
return true;
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return new Proxy(target, handler);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Utility to get the original object from a reactive proxy
|
|
275
|
+
*/
|
|
276
|
+
export function toRaw<T>(obj: T): T {
|
|
277
|
+
return getOriginalTarget(obj);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if Proxy is supported
|
|
282
|
+
*/
|
|
283
|
+
export function isProxySupported(): boolean {
|
|
284
|
+
return typeof Proxy !== 'undefined';
|
|
285
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import {bindingAttrs as configBindingAttrs, bindingDataReference} from './config';
|
|
3
3
|
import {
|
|
4
4
|
getViewModelPropValue,
|
|
@@ -15,14 +15,21 @@ import {
|
|
|
15
15
|
removeElemnetsByCommentWrap,
|
|
16
16
|
insertRenderedElements,
|
|
17
17
|
} from './commentWrapper';
|
|
18
|
+
import type {ViewModel, BindingCache, BindingAttrs, ElementCache} from './types';
|
|
18
19
|
|
|
19
|
-
const renderForOfBinding = ({bindingData, viewModel, bindingAttrs}
|
|
20
|
+
const renderForOfBinding = ({bindingData, viewModel, bindingAttrs}: {
|
|
21
|
+
bindingData: BindingCache;
|
|
22
|
+
viewModel: ViewModel;
|
|
23
|
+
bindingAttrs: BindingAttrs;
|
|
24
|
+
}): void => {
|
|
20
25
|
if (!bindingData || !viewModel || !bindingAttrs) {
|
|
21
26
|
return;
|
|
22
27
|
}
|
|
23
|
-
let keys;
|
|
24
|
-
let iterationDataLength;
|
|
25
|
-
|
|
28
|
+
let keys: string[] | undefined;
|
|
29
|
+
let iterationDataLength: number;
|
|
30
|
+
// FIX: Use bindingData.iterator instead of bindingData to get the iteration data
|
|
31
|
+
// The iterator object has the dataKey pointing to the array/object to iterate over
|
|
32
|
+
const iterationData = getViewModelPropValue(viewModel, bindingData.iterator as BindingCache);
|
|
26
33
|
let isRegenerate = false;
|
|
27
34
|
|
|
28
35
|
// check iterationData and set iterationDataLength
|
|
@@ -57,19 +64,19 @@ const renderForOfBinding = ({bindingData, viewModel, bindingAttrs}) => {
|
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
if (!isRegenerate) {
|
|
60
|
-
bindingData.iterationBindingCache
|
|
67
|
+
(bindingData.iterationBindingCache as ElementCache[])?.forEach((elementCache: ElementCache, i: number) => {
|
|
61
68
|
if (!isEmptyObject(elementCache)) {
|
|
62
69
|
const iterationVm = createIterationViewModel({
|
|
63
|
-
bindingData
|
|
64
|
-
viewModel
|
|
65
|
-
iterationData
|
|
66
|
-
keys
|
|
70
|
+
bindingData,
|
|
71
|
+
viewModel,
|
|
72
|
+
iterationData,
|
|
73
|
+
keys,
|
|
67
74
|
index: i,
|
|
68
75
|
});
|
|
69
76
|
renderIteration({
|
|
70
|
-
elementCache
|
|
71
|
-
iterationVm
|
|
72
|
-
bindingAttrs
|
|
77
|
+
elementCache,
|
|
78
|
+
iterationVm,
|
|
79
|
+
bindingAttrs,
|
|
73
80
|
isRegenerate: false,
|
|
74
81
|
});
|
|
75
82
|
}
|
|
@@ -97,27 +104,42 @@ const renderForOfBinding = ({bindingData, viewModel, bindingAttrs}) => {
|
|
|
97
104
|
* @param {*} param0
|
|
98
105
|
* @return {object} virtual viewModel
|
|
99
106
|
*/
|
|
100
|
-
const createIterationViewModel = ({bindingData, viewModel, iterationData, keys, index}
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
const createIterationViewModel = ({bindingData, viewModel, iterationData, keys, index}: {
|
|
108
|
+
bindingData: BindingCache;
|
|
109
|
+
viewModel: ViewModel;
|
|
110
|
+
iterationData: unknown;
|
|
111
|
+
keys: string[] | undefined;
|
|
112
|
+
index: number;
|
|
113
|
+
}): ViewModel => {
|
|
114
|
+
const iterationVm: ViewModel = {};
|
|
115
|
+
const alias = bindingData.iterator?.alias;
|
|
116
|
+
if (alias) {
|
|
117
|
+
iterationVm[alias] = keys ? (iterationData as Record<string, unknown>)[keys[index]] : (iterationData as unknown[])[index];
|
|
118
|
+
}
|
|
103
119
|
// populate common binding data reference
|
|
104
120
|
iterationVm[bindingDataReference.rootDataKey] = viewModel.$root || viewModel;
|
|
105
|
-
iterationVm[bindingDataReference.currentData] = iterationVm[
|
|
121
|
+
iterationVm[bindingDataReference.currentData] = alias ? iterationVm[alias] : undefined;
|
|
106
122
|
iterationVm[bindingDataReference.currentIndex] = index;
|
|
107
123
|
return iterationVm;
|
|
108
124
|
};
|
|
109
125
|
|
|
110
|
-
const generateForOfElements = (
|
|
126
|
+
const generateForOfElements = (
|
|
127
|
+
bindingData: BindingCache,
|
|
128
|
+
viewModel: ViewModel,
|
|
129
|
+
bindingAttrs: BindingAttrs,
|
|
130
|
+
iterationData: unknown,
|
|
131
|
+
keys: string[] | undefined,
|
|
132
|
+
): DocumentFragment => {
|
|
111
133
|
const fragment = document.createDocumentFragment();
|
|
112
|
-
const iterationDataLength = bindingData.iterationSize;
|
|
113
|
-
let clonedItem;
|
|
114
|
-
let iterationVm;
|
|
115
|
-
let iterationBindingCache;
|
|
134
|
+
const iterationDataLength = bindingData.iterationSize as number;
|
|
135
|
+
let clonedItem: HTMLElement;
|
|
136
|
+
let iterationVm: ViewModel;
|
|
137
|
+
let iterationBindingCache: ElementCache;
|
|
116
138
|
let i = 0;
|
|
117
139
|
|
|
118
140
|
// create or clear exisitng iterationBindingCache
|
|
119
141
|
if (isArray(bindingData.iterationBindingCache)) {
|
|
120
|
-
bindingData.iterationBindingCache.length = 0;
|
|
142
|
+
(bindingData.iterationBindingCache as ElementCache[]).length = 0;
|
|
121
143
|
} else {
|
|
122
144
|
bindingData.iterationBindingCache = [];
|
|
123
145
|
}
|
|
@@ -129,25 +151,25 @@ const generateForOfElements = (bindingData, viewModel, bindingAttrs, iterationDa
|
|
|
129
151
|
// create bindingCache per iteration
|
|
130
152
|
iterationBindingCache = createBindingCache({
|
|
131
153
|
rootNode: clonedItem,
|
|
132
|
-
bindingAttrs
|
|
154
|
+
bindingAttrs,
|
|
133
155
|
});
|
|
134
156
|
|
|
135
|
-
bindingData.iterationBindingCache.push(iterationBindingCache);
|
|
157
|
+
(bindingData.iterationBindingCache as ElementCache[]).push(iterationBindingCache);
|
|
136
158
|
|
|
137
159
|
if (!isEmptyObject(iterationBindingCache)) {
|
|
138
160
|
// create an iterationVm match iterator alias
|
|
139
161
|
iterationVm = createIterationViewModel({
|
|
140
|
-
bindingData
|
|
141
|
-
viewModel
|
|
142
|
-
iterationData
|
|
143
|
-
keys
|
|
162
|
+
bindingData,
|
|
163
|
+
viewModel,
|
|
164
|
+
iterationData,
|
|
165
|
+
keys,
|
|
144
166
|
index: i,
|
|
145
167
|
});
|
|
146
168
|
|
|
147
169
|
renderIteration({
|
|
148
|
-
elementCache: bindingData.iterationBindingCache[i],
|
|
149
|
-
iterationVm
|
|
150
|
-
bindingAttrs
|
|
170
|
+
elementCache: (bindingData.iterationBindingCache as ElementCache[])[i],
|
|
171
|
+
iterationVm,
|
|
172
|
+
bindingAttrs,
|
|
151
173
|
isRegenerate: true,
|
|
152
174
|
});
|
|
153
175
|
}
|
|
@@ -3,6 +3,13 @@ import renderIteration from './renderIteration';
|
|
|
3
3
|
import createBindingCache from './domWalker';
|
|
4
4
|
import {commentSuffix} from './config';
|
|
5
5
|
import {removeElemnetsByCommentWrap, insertRenderedElements} from './commentWrapper';
|
|
6
|
+
import type {BindingCache, ViewModel, BindingAttrs, ElementCache} from './types';
|
|
7
|
+
|
|
8
|
+
interface RenderIfBindingParams {
|
|
9
|
+
bindingData: BindingCache;
|
|
10
|
+
viewModel: ViewModel;
|
|
11
|
+
bindingAttrs: BindingAttrs;
|
|
12
|
+
}
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* isTargetDomRemoved
|
|
@@ -10,13 +17,13 @@ import {removeElemnetsByCommentWrap, insertRenderedElements} from './commentWrap
|
|
|
10
17
|
* @param {object} bindingData
|
|
11
18
|
* @return {boolean}
|
|
12
19
|
*/
|
|
13
|
-
const isTargetDomRemoved = (bindingData) => {
|
|
20
|
+
const isTargetDomRemoved = (bindingData: BindingCache): boolean => {
|
|
14
21
|
let ret = false;
|
|
15
22
|
if (bindingData && bindingData.previousNonTemplateElement) {
|
|
16
23
|
const commentStartTextContent = bindingData.previousNonTemplateElement.textContent;
|
|
17
24
|
const endCommentTag = bindingData.previousNonTemplateElement.nextSibling;
|
|
18
25
|
|
|
19
|
-
if (endCommentTag.nodeType === 8) {
|
|
26
|
+
if (endCommentTag && endCommentTag.nodeType === 8) {
|
|
20
27
|
if (endCommentTag.textContent === commentStartTextContent + commentSuffix) {
|
|
21
28
|
ret = true;
|
|
22
29
|
}
|
|
@@ -25,27 +32,51 @@ const isTargetDomRemoved = (bindingData) => {
|
|
|
25
32
|
return ret;
|
|
26
33
|
};
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
/**
|
|
36
|
+
* removeIfBinding
|
|
37
|
+
* @description remove if binding DOM and clean up cache
|
|
38
|
+
* @param {object} bindingData
|
|
39
|
+
*/
|
|
40
|
+
const removeIfBinding = (bindingData: BindingCache): void => {
|
|
41
|
+
removeElemnetsByCommentWrap(bindingData);
|
|
42
|
+
// remove cache.IterationBindingCache to prevent memory leak
|
|
43
|
+
if (bindingData.hasIterationBindingCache) {
|
|
44
|
+
delete bindingData.iterationBindingCache;
|
|
45
|
+
delete bindingData.hasIterationBindingCache;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* renderIfBinding
|
|
51
|
+
* @description render if binding DOM
|
|
52
|
+
* @param {object} bindingData
|
|
53
|
+
* @param {object} viewModel
|
|
54
|
+
* @param {object} bindingAttrs
|
|
55
|
+
*/
|
|
56
|
+
const renderIfBinding = ({bindingData, viewModel, bindingAttrs}: RenderIfBindingParams): void => {
|
|
29
57
|
if (!bindingData.fragment) {
|
|
30
58
|
return;
|
|
31
59
|
}
|
|
32
60
|
|
|
33
61
|
const isDomRemoved = isTargetDomRemoved(bindingData);
|
|
34
|
-
let rootElement = bindingData.el;
|
|
62
|
+
let rootElement: Node = bindingData.el;
|
|
35
63
|
|
|
36
64
|
// remove current old DOM.
|
|
37
65
|
// TODO: try preserve DOM
|
|
38
66
|
if (!isDomRemoved && !bindingData.isOnce) {
|
|
39
67
|
removeIfBinding(bindingData);
|
|
40
68
|
// use fragment for create iterationBindingCache
|
|
41
|
-
|
|
69
|
+
const firstChild = bindingData.fragment.firstChild;
|
|
70
|
+
if (firstChild) {
|
|
71
|
+
rootElement = firstChild.cloneNode(true);
|
|
72
|
+
}
|
|
42
73
|
}
|
|
43
74
|
|
|
44
75
|
// walk clonedElement to create iterationBindingCache once
|
|
45
76
|
if (!bindingData.iterationBindingCache || !bindingData.hasIterationBindingCache) {
|
|
46
77
|
bindingData.iterationBindingCache = createBindingCache({
|
|
47
|
-
rootNode: rootElement,
|
|
48
|
-
bindingAttrs
|
|
78
|
+
rootNode: rootElement as HTMLElement,
|
|
79
|
+
bindingAttrs,
|
|
49
80
|
});
|
|
50
81
|
}
|
|
51
82
|
|
|
@@ -54,25 +85,16 @@ const renderIfBinding = ({bindingData, viewModel, bindingAttrs}) => {
|
|
|
54
85
|
if (!isEmptyObject(bindingData.iterationBindingCache)) {
|
|
55
86
|
bindingData.hasIterationBindingCache = true;
|
|
56
87
|
renderIteration({
|
|
57
|
-
elementCache: bindingData.iterationBindingCache,
|
|
88
|
+
elementCache: bindingData.iterationBindingCache as ElementCache,
|
|
58
89
|
iterationVm: viewModel,
|
|
59
|
-
bindingAttrs
|
|
90
|
+
bindingAttrs,
|
|
60
91
|
isRegenerate: true,
|
|
61
92
|
});
|
|
62
93
|
}
|
|
63
94
|
|
|
64
95
|
// insert to new rendered DOM
|
|
65
96
|
// TODO: check unnecessary insertion when DOM is preserved
|
|
66
|
-
insertRenderedElements(bindingData, rootElement);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const removeIfBinding = (bindingData) => {
|
|
70
|
-
removeElemnetsByCommentWrap(bindingData);
|
|
71
|
-
// remove cache.IterationBindingCache to prevent memory leak
|
|
72
|
-
if (bindingData.hasIterationBindingCache) {
|
|
73
|
-
delete bindingData.iterationBindingCache;
|
|
74
|
-
delete bindingData.hasIterationBindingCache;
|
|
75
|
-
}
|
|
97
|
+
insertRenderedElements(bindingData, rootElement as DocumentFragment);
|
|
76
98
|
};
|
|
77
99
|
|
|
78
100
|
export {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {bindingUpdateConditions} from './config';
|
|
2
|
-
import applyBinding from './applyBindingExport';
|
|
3
2
|
import createBindingOption from './createBindingOption';
|
|
4
3
|
import renderTemplatesBinding from './renderTemplatesBinding';
|
|
4
|
+
import * as applyBindingModule from './applyBinding';
|
|
5
|
+
import type {ElementCache, ViewModel, BindingAttrs} from './types';
|
|
6
|
+
import type Binder from './binder';
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* renderIteration
|
|
7
10
|
* @param {object} opt
|
|
@@ -9,7 +12,17 @@ import renderTemplatesBinding from './renderTemplatesBinding';
|
|
|
9
12
|
* render element's binding by supplied elementCache
|
|
10
13
|
* This function is desidned for FoOf, If, switch bindings
|
|
11
14
|
*/
|
|
12
|
-
const renderIteration = ({
|
|
15
|
+
const renderIteration = ({
|
|
16
|
+
elementCache,
|
|
17
|
+
iterationVm,
|
|
18
|
+
bindingAttrs,
|
|
19
|
+
isRegenerate,
|
|
20
|
+
}: {
|
|
21
|
+
elementCache: ElementCache;
|
|
22
|
+
iterationVm: ViewModel;
|
|
23
|
+
bindingAttrs: BindingAttrs;
|
|
24
|
+
isRegenerate: boolean;
|
|
25
|
+
}): void => {
|
|
13
26
|
const bindingUpdateOption = isRegenerate ? createBindingOption(bindingUpdateConditions.init) : createBindingOption();
|
|
14
27
|
|
|
15
28
|
// enforce render even element is not in DOM tree
|
|
@@ -19,17 +32,20 @@ const renderIteration = ({elementCache, iterationVm, bindingAttrs, isRegenerate}
|
|
|
19
32
|
// this is an share function therefore passing current APP 'this' context
|
|
20
33
|
// viewModel is a dynamic generated iterationVm
|
|
21
34
|
renderTemplatesBinding({
|
|
22
|
-
ctx: iterationVm.$root ? iterationVm.$root.APP : iterationVm.APP,
|
|
23
|
-
elementCache
|
|
35
|
+
ctx: (iterationVm.$root ? iterationVm.$root.APP : iterationVm.APP) as Binder,
|
|
36
|
+
elementCache,
|
|
24
37
|
updateOption: bindingUpdateOption,
|
|
25
|
-
bindingAttrs
|
|
38
|
+
bindingAttrs,
|
|
26
39
|
viewModel: iterationVm,
|
|
27
40
|
});
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
// Use namespace import to access the function at runtime,
|
|
43
|
+
// which breaks the circular dependency during module initialization
|
|
44
|
+
applyBindingModule.default({
|
|
45
|
+
ctx: (iterationVm.$root ? iterationVm.$root.APP : iterationVm.APP) as Binder,
|
|
46
|
+
elementCache,
|
|
31
47
|
updateOption: bindingUpdateOption,
|
|
32
|
-
bindingAttrs
|
|
48
|
+
bindingAttrs,
|
|
33
49
|
viewModel: iterationVm,
|
|
34
50
|
});
|
|
35
51
|
};
|