@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.
Files changed (271) hide show
  1. package/.editorconfig +14 -14
  2. package/.vscode/launch.json +12 -12
  3. package/CONFIGURATION.md +294 -0
  4. package/REACTIVE_MODE.md +553 -0
  5. package/README.md +266 -829
  6. package/babel.config.json +30 -0
  7. package/dist/js/_escape.d.ts +14 -0
  8. package/dist/js/_escape.d.ts.map +1 -0
  9. package/dist/js/applyBinding.d.ts +11 -0
  10. package/dist/js/applyBinding.d.ts.map +1 -0
  11. package/dist/js/attrBinding.d.ts +12 -0
  12. package/dist/js/attrBinding.d.ts.map +1 -0
  13. package/dist/js/binder.d.ts +67 -0
  14. package/dist/js/binder.d.ts.map +1 -0
  15. package/dist/js/changeBinding.d.ts +19 -0
  16. package/dist/js/changeBinding.d.ts.map +1 -0
  17. package/dist/js/commentWrapper.d.ts +39 -0
  18. package/dist/js/commentWrapper.d.ts.map +1 -0
  19. package/dist/js/config.d.ts +55 -0
  20. package/dist/js/config.d.ts.map +1 -0
  21. package/dist/js/createBindingOption.d.ts +32 -0
  22. package/dist/js/createBindingOption.d.ts.map +1 -0
  23. package/dist/js/createEventBinding.d.ts +10 -0
  24. package/dist/js/createEventBinding.d.ts.map +1 -0
  25. package/dist/js/cssBinding.d.ts +15 -0
  26. package/dist/js/cssBinding.d.ts.map +1 -0
  27. package/dist/js/dataBind.js +2756 -2530
  28. package/dist/js/dataBind.min.js +8 -1
  29. package/dist/js/dataBind.min.js.map +1 -1
  30. package/dist/js/domWalker.d.ts +9 -0
  31. package/dist/js/domWalker.d.ts.map +1 -0
  32. package/dist/js/forOfBinding.d.ts +12 -0
  33. package/dist/js/forOfBinding.d.ts.map +1 -0
  34. package/dist/js/hoverBinding.d.ts +13 -0
  35. package/dist/js/hoverBinding.d.ts.map +1 -0
  36. package/dist/js/ifBinding.d.ts +12 -0
  37. package/dist/js/ifBinding.d.ts.map +1 -0
  38. package/dist/js/index.d.ts +10 -0
  39. package/dist/js/index.d.ts.map +1 -0
  40. package/dist/js/modelBinding.d.ts +12 -0
  41. package/dist/js/modelBinding.d.ts.map +1 -0
  42. package/dist/js/postProcess.d.ts +3 -0
  43. package/dist/js/postProcess.d.ts.map +1 -0
  44. package/dist/js/pubSub.d.ts +11 -0
  45. package/dist/js/pubSub.d.ts.map +1 -0
  46. package/dist/js/reactiveProxy.d.ts +28 -0
  47. package/dist/js/reactiveProxy.d.ts.map +1 -0
  48. package/dist/js/renderForOfBinding.d.ts +8 -0
  49. package/dist/js/renderForOfBinding.d.ts.map +1 -0
  50. package/dist/js/renderIfBinding.d.ts +22 -0
  51. package/dist/js/renderIfBinding.d.ts.map +1 -0
  52. package/dist/js/renderIteration.d.ts +16 -0
  53. package/dist/js/renderIteration.d.ts.map +1 -0
  54. package/dist/js/renderTemplate.d.ts +14 -0
  55. package/dist/js/renderTemplate.d.ts.map +1 -0
  56. package/dist/js/renderTemplatesBinding.d.ts +19 -0
  57. package/dist/js/renderTemplatesBinding.d.ts.map +1 -0
  58. package/dist/js/showBinding.d.ts +13 -0
  59. package/dist/js/showBinding.d.ts.map +1 -0
  60. package/dist/js/switchBinding.d.ts +13 -0
  61. package/dist/js/switchBinding.d.ts.map +1 -0
  62. package/dist/js/textBinding.d.ts +13 -0
  63. package/dist/js/textBinding.d.ts.map +1 -0
  64. package/dist/js/types/_escape.d.ts +14 -0
  65. package/dist/js/types/_escape.d.ts.map +1 -0
  66. package/dist/js/types/applyBinding.d.ts +11 -0
  67. package/dist/js/types/applyBinding.d.ts.map +1 -0
  68. package/dist/js/types/attrBinding.d.ts +12 -0
  69. package/dist/js/types/attrBinding.d.ts.map +1 -0
  70. package/dist/js/types/binder.d.ts +67 -0
  71. package/dist/js/types/binder.d.ts.map +1 -0
  72. package/dist/js/types/changeBinding.d.ts +19 -0
  73. package/dist/js/types/changeBinding.d.ts.map +1 -0
  74. package/dist/js/types/commentWrapper.d.ts +39 -0
  75. package/dist/js/types/commentWrapper.d.ts.map +1 -0
  76. package/dist/js/types/config.d.ts +55 -0
  77. package/dist/js/types/config.d.ts.map +1 -0
  78. package/dist/js/types/createBindingOption.d.ts +32 -0
  79. package/dist/js/types/createBindingOption.d.ts.map +1 -0
  80. package/dist/js/types/createEventBinding.d.ts +10 -0
  81. package/dist/js/types/createEventBinding.d.ts.map +1 -0
  82. package/dist/js/types/cssBinding.d.ts +15 -0
  83. package/dist/js/types/cssBinding.d.ts.map +1 -0
  84. package/dist/js/types/domWalker.d.ts +9 -0
  85. package/dist/js/types/domWalker.d.ts.map +1 -0
  86. package/dist/js/types/forOfBinding.d.ts +12 -0
  87. package/dist/js/types/forOfBinding.d.ts.map +1 -0
  88. package/dist/js/types/hoverBinding.d.ts +13 -0
  89. package/dist/js/types/hoverBinding.d.ts.map +1 -0
  90. package/dist/js/types/ifBinding.d.ts +12 -0
  91. package/dist/js/types/ifBinding.d.ts.map +1 -0
  92. package/dist/js/types/index.d.ts +10 -0
  93. package/dist/js/types/index.d.ts.map +1 -0
  94. package/dist/js/types/modelBinding.d.ts +12 -0
  95. package/dist/js/types/modelBinding.d.ts.map +1 -0
  96. package/dist/js/types/postProcess.d.ts +3 -0
  97. package/dist/js/types/postProcess.d.ts.map +1 -0
  98. package/dist/js/types/pubSub.d.ts +11 -0
  99. package/dist/js/types/pubSub.d.ts.map +1 -0
  100. package/dist/js/types/reactiveProxy.d.ts +28 -0
  101. package/dist/js/types/reactiveProxy.d.ts.map +1 -0
  102. package/dist/js/types/renderForOfBinding.d.ts +8 -0
  103. package/dist/js/types/renderForOfBinding.d.ts.map +1 -0
  104. package/dist/js/types/renderIfBinding.d.ts +22 -0
  105. package/dist/js/types/renderIfBinding.d.ts.map +1 -0
  106. package/dist/js/types/renderIteration.d.ts +16 -0
  107. package/dist/js/types/renderIteration.d.ts.map +1 -0
  108. package/dist/js/types/renderTemplate.d.ts +14 -0
  109. package/dist/js/types/renderTemplate.d.ts.map +1 -0
  110. package/dist/js/types/renderTemplatesBinding.d.ts +19 -0
  111. package/dist/js/types/renderTemplatesBinding.d.ts.map +1 -0
  112. package/dist/js/types/showBinding.d.ts +13 -0
  113. package/dist/js/types/showBinding.d.ts.map +1 -0
  114. package/dist/js/types/switchBinding.d.ts +13 -0
  115. package/dist/js/types/switchBinding.d.ts.map +1 -0
  116. package/dist/js/types/textBinding.d.ts +13 -0
  117. package/dist/js/types/textBinding.d.ts.map +1 -0
  118. package/dist/js/types/types.d.ts +111 -0
  119. package/dist/js/types/types.d.ts.map +1 -0
  120. package/dist/js/types/util.d.ts +119 -0
  121. package/dist/js/types/util.d.ts.map +1 -0
  122. package/dist/js/types.d.ts +111 -0
  123. package/dist/js/types.d.ts.map +1 -0
  124. package/dist/js/util.d.ts +119 -0
  125. package/dist/js/util.d.ts.map +1 -0
  126. package/eslint.config.js +124 -0
  127. package/examples/DBMONSTER_COMPARISON.md +123 -0
  128. package/examples/afterRenderDemo.html +119 -0
  129. package/examples/bootstrap/css/animate.css +1579 -1579
  130. package/examples/bootstrap/css/bootstrap.min.css +6 -6
  131. package/examples/bootstrap/css/homeservices.css +378 -390
  132. package/examples/bootstrap/css/open-iconic.css +511 -511
  133. package/examples/bootstrap/fonts/open-iconic.svg +543 -543
  134. package/examples/bootstrap/js/compMessageDialog.js +20 -19
  135. package/examples/bootstrap/js/compSearchBar.js +12 -19
  136. package/examples/bootstrap/js/compSearchResults.js +50 -46
  137. package/examples/bootstrap/js/featureAdsResult.json +65 -65
  138. package/examples/bootstrap/js/searchResult.json +57 -57
  139. package/examples/bootstrap.html +343 -332
  140. package/examples/css/baseTodo.css +141 -141
  141. package/examples/css/dbMonsterStyles.css +27 -27
  142. package/examples/css/indexTodo.css +374 -374
  143. package/examples/dbmonsterForOfReactive.html +40 -0
  144. package/examples/dbmonsterReact.html +19 -0
  145. package/examples/forOfBindingSimpleDebug.html +45 -0
  146. package/examples/globalConfig.html +131 -0
  147. package/examples/js/afterRenderDemo.js +190 -0
  148. package/examples/js/appTodo.js +46 -46
  149. package/examples/js/attrBindingDemo.js +2 -2
  150. package/examples/js/dbMonApp.js +24 -26
  151. package/examples/js/dbMonAppReact.jsx +79 -0
  152. package/examples/js/dbMonAppReactive.js +28 -0
  153. package/examples/js/fiberDemo.js +4 -4
  154. package/examples/js/filtersDemo.js +8 -8
  155. package/examples/js/forOfDemo.js +7 -9
  156. package/examples/js/forOfDemoComplex.js +44 -17
  157. package/examples/js/form.js +14 -14
  158. package/examples/js/globalConfig.js +117 -0
  159. package/examples/js/ifBindingDemo.js +16 -16
  160. package/examples/js/reactiveDemo.js +119 -0
  161. package/examples/js/switchBindingDemo.js +8 -8
  162. package/examples/react-dbmonster/dist/bundle.js +43 -0
  163. package/examples/react-dbmonster/package-lock.json +537 -0
  164. package/examples/react-dbmonster/package.json +16 -0
  165. package/examples/react-dbmonster/src/index.jsx +80 -0
  166. package/examples/reactiveDemo.html +127 -0
  167. package/examples/refreshRateTest.html +75 -75
  168. package/index.html +841 -0
  169. package/package.json +31 -34
  170. package/rollup.config.js +79 -36
  171. package/src/{_escape.js → _escape.ts} +19 -17
  172. package/src/{applyBinding.js → applyBinding.ts} +27 -18
  173. package/src/{attrBinding.js → attrBinding.ts} +14 -13
  174. package/src/{binder.js → binder.ts} +289 -181
  175. package/src/changeBinding.ts +93 -0
  176. package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
  177. package/src/config.ts +107 -0
  178. package/src/{createBindingOption.js → createBindingOption.ts} +39 -15
  179. package/src/createEventBinding.ts +88 -0
  180. package/src/{cssBinding.js → cssBinding.ts} +13 -11
  181. package/src/{domWalker.js → domWalker.ts} +44 -30
  182. package/src/{forOfBinding.js → forOfBinding.ts} +4 -3
  183. package/src/hoverBinding.ts +84 -0
  184. package/src/{ifBinding.js → ifBinding.ts} +14 -12
  185. package/src/index.ts +53 -0
  186. package/src/{modelBinding.js → modelBinding.ts} +11 -9
  187. package/src/{postProcess.js → postProcess.ts} +6 -4
  188. package/src/{pubSub.js → pubSub.ts} +24 -21
  189. package/src/reactiveProxy.ts +285 -0
  190. package/src/{renderForOfBinding.js → renderForOfBinding.ts} +54 -32
  191. package/src/{renderIfBinding.js → renderIfBinding.ts} +41 -19
  192. package/src/{renderIteration.js → renderIteration.ts} +24 -8
  193. package/src/renderTemplate.ts +165 -0
  194. package/src/renderTemplatesBinding.ts +73 -0
  195. package/src/{showBinding.js → showBinding.ts} +4 -3
  196. package/src/{switchBinding.js → switchBinding.ts} +18 -15
  197. package/src/{textBinding.js → textBinding.ts} +5 -4
  198. package/src/types.ts +124 -0
  199. package/src/util.ts +810 -0
  200. package/test/css/reporter.css +9 -9
  201. package/test/globals.d.ts +19 -0
  202. package/test/helpers/testHelper.js +46 -11
  203. package/test/mocks/featureAdsResult.json +65 -65
  204. package/test/mocks/searchResult.json +57 -57
  205. package/test/specs/{attrBinding.spec.js → attrBinding.spec.ts} +103 -106
  206. package/test/specs/{binder.spec.js → binder.spec.ts} +29 -27
  207. package/test/specs/blurBinding.spec.ts +60 -0
  208. package/test/specs/chainableUse.spec.ts +125 -0
  209. package/test/specs/clickBinding.spec.ts +194 -0
  210. package/test/specs/{cssBinding.spec.js → cssBinding.spec.ts} +72 -79
  211. package/test/specs/{dataBindBootstrap.spec.js → dataBindBootstrap.spec.ts} +332 -313
  212. package/test/specs/{filter.spec.js → filter.spec.ts} +75 -76
  213. package/test/specs/{forOfBinding.spec.js → forOfBinding.spec.ts} +208 -219
  214. package/test/specs/formBinding.spec.ts +272 -0
  215. package/test/specs/ifBinding.spec.ts +165 -0
  216. package/test/specs/{nestedComponent.spec.js → nestedComponent.spec.ts} +88 -88
  217. package/test/specs/reactiveProxy.spec.ts +465 -0
  218. package/test/specs/{showBinding.spec.js → showBinding.spec.ts} +148 -149
  219. package/test/specs/{switchBinding.spec.js → switchBinding.spec.ts} +172 -173
  220. package/test/specs/templateBinding.spec.ts +273 -0
  221. package/test/specs/{textBinding.spec.js → textBinding.spec.ts} +47 -48
  222. package/test/tsconfig.json +31 -0
  223. package/test-output.txt +200 -0
  224. package/test-reactive.html +224 -0
  225. package/tsconfig.json +28 -0
  226. package/vendors/lodash.custom.js +4577 -4577
  227. package/vendors/lodash.custom.min.js +45 -45
  228. package/vitest.config.js +27 -0
  229. package/.eslintrc.js +0 -1
  230. package/.grunt/grunt-contrib-jasmine/boot.js +0 -161
  231. package/.grunt/grunt-contrib-jasmine/dist/js/dataBind.js +0 -9
  232. package/.grunt/grunt-contrib-jasmine/grunt-template-jasmine-istanbul/reporter.js +0 -23
  233. package/.grunt/grunt-contrib-jasmine/jasmine-html.js +0 -853
  234. package/.grunt/grunt-contrib-jasmine/jasmine.css +0 -271
  235. package/.grunt/grunt-contrib-jasmine/jasmine.js +0 -9761
  236. package/.grunt/grunt-contrib-jasmine/jasmine_favicon.png +0 -0
  237. package/.grunt/grunt-contrib-jasmine/json2.js +0 -489
  238. package/.grunt/grunt-contrib-jasmine/reporter.js +0 -107
  239. package/coverage/coverage.json +0 -1
  240. package/coverage/lcov/lcov-report/base.css +0 -213
  241. package/coverage/lcov/lcov-report/index.html +0 -93
  242. package/coverage/lcov/lcov-report/js/dataBind.js.html +0 -6596
  243. package/coverage/lcov/lcov-report/js/index.html +0 -93
  244. package/coverage/lcov/lcov-report/prettify.css +0 -1
  245. package/coverage/lcov/lcov-report/prettify.js +0 -1
  246. package/coverage/lcov/lcov-report/sort-arrow-sprite.png +0 -0
  247. package/coverage/lcov/lcov-report/sorter.js +0 -158
  248. package/coverage/lcov/lcov.info +0 -1991
  249. package/eslintrc.json +0 -40
  250. package/examples/bootstrap/js/bootstrap.min.js +0 -6
  251. package/examples/bootstrap/js/popper.min.js +0 -5
  252. package/examples/bootstrap/js/searchSuggestion.js +0 -58
  253. package/examples/bootstrap/js/typeahead.jquery.js +0 -1538
  254. package/gruntfile.js +0 -92
  255. package/gulpfile.js +0 -32
  256. package/src/applyBindingExport.js +0 -5
  257. package/src/changeBinding.js +0 -63
  258. package/src/config.js +0 -66
  259. package/src/createEventBinding.js +0 -46
  260. package/src/eventSystem.js +0 -46
  261. package/src/hoverBinding.js +0 -57
  262. package/src/index.js +0 -26
  263. package/src/renderTemplate.js +0 -128
  264. package/src/renderTemplatesBinding.js +0 -44
  265. package/src/util.js +0 -648
  266. package/test/specs/blurBinding.spec.js +0 -57
  267. package/test/specs/formBinding.spec.js +0 -316
  268. package/test/specs/ifBinding.spec.js +0 -169
  269. package/test/specs/templateBinding.spec.js +0 -117
  270. package/vendors/jasmine-jquery.js +0 -841
  271. 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
- /* eslint-disable no-invalid-this */
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
- const iterationData = getViewModelPropValue(viewModel, bindingData.iterator);
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.forEach(function(elementCache, i) {
67
+ (bindingData.iterationBindingCache as ElementCache[])?.forEach((elementCache: ElementCache, i: number) => {
61
68
  if (!isEmptyObject(elementCache)) {
62
69
  const iterationVm = createIterationViewModel({
63
- bindingData: bindingData,
64
- viewModel: viewModel,
65
- iterationData: iterationData,
66
- keys: keys,
70
+ bindingData,
71
+ viewModel,
72
+ iterationData,
73
+ keys,
67
74
  index: i,
68
75
  });
69
76
  renderIteration({
70
- elementCache: elementCache,
71
- iterationVm: iterationVm,
72
- bindingAttrs: 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
- const iterationVm = {};
102
- iterationVm[bindingData.iterator.alias] = keys ? iterationData[keys[index]] : iterationData[index];
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[bindingData.iterator.alias];
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 = (bindingData, viewModel, bindingAttrs, iterationData, keys) => {
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: 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: bindingData,
141
- viewModel: viewModel,
142
- iterationData: iterationData,
143
- keys: 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: iterationVm,
150
- bindingAttrs: 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
- const renderIfBinding = ({bindingData, viewModel, bindingAttrs}) => {
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
- rootElement = bindingData.fragment.firstChild.cloneNode(true);
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: 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: 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 = ({elementCache, iterationVm, bindingAttrs, isRegenerate}) => {
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: elementCache,
35
+ ctx: (iterationVm.$root ? iterationVm.$root.APP : iterationVm.APP) as Binder,
36
+ elementCache,
24
37
  updateOption: bindingUpdateOption,
25
- bindingAttrs: bindingAttrs,
38
+ bindingAttrs,
26
39
  viewModel: iterationVm,
27
40
  });
28
41
 
29
- applyBinding({
30
- elementCache: elementCache,
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: bindingAttrs,
48
+ bindingAttrs,
33
49
  viewModel: iterationVm,
34
50
  });
35
51
  };