slicejs-web-framework 3.2.3 → 3.3.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 (117) hide show
  1. package/.opencode/opencode.json +13 -13
  2. package/LICENSE +21 -21
  3. package/README.md +97 -174
  4. package/Slice/Components/Structural/ContextManager/ContextManager.js +369 -369
  5. package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +420 -297
  6. package/Slice/Components/Structural/Controller/Controller.js +1131 -1131
  7. package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
  8. package/Slice/Components/Structural/Debugger/Debugger.js +1497 -1547
  9. package/Slice/Components/Structural/EventManager/EventManager.js +338 -338
  10. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +476 -361
  11. package/Slice/Components/Structural/Logger/Log.js +10 -10
  12. package/Slice/Components/Structural/Logger/Logger.js +146 -146
  13. package/Slice/Components/Structural/Router/Router.js +752 -721
  14. package/Slice/Components/Structural/StylesManager/StylesManager.js +78 -78
  15. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +84 -84
  16. package/Slice/Slice.js +542 -542
  17. package/Slice/tests/build-bundled-component-without-category.test.js +103 -103
  18. package/Slice/tests/build-js-only-visual-components.test.js +144 -144
  19. package/Slice/tests/bundle-v2-runtime-contract.test.js +728 -728
  20. package/Slice/tests/public-env-runtime-accessors.test.js +44 -44
  21. package/Slice/tests/router-loading-finally.test.js +68 -68
  22. package/api/index.js +286 -286
  23. package/api/middleware/securityMiddleware.js +252 -252
  24. package/api/tests/public-env-resolver.test.js +193 -193
  25. package/api/utils/publicEnvResolver.js +117 -117
  26. package/package.json +40 -38
  27. package/sliceConfig.schema.json +109 -109
  28. package/src/App/index.html +16 -22
  29. package/src/App/index.js +20 -23
  30. package/src/App/style.css +11 -40
  31. package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -0
  32. package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -0
  33. package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -0
  34. package/src/Components/AppComponents/AppShell/AppShell.css +10 -0
  35. package/src/Components/AppComponents/AppShell/AppShell.html +4 -0
  36. package/src/Components/AppComponents/AppShell/AppShell.js +36 -0
  37. package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -0
  38. package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -0
  39. package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -0
  40. package/src/Components/Service/FetchManager/FetchManager.js +133 -133
  41. package/src/Components/Service/IndexedDbManager/IndexedDbManager.js +141 -141
  42. package/src/Components/Service/LocalStorageManager/LocalStorageManager.js +45 -45
  43. package/src/Components/Visual/Button/Button.css +47 -47
  44. package/src/Components/Visual/Button/Button.html +5 -5
  45. package/src/Components/Visual/Button/Button.js +92 -92
  46. package/src/Components/Visual/Card/Card.css +68 -68
  47. package/src/Components/Visual/Card/Card.html +7 -7
  48. package/src/Components/Visual/Card/Card.js +107 -107
  49. package/src/Components/Visual/Checkbox/Checkbox.css +87 -87
  50. package/src/Components/Visual/Checkbox/Checkbox.html +8 -8
  51. package/src/Components/Visual/Checkbox/Checkbox.js +86 -86
  52. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.css +129 -129
  53. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.html +3 -3
  54. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.js +262 -262
  55. package/src/Components/Visual/Details/Details.css +70 -70
  56. package/src/Components/Visual/Details/Details.html +9 -9
  57. package/src/Components/Visual/Details/Details.js +76 -76
  58. package/src/Components/Visual/DropDown/DropDown.css +60 -60
  59. package/src/Components/Visual/DropDown/DropDown.html +5 -5
  60. package/src/Components/Visual/DropDown/DropDown.js +63 -63
  61. package/src/Components/Visual/Grid/Grid.css +7 -7
  62. package/src/Components/Visual/Grid/Grid.html +1 -1
  63. package/src/Components/Visual/Grid/Grid.js +57 -57
  64. package/src/Components/Visual/Icon/Icon.css +510 -510
  65. package/src/Components/Visual/Icon/Icon.js +89 -89
  66. package/src/Components/Visual/Icon/slc.json +554 -554
  67. package/src/Components/Visual/Icon/slc.styl +507 -507
  68. package/src/Components/Visual/Icon/slc.svg +1485 -1485
  69. package/src/Components/Visual/Icon/slc.symbol.svg +1058 -1058
  70. package/src/Components/Visual/Input/Input.css +91 -91
  71. package/src/Components/Visual/Input/Input.html +4 -4
  72. package/src/Components/Visual/Input/Input.js +215 -215
  73. package/src/Components/Visual/Layout/Layout.js +49 -49
  74. package/src/Components/Visual/Link/Link.css +8 -8
  75. package/src/Components/Visual/Link/Link.html +1 -1
  76. package/src/Components/Visual/Link/Link.js +63 -63
  77. package/src/Components/Visual/Loading/Loading.css +56 -56
  78. package/src/Components/Visual/Loading/Loading.html +83 -83
  79. package/src/Components/Visual/Loading/Loading.js +38 -38
  80. package/src/Components/Visual/MultiRoute/MultiRoute.js +100 -93
  81. package/src/Components/Visual/Navbar/Navbar.css +115 -115
  82. package/src/Components/Visual/Navbar/Navbar.html +44 -44
  83. package/src/Components/Visual/Navbar/Navbar.js +141 -141
  84. package/src/Components/Visual/NotFound/NotFound.css +116 -116
  85. package/src/Components/Visual/NotFound/NotFound.html +23 -23
  86. package/src/Components/Visual/NotFound/NotFound.js +16 -16
  87. package/src/Components/Visual/Route/Route.js +93 -93
  88. package/src/Components/Visual/Select/Select.css +84 -84
  89. package/src/Components/Visual/Select/Select.html +8 -8
  90. package/src/Components/Visual/Select/Select.js +195 -195
  91. package/src/Components/Visual/Switch/Switch.css +76 -76
  92. package/src/Components/Visual/Switch/Switch.html +8 -8
  93. package/src/Components/Visual/Switch/Switch.js +102 -102
  94. package/src/Components/Visual/TreeItem/TreeItem.css +36 -36
  95. package/src/Components/Visual/TreeItem/TreeItem.html +1 -1
  96. package/src/Components/Visual/TreeItem/TreeItem.js +126 -126
  97. package/src/Components/Visual/TreeView/TreeView.css +8 -8
  98. package/src/Components/Visual/TreeView/TreeView.html +1 -1
  99. package/src/Components/Visual/TreeView/TreeView.js +48 -48
  100. package/src/Components/components.js +15 -27
  101. package/src/Styles/sliceStyles.css +34 -34
  102. package/src/Themes/Dark.css +42 -42
  103. package/src/Themes/Light.css +31 -31
  104. package/src/Themes/Slice.css +47 -47
  105. package/src/routes.js +9 -15
  106. package/src/sliceConfig.json +74 -73
  107. package/types/index.d.ts +207 -207
  108. package/Slice/Components/Structural/Debugger/Debugger.css +0 -620
  109. package/src/Components/AppComponents/HomePage/HomePage.css +0 -201
  110. package/src/Components/AppComponents/HomePage/HomePage.html +0 -37
  111. package/src/Components/AppComponents/HomePage/HomePage.js +0 -210
  112. package/src/Components/AppComponents/Playground/Playground.css +0 -12
  113. package/src/Components/AppComponents/Playground/Playground.html +0 -0
  114. package/src/Components/AppComponents/Playground/Playground.js +0 -111
  115. package/src/images/Slice.js-logo.png +0 -0
  116. package/src/images/im2/Slice.js-logo.png +0 -0
  117. package/src/testing.js +0 -888
@@ -1,906 +1,906 @@
1
1
  import components from '/Components/components.js';
2
2
  import { collectInvalidAllowedValueProps, formatAllowedValuesForLog } from './allowedValuesValidation.js';
3
-
4
- export default class Controller {
5
- constructor() {
6
- this.componentCategories = new Map(Object.entries(components));
7
- this.templates = new Map();
8
- this.classes = new Map();
9
- this.requestedStyles = new Set(); // ✅ CRÍTICO: Para tracking de CSS cargados
10
- this.activeComponents = new Map();
11
-
12
- // 🚀 OPTIMIZACIÓN: Índice inverso para búsqueda rápida de hijos
13
- // parentSliceId → Set<childSliceId>
14
- this.childrenIndex = new Map();
15
-
16
- // 📦 Bundle system
17
- this.loadedBundles = new Set();
18
- this.bundleConfig = null;
19
- this.criticalBundleLoaded = false;
20
- this.bundleImportPromises = new Map();
21
- this.bundleLoadPromises = new Map();
22
-
23
- this.idCounter = 0;
24
- }
25
-
26
- /**
27
- * 📦 Initializes bundle system (called automatically when config is loaded)
28
- */
29
- initializeBundles(config = null) {
30
- if (config) {
31
- this.bundleConfig = config;
32
-
33
- // Register critical bundle components if available
34
- if (config.bundles?.critical) {
35
- // Critical bundle will be loaded explicitly
36
- }
37
- this.criticalBundleLoaded = false;
38
- } else {
39
- // No bundles available, will use individual component loading
40
- this.bundleConfig = null;
41
- this.criticalBundleLoaded = false;
42
- }
43
- }
44
-
45
- /**
46
- * Import a bundle URL once per page session.
47
- * Reuses the same Promise for concurrent callers.
48
- * @param {string} bundlePath
49
- * @returns {Promise<any>}
50
- */
51
- importBundleOnce(bundlePath) {
52
- if (!bundlePath) {
53
- return Promise.reject(new Error('Bundle path is required'));
54
- }
55
-
56
- if (this.bundleImportPromises.has(bundlePath)) {
57
- return this.bundleImportPromises.get(bundlePath);
58
- }
59
-
60
- const importPromise = import(bundlePath).catch((error) => {
61
- this.bundleImportPromises.delete(bundlePath);
62
- throw error;
63
- });
64
-
65
- this.bundleImportPromises.set(bundlePath, importPromise);
66
- return importPromise;
67
- }
68
-
69
- buildBundleImportPath(bundleInfo) {
70
- if (!bundleInfo || typeof bundleInfo.file !== 'string' || bundleInfo.file.length === 0) {
71
- throw new Error('Bundle file is required');
72
- }
73
-
74
- const basePath = `/bundles/${bundleInfo.file}`;
75
- const bundleHash = typeof bundleInfo.hash === 'string' ? bundleInfo.hash.trim() : '';
76
- if (!bundleHash) {
77
- return basePath;
78
- }
79
-
80
- return `${basePath}?v=${encodeURIComponent(bundleHash)}`;
81
- }
82
-
83
- /**
84
- * Validate Bundling V2 module contract.
85
- * Requires named exports: SLICE_BUNDLE_META and registerAll.
86
- * @param {any} bundleModule
87
- * @param {string} [bundleName]
88
- * @returns {{metadata: object, registerAll: Function}}
89
- */
90
- async validateBundleModule(bundleModule, bundleName = 'unknown') {
91
- const metadata = bundleModule?.SLICE_BUNDLE_META;
92
- const registerAll = bundleModule?.registerAll;
93
-
94
- if (!metadata || typeof metadata !== 'object' || typeof registerAll !== 'function') {
95
- throw new Error(
96
- `Bundle "${bundleName}" missing Bundling V2 exports contract: requires SLICE_BUNDLE_META and registerAll`
97
- );
98
- }
99
-
100
- return { metadata, registerAll };
101
- }
102
-
103
- /**
104
- * 📦 Loads a bundle by name or category
105
- */
106
- async loadBundle(bundleName) {
107
- const resolvedBundleName = this.resolveBundleName(bundleName);
108
- if (this.loadedBundles.has(resolvedBundleName)) {
109
- return; // Already loaded
110
- }
111
-
112
- return this.loadBundleWithDependencies(resolvedBundleName, new Set());
113
- }
114
-
115
- async loadBundleWithDependencies(bundleName, loadingStack = new Set()) {
116
- const resolvedBundleName = this.resolveBundleName(bundleName);
117
-
118
- if (this.loadedBundles.has(resolvedBundleName)) {
119
- return;
120
- }
121
-
122
- if (loadingStack.has(resolvedBundleName)) {
123
- throw new Error(`Circular bundle dependency detected: ${Array.from(loadingStack).join(' -> ')} -> ${resolvedBundleName}`);
124
- }
125
-
126
- if (this.bundleLoadPromises.has(resolvedBundleName)) {
127
- return this.bundleLoadPromises.get(resolvedBundleName);
128
- }
129
-
130
- const loadPromise = (async () => {
131
- loadingStack.add(resolvedBundleName);
132
- try {
133
- const bundleInfo = this.getBundleInfo(resolvedBundleName);
134
- if (!bundleInfo) {
135
- console.warn(`Bundle ${resolvedBundleName} not found in configuration`);
136
- return;
137
- }
138
-
139
- const dependencies = this.getBundleDependencies(bundleInfo);
140
- for (const dependencyName of dependencies) {
141
- await this.loadBundleWithDependencies(dependencyName, loadingStack);
142
- }
143
-
144
- const bundlePath = this.buildBundleImportPath(bundleInfo);
145
- const bundleModule = await this.importBundleOnce(bundlePath);
146
- const { metadata, registerAll } = await this.validateBundleModule(bundleModule, resolvedBundleName);
147
-
148
- const registerResult = await registerAll(this, slice.stylesManager);
149
- this.registerVendorSharedDependencies(bundleModule, metadata, resolvedBundleName, registerResult);
150
-
151
- this.loadedBundles.add(resolvedBundleName);
152
- const loadedBundleKey = metadata.bundleKey;
153
- if (loadedBundleKey && loadedBundleKey !== resolvedBundleName) {
154
- this.loadedBundles.add(loadedBundleKey);
155
- }
156
-
157
- if (metadata.type === 'critical' || resolvedBundleName === 'critical') {
158
- this.criticalBundleLoaded = true;
159
- }
160
- } finally {
161
- loadingStack.delete(resolvedBundleName);
162
- }
163
- })();
164
-
165
- this.bundleLoadPromises.set(resolvedBundleName, loadPromise);
166
- try {
167
- return await loadPromise;
168
- } finally {
169
- this.bundleLoadPromises.delete(resolvedBundleName);
170
- }
171
- }
172
-
173
- resolveBundleName(bundleName) {
174
- if (typeof bundleName !== 'string' || bundleName.length === 0) {
175
- return bundleName;
176
- }
177
-
178
- if (bundleName.toLowerCase() === 'critical') {
179
- return 'critical';
180
- }
181
-
182
- if (this.isVendorSharedAlias(bundleName) && this.getVendorSharedBundleInfo()) {
183
- return 'vendor-shared';
184
- }
185
-
186
- const routeBundleName = this.findBundleNameByAlias(this.bundleConfig?.bundles?.routes, bundleName);
187
- if (routeBundleName) {
188
- return routeBundleName;
189
- }
190
-
191
- const sharedBundleName = this.findBundleNameByAlias(this.bundleConfig?.bundles?.shared, bundleName);
192
- if (sharedBundleName) {
193
- return sharedBundleName;
194
- }
195
-
196
- return bundleName;
197
- }
198
-
199
- findBundleNameByAlias(bundleRegistry, bundleName) {
200
- if (!bundleRegistry || typeof bundleRegistry !== 'object') {
201
- return null;
202
- }
203
-
204
- if (bundleRegistry[bundleName]) {
205
- return bundleName;
206
- }
207
-
208
- const normalizedName = bundleName?.toLowerCase();
209
- if (!normalizedName) {
210
- return null;
211
- }
212
-
213
- return Object.keys(bundleRegistry).find((key) => key.toLowerCase() === normalizedName) || null;
214
- }
215
-
216
- getBundleDependencies(bundleInfo) {
217
- if (!bundleInfo || !Array.isArray(bundleInfo.dependencies)) {
218
- return [];
219
- }
220
-
221
- return bundleInfo.dependencies.filter((dependency) => typeof dependency === 'string' && dependency.length > 0);
222
- }
223
-
224
- findBundleEntryByName(bundleRegistry, bundleName) {
225
- if (!bundleRegistry || typeof bundleRegistry !== 'object') {
226
- return null;
227
- }
228
-
229
- if (bundleRegistry[bundleName]) {
230
- return bundleRegistry[bundleName];
231
- }
232
-
233
- const normalizedName = bundleName?.toLowerCase();
234
- if (!normalizedName) {
235
- return null;
236
- }
237
-
238
- const matchedKey = Object.keys(bundleRegistry).find((key) => key.toLowerCase() === normalizedName);
239
- return matchedKey ? bundleRegistry[matchedKey] : null;
240
- }
241
-
242
- getBundleInfo(bundleName) {
243
- if (bundleName === 'critical') {
244
- return this.bundleConfig?.bundles?.critical || null;
245
- }
246
-
247
- if (this.isVendorSharedAlias(bundleName)) {
248
- return this.getVendorSharedBundleInfo();
249
- }
250
-
251
- return (
252
- this.findBundleEntryByName(this.bundleConfig?.bundles?.routes, bundleName)
253
- || this.findBundleEntryByName(this.bundleConfig?.bundles?.shared, bundleName)
254
- );
255
- }
256
-
257
- getVendorSharedBundleInfo() {
258
- if (this.bundleConfig?.bundles?.vendorShared && typeof this.bundleConfig.bundles.vendorShared === 'object') {
259
- return this.bundleConfig.bundles.vendorShared;
260
- }
261
-
262
- return this.findBundleEntryByName(this.bundleConfig?.bundles?.shared, 'vendor-shared');
263
- }
264
-
265
- isVendorSharedAlias(bundleName) {
266
- if (typeof bundleName !== 'string') {
267
- return false;
268
- }
269
-
270
- const normalized = bundleName.toLowerCase();
271
- return normalized === 'vendor-shared' || normalized === 'vendorshared';
272
- }
273
-
274
- registerVendorSharedDependencies(bundleModule, metadata, bundleName, registerResult) {
275
- const isVendorShared = this.isVendorSharedBundleName(metadata?.bundleKey)
276
- || this.isVendorSharedBundleName(bundleName)
277
- || metadata?.registerVendorSharedDependencies === true;
278
-
279
- if (!isVendorShared) {
280
- return;
281
- }
282
-
283
- let sharedDeps = bundleModule?.SLICE_SHARED_DEPS;
284
- if (!sharedDeps && registerResult && typeof registerResult === 'object') {
285
- sharedDeps = registerResult.SLICE_SHARED_DEPS
286
- || registerResult.SLICE_BUNDLE_DEPENDENCIES
287
- || registerResult;
288
- }
289
-
290
- if (!sharedDeps || typeof sharedDeps !== 'object') {
291
- return;
292
- }
293
-
294
- if (!window.__SLICE_SHARED_DEPS__ || typeof window.__SLICE_SHARED_DEPS__ !== 'object') {
295
- window.__SLICE_SHARED_DEPS__ = {};
296
- }
297
-
298
- Object.assign(window.__SLICE_SHARED_DEPS__, sharedDeps);
299
- }
300
-
301
- isVendorSharedBundleName(bundleName) {
302
- return typeof bundleName === 'string' && bundleName.toLowerCase() === 'vendor-shared';
303
- }
304
-
305
- /**
306
- * 📦 Registers a bundle's components (called automatically by bundle files)
307
- */
308
- registerBundleLegacy(bundle) {
309
- const { components, metadata } = bundle;
310
-
311
- console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
312
-
313
- // Phase 1: Register templates and CSS for all components first
314
- for (const [componentName, componentData] of Object.entries(components)) {
315
- try {
316
- // Register HTML template
317
- if (componentData.html !== undefined && !this.templates.has(componentName)) {
318
- const template = document.createElement('template');
319
- template.innerHTML = componentData.html || '';
320
- this.templates.set(componentName, template);
321
- }
322
-
323
- // Register CSS styles
324
- if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
325
- // Use the existing stylesManager to register component styles
326
- if (window.slice && window.slice.stylesManager) {
327
- window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
328
- this.requestedStyles.add(componentName);
329
- }
330
- }
331
- } catch (error) {
332
- console.warn(`❌ Failed to register assets for ${componentName}:`, error);
333
- }
334
- }
335
-
336
- // Phase 2: Evaluate all external file dependencies
337
- const processedDeps = new Set();
338
- for (const [componentName, componentData] of Object.entries(components)) {
339
- if (componentData.dependencies) {
340
- for (const [depName, depContent] of Object.entries(componentData.dependencies)) {
341
- if (!processedDeps.has(depName)) {
342
- try {
343
- // Convert ES6 exports to global assignments
344
- let processedContent = depContent
345
- .replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
346
- .replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
347
- .replace(/export\s+var\s+(\w+)\s*=/g, 'window.$1 =')
348
- .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
349
- .replace(/export\s+default\s+/g, 'window.defaultExport =')
350
- .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
351
- return exports
352
- .split(',')
353
- .map((exp) => {
354
- const cleanExp = exp.trim();
355
- const varName = cleanExp.split(' as ')[0].trim();
356
- return `window.${varName} = ${varName};`;
357
- })
358
- .join('\n');
359
- })
360
- // Remove any remaining export keywords
361
- .replace(/^\s*export\s+/gm, '');
362
-
363
- // Evaluate the dependency
364
- try {
365
- new Function('slice', 'customElements', 'window', 'document', processedContent)(
366
- window.slice,
367
- window.customElements,
368
- window,
369
- window.document
370
- );
371
- } catch (evalError) {
372
- console.warn(`❌ Failed to evaluate processed dependency ${depName}:`, evalError);
373
- console.warn('Processed content preview:', processedContent.substring(0, 200));
374
- // Try evaluating the original content as fallback
375
- try {
376
- new Function('slice', 'customElements', 'window', 'document', depContent)(
377
- window.slice,
378
- window.customElements,
379
- window,
380
- window.document
381
- );
382
- console.log(`✅ Fallback evaluation succeeded for ${depName}`);
383
- } catch (fallbackError) {
384
- console.warn(`❌ Fallback evaluation also failed for ${depName}:`, fallbackError);
385
- }
386
- }
387
-
388
- processedDeps.add(depName);
389
- console.log(`📄 Dependency loaded: ${depName}`);
390
- } catch (depError) {
391
- console.warn(`⚠️ Failed to load dependency ${depName} for ${componentName}:`, depError);
392
- }
393
- }
394
- }
395
- }
396
- }
397
-
398
- // Phase 3: Evaluate all component classes (now that dependencies are available)
399
- for (const [componentName, componentData] of Object.entries(components)) {
400
- // For JavaScript classes, we need to evaluate the code
401
- if (componentData.js && !this.classes.has(componentName)) {
402
- try {
403
- // Create evaluation context with dependencies
404
- let evalCode = componentData.js;
405
-
406
- // Prepend dependencies to make them available
407
- if (componentData.dependencies) {
408
- const depCode = Object.entries(componentData.dependencies)
409
- .map(([depName, depContent]) => {
410
- // Convert ES6 exports to global assignments
411
- return depContent
412
- .replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
413
- .replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
414
- .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
415
- .replace(/export\s+default\s+/g, 'window.defaultExport =')
416
- .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
417
- return exports
418
- .split(',')
419
- .map((exp) => {
420
- const cleanExp = exp.trim();
421
- return `window.${cleanExp} = ${cleanExp};`;
422
- })
423
- .join('\n');
424
- });
425
- })
426
- .join('\n\n');
427
-
428
- evalCode = depCode + '\n\n' + evalCode;
429
- }
430
-
431
- // Evaluate the complete code
432
- const componentClass = new Function(
433
- 'slice',
434
- 'customElements',
435
- 'window',
436
- 'document',
437
- `
438
- "use strict";
439
- ${evalCode}
440
- return ${componentName};
441
- `
442
- )(window.slice, window.customElements, window, window.document);
443
-
444
- if (componentClass) {
445
- this.classes.set(componentName, componentClass);
446
- console.log(`📝 Class registered for: ${componentName}`);
447
- }
448
- } catch (error) {
449
- console.warn(`❌ Failed to evaluate class for ${componentName}:`, error);
450
- console.warn('Code that failed:', componentData.js.substring(0, 200) + '...');
451
- }
452
- }
453
- }
454
- }
455
-
456
- /**
457
- * 📦 New bundle registration method (simplified and robust)
458
- */
459
- registerBundle(bundle) {
460
- const validation = this.validateBundle(bundle);
461
- if (!validation.isValid) {
462
- console.warn(`❌ Bundle validation failed: ${validation.error}`);
463
- return Promise.resolve(false);
464
- }
465
-
466
- // Set tracking flags synchronously before any async work, so callers that
467
- // await import() see the flags set immediately when the Promise resolves.
468
- const { components, metadata } = bundle;
469
- const bundleKey = metadata?.bundleKey;
470
- if (bundleKey) {
471
- this.loadedBundles.add(bundleKey);
472
- if (metadata?.type === 'critical') {
473
- this.criticalBundleLoaded = true;
474
- }
475
- }
476
-
477
- console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
478
-
479
- const entries = Object.entries(components);
480
- const chunkSize = 50;
481
- let index = 0;
482
-
483
- return new Promise((resolve) => {
484
- const processChunk = () => {
485
- const sliceEntries = entries.slice(index, index + chunkSize);
486
-
487
- for (const [componentName, componentData] of sliceEntries) {
488
- try {
489
- if (componentData.html !== undefined && !this.templates.has(componentName)) {
490
- const template = document.createElement('template');
491
- template.innerHTML = componentData.html || '';
492
- this.templates.set(componentName, template);
493
- }
494
-
495
- if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
496
- if (window.slice && window.slice.stylesManager) {
497
- window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
498
- this.requestedStyles.add(componentName);
499
- }
500
- }
501
-
502
- if (componentData.class && !this.classes.has(componentName)) {
503
- const registeredName = componentData.isFramework
504
- ? `Framework/Structural/${componentName}`
505
- : componentName;
506
- this.classes.set(registeredName, componentData.class);
507
- if (componentName === 'Loading') {
508
- console.log('🔎 Bundle class registered: Loading', {
509
- registeredName,
510
- type: typeof componentData.class,
511
- isFunction: typeof componentData.class === 'function'
512
- });
513
- }
514
- if (componentName === 'InputSearchDocs' || componentName === 'MainMenu') {
515
- console.log(`🔎 Bundle class registered: ${componentName}`, {
516
- registeredName,
517
- type: typeof componentData.class,
518
- isFunction: typeof componentData.class === 'function'
519
- });
520
- }
521
- }
522
- } catch (error) {
523
- console.warn(`❌ Failed to register component ${componentName}:`, error);
524
- }
525
- }
526
-
527
- index += chunkSize;
528
- if (index < entries.length) {
529
- if (typeof requestIdleCallback === 'function') {
530
- requestIdleCallback(processChunk);
531
- } else {
532
- setTimeout(processChunk, 0);
533
- }
534
- return;
535
- }
536
-
537
- console.log(`✅ Bundle registration completed: ${metadata.componentCount} components processed`);
538
- resolve(true);
539
- };
540
-
541
- processChunk();
542
- });
543
- }
544
-
545
- /**
546
- * Validates bundle structure before registering.
547
- * @param {object} bundle
548
- * @returns {{isValid: boolean, error?: string}}
549
- */
550
- validateBundle(bundle) {
551
- if (!bundle || typeof bundle !== 'object') {
552
- return { isValid: false, error: 'Bundle payload is invalid' };
553
- }
554
-
555
- if (!bundle.metadata || typeof bundle.metadata !== 'object') {
556
- return { isValid: false, error: 'Bundle metadata missing' };
557
- }
558
-
559
- if (!bundle.components || typeof bundle.components !== 'object') {
560
- return { isValid: false, error: 'Bundle components missing' };
561
- }
562
-
563
- if (typeof bundle.metadata.componentCount !== 'number') {
564
- return { isValid: false, error: 'Bundle metadata missing componentCount' };
565
- }
566
-
567
- if (bundle.metadata.componentCount !== Object.keys(bundle.components).length) {
568
- return { isValid: false, error: 'Bundle component count mismatch' };
569
- }
570
-
571
- const maxComponents = 5000;
572
- if (bundle.metadata.componentCount > maxComponents) {
573
- return { isValid: false, error: 'Bundle component count exceeds limit' };
574
- }
575
-
576
- return { isValid: true };
577
- }
578
-
579
- /**
580
- * 📦 Determines which bundle to load for a component
581
- */
582
- getBundleForComponent(componentName) {
583
- if (!this.bundleConfig?.bundles) return null;
584
-
585
- // Check if component is in critical bundle
586
- if (this.bundleConfig.bundles.critical?.components?.includes(componentName)) {
587
- return 'critical';
588
- }
589
-
590
- // Find component in route bundles
591
- if (this.bundleConfig.bundles.routes) {
592
- for (const [bundleName, bundleInfo] of Object.entries(this.bundleConfig.bundles.routes)) {
593
- if (bundleInfo.components?.includes(componentName)) {
594
- return bundleName;
595
- }
596
- }
597
- }
598
-
599
- return null;
600
- }
601
-
602
- /**
603
- * 📦 Checks if a component is available from loaded bundles
604
- */
605
- isComponentFromBundle(componentName) {
606
- if (!this.bundleConfig?.bundles) return false;
607
-
608
- // Check critical bundle
609
- if (this.bundleConfig.bundles.critical?.components?.includes(componentName)) {
610
- return this.criticalBundleLoaded;
611
- }
612
-
613
- // Check route bundles
614
- if (this.bundleConfig.bundles.routes) {
615
- for (const [bundleName, bundleInfo] of Object.entries(this.bundleConfig.bundles.routes)) {
616
- if (bundleInfo.components?.includes(componentName)) {
617
- return this.loadedBundles.has(bundleName);
618
- }
619
- }
620
- }
621
-
622
- return false;
623
- }
624
-
625
- /**
626
- * 📦 Gets component data from loaded bundles
627
- */
628
- getComponentFromBundle(componentName) {
629
- if (!this.bundleConfig?.bundles) return null;
630
-
631
- // Find component in any loaded bundle
632
- const allBundles = [
633
- { name: 'critical', data: this.bundleConfig.bundles.critical },
634
- ...Object.entries(this.bundleConfig.bundles.routes || {}).map(([name, data]) => ({ name, data })),
635
- ];
636
-
637
- for (const { name: bundleName, data: bundleData } of allBundles) {
638
- if (bundleData?.components?.includes(componentName) && this.loadedBundles.has(bundleName)) {
639
- // Find the bundle file and extract component data
640
- // This is a simplified version - in practice you'd need to access the loaded bundle data
641
- return { bundleName, componentName };
642
- }
643
- }
644
-
645
- return null;
646
- }
647
-
648
- logActiveComponents() {
649
- this.activeComponents.forEach((component) => {
650
- let parent = component.parentComponent;
651
- let parentName = parent ? parent.constructor.name : null;
652
- });
653
- }
654
-
655
- getTopParentsLinkedToActiveComponents() {
656
- let topParentsLinkedToActiveComponents = new Map();
657
- this.activeComponents.forEach((component) => {
658
- let parent = component.parentComponent;
659
- while (parent && parent.parentComponent) {
660
- parent = parent.parentComponent;
661
- }
662
- if (!topParentsLinkedToActiveComponents.has(parent)) {
663
- topParentsLinkedToActiveComponents.set(parent, []);
664
- }
665
- topParentsLinkedToActiveComponents.get(parent).push(component);
666
- });
667
- return topParentsLinkedToActiveComponents;
668
- }
669
-
670
- verifyComponentIds(component) {
671
- const htmlId = component.id;
672
-
673
- if (htmlId && htmlId.trim() !== '') {
674
- if (this.activeComponents.has(htmlId)) {
675
- slice.logger.logError(
676
- 'Controller',
677
- `A component with the same html id attribute is already registered: ${htmlId}`
678
- );
679
- return false;
680
- }
681
- }
682
-
683
- let sliceId = component.sliceId;
684
-
685
- if (sliceId && sliceId.trim() !== '') {
686
- if (this.activeComponents.has(sliceId)) {
687
- slice.logger.logError(
688
- 'Controller',
689
- `A component with the same slice id attribute is already registered: ${sliceId}`
690
- );
691
- return false;
692
- }
693
- } else {
694
- sliceId = `${component.constructor.name[0].toLowerCase()}${component.constructor.name.slice(1)}-${this.idCounter}`;
695
- component.sliceId = sliceId;
696
- this.idCounter++;
697
- }
698
-
699
- component.sliceId = sliceId;
700
- return true;
701
- }
702
-
703
- /**
704
- * Registra un componente y actualiza el índice de relaciones padre-hijo
705
- * 🚀 OPTIMIZADO: Ahora mantiene childrenIndex y precalcula profundidad
706
- */
707
- registerComponent(component, parent = null) {
708
- component.parentComponent = parent;
709
-
710
- // 🚀 OPTIMIZACIÓN: Precalcular y guardar profundidad
711
- component._depth = parent ? (parent._depth || 0) + 1 : 0;
712
-
713
- // Registrar en activeComponents
714
- this.activeComponents.set(component.sliceId, component);
715
-
716
- // 🚀 OPTIMIZACIÓN: Actualizar índice inverso de hijos
717
- if (parent) {
718
- if (!this.childrenIndex.has(parent.sliceId)) {
719
- this.childrenIndex.set(parent.sliceId, new Set());
720
- }
721
- this.childrenIndex.get(parent.sliceId).add(component.sliceId);
722
- }
723
-
724
- return true;
725
- }
726
-
727
- registerComponentsRecursively(component, parent = null) {
728
- // Assign parent if not already set
729
- if (!component.parentComponent) {
730
- component.parentComponent = parent;
731
- }
732
-
733
- // Recursively assign parent to children
734
- component.querySelectorAll('*').forEach((child) => {
735
- if (child.tagName.startsWith('SLICE-')) {
736
- if (!child.parentComponent) {
737
- child.parentComponent = component;
738
- }
739
- this.registerComponentsRecursively(child, component);
740
- }
741
- });
742
- }
743
-
744
- /**
745
- * Get a registered component by sliceId.
746
- * @param {string} sliceId
747
- * @returns {HTMLElement|undefined}
748
- */
749
- getComponent(sliceId) {
750
- return this.activeComponents.get(sliceId);
751
- }
752
-
753
- loadTemplateToComponent(component) {
754
- const className = component.constructor.name;
755
- const template = this.templates.get(className);
756
-
757
- if (!template) {
758
- slice.logger.logError(`Template not found for component: ${className}`);
759
- return;
760
- }
761
-
762
- component.innerHTML = template.innerHTML;
763
- return component;
764
- }
765
-
766
- getComponentCategory(componentSliceId) {
767
- return this.componentCategories.get(componentSliceId);
768
- }
769
-
770
- /**
771
- * Fetch component resources (html, css, styles, theme).
772
- * @param {string} componentName
773
- * @param {'html'|'css'|'theme'|'styles'} resourceType
774
- * @param {string} [componentCategory]
775
- * @param {string} [customPath]
776
- * @returns {Promise<string>}
777
- */
778
- async fetchText(componentName, resourceType, componentCategory, customPath) {
779
- try {
780
- const baseUrl = window.location.origin;
781
- let path;
782
-
783
- if (!componentCategory) {
784
- componentCategory = this.componentCategories.get(componentName);
785
- }
786
-
787
- let isVisual = resourceType === 'html' || resourceType === 'css';
788
-
789
- if (isVisual) {
790
- if (slice.paths.components[componentCategory]) {
791
- path = `${baseUrl}${slice.paths.components[componentCategory].path}/${componentName}`;
792
- resourceType === 'html' ? (path += `/${componentName}.html`) : (path += `/${componentName}.css`);
793
- } else {
794
- if (componentCategory === 'Structural') {
795
- path = `${baseUrl}/Slice/Components/Structural/${componentName}`;
796
- resourceType === 'html' ? (path += `/${componentName}.html`) : (path += `/${componentName}.css`);
797
- } else {
798
- throw new Error(`Component category '${componentCategory}' not found in paths configuration`);
799
- }
800
- }
801
- }
802
-
803
- if (resourceType === 'theme') {
804
- path = `${baseUrl}${slice.paths.themes}/${componentName}.css`;
805
- }
806
-
807
- if (resourceType === 'styles') {
808
- path = `${baseUrl}${slice.paths.styles}/${componentName}.css`;
809
- }
810
-
811
- if (customPath) {
812
- path = customPath;
813
- }
814
-
815
- slice.logger.logInfo('Controller', `Fetching ${resourceType} from: ${path}`);
816
-
817
- const response = await fetch(path);
818
-
819
- if (!response.ok) {
820
- throw new Error(`Failed to fetch ${path}: ${response.statusText}`);
821
- }
822
-
823
- const content = await response.text();
824
- slice.logger.logInfo('Controller', `Successfully fetched ${resourceType} for ${componentName}`);
825
- return content;
826
- } catch (error) {
827
- slice.logger.logError('Controller', `Error fetching ${resourceType} for component ${componentName}:`, error);
828
- throw error;
829
- }
830
- }
831
-
832
- /**
833
- * Apply props to a component using static defaults and setters.
834
- * @param {HTMLElement} component
835
- * @param {Object} props
836
- * @returns {void}
837
- */
838
- setComponentProps(component, props) {
839
- const ComponentClass = component.constructor;
840
- const componentName = ComponentClass.name;
841
-
842
- // Aplicar defaults si tiene static props
843
- if (ComponentClass.props) {
844
- this.applyDefaultProps(component, ComponentClass.props, props);
845
- }
846
-
847
- // Validar solo en desarrollo
848
- if (ComponentClass.props && !slice.isProduction()) {
849
- this.validatePropsInDevelopment(ComponentClass, props, componentName);
850
- }
851
-
852
- // Aplicar props
853
- for (const prop in props) {
854
- component[`_${prop}`] = null;
855
- component[prop] = props[prop];
856
- }
857
- }
858
-
859
- getComponentPropsForDebugger(component) {
860
- const ComponentClass = component.constructor;
861
-
862
- if (ComponentClass.props) {
863
- return {
864
- availableProps: Object.keys(ComponentClass.props),
865
- propsConfig: ComponentClass.props,
866
- usedProps: this.extractUsedProps(component, ComponentClass.props),
867
- };
868
- } else {
869
- return {
870
- availableProps: this.extractUsedProps(component),
871
- propsConfig: null,
872
- usedProps: this.extractUsedProps(component),
873
- };
874
- }
875
- }
876
-
877
- applyDefaultProps(component, staticProps, providedProps) {
878
- Object.entries(staticProps).forEach(([prop, config]) => {
879
- if (config.default !== undefined && !(prop in (providedProps || {}))) {
880
- component[`_${prop}`] = null;
881
- component[prop] = config.default;
882
- }
883
- });
884
- }
885
-
3
+
4
+ export default class Controller {
5
+ constructor() {
6
+ this.componentCategories = new Map(Object.entries(components));
7
+ this.templates = new Map();
8
+ this.classes = new Map();
9
+ this.requestedStyles = new Set(); // ✅ CRÍTICO: Para tracking de CSS cargados
10
+ this.activeComponents = new Map();
11
+
12
+ // 🚀 OPTIMIZACIÓN: Índice inverso para búsqueda rápida de hijos
13
+ // parentSliceId → Set<childSliceId>
14
+ this.childrenIndex = new Map();
15
+
16
+ // 📦 Bundle system
17
+ this.loadedBundles = new Set();
18
+ this.bundleConfig = null;
19
+ this.criticalBundleLoaded = false;
20
+ this.bundleImportPromises = new Map();
21
+ this.bundleLoadPromises = new Map();
22
+
23
+ this.idCounter = 0;
24
+ }
25
+
26
+ /**
27
+ * 📦 Initializes bundle system (called automatically when config is loaded)
28
+ */
29
+ initializeBundles(config = null) {
30
+ if (config) {
31
+ this.bundleConfig = config;
32
+
33
+ // Register critical bundle components if available
34
+ if (config.bundles?.critical) {
35
+ // Critical bundle will be loaded explicitly
36
+ }
37
+ this.criticalBundleLoaded = false;
38
+ } else {
39
+ // No bundles available, will use individual component loading
40
+ this.bundleConfig = null;
41
+ this.criticalBundleLoaded = false;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Import a bundle URL once per page session.
47
+ * Reuses the same Promise for concurrent callers.
48
+ * @param {string} bundlePath
49
+ * @returns {Promise<any>}
50
+ */
51
+ importBundleOnce(bundlePath) {
52
+ if (!bundlePath) {
53
+ return Promise.reject(new Error('Bundle path is required'));
54
+ }
55
+
56
+ if (this.bundleImportPromises.has(bundlePath)) {
57
+ return this.bundleImportPromises.get(bundlePath);
58
+ }
59
+
60
+ const importPromise = import(bundlePath).catch((error) => {
61
+ this.bundleImportPromises.delete(bundlePath);
62
+ throw error;
63
+ });
64
+
65
+ this.bundleImportPromises.set(bundlePath, importPromise);
66
+ return importPromise;
67
+ }
68
+
69
+ buildBundleImportPath(bundleInfo) {
70
+ if (!bundleInfo || typeof bundleInfo.file !== 'string' || bundleInfo.file.length === 0) {
71
+ throw new Error('Bundle file is required');
72
+ }
73
+
74
+ const basePath = `/bundles/${bundleInfo.file}`;
75
+ const bundleHash = typeof bundleInfo.hash === 'string' ? bundleInfo.hash.trim() : '';
76
+ if (!bundleHash) {
77
+ return basePath;
78
+ }
79
+
80
+ return `${basePath}?v=${encodeURIComponent(bundleHash)}`;
81
+ }
82
+
83
+ /**
84
+ * Validate Bundling V2 module contract.
85
+ * Requires named exports: SLICE_BUNDLE_META and registerAll.
86
+ * @param {any} bundleModule
87
+ * @param {string} [bundleName]
88
+ * @returns {{metadata: object, registerAll: Function}}
89
+ */
90
+ async validateBundleModule(bundleModule, bundleName = 'unknown') {
91
+ const metadata = bundleModule?.SLICE_BUNDLE_META;
92
+ const registerAll = bundleModule?.registerAll;
93
+
94
+ if (!metadata || typeof metadata !== 'object' || typeof registerAll !== 'function') {
95
+ throw new Error(
96
+ `Bundle "${bundleName}" missing Bundling V2 exports contract: requires SLICE_BUNDLE_META and registerAll`
97
+ );
98
+ }
99
+
100
+ return { metadata, registerAll };
101
+ }
102
+
103
+ /**
104
+ * 📦 Loads a bundle by name or category
105
+ */
106
+ async loadBundle(bundleName) {
107
+ const resolvedBundleName = this.resolveBundleName(bundleName);
108
+ if (this.loadedBundles.has(resolvedBundleName)) {
109
+ return; // Already loaded
110
+ }
111
+
112
+ return this.loadBundleWithDependencies(resolvedBundleName, new Set());
113
+ }
114
+
115
+ async loadBundleWithDependencies(bundleName, loadingStack = new Set()) {
116
+ const resolvedBundleName = this.resolveBundleName(bundleName);
117
+
118
+ if (this.loadedBundles.has(resolvedBundleName)) {
119
+ return;
120
+ }
121
+
122
+ if (loadingStack.has(resolvedBundleName)) {
123
+ throw new Error(`Circular bundle dependency detected: ${Array.from(loadingStack).join(' -> ')} -> ${resolvedBundleName}`);
124
+ }
125
+
126
+ if (this.bundleLoadPromises.has(resolvedBundleName)) {
127
+ return this.bundleLoadPromises.get(resolvedBundleName);
128
+ }
129
+
130
+ const loadPromise = (async () => {
131
+ loadingStack.add(resolvedBundleName);
132
+ try {
133
+ const bundleInfo = this.getBundleInfo(resolvedBundleName);
134
+ if (!bundleInfo) {
135
+ console.warn(`Bundle ${resolvedBundleName} not found in configuration`);
136
+ return;
137
+ }
138
+
139
+ const dependencies = this.getBundleDependencies(bundleInfo);
140
+ for (const dependencyName of dependencies) {
141
+ await this.loadBundleWithDependencies(dependencyName, loadingStack);
142
+ }
143
+
144
+ const bundlePath = this.buildBundleImportPath(bundleInfo);
145
+ const bundleModule = await this.importBundleOnce(bundlePath);
146
+ const { metadata, registerAll } = await this.validateBundleModule(bundleModule, resolvedBundleName);
147
+
148
+ const registerResult = await registerAll(this, slice.stylesManager);
149
+ this.registerVendorSharedDependencies(bundleModule, metadata, resolvedBundleName, registerResult);
150
+
151
+ this.loadedBundles.add(resolvedBundleName);
152
+ const loadedBundleKey = metadata.bundleKey;
153
+ if (loadedBundleKey && loadedBundleKey !== resolvedBundleName) {
154
+ this.loadedBundles.add(loadedBundleKey);
155
+ }
156
+
157
+ if (metadata.type === 'critical' || resolvedBundleName === 'critical') {
158
+ this.criticalBundleLoaded = true;
159
+ }
160
+ } finally {
161
+ loadingStack.delete(resolvedBundleName);
162
+ }
163
+ })();
164
+
165
+ this.bundleLoadPromises.set(resolvedBundleName, loadPromise);
166
+ try {
167
+ return await loadPromise;
168
+ } finally {
169
+ this.bundleLoadPromises.delete(resolvedBundleName);
170
+ }
171
+ }
172
+
173
+ resolveBundleName(bundleName) {
174
+ if (typeof bundleName !== 'string' || bundleName.length === 0) {
175
+ return bundleName;
176
+ }
177
+
178
+ if (bundleName.toLowerCase() === 'critical') {
179
+ return 'critical';
180
+ }
181
+
182
+ if (this.isVendorSharedAlias(bundleName) && this.getVendorSharedBundleInfo()) {
183
+ return 'vendor-shared';
184
+ }
185
+
186
+ const routeBundleName = this.findBundleNameByAlias(this.bundleConfig?.bundles?.routes, bundleName);
187
+ if (routeBundleName) {
188
+ return routeBundleName;
189
+ }
190
+
191
+ const sharedBundleName = this.findBundleNameByAlias(this.bundleConfig?.bundles?.shared, bundleName);
192
+ if (sharedBundleName) {
193
+ return sharedBundleName;
194
+ }
195
+
196
+ return bundleName;
197
+ }
198
+
199
+ findBundleNameByAlias(bundleRegistry, bundleName) {
200
+ if (!bundleRegistry || typeof bundleRegistry !== 'object') {
201
+ return null;
202
+ }
203
+
204
+ if (bundleRegistry[bundleName]) {
205
+ return bundleName;
206
+ }
207
+
208
+ const normalizedName = bundleName?.toLowerCase();
209
+ if (!normalizedName) {
210
+ return null;
211
+ }
212
+
213
+ return Object.keys(bundleRegistry).find((key) => key.toLowerCase() === normalizedName) || null;
214
+ }
215
+
216
+ getBundleDependencies(bundleInfo) {
217
+ if (!bundleInfo || !Array.isArray(bundleInfo.dependencies)) {
218
+ return [];
219
+ }
220
+
221
+ return bundleInfo.dependencies.filter((dependency) => typeof dependency === 'string' && dependency.length > 0);
222
+ }
223
+
224
+ findBundleEntryByName(bundleRegistry, bundleName) {
225
+ if (!bundleRegistry || typeof bundleRegistry !== 'object') {
226
+ return null;
227
+ }
228
+
229
+ if (bundleRegistry[bundleName]) {
230
+ return bundleRegistry[bundleName];
231
+ }
232
+
233
+ const normalizedName = bundleName?.toLowerCase();
234
+ if (!normalizedName) {
235
+ return null;
236
+ }
237
+
238
+ const matchedKey = Object.keys(bundleRegistry).find((key) => key.toLowerCase() === normalizedName);
239
+ return matchedKey ? bundleRegistry[matchedKey] : null;
240
+ }
241
+
242
+ getBundleInfo(bundleName) {
243
+ if (bundleName === 'critical') {
244
+ return this.bundleConfig?.bundles?.critical || null;
245
+ }
246
+
247
+ if (this.isVendorSharedAlias(bundleName)) {
248
+ return this.getVendorSharedBundleInfo();
249
+ }
250
+
251
+ return (
252
+ this.findBundleEntryByName(this.bundleConfig?.bundles?.routes, bundleName)
253
+ || this.findBundleEntryByName(this.bundleConfig?.bundles?.shared, bundleName)
254
+ );
255
+ }
256
+
257
+ getVendorSharedBundleInfo() {
258
+ if (this.bundleConfig?.bundles?.vendorShared && typeof this.bundleConfig.bundles.vendorShared === 'object') {
259
+ return this.bundleConfig.bundles.vendorShared;
260
+ }
261
+
262
+ return this.findBundleEntryByName(this.bundleConfig?.bundles?.shared, 'vendor-shared');
263
+ }
264
+
265
+ isVendorSharedAlias(bundleName) {
266
+ if (typeof bundleName !== 'string') {
267
+ return false;
268
+ }
269
+
270
+ const normalized = bundleName.toLowerCase();
271
+ return normalized === 'vendor-shared' || normalized === 'vendorshared';
272
+ }
273
+
274
+ registerVendorSharedDependencies(bundleModule, metadata, bundleName, registerResult) {
275
+ const isVendorShared = this.isVendorSharedBundleName(metadata?.bundleKey)
276
+ || this.isVendorSharedBundleName(bundleName)
277
+ || metadata?.registerVendorSharedDependencies === true;
278
+
279
+ if (!isVendorShared) {
280
+ return;
281
+ }
282
+
283
+ let sharedDeps = bundleModule?.SLICE_SHARED_DEPS;
284
+ if (!sharedDeps && registerResult && typeof registerResult === 'object') {
285
+ sharedDeps = registerResult.SLICE_SHARED_DEPS
286
+ || registerResult.SLICE_BUNDLE_DEPENDENCIES
287
+ || registerResult;
288
+ }
289
+
290
+ if (!sharedDeps || typeof sharedDeps !== 'object') {
291
+ return;
292
+ }
293
+
294
+ if (!window.__SLICE_SHARED_DEPS__ || typeof window.__SLICE_SHARED_DEPS__ !== 'object') {
295
+ window.__SLICE_SHARED_DEPS__ = {};
296
+ }
297
+
298
+ Object.assign(window.__SLICE_SHARED_DEPS__, sharedDeps);
299
+ }
300
+
301
+ isVendorSharedBundleName(bundleName) {
302
+ return typeof bundleName === 'string' && bundleName.toLowerCase() === 'vendor-shared';
303
+ }
304
+
305
+ /**
306
+ * 📦 Registers a bundle's components (called automatically by bundle files)
307
+ */
308
+ registerBundleLegacy(bundle) {
309
+ const { components, metadata } = bundle;
310
+
311
+ console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
312
+
313
+ // Phase 1: Register templates and CSS for all components first
314
+ for (const [componentName, componentData] of Object.entries(components)) {
315
+ try {
316
+ // Register HTML template
317
+ if (componentData.html !== undefined && !this.templates.has(componentName)) {
318
+ const template = document.createElement('template');
319
+ template.innerHTML = componentData.html || '';
320
+ this.templates.set(componentName, template);
321
+ }
322
+
323
+ // Register CSS styles
324
+ if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
325
+ // Use the existing stylesManager to register component styles
326
+ if (window.slice && window.slice.stylesManager) {
327
+ window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
328
+ this.requestedStyles.add(componentName);
329
+ }
330
+ }
331
+ } catch (error) {
332
+ console.warn(`❌ Failed to register assets for ${componentName}:`, error);
333
+ }
334
+ }
335
+
336
+ // Phase 2: Evaluate all external file dependencies
337
+ const processedDeps = new Set();
338
+ for (const [componentName, componentData] of Object.entries(components)) {
339
+ if (componentData.dependencies) {
340
+ for (const [depName, depContent] of Object.entries(componentData.dependencies)) {
341
+ if (!processedDeps.has(depName)) {
342
+ try {
343
+ // Convert ES6 exports to global assignments
344
+ let processedContent = depContent
345
+ .replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
346
+ .replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
347
+ .replace(/export\s+var\s+(\w+)\s*=/g, 'window.$1 =')
348
+ .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
349
+ .replace(/export\s+default\s+/g, 'window.defaultExport =')
350
+ .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
351
+ return exports
352
+ .split(',')
353
+ .map((exp) => {
354
+ const cleanExp = exp.trim();
355
+ const varName = cleanExp.split(' as ')[0].trim();
356
+ return `window.${varName} = ${varName};`;
357
+ })
358
+ .join('\n');
359
+ })
360
+ // Remove any remaining export keywords
361
+ .replace(/^\s*export\s+/gm, '');
362
+
363
+ // Evaluate the dependency
364
+ try {
365
+ new Function('slice', 'customElements', 'window', 'document', processedContent)(
366
+ window.slice,
367
+ window.customElements,
368
+ window,
369
+ window.document
370
+ );
371
+ } catch (evalError) {
372
+ console.warn(`❌ Failed to evaluate processed dependency ${depName}:`, evalError);
373
+ console.warn('Processed content preview:', processedContent.substring(0, 200));
374
+ // Try evaluating the original content as fallback
375
+ try {
376
+ new Function('slice', 'customElements', 'window', 'document', depContent)(
377
+ window.slice,
378
+ window.customElements,
379
+ window,
380
+ window.document
381
+ );
382
+ console.log(`✅ Fallback evaluation succeeded for ${depName}`);
383
+ } catch (fallbackError) {
384
+ console.warn(`❌ Fallback evaluation also failed for ${depName}:`, fallbackError);
385
+ }
386
+ }
387
+
388
+ processedDeps.add(depName);
389
+ console.log(`📄 Dependency loaded: ${depName}`);
390
+ } catch (depError) {
391
+ console.warn(`⚠️ Failed to load dependency ${depName} for ${componentName}:`, depError);
392
+ }
393
+ }
394
+ }
395
+ }
396
+ }
397
+
398
+ // Phase 3: Evaluate all component classes (now that dependencies are available)
399
+ for (const [componentName, componentData] of Object.entries(components)) {
400
+ // For JavaScript classes, we need to evaluate the code
401
+ if (componentData.js && !this.classes.has(componentName)) {
402
+ try {
403
+ // Create evaluation context with dependencies
404
+ let evalCode = componentData.js;
405
+
406
+ // Prepend dependencies to make them available
407
+ if (componentData.dependencies) {
408
+ const depCode = Object.entries(componentData.dependencies)
409
+ .map(([depName, depContent]) => {
410
+ // Convert ES6 exports to global assignments
411
+ return depContent
412
+ .replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
413
+ .replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
414
+ .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
415
+ .replace(/export\s+default\s+/g, 'window.defaultExport =')
416
+ .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
417
+ return exports
418
+ .split(',')
419
+ .map((exp) => {
420
+ const cleanExp = exp.trim();
421
+ return `window.${cleanExp} = ${cleanExp};`;
422
+ })
423
+ .join('\n');
424
+ });
425
+ })
426
+ .join('\n\n');
427
+
428
+ evalCode = depCode + '\n\n' + evalCode;
429
+ }
430
+
431
+ // Evaluate the complete code
432
+ const componentClass = new Function(
433
+ 'slice',
434
+ 'customElements',
435
+ 'window',
436
+ 'document',
437
+ `
438
+ "use strict";
439
+ ${evalCode}
440
+ return ${componentName};
441
+ `
442
+ )(window.slice, window.customElements, window, window.document);
443
+
444
+ if (componentClass) {
445
+ this.classes.set(componentName, componentClass);
446
+ console.log(`📝 Class registered for: ${componentName}`);
447
+ }
448
+ } catch (error) {
449
+ console.warn(`❌ Failed to evaluate class for ${componentName}:`, error);
450
+ console.warn('Code that failed:', componentData.js.substring(0, 200) + '...');
451
+ }
452
+ }
453
+ }
454
+ }
455
+
456
+ /**
457
+ * 📦 New bundle registration method (simplified and robust)
458
+ */
459
+ registerBundle(bundle) {
460
+ const validation = this.validateBundle(bundle);
461
+ if (!validation.isValid) {
462
+ console.warn(`❌ Bundle validation failed: ${validation.error}`);
463
+ return Promise.resolve(false);
464
+ }
465
+
466
+ // Set tracking flags synchronously before any async work, so callers that
467
+ // await import() see the flags set immediately when the Promise resolves.
468
+ const { components, metadata } = bundle;
469
+ const bundleKey = metadata?.bundleKey;
470
+ if (bundleKey) {
471
+ this.loadedBundles.add(bundleKey);
472
+ if (metadata?.type === 'critical') {
473
+ this.criticalBundleLoaded = true;
474
+ }
475
+ }
476
+
477
+ console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
478
+
479
+ const entries = Object.entries(components);
480
+ const chunkSize = 50;
481
+ let index = 0;
482
+
483
+ return new Promise((resolve) => {
484
+ const processChunk = () => {
485
+ const sliceEntries = entries.slice(index, index + chunkSize);
486
+
487
+ for (const [componentName, componentData] of sliceEntries) {
488
+ try {
489
+ if (componentData.html !== undefined && !this.templates.has(componentName)) {
490
+ const template = document.createElement('template');
491
+ template.innerHTML = componentData.html || '';
492
+ this.templates.set(componentName, template);
493
+ }
494
+
495
+ if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
496
+ if (window.slice && window.slice.stylesManager) {
497
+ window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
498
+ this.requestedStyles.add(componentName);
499
+ }
500
+ }
501
+
502
+ if (componentData.class && !this.classes.has(componentName)) {
503
+ const registeredName = componentData.isFramework
504
+ ? `Framework/Structural/${componentName}`
505
+ : componentName;
506
+ this.classes.set(registeredName, componentData.class);
507
+ if (componentName === 'Loading') {
508
+ console.log('🔎 Bundle class registered: Loading', {
509
+ registeredName,
510
+ type: typeof componentData.class,
511
+ isFunction: typeof componentData.class === 'function'
512
+ });
513
+ }
514
+ if (componentName === 'InputSearchDocs' || componentName === 'MainMenu') {
515
+ console.log(`🔎 Bundle class registered: ${componentName}`, {
516
+ registeredName,
517
+ type: typeof componentData.class,
518
+ isFunction: typeof componentData.class === 'function'
519
+ });
520
+ }
521
+ }
522
+ } catch (error) {
523
+ console.warn(`❌ Failed to register component ${componentName}:`, error);
524
+ }
525
+ }
526
+
527
+ index += chunkSize;
528
+ if (index < entries.length) {
529
+ if (typeof requestIdleCallback === 'function') {
530
+ requestIdleCallback(processChunk);
531
+ } else {
532
+ setTimeout(processChunk, 0);
533
+ }
534
+ return;
535
+ }
536
+
537
+ console.log(`✅ Bundle registration completed: ${metadata.componentCount} components processed`);
538
+ resolve(true);
539
+ };
540
+
541
+ processChunk();
542
+ });
543
+ }
544
+
545
+ /**
546
+ * Validates bundle structure before registering.
547
+ * @param {object} bundle
548
+ * @returns {{isValid: boolean, error?: string}}
549
+ */
550
+ validateBundle(bundle) {
551
+ if (!bundle || typeof bundle !== 'object') {
552
+ return { isValid: false, error: 'Bundle payload is invalid' };
553
+ }
554
+
555
+ if (!bundle.metadata || typeof bundle.metadata !== 'object') {
556
+ return { isValid: false, error: 'Bundle metadata missing' };
557
+ }
558
+
559
+ if (!bundle.components || typeof bundle.components !== 'object') {
560
+ return { isValid: false, error: 'Bundle components missing' };
561
+ }
562
+
563
+ if (typeof bundle.metadata.componentCount !== 'number') {
564
+ return { isValid: false, error: 'Bundle metadata missing componentCount' };
565
+ }
566
+
567
+ if (bundle.metadata.componentCount !== Object.keys(bundle.components).length) {
568
+ return { isValid: false, error: 'Bundle component count mismatch' };
569
+ }
570
+
571
+ const maxComponents = 5000;
572
+ if (bundle.metadata.componentCount > maxComponents) {
573
+ return { isValid: false, error: 'Bundle component count exceeds limit' };
574
+ }
575
+
576
+ return { isValid: true };
577
+ }
578
+
579
+ /**
580
+ * 📦 Determines which bundle to load for a component
581
+ */
582
+ getBundleForComponent(componentName) {
583
+ if (!this.bundleConfig?.bundles) return null;
584
+
585
+ // Check if component is in critical bundle
586
+ if (this.bundleConfig.bundles.critical?.components?.includes(componentName)) {
587
+ return 'critical';
588
+ }
589
+
590
+ // Find component in route bundles
591
+ if (this.bundleConfig.bundles.routes) {
592
+ for (const [bundleName, bundleInfo] of Object.entries(this.bundleConfig.bundles.routes)) {
593
+ if (bundleInfo.components?.includes(componentName)) {
594
+ return bundleName;
595
+ }
596
+ }
597
+ }
598
+
599
+ return null;
600
+ }
601
+
602
+ /**
603
+ * 📦 Checks if a component is available from loaded bundles
604
+ */
605
+ isComponentFromBundle(componentName) {
606
+ if (!this.bundleConfig?.bundles) return false;
607
+
608
+ // Check critical bundle
609
+ if (this.bundleConfig.bundles.critical?.components?.includes(componentName)) {
610
+ return this.criticalBundleLoaded;
611
+ }
612
+
613
+ // Check route bundles
614
+ if (this.bundleConfig.bundles.routes) {
615
+ for (const [bundleName, bundleInfo] of Object.entries(this.bundleConfig.bundles.routes)) {
616
+ if (bundleInfo.components?.includes(componentName)) {
617
+ return this.loadedBundles.has(bundleName);
618
+ }
619
+ }
620
+ }
621
+
622
+ return false;
623
+ }
624
+
625
+ /**
626
+ * 📦 Gets component data from loaded bundles
627
+ */
628
+ getComponentFromBundle(componentName) {
629
+ if (!this.bundleConfig?.bundles) return null;
630
+
631
+ // Find component in any loaded bundle
632
+ const allBundles = [
633
+ { name: 'critical', data: this.bundleConfig.bundles.critical },
634
+ ...Object.entries(this.bundleConfig.bundles.routes || {}).map(([name, data]) => ({ name, data })),
635
+ ];
636
+
637
+ for (const { name: bundleName, data: bundleData } of allBundles) {
638
+ if (bundleData?.components?.includes(componentName) && this.loadedBundles.has(bundleName)) {
639
+ // Find the bundle file and extract component data
640
+ // This is a simplified version - in practice you'd need to access the loaded bundle data
641
+ return { bundleName, componentName };
642
+ }
643
+ }
644
+
645
+ return null;
646
+ }
647
+
648
+ logActiveComponents() {
649
+ this.activeComponents.forEach((component) => {
650
+ let parent = component.parentComponent;
651
+ let parentName = parent ? parent.constructor.name : null;
652
+ });
653
+ }
654
+
655
+ getTopParentsLinkedToActiveComponents() {
656
+ let topParentsLinkedToActiveComponents = new Map();
657
+ this.activeComponents.forEach((component) => {
658
+ let parent = component.parentComponent;
659
+ while (parent && parent.parentComponent) {
660
+ parent = parent.parentComponent;
661
+ }
662
+ if (!topParentsLinkedToActiveComponents.has(parent)) {
663
+ topParentsLinkedToActiveComponents.set(parent, []);
664
+ }
665
+ topParentsLinkedToActiveComponents.get(parent).push(component);
666
+ });
667
+ return topParentsLinkedToActiveComponents;
668
+ }
669
+
670
+ verifyComponentIds(component) {
671
+ const htmlId = component.id;
672
+
673
+ if (htmlId && htmlId.trim() !== '') {
674
+ if (this.activeComponents.has(htmlId)) {
675
+ slice.logger.logError(
676
+ 'Controller',
677
+ `A component with the same html id attribute is already registered: ${htmlId}`
678
+ );
679
+ return false;
680
+ }
681
+ }
682
+
683
+ let sliceId = component.sliceId;
684
+
685
+ if (sliceId && sliceId.trim() !== '') {
686
+ if (this.activeComponents.has(sliceId)) {
687
+ slice.logger.logError(
688
+ 'Controller',
689
+ `A component with the same slice id attribute is already registered: ${sliceId}`
690
+ );
691
+ return false;
692
+ }
693
+ } else {
694
+ sliceId = `${component.constructor.name[0].toLowerCase()}${component.constructor.name.slice(1)}-${this.idCounter}`;
695
+ component.sliceId = sliceId;
696
+ this.idCounter++;
697
+ }
698
+
699
+ component.sliceId = sliceId;
700
+ return true;
701
+ }
702
+
703
+ /**
704
+ * Registra un componente y actualiza el índice de relaciones padre-hijo
705
+ * 🚀 OPTIMIZADO: Ahora mantiene childrenIndex y precalcula profundidad
706
+ */
707
+ registerComponent(component, parent = null) {
708
+ component.parentComponent = parent;
709
+
710
+ // 🚀 OPTIMIZACIÓN: Precalcular y guardar profundidad
711
+ component._depth = parent ? (parent._depth || 0) + 1 : 0;
712
+
713
+ // Registrar en activeComponents
714
+ this.activeComponents.set(component.sliceId, component);
715
+
716
+ // 🚀 OPTIMIZACIÓN: Actualizar índice inverso de hijos
717
+ if (parent) {
718
+ if (!this.childrenIndex.has(parent.sliceId)) {
719
+ this.childrenIndex.set(parent.sliceId, new Set());
720
+ }
721
+ this.childrenIndex.get(parent.sliceId).add(component.sliceId);
722
+ }
723
+
724
+ return true;
725
+ }
726
+
727
+ registerComponentsRecursively(component, parent = null) {
728
+ // Assign parent if not already set
729
+ if (!component.parentComponent) {
730
+ component.parentComponent = parent;
731
+ }
732
+
733
+ // Recursively assign parent to children
734
+ component.querySelectorAll('*').forEach((child) => {
735
+ if (child.tagName.startsWith('SLICE-')) {
736
+ if (!child.parentComponent) {
737
+ child.parentComponent = component;
738
+ }
739
+ this.registerComponentsRecursively(child, component);
740
+ }
741
+ });
742
+ }
743
+
744
+ /**
745
+ * Get a registered component by sliceId.
746
+ * @param {string} sliceId
747
+ * @returns {HTMLElement|undefined}
748
+ */
749
+ getComponent(sliceId) {
750
+ return this.activeComponents.get(sliceId);
751
+ }
752
+
753
+ loadTemplateToComponent(component) {
754
+ const className = component.constructor.name;
755
+ const template = this.templates.get(className);
756
+
757
+ if (!template) {
758
+ slice.logger.logError(`Template not found for component: ${className}`);
759
+ return;
760
+ }
761
+
762
+ component.innerHTML = template.innerHTML;
763
+ return component;
764
+ }
765
+
766
+ getComponentCategory(componentSliceId) {
767
+ return this.componentCategories.get(componentSliceId);
768
+ }
769
+
770
+ /**
771
+ * Fetch component resources (html, css, styles, theme).
772
+ * @param {string} componentName
773
+ * @param {'html'|'css'|'theme'|'styles'} resourceType
774
+ * @param {string} [componentCategory]
775
+ * @param {string} [customPath]
776
+ * @returns {Promise<string>}
777
+ */
778
+ async fetchText(componentName, resourceType, componentCategory, customPath) {
779
+ try {
780
+ const baseUrl = window.location.origin;
781
+ let path;
782
+
783
+ if (!componentCategory) {
784
+ componentCategory = this.componentCategories.get(componentName);
785
+ }
786
+
787
+ let isVisual = resourceType === 'html' || resourceType === 'css';
788
+
789
+ if (isVisual) {
790
+ if (slice.paths.components[componentCategory]) {
791
+ path = `${baseUrl}${slice.paths.components[componentCategory].path}/${componentName}`;
792
+ resourceType === 'html' ? (path += `/${componentName}.html`) : (path += `/${componentName}.css`);
793
+ } else {
794
+ if (componentCategory === 'Structural') {
795
+ path = `${baseUrl}/Slice/Components/Structural/${componentName}`;
796
+ resourceType === 'html' ? (path += `/${componentName}.html`) : (path += `/${componentName}.css`);
797
+ } else {
798
+ throw new Error(`Component category '${componentCategory}' not found in paths configuration`);
799
+ }
800
+ }
801
+ }
802
+
803
+ if (resourceType === 'theme') {
804
+ path = `${baseUrl}${slice.paths.themes}/${componentName}.css`;
805
+ }
806
+
807
+ if (resourceType === 'styles') {
808
+ path = `${baseUrl}${slice.paths.styles}/${componentName}.css`;
809
+ }
810
+
811
+ if (customPath) {
812
+ path = customPath;
813
+ }
814
+
815
+ slice.logger.logInfo('Controller', `Fetching ${resourceType} from: ${path}`);
816
+
817
+ const response = await fetch(path);
818
+
819
+ if (!response.ok) {
820
+ throw new Error(`Failed to fetch ${path}: ${response.statusText}`);
821
+ }
822
+
823
+ const content = await response.text();
824
+ slice.logger.logInfo('Controller', `Successfully fetched ${resourceType} for ${componentName}`);
825
+ return content;
826
+ } catch (error) {
827
+ slice.logger.logError('Controller', `Error fetching ${resourceType} for component ${componentName}:`, error);
828
+ throw error;
829
+ }
830
+ }
831
+
832
+ /**
833
+ * Apply props to a component using static defaults and setters.
834
+ * @param {HTMLElement} component
835
+ * @param {Object} props
836
+ * @returns {void}
837
+ */
838
+ setComponentProps(component, props) {
839
+ const ComponentClass = component.constructor;
840
+ const componentName = ComponentClass.name;
841
+
842
+ // Aplicar defaults si tiene static props
843
+ if (ComponentClass.props) {
844
+ this.applyDefaultProps(component, ComponentClass.props, props);
845
+ }
846
+
847
+ // Validar solo en desarrollo
848
+ if (ComponentClass.props && !slice.isProduction()) {
849
+ this.validatePropsInDevelopment(ComponentClass, props, componentName);
850
+ }
851
+
852
+ // Aplicar props
853
+ for (const prop in props) {
854
+ component[`_${prop}`] = null;
855
+ component[prop] = props[prop];
856
+ }
857
+ }
858
+
859
+ getComponentPropsForDebugger(component) {
860
+ const ComponentClass = component.constructor;
861
+
862
+ if (ComponentClass.props) {
863
+ return {
864
+ availableProps: Object.keys(ComponentClass.props),
865
+ propsConfig: ComponentClass.props,
866
+ usedProps: this.extractUsedProps(component, ComponentClass.props),
867
+ };
868
+ } else {
869
+ return {
870
+ availableProps: this.extractUsedProps(component),
871
+ propsConfig: null,
872
+ usedProps: this.extractUsedProps(component),
873
+ };
874
+ }
875
+ }
876
+
877
+ applyDefaultProps(component, staticProps, providedProps) {
878
+ Object.entries(staticProps).forEach(([prop, config]) => {
879
+ if (config.default !== undefined && !(prop in (providedProps || {}))) {
880
+ component[`_${prop}`] = null;
881
+ component[prop] = config.default;
882
+ }
883
+ });
884
+ }
885
+
886
886
  validatePropsInDevelopment(ComponentClass, providedProps, componentName) {
887
887
  const staticProps = ComponentClass.props;
888
888
  const usedProps = Object.keys(providedProps || {});
889
-
890
- const availableProps = Object.keys(staticProps);
891
- const unknownProps = usedProps.filter((prop) => !availableProps.includes(prop));
892
-
893
- if (unknownProps.length > 0) {
894
- slice.logger.logWarning(
895
- 'PropsValidator',
896
- `${componentName}: Unknown props [${unknownProps.join(', ')}]. Available: [${availableProps.join(', ')}]`
897
- );
898
- }
899
-
900
- const requiredProps = Object.entries(staticProps)
901
- .filter(([_, config]) => config.required)
902
- .map(([prop, _]) => prop);
903
-
889
+
890
+ const availableProps = Object.keys(staticProps);
891
+ const unknownProps = usedProps.filter((prop) => !availableProps.includes(prop));
892
+
893
+ if (unknownProps.length > 0) {
894
+ slice.logger.logWarning(
895
+ 'PropsValidator',
896
+ `${componentName}: Unknown props [${unknownProps.join(', ')}]. Available: [${availableProps.join(', ')}]`
897
+ );
898
+ }
899
+
900
+ const requiredProps = Object.entries(staticProps)
901
+ .filter(([_, config]) => config.required)
902
+ .map(([prop, _]) => prop);
903
+
904
904
  const missingRequired = requiredProps.filter((prop) => !(prop in (providedProps || {})));
905
905
  if (missingRequired.length > 0) {
906
906
  slice.logger.logError(componentName, `Missing required props: [${missingRequired.join(', ')}]`);
@@ -914,236 +914,236 @@ export default class Controller {
914
914
  );
915
915
  });
916
916
  }
917
-
918
- extractUsedProps(component, staticProps = null) {
919
- const usedProps = {};
920
-
921
- if (staticProps) {
922
- Object.keys(staticProps).forEach((prop) => {
923
- if (component[prop] !== undefined) {
924
- usedProps[prop] = component[prop];
925
- }
926
- });
927
- } else {
928
- Object.getOwnPropertyNames(component).forEach((key) => {
929
- if (key.startsWith('_') && key !== '_isActive') {
930
- const propName = key.substring(1);
931
- usedProps[propName] = component[propName];
932
- }
933
- });
934
- }
935
-
936
- return usedProps;
937
- }
938
-
939
- // ============================================================================
940
- // 🚀 MÉTODOS DE DESTRUCCIÓN OPTIMIZADOS
941
- // ============================================================================
942
-
943
- /**
944
- * Encuentra recursivamente todos los hijos de un componente
945
- * 🚀 OPTIMIZADO: O(m) en lugar de O(n*d) - usa childrenIndex
946
- * @param {string} parentSliceId - sliceId del componente padre
947
- * @param {Set<string>} collected - Set de sliceIds ya recolectados
948
- * @returns {Set<string>} Set de todos los sliceIds de componentes hijos
949
- */
950
- findAllChildComponents(parentSliceId, collected = new Set()) {
951
- // 🚀 Buscar directamente en el índice: O(1)
952
- const children = this.childrenIndex.get(parentSliceId);
953
-
954
- if (!children) return collected;
955
-
956
- // 🚀 Iterar solo los hijos directos: O(k) donde k = número de hijos
957
- for (const childSliceId of children) {
958
- collected.add(childSliceId);
959
- // Recursión solo sobre hijos, no todos los componentes
960
- this.findAllChildComponents(childSliceId, collected);
961
- }
962
-
963
- return collected;
964
- }
965
-
966
- /**
967
- * Encuentra recursivamente todos los componentes dentro de un contenedor DOM
968
- * Útil para destroyByContainer cuando no tenemos el sliceId del padre
969
- * @param {HTMLElement} container - Elemento contenedor
970
- * @param {Set<string>} collected - Set de sliceIds ya recolectados
971
- * @returns {Set<string>} Set de todos los sliceIds encontrados
972
- */
973
- findAllNestedComponentsInContainer(container, collected = new Set()) {
974
- // Buscar todos los elementos con slice-id en el contenedor
975
- const sliceComponents = container.querySelectorAll('[slice-id]');
976
-
977
- sliceComponents.forEach((element) => {
978
- const sliceId = element.getAttribute('slice-id') || element.sliceId;
979
- if (sliceId && this.activeComponents.has(sliceId)) {
980
- collected.add(sliceId);
981
- // 🚀 Usar índice para buscar hijos recursivamente
982
- this.findAllChildComponents(sliceId, collected);
983
- }
984
- });
985
-
986
- return collected;
987
- }
988
-
989
- /**
990
- * Destruye uno o múltiples componentes DE FORMA RECURSIVA
991
- * 🚀 OPTIMIZADO: O(m log m) en lugar de O(n*d + m log m)
992
- * @param {HTMLElement|Array<HTMLElement>|string|Array<string>} components
993
- * @returns {number} Cantidad de componentes destruidos (incluyendo hijos)
994
- */
995
- destroyComponent(components) {
996
- const toDestroy = Array.isArray(components) ? components : [components];
997
- const allSliceIdsToDestroy = new Set();
998
-
999
- // PASO 1: Recolectar todos los componentes padres y sus hijos recursivamente
1000
- for (const item of toDestroy) {
1001
- let sliceId = null;
1002
-
1003
- if (typeof item === 'string') {
1004
- if (!this.activeComponents.has(item)) {
1005
- slice.logger.logWarning('Controller', `Component with sliceId "${item}" not found`);
1006
- continue;
1007
- }
1008
- sliceId = item;
1009
- } else if (item && item.sliceId) {
1010
- sliceId = item.sliceId;
1011
- } else {
1012
- slice.logger.logWarning('Controller', `Invalid component or sliceId provided to destroyComponent`);
1013
- continue;
1014
- }
1015
-
1016
- allSliceIdsToDestroy.add(sliceId);
1017
-
1018
- // 🚀 OPTIMIZADO: Usa childrenIndex en lugar de recorrer todos los componentes
1019
- this.findAllChildComponents(sliceId, allSliceIdsToDestroy);
1020
- }
1021
-
1022
- // PASO 2: Ordenar por profundidad (más profundos primero)
1023
- // 🚀 OPTIMIZADO: Usa _depth precalculada en lugar de calcularla cada vez
1024
- const sortedSliceIds = Array.from(allSliceIdsToDestroy).sort((a, b) => {
1025
- const compA = this.activeComponents.get(a);
1026
- const compB = this.activeComponents.get(b);
1027
-
1028
- if (!compA || !compB) return 0;
1029
-
1030
- // 🚀 O(1) en lugar de O(d) - usa profundidad precalculada
1031
- return (compB._depth || 0) - (compA._depth || 0);
1032
- });
1033
-
1034
- let destroyedCount = 0;
1035
-
1036
- // PASO 3: Destruir en orden correcto (hijos antes que padres)
1037
- for (const sliceId of sortedSliceIds) {
1038
- const component = this.activeComponents.get(sliceId);
1039
-
1040
- if (!component) continue;
1041
-
1042
- // Ejecutar hook beforeDestroy si existe
1043
- if (typeof component.beforeDestroy === 'function') {
1044
- try {
1045
- component.beforeDestroy();
1046
- } catch (error) {
1047
- slice.logger.logError('Controller', `Error in beforeDestroy for ${sliceId}`, error);
1048
- }
1049
- }
1050
-
1051
- // Limpiar suscripciones de eventos del componente
1052
- if (slice.events) {
1053
- slice.events.cleanupComponent(sliceId);
1054
- }
1055
-
1056
- // 🚀 Limpiar del índice de hijos
1057
- this.childrenIndex.delete(sliceId);
1058
-
1059
- // Si tiene padre, remover de la lista de hijos del padre
1060
- if (component.parentComponent) {
1061
- const parentChildren = this.childrenIndex.get(component.parentComponent.sliceId);
1062
- if (parentChildren) {
1063
- parentChildren.delete(sliceId);
1064
- // Si el padre no tiene más hijos, eliminar entrada vacía
1065
- if (parentChildren.size === 0) {
1066
- this.childrenIndex.delete(component.parentComponent.sliceId);
1067
- }
1068
- }
1069
- }
1070
-
1071
- // Eliminar del mapa de componentes activos
1072
- this.activeComponents.delete(sliceId);
1073
-
1074
- // Remover del DOM si está conectado
1075
- if (component.isConnected) {
1076
- component.remove();
1077
- }
1078
-
1079
- destroyedCount++;
1080
- }
1081
-
1082
- if (destroyedCount > 0) {
1083
- slice.logger.logInfo('Controller', `Destroyed ${destroyedCount} component(s) recursively`);
1084
- }
1085
-
1086
- return destroyedCount;
1087
- }
1088
-
1089
- /**
1090
- * Destruye todos los componentes Slice dentro de un contenedor (RECURSIVO)
1091
- * 🚀 OPTIMIZADO: Usa el índice inverso para búsqueda rápida
1092
- * @param {HTMLElement} container - Elemento contenedor
1093
- * @returns {number} Cantidad de componentes destruidos
1094
- */
1095
- destroyByContainer(container) {
1096
- if (!container) {
1097
- slice.logger.logWarning('Controller', 'No container provided to destroyByContainer');
1098
- return 0;
1099
- }
1100
-
1101
- // 🚀 Recolectar componentes usando índice optimizado
1102
- const allSliceIds = this.findAllNestedComponentsInContainer(container);
1103
-
1104
- if (allSliceIds.size === 0) {
1105
- return 0;
1106
- }
1107
-
1108
- // Destruir usando el método principal optimizado
1109
- const count = this.destroyComponent(Array.from(allSliceIds));
1110
-
1111
- if (count > 0) {
1112
- slice.logger.logInfo('Controller', `Destroyed ${count} component(s) from container (including nested)`);
1113
- }
1114
-
1115
- return count;
1116
- }
1117
-
1118
- /**
1119
- * Destruye componentes cuyos sliceId coincidan con un patrón (RECURSIVO)
1120
- * 🚀 OPTIMIZADO: Usa destrucción optimizada
1121
- * @param {string|RegExp} pattern - Patrón a buscar
1122
- * @returns {number} Cantidad de componentes destruidos
1123
- */
1124
- destroyByPattern(pattern) {
1125
- const componentsToDestroy = [];
1126
- const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern);
1127
-
1128
- for (const [sliceId, component] of this.activeComponents) {
1129
- if (regex.test(sliceId)) {
1130
- componentsToDestroy.push(component);
1131
- }
1132
- }
1133
-
1134
- if (componentsToDestroy.length === 0) {
1135
- return 0;
1136
- }
1137
-
1138
- const count = this.destroyComponent(componentsToDestroy);
1139
-
1140
- if (count > 0) {
1141
- slice.logger.logInfo(
1142
- 'Controller',
1143
- `Destroyed ${count} component(s) matching pattern: ${pattern} (including nested)`
1144
- );
1145
- }
1146
-
1147
- return count;
1148
- }
1149
- }
917
+
918
+ extractUsedProps(component, staticProps = null) {
919
+ const usedProps = {};
920
+
921
+ if (staticProps) {
922
+ Object.keys(staticProps).forEach((prop) => {
923
+ if (component[prop] !== undefined) {
924
+ usedProps[prop] = component[prop];
925
+ }
926
+ });
927
+ } else {
928
+ Object.getOwnPropertyNames(component).forEach((key) => {
929
+ if (key.startsWith('_') && key !== '_isActive') {
930
+ const propName = key.substring(1);
931
+ usedProps[propName] = component[propName];
932
+ }
933
+ });
934
+ }
935
+
936
+ return usedProps;
937
+ }
938
+
939
+ // ============================================================================
940
+ // 🚀 MÉTODOS DE DESTRUCCIÓN OPTIMIZADOS
941
+ // ============================================================================
942
+
943
+ /**
944
+ * Encuentra recursivamente todos los hijos de un componente
945
+ * 🚀 OPTIMIZADO: O(m) en lugar de O(n*d) - usa childrenIndex
946
+ * @param {string} parentSliceId - sliceId del componente padre
947
+ * @param {Set<string>} collected - Set de sliceIds ya recolectados
948
+ * @returns {Set<string>} Set de todos los sliceIds de componentes hijos
949
+ */
950
+ findAllChildComponents(parentSliceId, collected = new Set()) {
951
+ // 🚀 Buscar directamente en el índice: O(1)
952
+ const children = this.childrenIndex.get(parentSliceId);
953
+
954
+ if (!children) return collected;
955
+
956
+ // 🚀 Iterar solo los hijos directos: O(k) donde k = número de hijos
957
+ for (const childSliceId of children) {
958
+ collected.add(childSliceId);
959
+ // Recursión solo sobre hijos, no todos los componentes
960
+ this.findAllChildComponents(childSliceId, collected);
961
+ }
962
+
963
+ return collected;
964
+ }
965
+
966
+ /**
967
+ * Encuentra recursivamente todos los componentes dentro de un contenedor DOM
968
+ * Útil para destroyByContainer cuando no tenemos el sliceId del padre
969
+ * @param {HTMLElement} container - Elemento contenedor
970
+ * @param {Set<string>} collected - Set de sliceIds ya recolectados
971
+ * @returns {Set<string>} Set de todos los sliceIds encontrados
972
+ */
973
+ findAllNestedComponentsInContainer(container, collected = new Set()) {
974
+ // Buscar todos los elementos con slice-id en el contenedor
975
+ const sliceComponents = container.querySelectorAll('[slice-id]');
976
+
977
+ sliceComponents.forEach((element) => {
978
+ const sliceId = element.getAttribute('slice-id') || element.sliceId;
979
+ if (sliceId && this.activeComponents.has(sliceId)) {
980
+ collected.add(sliceId);
981
+ // 🚀 Usar índice para buscar hijos recursivamente
982
+ this.findAllChildComponents(sliceId, collected);
983
+ }
984
+ });
985
+
986
+ return collected;
987
+ }
988
+
989
+ /**
990
+ * Destruye uno o múltiples componentes DE FORMA RECURSIVA
991
+ * 🚀 OPTIMIZADO: O(m log m) en lugar de O(n*d + m log m)
992
+ * @param {HTMLElement|Array<HTMLElement>|string|Array<string>} components
993
+ * @returns {number} Cantidad de componentes destruidos (incluyendo hijos)
994
+ */
995
+ destroyComponent(components) {
996
+ const toDestroy = Array.isArray(components) ? components : [components];
997
+ const allSliceIdsToDestroy = new Set();
998
+
999
+ // PASO 1: Recolectar todos los componentes padres y sus hijos recursivamente
1000
+ for (const item of toDestroy) {
1001
+ let sliceId = null;
1002
+
1003
+ if (typeof item === 'string') {
1004
+ if (!this.activeComponents.has(item)) {
1005
+ slice.logger.logWarning('Controller', `Component with sliceId "${item}" not found`);
1006
+ continue;
1007
+ }
1008
+ sliceId = item;
1009
+ } else if (item && item.sliceId) {
1010
+ sliceId = item.sliceId;
1011
+ } else {
1012
+ slice.logger.logWarning('Controller', `Invalid component or sliceId provided to destroyComponent`);
1013
+ continue;
1014
+ }
1015
+
1016
+ allSliceIdsToDestroy.add(sliceId);
1017
+
1018
+ // 🚀 OPTIMIZADO: Usa childrenIndex en lugar de recorrer todos los componentes
1019
+ this.findAllChildComponents(sliceId, allSliceIdsToDestroy);
1020
+ }
1021
+
1022
+ // PASO 2: Ordenar por profundidad (más profundos primero)
1023
+ // 🚀 OPTIMIZADO: Usa _depth precalculada en lugar de calcularla cada vez
1024
+ const sortedSliceIds = Array.from(allSliceIdsToDestroy).sort((a, b) => {
1025
+ const compA = this.activeComponents.get(a);
1026
+ const compB = this.activeComponents.get(b);
1027
+
1028
+ if (!compA || !compB) return 0;
1029
+
1030
+ // 🚀 O(1) en lugar de O(d) - usa profundidad precalculada
1031
+ return (compB._depth || 0) - (compA._depth || 0);
1032
+ });
1033
+
1034
+ let destroyedCount = 0;
1035
+
1036
+ // PASO 3: Destruir en orden correcto (hijos antes que padres)
1037
+ for (const sliceId of sortedSliceIds) {
1038
+ const component = this.activeComponents.get(sliceId);
1039
+
1040
+ if (!component) continue;
1041
+
1042
+ // Ejecutar hook beforeDestroy si existe
1043
+ if (typeof component.beforeDestroy === 'function') {
1044
+ try {
1045
+ component.beforeDestroy();
1046
+ } catch (error) {
1047
+ slice.logger.logError('Controller', `Error in beforeDestroy for ${sliceId}`, error);
1048
+ }
1049
+ }
1050
+
1051
+ // Limpiar suscripciones de eventos del componente
1052
+ if (slice.events) {
1053
+ slice.events.cleanupComponent(sliceId);
1054
+ }
1055
+
1056
+ // 🚀 Limpiar del índice de hijos
1057
+ this.childrenIndex.delete(sliceId);
1058
+
1059
+ // Si tiene padre, remover de la lista de hijos del padre
1060
+ if (component.parentComponent) {
1061
+ const parentChildren = this.childrenIndex.get(component.parentComponent.sliceId);
1062
+ if (parentChildren) {
1063
+ parentChildren.delete(sliceId);
1064
+ // Si el padre no tiene más hijos, eliminar entrada vacía
1065
+ if (parentChildren.size === 0) {
1066
+ this.childrenIndex.delete(component.parentComponent.sliceId);
1067
+ }
1068
+ }
1069
+ }
1070
+
1071
+ // Eliminar del mapa de componentes activos
1072
+ this.activeComponents.delete(sliceId);
1073
+
1074
+ // Remover del DOM si está conectado
1075
+ if (component.isConnected) {
1076
+ component.remove();
1077
+ }
1078
+
1079
+ destroyedCount++;
1080
+ }
1081
+
1082
+ if (destroyedCount > 0) {
1083
+ slice.logger.logInfo('Controller', `Destroyed ${destroyedCount} component(s) recursively`);
1084
+ }
1085
+
1086
+ return destroyedCount;
1087
+ }
1088
+
1089
+ /**
1090
+ * Destruye todos los componentes Slice dentro de un contenedor (RECURSIVO)
1091
+ * 🚀 OPTIMIZADO: Usa el índice inverso para búsqueda rápida
1092
+ * @param {HTMLElement} container - Elemento contenedor
1093
+ * @returns {number} Cantidad de componentes destruidos
1094
+ */
1095
+ destroyByContainer(container) {
1096
+ if (!container) {
1097
+ slice.logger.logWarning('Controller', 'No container provided to destroyByContainer');
1098
+ return 0;
1099
+ }
1100
+
1101
+ // 🚀 Recolectar componentes usando índice optimizado
1102
+ const allSliceIds = this.findAllNestedComponentsInContainer(container);
1103
+
1104
+ if (allSliceIds.size === 0) {
1105
+ return 0;
1106
+ }
1107
+
1108
+ // Destruir usando el método principal optimizado
1109
+ const count = this.destroyComponent(Array.from(allSliceIds));
1110
+
1111
+ if (count > 0) {
1112
+ slice.logger.logInfo('Controller', `Destroyed ${count} component(s) from container (including nested)`);
1113
+ }
1114
+
1115
+ return count;
1116
+ }
1117
+
1118
+ /**
1119
+ * Destruye componentes cuyos sliceId coincidan con un patrón (RECURSIVO)
1120
+ * 🚀 OPTIMIZADO: Usa destrucción optimizada
1121
+ * @param {string|RegExp} pattern - Patrón a buscar
1122
+ * @returns {number} Cantidad de componentes destruidos
1123
+ */
1124
+ destroyByPattern(pattern) {
1125
+ const componentsToDestroy = [];
1126
+ const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern);
1127
+
1128
+ for (const [sliceId, component] of this.activeComponents) {
1129
+ if (regex.test(sliceId)) {
1130
+ componentsToDestroy.push(component);
1131
+ }
1132
+ }
1133
+
1134
+ if (componentsToDestroy.length === 0) {
1135
+ return 0;
1136
+ }
1137
+
1138
+ const count = this.destroyComponent(componentsToDestroy);
1139
+
1140
+ if (count > 0) {
1141
+ slice.logger.logInfo(
1142
+ 'Controller',
1143
+ `Destroyed ${count} component(s) matching pattern: ${pattern} (including nested)`
1144
+ );
1145
+ }
1146
+
1147
+ return count;
1148
+ }
1149
+ }