slicejs-web-framework 2.2.13 → 2.3.1

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.
@@ -54,8 +54,9 @@ export default class Controller {
54
54
 
55
55
  if (!bundleInfo && this.bundleConfig?.bundles?.routes) {
56
56
  const normalizedName = bundleName?.toLowerCase();
57
- const matchedKey = Object.keys(this.bundleConfig.bundles.routes)
58
- .find(key => key.toLowerCase() === normalizedName);
57
+ const matchedKey = Object.keys(this.bundleConfig.bundles.routes).find(
58
+ (key) => key.toLowerCase() === normalizedName
59
+ );
59
60
  if (matchedKey) {
60
61
  bundleInfo = this.bundleConfig.bundles.routes[matchedKey];
61
62
  }
@@ -77,7 +78,6 @@ export default class Controller {
77
78
  }
78
79
 
79
80
  this.loadedBundles.add(bundleName);
80
-
81
81
  } catch (error) {
82
82
  console.warn(`Failed to load bundle ${bundleName}:`, error);
83
83
  }
@@ -129,26 +129,37 @@ export default class Controller {
129
129
  .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
130
130
  .replace(/export\s+default\s+/g, 'window.defaultExport =')
131
131
  .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
132
- return exports.split(',').map(exp => {
133
- const cleanExp = exp.trim();
134
- const varName = cleanExp.split(' as ')[0].trim();
135
- return `window.${varName} = ${varName};`;
136
- }).join('\n');
132
+ return exports
133
+ .split(',')
134
+ .map((exp) => {
135
+ const cleanExp = exp.trim();
136
+ const varName = cleanExp.split(' as ')[0].trim();
137
+ return `window.${varName} = ${varName};`;
138
+ })
139
+ .join('\n');
137
140
  })
138
141
  // Remove any remaining export keywords
139
142
  .replace(/^\s*export\s+/gm, '');
140
143
 
141
144
  // Evaluate the dependency
142
145
  try {
143
- new Function('slice', 'customElements', 'window', 'document', processedContent)
144
- (window.slice, window.customElements, window, window.document);
146
+ new Function('slice', 'customElements', 'window', 'document', processedContent)(
147
+ window.slice,
148
+ window.customElements,
149
+ window,
150
+ window.document
151
+ );
145
152
  } catch (evalError) {
146
153
  console.warn(`❌ Failed to evaluate processed dependency ${depName}:`, evalError);
147
154
  console.warn('Processed content preview:', processedContent.substring(0, 200));
148
155
  // Try evaluating the original content as fallback
149
156
  try {
150
- new Function('slice', 'customElements', 'window', 'document', depContent)
151
- (window.slice, window.customElements, window, window.document);
157
+ new Function('slice', 'customElements', 'window', 'document', depContent)(
158
+ window.slice,
159
+ window.customElements,
160
+ window,
161
+ window.document
162
+ );
152
163
  console.log(`✅ Fallback evaluation succeeded for ${depName}`);
153
164
  } catch (fallbackError) {
154
165
  console.warn(`❌ Fallback evaluation also failed for ${depName}:`, fallbackError);
@@ -169,52 +180,59 @@ export default class Controller {
169
180
  for (const [componentName, componentData] of Object.entries(components)) {
170
181
  // For JavaScript classes, we need to evaluate the code
171
182
  if (componentData.js && !this.classes.has(componentName)) {
172
- try {
173
- // Create evaluation context with dependencies
174
- let evalCode = componentData.js;
175
-
176
- // Prepend dependencies to make them available
177
- if (componentData.dependencies) {
178
- const depCode = Object.entries(componentData.dependencies)
179
- .map(([depName, depContent]) => {
180
- // Convert ES6 exports to global assignments
181
- return depContent
182
- .replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
183
- .replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
184
- .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
185
- .replace(/export\s+default\s+/g, 'window.defaultExport =')
186
- .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
187
- return exports.split(',').map(exp => {
183
+ try {
184
+ // Create evaluation context with dependencies
185
+ let evalCode = componentData.js;
186
+
187
+ // Prepend dependencies to make them available
188
+ if (componentData.dependencies) {
189
+ const depCode = Object.entries(componentData.dependencies)
190
+ .map(([depName, depContent]) => {
191
+ // Convert ES6 exports to global assignments
192
+ return depContent
193
+ .replace(/export\s+const\s+(\w+)\s*=/g, 'window.$1 =')
194
+ .replace(/export\s+let\s+(\w+)\s*=/g, 'window.$1 =')
195
+ .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
196
+ .replace(/export\s+default\s+/g, 'window.defaultExport =')
197
+ .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exports) => {
198
+ return exports
199
+ .split(',')
200
+ .map((exp) => {
188
201
  const cleanExp = exp.trim();
189
202
  return `window.${cleanExp} = ${cleanExp};`;
190
- }).join('\n');
191
- });
192
- })
193
- .join('\n\n');
203
+ })
204
+ .join('\n');
205
+ });
206
+ })
207
+ .join('\n\n');
194
208
 
195
- evalCode = depCode + '\n\n' + evalCode;
196
- }
209
+ evalCode = depCode + '\n\n' + evalCode;
210
+ }
197
211
 
198
- // Evaluate the complete code
199
- const componentClass = new Function('slice', 'customElements', 'window', 'document', `
212
+ // Evaluate the complete code
213
+ const componentClass = new Function(
214
+ 'slice',
215
+ 'customElements',
216
+ 'window',
217
+ 'document',
218
+ `
200
219
  "use strict";
201
220
  ${evalCode}
202
221
  return ${componentName};
203
- `)(window.slice, window.customElements, window, window.document);
222
+ `
223
+ )(window.slice, window.customElements, window, window.document);
204
224
 
205
- if (componentClass) {
206
- this.classes.set(componentName, componentClass);
207
- console.log(`📝 Class registered for: ${componentName}`);
208
- }
209
- } catch (error) {
210
- console.warn(`❌ Failed to evaluate class for ${componentName}:`, error);
211
- console.warn('Code that failed:', componentData.js.substring(0, 200) + '...');
225
+ if (componentClass) {
226
+ this.classes.set(componentName, componentClass);
227
+ console.log(`📝 Class registered for: ${componentName}`);
212
228
  }
229
+ } catch (error) {
230
+ console.warn(`❌ Failed to evaluate class for ${componentName}:`, error);
231
+ console.warn('Code that failed:', componentData.js.substring(0, 200) + '...');
213
232
  }
214
233
  }
215
234
  }
216
-
217
-
235
+ }
218
236
 
219
237
  /**
220
238
  * 📦 New bundle registration method (simplified and robust)
@@ -254,10 +272,13 @@ export default class Controller {
254
272
  if (!processedDeps.has(depKey)) {
255
273
  try {
256
274
  const depContent = typeof depEntry === 'string' ? depEntry : depEntry.content;
257
- const bindings = typeof depEntry === 'string' ? [] : (depEntry.bindings || []);
275
+ const bindings = typeof depEntry === 'string' ? [] : depEntry.bindings || [];
258
276
 
259
277
  const fileBaseName = depKey
260
- ? depKey.split('/').pop().replace(/\.[^.]+$/, '')
278
+ ? depKey
279
+ .split('/')
280
+ .pop()
281
+ .replace(/\.[^.]+$/, '')
261
282
  : '';
262
283
  const dataName = fileBaseName ? `${fileBaseName}Data` : '';
263
284
  const exportPrefix = dataName ? `window.${dataName} = ` : '';
@@ -273,15 +294,19 @@ export default class Controller {
273
294
  .replace(/window\.defaultExport\s*=\s*/g, exportPrefix || 'window.defaultExport = ')
274
295
  // Handle export { var1, var2 } statements
275
296
  .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exportsStr) => {
276
- const exports = exportsStr.split(',').map(exp => exp.trim().split(' as ')[0].trim());
277
- return exports.map(varName => `window.${varName} = ${varName};`).join('\n');
297
+ const exports = exportsStr.split(',').map((exp) => exp.trim().split(' as ')[0].trim());
298
+ return exports.map((varName) => `window.${varName} = ${varName};`).join('\n');
278
299
  })
279
300
  // Remove any remaining export keywords
280
301
  .replace(/^\s*export\s+/gm, '');
281
302
 
282
303
  // Evaluate the processed content
283
- new Function('slice', 'customElements', 'window', 'document', processedContent)
284
- (window.slice, window.customElements, window, window.document);
304
+ new Function('slice', 'customElements', 'window', 'document', processedContent)(
305
+ window.slice,
306
+ window.customElements,
307
+ window,
308
+ window.document
309
+ );
285
310
 
286
311
  // Apply import bindings to map local identifiers to globals
287
312
  for (const binding of bindings) {
@@ -289,9 +314,8 @@ export default class Controller {
289
314
 
290
315
  if (binding.type === 'default') {
291
316
  if (!window[binding.localName]) {
292
- const fallbackValue = dataName && window[dataName] !== undefined
293
- ? window[dataName]
294
- : window.defaultExport;
317
+ const fallbackValue =
318
+ dataName && window[dataName] !== undefined ? window[dataName] : window.defaultExport;
295
319
  if (fallbackValue !== undefined) {
296
320
  window[binding.localName] = fallbackValue;
297
321
  }
@@ -330,10 +354,16 @@ export default class Controller {
330
354
  if (componentData.js && !this.classes.has(componentName)) {
331
355
  try {
332
356
  // Simple evaluation
333
- const componentClass = new Function('slice', 'customElements', 'window', 'document', `
357
+ const componentClass = new Function(
358
+ 'slice',
359
+ 'customElements',
360
+ 'window',
361
+ 'document',
362
+ `
334
363
  ${componentData.js}
335
364
  return ${componentName};
336
- `)(window.slice, window.customElements, window, window.document);
365
+ `
366
+ )(window.slice, window.customElements, window, window.document);
337
367
 
338
368
  if (componentClass) {
339
369
  this.classes.set(componentName, componentClass);
@@ -404,7 +434,7 @@ export default class Controller {
404
434
  // Find component in any loaded bundle
405
435
  const allBundles = [
406
436
  { name: 'critical', data: this.bundleConfig.bundles.critical },
407
- ...Object.entries(this.bundleConfig.bundles.routes || {}).map(([name, data]) => ({ name, data }))
437
+ ...Object.entries(this.bundleConfig.bundles.routes || {}).map(([name, data]) => ({ name, data })),
408
438
  ];
409
439
 
410
440
  for (const { name: bundleName, data: bundleData } of allBundles) {
@@ -479,13 +509,13 @@ export default class Controller {
479
509
  */
480
510
  registerComponent(component, parent = null) {
481
511
  component.parentComponent = parent;
482
-
512
+
483
513
  // 🚀 OPTIMIZACIÓN: Precalcular y guardar profundidad
484
514
  component._depth = parent ? (parent._depth || 0) + 1 : 0;
485
-
515
+
486
516
  // Registrar en activeComponents
487
517
  this.activeComponents.set(component.sliceId, component);
488
-
518
+
489
519
  // 🚀 OPTIMIZACIÓN: Actualizar índice inverso de hijos
490
520
  if (parent) {
491
521
  if (!this.childrenIndex.has(parent.sliceId)) {
@@ -493,7 +523,7 @@ export default class Controller {
493
523
  }
494
524
  this.childrenIndex.get(parent.sliceId).add(component.sliceId);
495
525
  }
496
-
526
+
497
527
  return true;
498
528
  }
499
529
 
@@ -549,11 +579,11 @@ export default class Controller {
549
579
  if (isVisual) {
550
580
  if (slice.paths.components[componentCategory]) {
551
581
  path = `${baseUrl}${slice.paths.components[componentCategory].path}/${componentName}`;
552
- resourceType === 'html' ? path += `/${componentName}.html` : path += `/${componentName}.css`;
582
+ resourceType === 'html' ? (path += `/${componentName}.html`) : (path += `/${componentName}.css`);
553
583
  } else {
554
584
  if (componentCategory === 'Structural') {
555
585
  path = `${baseUrl}/Slice/Components/Structural/${componentName}`;
556
- resourceType === 'html' ? path += `/${componentName}.html` : path += `/${componentName}.css`;
586
+ resourceType === 'html' ? (path += `/${componentName}.html`) : (path += `/${componentName}.css`);
557
587
  } else {
558
588
  throw new Error(`Component category '${componentCategory}' not found in paths configuration`);
559
589
  }
@@ -612,18 +642,18 @@ export default class Controller {
612
642
 
613
643
  getComponentPropsForDebugger(component) {
614
644
  const ComponentClass = component.constructor;
615
-
645
+
616
646
  if (ComponentClass.props) {
617
647
  return {
618
648
  availableProps: Object.keys(ComponentClass.props),
619
649
  propsConfig: ComponentClass.props,
620
- usedProps: this.extractUsedProps(component, ComponentClass.props)
650
+ usedProps: this.extractUsedProps(component, ComponentClass.props),
621
651
  };
622
652
  } else {
623
653
  return {
624
654
  availableProps: this.extractUsedProps(component),
625
655
  propsConfig: null,
626
- usedProps: this.extractUsedProps(component)
656
+ usedProps: this.extractUsedProps(component),
627
657
  };
628
658
  }
629
659
  }
@@ -640,13 +670,13 @@ export default class Controller {
640
670
  validatePropsInDevelopment(ComponentClass, providedProps, componentName) {
641
671
  const staticProps = ComponentClass.props;
642
672
  const usedProps = Object.keys(providedProps || {});
643
-
673
+
644
674
  const availableProps = Object.keys(staticProps);
645
- const unknownProps = usedProps.filter(prop => !availableProps.includes(prop));
646
-
675
+ const unknownProps = usedProps.filter((prop) => !availableProps.includes(prop));
676
+
647
677
  if (unknownProps.length > 0) {
648
678
  slice.logger.logWarning(
649
- 'PropsValidator',
679
+ 'PropsValidator',
650
680
  `${componentName}: Unknown props [${unknownProps.join(', ')}]. Available: [${availableProps.join(', ')}]`
651
681
  );
652
682
  }
@@ -654,34 +684,31 @@ export default class Controller {
654
684
  const requiredProps = Object.entries(staticProps)
655
685
  .filter(([_, config]) => config.required)
656
686
  .map(([prop, _]) => prop);
657
-
658
- const missingRequired = requiredProps.filter(prop => !(prop in (providedProps || {})));
687
+
688
+ const missingRequired = requiredProps.filter((prop) => !(prop in (providedProps || {})));
659
689
  if (missingRequired.length > 0) {
660
- slice.logger.logError(
661
- componentName,
662
- `Missing required props: [${missingRequired.join(', ')}]`
663
- );
690
+ slice.logger.logError(componentName, `Missing required props: [${missingRequired.join(', ')}]`);
664
691
  }
665
692
  }
666
693
 
667
694
  extractUsedProps(component, staticProps = null) {
668
695
  const usedProps = {};
669
-
696
+
670
697
  if (staticProps) {
671
- Object.keys(staticProps).forEach(prop => {
698
+ Object.keys(staticProps).forEach((prop) => {
672
699
  if (component[prop] !== undefined) {
673
700
  usedProps[prop] = component[prop];
674
701
  }
675
702
  });
676
703
  } else {
677
- Object.getOwnPropertyNames(component).forEach(key => {
704
+ Object.getOwnPropertyNames(component).forEach((key) => {
678
705
  if (key.startsWith('_') && key !== '_isActive') {
679
706
  const propName = key.substring(1);
680
707
  usedProps[propName] = component[propName];
681
708
  }
682
709
  });
683
710
  }
684
-
711
+
685
712
  return usedProps;
686
713
  }
687
714
 
@@ -699,16 +726,16 @@ export default class Controller {
699
726
  findAllChildComponents(parentSliceId, collected = new Set()) {
700
727
  // 🚀 Buscar directamente en el índice: O(1)
701
728
  const children = this.childrenIndex.get(parentSliceId);
702
-
729
+
703
730
  if (!children) return collected;
704
-
731
+
705
732
  // 🚀 Iterar solo los hijos directos: O(k) donde k = número de hijos
706
733
  for (const childSliceId of children) {
707
734
  collected.add(childSliceId);
708
735
  // Recursión solo sobre hijos, no todos los componentes
709
736
  this.findAllChildComponents(childSliceId, collected);
710
737
  }
711
-
738
+
712
739
  return collected;
713
740
  }
714
741
 
@@ -722,8 +749,8 @@ export default class Controller {
722
749
  findAllNestedComponentsInContainer(container, collected = new Set()) {
723
750
  // Buscar todos los elementos con slice-id en el contenedor
724
751
  const sliceComponents = container.querySelectorAll('[slice-id]');
725
-
726
- sliceComponents.forEach(element => {
752
+
753
+ sliceComponents.forEach((element) => {
727
754
  const sliceId = element.getAttribute('slice-id') || element.sliceId;
728
755
  if (sliceId && this.activeComponents.has(sliceId)) {
729
756
  collected.add(sliceId);
@@ -738,7 +765,7 @@ export default class Controller {
738
765
  /**
739
766
  * Destruye uno o múltiples componentes DE FORMA RECURSIVA
740
767
  * 🚀 OPTIMIZADO: O(m log m) en lugar de O(n*d + m log m)
741
- * @param {HTMLElement|Array<HTMLElement>|string|Array<string>} components
768
+ * @param {HTMLElement|Array<HTMLElement>|string|Array<string>} components
742
769
  * @returns {number} Cantidad de componentes destruidos (incluyendo hijos)
743
770
  */
744
771
  destroyComponent(components) {
@@ -763,7 +790,7 @@ export default class Controller {
763
790
  }
764
791
 
765
792
  allSliceIdsToDestroy.add(sliceId);
766
-
793
+
767
794
  // 🚀 OPTIMIZADO: Usa childrenIndex en lugar de recorrer todos los componentes
768
795
  this.findAllChildComponents(sliceId, allSliceIdsToDestroy);
769
796
  }
@@ -773,9 +800,9 @@ export default class Controller {
773
800
  const sortedSliceIds = Array.from(allSliceIdsToDestroy).sort((a, b) => {
774
801
  const compA = this.activeComponents.get(a);
775
802
  const compB = this.activeComponents.get(b);
776
-
803
+
777
804
  if (!compA || !compB) return 0;
778
-
805
+
779
806
  // 🚀 O(1) en lugar de O(d) - usa profundidad precalculada
780
807
  return (compB._depth || 0) - (compA._depth || 0);
781
808
  });
@@ -785,7 +812,7 @@ export default class Controller {
785
812
  // PASO 3: Destruir en orden correcto (hijos antes que padres)
786
813
  for (const sliceId of sortedSliceIds) {
787
814
  const component = this.activeComponents.get(sliceId);
788
-
815
+
789
816
  if (!component) continue;
790
817
 
791
818
  // Ejecutar hook beforeDestroy si existe
@@ -797,9 +824,14 @@ export default class Controller {
797
824
  }
798
825
  }
799
826
 
827
+ // Limpiar suscripciones de eventos del componente
828
+ if (slice.events) {
829
+ slice.events.cleanupComponent(sliceId);
830
+ }
831
+
800
832
  // 🚀 Limpiar del índice de hijos
801
833
  this.childrenIndex.delete(sliceId);
802
-
834
+
803
835
  // Si tiene padre, remover de la lista de hijos del padre
804
836
  if (component.parentComponent) {
805
837
  const parentChildren = this.childrenIndex.get(component.parentComponent.sliceId);
@@ -844,14 +876,14 @@ export default class Controller {
844
876
 
845
877
  // 🚀 Recolectar componentes usando índice optimizado
846
878
  const allSliceIds = this.findAllNestedComponentsInContainer(container);
847
-
879
+
848
880
  if (allSliceIds.size === 0) {
849
881
  return 0;
850
882
  }
851
883
 
852
884
  // Destruir usando el método principal optimizado
853
885
  const count = this.destroyComponent(Array.from(allSliceIds));
854
-
886
+
855
887
  if (count > 0) {
856
888
  slice.logger.logInfo('Controller', `Destroyed ${count} component(s) from container (including nested)`);
857
889
  }
@@ -880,9 +912,12 @@ export default class Controller {
880
912
  }
881
913
 
882
914
  const count = this.destroyComponent(componentsToDestroy);
883
-
915
+
884
916
  if (count > 0) {
885
- slice.logger.logInfo('Controller', `Destroyed ${count} component(s) matching pattern: ${pattern} (including nested)`);
917
+ slice.logger.logInfo(
918
+ 'Controller',
919
+ `Destroyed ${count} component(s) matching pattern: ${pattern} (including nested)`
920
+ );
886
921
  }
887
922
 
888
923
  return count;