@gogocat/data-bind 1.11.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 +2772 -2519
- 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/form.html +20 -4
- 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 +44 -12
- 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.ts +179 -0
- package/src/{attrBinding.js → attrBinding.ts} +14 -13
- package/src/binder.ts +289 -0
- package/src/changeBinding.ts +93 -0
- package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
- package/src/config.ts +107 -0
- package/src/createBindingOption.ts +91 -0
- 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.ts +22 -0
- package/src/{pubSub.js → pubSub.ts} +24 -15
- package/src/reactiveProxy.ts +285 -0
- package/src/{renderForOfBinding.js → renderForOfBinding.ts} +55 -33
- package/src/{renderIfBinding.js → renderIfBinding.ts} +45 -20
- package/src/renderIteration.ts +53 -0
- 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/fixtures/dataBindBootstrap.html +2 -2
- package/test/fixtures/formBindings.html +9 -1
- 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/binder.js +0 -422
- package/src/changeBinding.js +0 -57
- package/src/config.js +0 -65
- package/src/createBindingOption.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/util.js +0 -648
- package/test/specs/blurBinding.spec.js +0 -57
- package/test/specs/formBinding.spec.js +0 -292
- 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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {getViewModelValue} from './util';
|
|
2
|
+
import type {BindingCache, ViewModel, BindingAttrs} from './types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* modelBinding
|
|
@@ -8,23 +9,23 @@ import {getViewModelValue} from './util';
|
|
|
8
9
|
* @param {object} bindingAttrs
|
|
9
10
|
* @param {boolean} forceRender
|
|
10
11
|
*/
|
|
11
|
-
const modelBinding = (cache, viewModel, bindingAttrs, forceRender) => {
|
|
12
|
+
const modelBinding = (cache: BindingCache, viewModel: ViewModel, bindingAttrs: BindingAttrs, forceRender: boolean): void => {
|
|
12
13
|
const dataKey = cache.dataKey;
|
|
13
|
-
let newValue = '';
|
|
14
|
-
const APP = viewModel.APP || viewModel.$root
|
|
14
|
+
let newValue: unknown = '';
|
|
15
|
+
const APP = viewModel.APP || viewModel.$root?.APP;
|
|
15
16
|
|
|
16
|
-
if (!dataKey || (!forceRender && !APP
|
|
17
|
+
if (!dataKey || (!forceRender && !(APP?.$rootElement as HTMLElement)?.contains(cache.el))) {
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
newValue = getViewModelValue(viewModel, dataKey);
|
|
21
22
|
|
|
22
23
|
if (typeof newValue !== 'undefined' && newValue !== null) {
|
|
23
|
-
const $element = cache.el;
|
|
24
|
+
const $element = cache.el as HTMLInputElement;
|
|
24
25
|
const isCheckbox = $element.type === 'checkbox';
|
|
25
26
|
const isRadio = $element.type === 'radio';
|
|
26
27
|
const inputName = $element.name;
|
|
27
|
-
const $radioGroup = isRadio ? APP
|
|
28
|
+
const $radioGroup = isRadio ? (APP?.$rootElement as HTMLElement).querySelectorAll(`input[name="${inputName}"]`) : [];
|
|
28
29
|
const oldValue = isCheckbox ? $element.checked : $element.value;
|
|
29
30
|
|
|
30
31
|
// update element value
|
|
@@ -36,13 +37,14 @@ const modelBinding = (cache, viewModel, bindingAttrs, forceRender) => {
|
|
|
36
37
|
const radioGroupLength = $radioGroup.length;
|
|
37
38
|
|
|
38
39
|
for (i = 0; i < radioGroupLength; i += 1) {
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
const radioInput = $radioGroup[i] as HTMLInputElement;
|
|
41
|
+
if (radioInput.value === newValue) {
|
|
42
|
+
radioInput.checked = true;
|
|
41
43
|
break;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
} else {
|
|
45
|
-
$element.value = newValue;
|
|
47
|
+
$element.value = String(newValue);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
each,
|
|
3
|
+
throwErrorMessage,
|
|
4
|
+
} from './util';
|
|
5
|
+
|
|
6
|
+
const postProcess = (tasks: Function[]): void => {
|
|
7
|
+
if (!tasks || !tasks.length) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
each(tasks, (index: number, task: Function) => {
|
|
12
|
+
if (typeof task === 'function') {
|
|
13
|
+
try {
|
|
14
|
+
task();
|
|
15
|
+
} catch (err) {
|
|
16
|
+
throwErrorMessage(err, `Error postProcess: ${ String(task)}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default postProcess;
|
|
@@ -10,47 +10,58 @@ import * as util from './util';
|
|
|
10
10
|
};
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
interface Subscriber {
|
|
14
|
+
[compId: string]: Function | boolean | undefined;
|
|
15
|
+
isOnce?: boolean;
|
|
16
|
+
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
interface Events {
|
|
19
|
+
[eventName: string]: Subscriber[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const EVENTS: Events = {};
|
|
23
|
+
|
|
24
|
+
export const subscribeEvent = (instance: unknown = null, eventName: string = '', fn: Function, isOnce: boolean = false): void => {
|
|
25
|
+
if (!instance || typeof instance !== 'object' || !('compId' in instance) || !instance.compId || !eventName || typeof fn !== 'function') {
|
|
17
26
|
return;
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
let subscriber;
|
|
29
|
+
let subscriber: Subscriber;
|
|
21
30
|
let isSubscribed = false;
|
|
22
31
|
|
|
23
32
|
eventName = eventName.replace(util.REGEX.WHITE_SPACES, '');
|
|
24
33
|
EVENTS[eventName] = EVENTS[eventName] || [];
|
|
25
34
|
// check if already subscribed and update callback fn
|
|
35
|
+
const instanceWithViewModel = instance as { compId: string | number; viewModel: unknown };
|
|
26
36
|
isSubscribed = EVENTS[eventName].some((subscriber) => {
|
|
27
|
-
if (subscriber[
|
|
28
|
-
subscriber[
|
|
37
|
+
if (subscriber[instanceWithViewModel.compId]) {
|
|
38
|
+
subscriber[instanceWithViewModel.compId] = fn.bind(instanceWithViewModel.viewModel);
|
|
29
39
|
subscriber.isOnce = isOnce;
|
|
30
40
|
return true;
|
|
31
41
|
}
|
|
42
|
+
return false;
|
|
32
43
|
});
|
|
33
44
|
// push if not yet subscribe
|
|
34
45
|
if (!isSubscribed) {
|
|
35
46
|
subscriber = {};
|
|
36
|
-
subscriber[
|
|
47
|
+
subscriber[instanceWithViewModel.compId] = fn.bind(instanceWithViewModel.viewModel);
|
|
37
48
|
subscriber.isOnce = isOnce;
|
|
38
49
|
EVENTS[eventName].push(subscriber);
|
|
39
50
|
}
|
|
40
51
|
};
|
|
41
52
|
|
|
42
|
-
const subscribeEventOnce = (instance = null, eventName = '', fn) => {
|
|
53
|
+
export const subscribeEventOnce = (instance: unknown = null, eventName: string = '', fn: Function): void => {
|
|
43
54
|
subscribeEvent(instance, eventName, fn, true);
|
|
44
55
|
};
|
|
45
56
|
|
|
46
|
-
const unsubscribeEvent = (compId = '', eventName = '') => {
|
|
57
|
+
export const unsubscribeEvent = (compId: string | number = '', eventName: string = ''): void => {
|
|
47
58
|
if (!compId || !eventName) {
|
|
48
59
|
return;
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
let i = 0;
|
|
52
63
|
let subscribersLength = 0;
|
|
53
|
-
let subscriber;
|
|
64
|
+
let subscriber: Subscriber;
|
|
54
65
|
|
|
55
66
|
eventName = eventName.replace(util.REGEX.WHITE_SPACES, '');
|
|
56
67
|
|
|
@@ -65,7 +76,7 @@ const unsubscribeEvent = (compId = '', eventName = '') => {
|
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
// delete the event if no more subscriber
|
|
68
|
-
if (!EVENTS[eventName].length) {
|
|
79
|
+
if (EVENTS[eventName] && !EVENTS[eventName].length) {
|
|
69
80
|
delete EVENTS[eventName];
|
|
70
81
|
}
|
|
71
82
|
};
|
|
@@ -75,7 +86,7 @@ const unsubscribeEvent = (compId = '', eventName = '') => {
|
|
|
75
86
|
* @description unsubscribe all event by compId. eg when a component removed
|
|
76
87
|
* @param {string} compId
|
|
77
88
|
*/
|
|
78
|
-
const unsubscribeAllEvent = (compId = '') => {
|
|
89
|
+
export const unsubscribeAllEvent = (compId: string | number = ''): void => {
|
|
79
90
|
if (!compId) {
|
|
80
91
|
return;
|
|
81
92
|
}
|
|
@@ -84,7 +95,7 @@ const unsubscribeAllEvent = (compId = '') => {
|
|
|
84
95
|
});
|
|
85
96
|
};
|
|
86
97
|
|
|
87
|
-
const publishEvent = (eventName = '', ...args) => {
|
|
98
|
+
export const publishEvent = (eventName: string = '', ...args: unknown[]): void => {
|
|
88
99
|
if (!eventName || !EVENTS[eventName]) {
|
|
89
100
|
return;
|
|
90
101
|
}
|
|
@@ -103,5 +114,3 @@ const publishEvent = (eventName = '', ...args) => {
|
|
|
103
114
|
});
|
|
104
115
|
});
|
|
105
116
|
};
|
|
106
|
-
|
|
107
|
-
export {subscribeEvent, subscribeEventOnce, unsubscribeEvent, unsubscribeAllEvent, publishEvent};
|
|
@@ -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,
|
|
@@ -9,20 +9,27 @@ import {
|
|
|
9
9
|
isEmptyObject,
|
|
10
10
|
} from './util';
|
|
11
11
|
import createBindingCache from './domWalker';
|
|
12
|
-
import
|
|
12
|
+
import renderIteration from './renderIteration';
|
|
13
13
|
import {
|
|
14
14
|
wrapCommentAround,
|
|
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
|
}
|