onelaraveljs 1.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 (67) hide show
  1. package/README.md +87 -0
  2. package/docs/integration_analysis.md +116 -0
  3. package/docs/onejs_analysis.md +108 -0
  4. package/docs/optimization_implementation_group2.md +458 -0
  5. package/docs/optimization_plan.md +130 -0
  6. package/index.js +16 -0
  7. package/package.json +13 -0
  8. package/src/app.js +61 -0
  9. package/src/core/API.js +72 -0
  10. package/src/core/ChildrenRegistry.js +410 -0
  11. package/src/core/DOMBatcher.js +207 -0
  12. package/src/core/ErrorBoundary.js +226 -0
  13. package/src/core/EventDelegator.js +416 -0
  14. package/src/core/Helper.js +817 -0
  15. package/src/core/LoopContext.js +97 -0
  16. package/src/core/OneDOM.js +246 -0
  17. package/src/core/OneMarkup.js +444 -0
  18. package/src/core/Router.js +996 -0
  19. package/src/core/SEOConfig.js +321 -0
  20. package/src/core/SectionEngine.js +75 -0
  21. package/src/core/TemplateEngine.js +83 -0
  22. package/src/core/View.js +273 -0
  23. package/src/core/ViewConfig.js +229 -0
  24. package/src/core/ViewController.js +1410 -0
  25. package/src/core/ViewControllerOptimized.js +164 -0
  26. package/src/core/ViewIdentifier.js +361 -0
  27. package/src/core/ViewLoader.js +272 -0
  28. package/src/core/ViewManager.js +1962 -0
  29. package/src/core/ViewState.js +761 -0
  30. package/src/core/ViewSystem.js +301 -0
  31. package/src/core/ViewTemplate.js +4 -0
  32. package/src/core/helpers/BindingHelper.js +239 -0
  33. package/src/core/helpers/ConfigHelper.js +37 -0
  34. package/src/core/helpers/EventHelper.js +172 -0
  35. package/src/core/helpers/LifecycleHelper.js +17 -0
  36. package/src/core/helpers/ReactiveHelper.js +169 -0
  37. package/src/core/helpers/RenderHelper.js +15 -0
  38. package/src/core/helpers/ResourceHelper.js +89 -0
  39. package/src/core/helpers/TemplateHelper.js +11 -0
  40. package/src/core/managers/BindingManager.js +671 -0
  41. package/src/core/managers/ConfigurationManager.js +136 -0
  42. package/src/core/managers/EventManager.js +309 -0
  43. package/src/core/managers/LifecycleManager.js +356 -0
  44. package/src/core/managers/ReactiveManager.js +334 -0
  45. package/src/core/managers/RenderEngine.js +292 -0
  46. package/src/core/managers/ResourceManager.js +441 -0
  47. package/src/core/managers/ViewHierarchyManager.js +258 -0
  48. package/src/core/managers/ViewTemplateManager.js +127 -0
  49. package/src/core/reactive/ReactiveComponent.js +592 -0
  50. package/src/core/services/EventService.js +418 -0
  51. package/src/core/services/HttpService.js +106 -0
  52. package/src/core/services/LoggerService.js +57 -0
  53. package/src/core/services/StateService.js +512 -0
  54. package/src/core/services/StorageService.js +856 -0
  55. package/src/core/services/StoreService.js +258 -0
  56. package/src/core/services/TemplateDetectorService.js +361 -0
  57. package/src/core/services/Test.js +18 -0
  58. package/src/helpers/devWarnings.js +205 -0
  59. package/src/helpers/performance.js +226 -0
  60. package/src/helpers/utils.js +287 -0
  61. package/src/init.js +343 -0
  62. package/src/plugins/auto-plugin.js +34 -0
  63. package/src/services/Test.js +18 -0
  64. package/src/types/index.js +193 -0
  65. package/src/utils/date-helper.js +51 -0
  66. package/src/utils/helpers.js +39 -0
  67. package/src/utils/validation.js +32 -0
@@ -0,0 +1,356 @@
1
+ /**
2
+ * LifecycleManager - Quản lý các lifecycle hooks của view
3
+ * Xử lý created, mounted, updated, destroyed và các hooks liên quan
4
+ *
5
+ * Tách từ ViewController.js để cải thiện khả năng bảo trì
6
+ * @module core/managers/LifecycleManager
7
+ * @author OneLaravel Team
8
+ * @since 2025-12-29
9
+ */
10
+
11
+ import logger from '../services/LoggerService.js';
12
+ import { devLog } from '../../helpers/devWarnings.js';
13
+
14
+ export class LifecycleManager {
15
+ /**
16
+ * @param {ViewController} controller - Parent controller instance
17
+ */
18
+ constructor(controller) {
19
+ this.controller = controller;
20
+ this.view = controller.view;
21
+ }
22
+
23
+ beforeCreate() {
24
+ // logger.log(`🔵 beforeCreate: ${this.controller.path}`);
25
+ // Lifecycle: Được gọi trước khi tạo view
26
+ if (typeof this.view.beforeCreate === 'function') {
27
+ this.view.beforeCreate();
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Lifecycle: Được gọi khi view được tạo (trước lần render đầu tiên)
33
+ */
34
+ created() {
35
+ // logger.log(`🟢 created: ${this.controller.path}`);
36
+ if (typeof this.view.created === 'function') {
37
+ this.view.created();
38
+ }
39
+
40
+ // Chèn styles trước khi render
41
+ this.controller.insertStyles();
42
+ }
43
+
44
+ /**
45
+ * Lifecycle: Được gọi trước khi view được cập nhật
46
+ */
47
+ beforeUpdate() {
48
+ logger.log(`🟡 beforeUpdate: ${this.controller.path}`);
49
+ if (typeof this.view.beforeUpdate === 'function') {
50
+ this.view.beforeUpdate();
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Lifecycle: Được gọi sau khi view được cập nhật
56
+ */
57
+ updated() {
58
+ // logger.log(`🟠 updated: ${this.controller.path}`);
59
+ if (typeof this.view.updated === 'function') {
60
+ this.view.updated();
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Lifecycle: Được gọi trước khi khởi tạo
66
+ */
67
+ beforeInit() {
68
+ // logger.log(`🔷 beforeInit: ${this.controller.path}`);
69
+ if (typeof this.view.beforeInit === 'function') {
70
+ this.view.beforeInit();
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Lifecycle: Được gọi trong quá trình khởi tạo
76
+ */
77
+ init() {
78
+ // logger.log(`🔶 init: ${this.controller.path}`);
79
+ if (typeof this.view.init === 'function') {
80
+ this.view.init();
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Lifecycle: Được gọi sau khi khởi tạo
86
+ */
87
+ afterInit() {
88
+ // logger.log(`🔸 afterInit: ${this.controller.path}`);
89
+ if (typeof this.view.afterInit === 'function') {
90
+ this.view.afterInit();
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Lifecycle: Được gọi trước khi view bị hủy
96
+ */
97
+ beforeDestroy() {
98
+ // logger.log(`🔴 beforeDestroy: ${this.controller.path}`);
99
+ if (typeof this.view.beforeDestroy === 'function') {
100
+ this.view.beforeDestroy();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Lifecycle: Được gọi trong quá trình hủy
106
+ */
107
+ destroying() {
108
+ // logger.log(`🟥 destroying: ${this.controller.path}`);
109
+ if (typeof this.view.destroying === 'function') {
110
+ this.view.destroying();
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Lifecycle: Được gọi sau khi view bị hủy
116
+ */
117
+ destroyed() {
118
+ // logger.log(`⬛ destroyed: ${this.controller.path}`);
119
+ if (typeof this.view.destroyed === 'function') {
120
+ this.view.destroyed();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Lifecycle: Được gọi trước khi view được mount
126
+ */
127
+ beforeMount() {
128
+ // logger.log(`🟦 beforeMount: ${this.controller.path}`);
129
+ if (typeof this.view.beforeMount === 'function') {
130
+ this.view.beforeMount();
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Lifecycle: Được gọi trong quá trình mounting
136
+ */
137
+ mounting() {
138
+ // logger.log(`🟪 mounting: ${this.controller.path}`);
139
+ if (typeof this.view.mounting === 'function') {
140
+ this.view.mounting();
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Lifecycle: Được gọi khi view được mount (sau khi DOM sẵn sàng)
146
+ * Đây là nơi scripts được chèn và event listeners được khởi động
147
+ */
148
+ mounted() {
149
+ const ctrl = this.controller;
150
+ // logger.log(`🟩 mounted START: ${ctrl.path}`);
151
+ ctrl.isDestroyed = false;
152
+
153
+ if (!ctrl.isMarkupScanned) {
154
+ ctrl.__scanDOMElements(ctrl.id);
155
+ ctrl.isMarkupScanned = true;
156
+ }
157
+
158
+ if (!ctrl.isMounted) {
159
+ this.beforeMount();
160
+
161
+ try {
162
+ this.mounting();
163
+
164
+ // Chèn scripts sau khi DOM sẵn sàng
165
+ ctrl.insertScripts();
166
+
167
+ // Thông báo super view và children
168
+ if (ctrl.originalView && ctrl.originalView instanceof this.App.View.Controller) {
169
+ ctrl.originalView.onSuperViewMounted();
170
+ }
171
+
172
+ // Thông báo children (controller.children được duy trì bởi ChildrenRegistry)
173
+ if (ctrl.children && ctrl.children.length > 0) {
174
+ ctrl.children.forEach(childCtrl => {
175
+ if (childCtrl && childCtrl instanceof this.App.View.Controller) {
176
+ childCtrl.onParentMounted();
177
+ }
178
+ });
179
+ }
180
+
181
+ // Mount ReactiveComponents (output & watch thống nhất)
182
+ if (ctrl._reactiveManager.reactiveComponents && ctrl._reactiveManager.reactiveComponents.size > 0) {
183
+ ctrl._reactiveManager.reactiveComponents.forEach(component => {
184
+ component.mounted();
185
+ });
186
+ }
187
+
188
+ // Khởi động event listeners
189
+ ctrl._eventManager.startEventListener();
190
+ ctrl._bindingManager.startBindingEventListener();
191
+ ctrl._bindingManager.startClassBindingEventListener();
192
+
193
+ ctrl.isMounted = true;
194
+ ctrl.isReady = true;
195
+ ctrl.isRendered = true;
196
+
197
+ if (typeof ctrl.view.mounted === 'function') {
198
+ ctrl.view.mounted();
199
+ }
200
+
201
+ // logger.log(`✅ mounted COMPLETE: ${ctrl.path}`);
202
+
203
+ } catch (error) {
204
+ logger.warn('Error in mounted lifecycle hook:', error);
205
+ }
206
+
207
+ ctrl.states.__.readyToCommit = true;
208
+ }
209
+
210
+ ctrl.isReadyToStateChangeListen = true;
211
+ }
212
+
213
+ /**
214
+ * Lifecycle: Called before view is unmounted
215
+ */
216
+ beforeUnmount() {
217
+ // logger.log(`🟨 beforeUnmount: ${this.controller.path}`);
218
+ if (typeof this.view.beforeUnmount === 'function') {
219
+ this.view.beforeUnmount();
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Lifecycle: Called during unmounting
225
+ */
226
+ unmounting() {
227
+ // logger.log(`🟧 unmounting: ${this.controller.path}`);
228
+ if (typeof this.view.unmounting === 'function') {
229
+ this.view.unmounting();
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Lifecycle: Called when view is unmounted
235
+ * This is where scripts are removed and event listeners are stopped
236
+ */
237
+ unmounted() {
238
+ const ctrl = this.controller;
239
+ // logger.log(`🔻 unmounted START: ${ctrl.path}`);
240
+
241
+ if (ctrl.isMounted) {
242
+ ctrl.isReadyToStateChangeListen = false;
243
+ ctrl.states.__.readyToCommit = false;
244
+
245
+ this.beforeUnmount();
246
+ this.unmounting();
247
+
248
+ // Remove scripts
249
+ ctrl.removeScripts();
250
+
251
+ // Stop event listeners
252
+ ctrl._eventManager.stopEventListener();
253
+ ctrl._bindingManager.stopBindingEventListener();
254
+ ctrl._bindingManager.stopClassBindingEventListener();
255
+
256
+ ctrl.isMounted = false;
257
+ }
258
+
259
+ // Notify super view and children
260
+ if (ctrl.originalView && ctrl.originalView instanceof this.App.View.Controller) {
261
+ ctrl.originalView.onSuperViewUnmounted();
262
+ }
263
+
264
+ if (ctrl.children && ctrl.children.length > 0) {
265
+ ctrl.children.forEach(childCtrl => {
266
+ if (childCtrl && childCtrl instanceof this.App.View.Controller) {
267
+ childCtrl.onParentUnmounted();
268
+ }
269
+ });
270
+ }
271
+
272
+ // Unmount ReactiveComponents (unified output & watch)
273
+ if (ctrl._reactiveManager.reactiveComponents && ctrl._reactiveManager.reactiveComponents.size > 0) {
274
+ ctrl._reactiveManager.reactiveComponents.forEach(component => {
275
+ component.unmounted();
276
+ });
277
+ }
278
+
279
+ if (typeof ctrl.view.unmounted === 'function') {
280
+ ctrl.view.unmounted();
281
+ }
282
+
283
+ // logger.log(`✅ unmounted COMPLETE: ${ctrl.path}`);
284
+ }
285
+
286
+ /**
287
+ * Destroy view and cleanup resources
288
+ */
289
+ destroy() {
290
+ const ctrl = this.controller;
291
+ // logger.log(`💀 destroy START: ${ctrl.path}`);
292
+
293
+ // Mark as destroyed to prevent processing after destroy
294
+ ctrl.isDestroyed = true;
295
+ this.beforeDestroy();
296
+ this.destroying();
297
+
298
+ // Save view path before cleanup (needed for removing styles from DOM)
299
+ const viewPath = ctrl.path;
300
+
301
+ // Reset pending flag to prevent processing after destroy
302
+ ctrl._stateChangePending = false;
303
+
304
+ // Clear state change collections
305
+ if (ctrl.changedStateKeys) {
306
+ ctrl.changedStateKeys.clear();
307
+ }
308
+ ctrl.changeStateQueueCount = 0;
309
+
310
+ this.unmounted(); // Will call removeScripts()
311
+
312
+ // Remove styles (will use fallback if styles array is empty)
313
+ ctrl.removeStyles();
314
+
315
+ // Final cleanup: Remove all styles with this view path from DOM
316
+ // This ensures CSS is removed even if registry is out of sync
317
+ if (viewPath) {
318
+ ctrl.removeStylesByViewPath(viewPath);
319
+ }
320
+
321
+ if (ctrl.originalView && ctrl.originalView instanceof this.App.View.Controller) {
322
+ ctrl.originalView._lifecycleManager.destroy();
323
+ }
324
+
325
+ // Destroy all children using ChildrenRegistry for proper cleanup
326
+ if (ctrl._childrenRegistry) {
327
+ ctrl._childrenRegistry.clear();
328
+ } else {
329
+ // Fallback: manual destroy if registry not available
330
+ if (ctrl.children && ctrl.children.length > 0) {
331
+ ctrl.children.forEach(childCtrl => {
332
+ if (childCtrl && childCtrl instanceof this.App.View.Controller) {
333
+ childCtrl._lifecycleManager.destroy();
334
+ }
335
+ });
336
+ }
337
+ }
338
+
339
+ ctrl._reactiveManager.destroy();
340
+ if (ctrl.refElements && ctrl.refElements.length > 0) {
341
+ ctrl.refElements.forEach(element => element.parentNode && element.parentNode.removeChild(element));
342
+ ctrl.refElements = [];
343
+ }
344
+
345
+ this.destroyed();
346
+ // logger.log(`☠️ destroy COMPLETE: ${ctrl.path}`);
347
+ }
348
+
349
+ get App() {
350
+ return this.controller.App;
351
+ }
352
+ set App(value) {
353
+ devLog('LifecycleManager.App is read-only.');
354
+ }
355
+
356
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * ReactiveManager - Quản lý các reactive component
3
+ * Xử lý ReactiveComponents và reactive bindings
4
+ * Sử dụng ReactiveComponent thống nhất cho mọi reactive rendering
5
+ */
6
+
7
+ import { ReactiveComponent } from '../reactive/ReactiveComponent.js';
8
+ import { escapeString, hasData, uniqId } from '../../helpers/utils.js';
9
+ import logger from '../services/LoggerService.js';
10
+
11
+ export class ReactiveManager {
12
+ constructor(controller) {
13
+ this.controller = controller;
14
+
15
+ // Reactive components (thống nhất output & watch)
16
+ this.reactiveComponents = new Map();
17
+ this.reactiveComponentConfig = [];
18
+ this.reactiveComponentIndex = 0;
19
+ this.reactiveComponentScanIndex = 0;
20
+ this.reactiveComponentIDs = [];
21
+ this.reactiveComponentPrerenderIDs = [];
22
+ this.reactiveComponentRenderIDs = [];
23
+
24
+ // Following IDs - theo dõi reactive IDs trong các giai đoạn render
25
+ this.followingIDs = [];
26
+ this.followingRenderIDs = [];
27
+ this.followingPrerenderIDs = [];
28
+
29
+ // Hỗ trợ legacy - map tới reactive components
30
+ this.outputComponents = this.reactiveComponents;
31
+ this.outputComponentIDs = this.reactiveComponentIDs;
32
+ this.watchComponents = this.reactiveComponents;
33
+ this.watchComponentIDs = this.reactiveComponentIDs;
34
+
35
+ this.parentWatchComponent = null;
36
+ }
37
+
38
+ destroy() {
39
+ if (this.reactiveComponents && this.reactiveComponents.size > 0) {
40
+ this.reactiveComponents.forEach(component => component.destroy());
41
+ this.reactiveComponents.clear();
42
+ }
43
+ }
44
+
45
+ resetScanIndex() {
46
+ this.reactiveComponentScanIndex = 0;
47
+ }
48
+
49
+ clearForRefresh() {
50
+ if (this.reactiveComponents && this.reactiveComponents.size > 0) {
51
+ this.reactiveComponents.forEach(component => {
52
+ component.destroy();
53
+ });
54
+ }
55
+ this.reactiveComponentIDs = [];
56
+ this.reactiveComponentScanIndex = 0;
57
+
58
+ // Reset các mảng following
59
+ this.followingIDs = [];
60
+ this.followingRenderIDs = [];
61
+ this.followingPrerenderIDs = [];
62
+ }
63
+
64
+ renderOutputComponent(stateKeys = [], renderBlock = () => '') {
65
+ if (!Array.isArray(stateKeys) || stateKeys.length === 0) {
66
+ return typeof renderBlock === 'function' ? renderBlock() : '';
67
+ }
68
+
69
+ if (typeof renderBlock !== 'function') {
70
+ return '';
71
+ }
72
+
73
+ if (this.controller.isVirtualRendering) {
74
+ this.controller.isVirtualRendering = false;
75
+ let result = this.renderOutputComponentScan(stateKeys, renderBlock);
76
+ this.controller.isVirtualRendering = true;
77
+ if (result !== false) {
78
+ return result;
79
+ }
80
+ }
81
+
82
+ const rc = new ReactiveComponent({
83
+ stateKeys,
84
+ renderBlock,
85
+ controller: this.controller,
86
+ App: this.controller.App,
87
+ type: 'output',
88
+ escapeHTML: false
89
+ });
90
+
91
+ this.reactiveComponentIDs.push(rc.id);
92
+ this.reactiveComponents.set(rc.id, rc);
93
+ return rc.render();
94
+ }
95
+
96
+ renderOutputEscapedComponent(stateKeys = [], renderBlock = () => '') {
97
+ if (!Array.isArray(stateKeys) || stateKeys.length === 0) {
98
+ return escapeString(typeof renderBlock === 'function' ? renderBlock() : '');
99
+ }
100
+
101
+ if (typeof renderBlock !== 'function') {
102
+ return '';
103
+ }
104
+
105
+ if (this.controller.isVirtualRendering) {
106
+ this.controller.isVirtualRendering = false;
107
+ let result = this.renderOutputComponentScan(stateKeys, renderBlock, true);
108
+ this.controller.isVirtualRendering = true;
109
+ if (result !== false) {
110
+ return result;
111
+ }
112
+ }
113
+
114
+ const rc = new ReactiveComponent({
115
+ stateKeys,
116
+ renderBlock,
117
+ controller: this.controller,
118
+ App: this.controller.App,
119
+ type: 'output',
120
+ escapeHTML: true
121
+ });
122
+
123
+ this.reactiveComponentIDs.push(rc.id);
124
+ this.reactiveComponents.set(rc.id, rc);
125
+ return rc.render();
126
+ }
127
+
128
+ renderOutputComponentScan(stateKeys = [], renderBlock = () => '', escapeHTML = false) {
129
+ let reactiveComponentIndex = this.reactiveComponentScanIndex;
130
+ let reactiveComponentConfig = this.reactiveComponentConfig[reactiveComponentIndex];
131
+
132
+ if (!reactiveComponentConfig || !reactiveComponentConfig.stateKeys ||
133
+ reactiveComponentConfig.stateKeys.length != stateKeys.length ||
134
+ !reactiveComponentConfig.stateKeys.every(value => stateKeys.includes(value))) {
135
+ return false;
136
+ }
137
+
138
+ const { id: renderID } = reactiveComponentConfig;
139
+ const rc = new ReactiveComponent({
140
+ renderID,
141
+ stateKeys,
142
+ renderBlock,
143
+ controller: this.controller,
144
+ App: this.controller.App,
145
+ type: 'output',
146
+ escapeHTML
147
+ });
148
+
149
+ this.reactiveComponentIDs.push(rc.id);
150
+ this.reactiveComponents.set(rc.id, rc);
151
+ this.reactiveComponentScanIndex++;
152
+ return rc.render();
153
+ }
154
+
155
+ renderWatchComponent(watchID, stateKeys = [], renderBlock = () => '') {
156
+ if (!Array.isArray(stateKeys) || stateKeys.length === 0) {
157
+ return typeof renderBlock === 'function' ? renderBlock() : '';
158
+ }
159
+
160
+ if (typeof renderBlock !== 'function') {
161
+ return '';
162
+ }
163
+
164
+ if (this.reactiveComponents.has(watchID)) {
165
+ this.reactiveComponents.get(watchID).renderBlock = renderBlock;
166
+ return this.reactiveComponents.get(watchID).render();
167
+ }
168
+
169
+ const parentWatchComponent = this.parentWatchComponent;
170
+ const originChildrenIDs = this.controller._hierarchyManager.scanChildrenIDs;
171
+ this.controller._hierarchyManager.scanChildrenIDs = [];
172
+
173
+ const rc = new ReactiveComponent({
174
+ stateKeys,
175
+ renderBlock,
176
+ controller: this.controller,
177
+ App: this.controller.App,
178
+ parentWatchComponent,
179
+ renderID: watchID,
180
+ type: 'watch',
181
+ escapeHTML: false
182
+ });
183
+
184
+ this.parentWatchComponent = rc;
185
+
186
+ if (!this.reactiveComponentIDs.includes(rc.id)) {
187
+ this.reactiveComponentIDs.push(rc.id);
188
+ }
189
+ this.reactiveComponents.set(rc.id, rc);
190
+
191
+ let result = rc.render();
192
+ const newChildrenIDs = this.controller._hierarchyManager.scanChildrenIDs;
193
+ this.controller._hierarchyManager.scanChildrenIDs = originChildrenIDs;
194
+ rc.childrenIDs = newChildrenIDs;
195
+ this.parentWatchComponent = parentWatchComponent;
196
+
197
+ return result;
198
+ }
199
+
200
+ renderBindingAttribute(attrs = {}) {
201
+ if (typeof attrs !== 'object' || attrs === null) {
202
+ return '';
203
+ }
204
+
205
+ let bindingID = null;
206
+ const bindingManager = this.controller._bindingManager;
207
+
208
+ if (this.controller.isVirtualRendering) {
209
+ const attributeIndex = bindingManager.attributeIndex++;
210
+ const attributeConfig = bindingManager.attributeConfigs[attributeIndex];
211
+ if (!attributeConfig) {
212
+ return '';
213
+ }
214
+ const { id, config } = attributeConfig;
215
+ bindingID = id;
216
+ } else {
217
+ bindingID = uniqId();
218
+ }
219
+
220
+ const id = bindingID;
221
+ const stateKeys = [];
222
+ const element = null;
223
+ const newAttrs = {};
224
+ let attrStrValues = '';
225
+ const specialKeys = ['#text', '#html', '#children', '#content'];
226
+
227
+ Object.entries(attrs).forEach(([attrKey, attrConfig]) => {
228
+ const { states: attrStateKeys, render } = attrConfig;
229
+ if (!Array.isArray(attrStateKeys) || attrStateKeys.length === 0 || typeof render !== 'function') {
230
+ return;
231
+ }
232
+
233
+ attrStateKeys.forEach(stateKey => {
234
+ if (!stateKeys.includes(stateKey)) {
235
+ stateKeys.push(stateKey);
236
+ }
237
+ });
238
+
239
+ newAttrs[attrKey] = { states: attrStateKeys, render };
240
+
241
+ if (specialKeys.includes(attrKey)) {
242
+ return;
243
+ }
244
+
245
+ try {
246
+ const renderResult = render();
247
+ if (renderResult === null || renderResult === undefined) {
248
+ return;
249
+ }
250
+ if(['checked', 'selected', 'disabled', 'readonly', 'multiple'].includes(attrKey)) {
251
+ if (renderResult && !['false', '0', 'undefined'].includes(String(renderResult).toLowerCase())) {
252
+ attrStrValues += ` ${attrKey}="${attrKey}"`;
253
+ }
254
+ return;
255
+ }
256
+ attrStrValues += ` ${attrKey}="${escapeString(renderResult)}"`;
257
+ } catch (err) {
258
+ logger.error(`❌ ReactiveManager.renderBindingAttribute: Error rendering attribute ${attrKey}`, err);
259
+ }
260
+ });
261
+
262
+ if (!hasData(newAttrs)) {
263
+ return '';
264
+ }
265
+
266
+ bindingManager.attributeListeners.push({ id, stateKeys, attrs: newAttrs, element });
267
+ return ` data-one-attribute-id="${id}"` + attrStrValues;
268
+ }
269
+
270
+ renderClassBinding(classes = {}) {
271
+ if (typeof classes !== 'object' || classes === null) {
272
+ return '';
273
+ }
274
+
275
+ const id = uniqId();
276
+ const stateKeys = [];
277
+ const element = null;
278
+ const config = {};
279
+ let classStrValues = '';
280
+
281
+ classes.forEach((classConfig) => {
282
+ const { type = 'static', value, states: classStateKeys = [], checker } = classConfig;
283
+
284
+ if (type === 'static') {
285
+ classStrValues += ` ${value}`;
286
+ return;
287
+ }
288
+
289
+ if (!Array.isArray(classStateKeys) || classStateKeys.length === 0 || typeof checker !== 'function') {
290
+ return;
291
+ }
292
+
293
+ if (checker()) {
294
+ classStrValues += ` ${value}`;
295
+ }
296
+
297
+ classStateKeys.forEach(stateKey => {
298
+ if (!stateKeys.includes(stateKey)) {
299
+ stateKeys.push(stateKey);
300
+ }
301
+ });
302
+
303
+ config[value] = { states: classStateKeys, checker };
304
+ });
305
+
306
+ let initialiClass = classStrValues.trim();
307
+ if (initialiClass === '') {
308
+ initialiClass = null;
309
+ }
310
+
311
+ this.controller._bindingManager.classBindingConfigs.push({ id, states: stateKeys, config, initialiClass, element });
312
+ return ` data-one-class-id="${id}" class="${classStrValues.trim()}"`;
313
+ }
314
+
315
+ onWatchComponentUpdating() {
316
+ this.controller._eventManager.stopEventListener();
317
+ this.controller._bindingManager.stopBindingEventListener();
318
+ setTimeout(() => {
319
+ if (!this.controller.eventListenerStatus) {
320
+ this.controller._eventManager.startEventListener();
321
+ }
322
+ // Khởi động lại binding cho các DOM element mới
323
+ this.controller._bindingManager.startBindingEventListener();
324
+ }, 10);
325
+ }
326
+
327
+ onWatchComponentUpdated() {
328
+ if (!this.controller.eventListenerStatus) {
329
+ this.controller._eventManager.startEventListener();
330
+ }
331
+ // Đảm bảo binding listeners được gắn vào các element mới
332
+ this.controller._bindingManager.startBindingEventListener();
333
+ }
334
+ }