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.
@@ -3,20 +3,20 @@ export default class Router {
3
3
  this.routes = routes;
4
4
  this.activeRoute = null;
5
5
  this.pathToRouteMap = this.createPathToRouteMap(routes);
6
-
6
+
7
7
  // Navigation Guards
8
8
  this._beforeEachGuard = null;
9
9
  this._afterEachGuard = null;
10
-
10
+
11
11
  // Router state
12
12
  this._started = false;
13
13
  this._autoStartTimeout = null;
14
-
14
+
15
15
  // Sistema de caché optimizado
16
16
  this.routeContainersCache = new Map();
17
17
  this.lastCacheUpdate = 0;
18
18
  this.CACHE_DURATION = 100; // ms - caché muy corto pero efectivo
19
-
19
+
20
20
  // Observer para invalidar caché automáticamente
21
21
  this.setupMutationObserver();
22
22
  }
@@ -27,7 +27,7 @@ export default class Router {
27
27
  */
28
28
  init() {
29
29
  window.addEventListener('popstate', this.onRouteChange.bind(this));
30
-
30
+
31
31
  // Auto-start después de 50ms si el usuario no llama start() manualmente
32
32
  // Esto da tiempo para que el usuario configure guards si lo necesita
33
33
  this._autoStartTimeout = setTimeout(async () => {
@@ -49,13 +49,13 @@ export default class Router {
49
49
  slice.logger.logWarning('Router', 'start() already called');
50
50
  return;
51
51
  }
52
-
52
+
53
53
  // Cancelar auto-start si existe
54
54
  if (this._autoStartTimeout) {
55
55
  clearTimeout(this._autoStartTimeout);
56
56
  this._autoStartTimeout = null;
57
57
  }
58
-
58
+
59
59
  this._started = true;
60
60
  await this.loadInitialRoute();
61
61
  }
@@ -104,7 +104,7 @@ export default class Router {
104
104
  component: 'NotFound',
105
105
  params: {},
106
106
  query: this._parseQueryParams(),
107
- metadata: {}
107
+ metadata: {},
108
108
  };
109
109
  }
110
110
 
@@ -113,7 +113,7 @@ export default class Router {
113
113
  component: route.parentRoute ? route.parentRoute.component : route.component,
114
114
  params: params,
115
115
  query: this._parseQueryParams(),
116
- metadata: route.metadata || {}
116
+ metadata: route.metadata || {},
117
117
  };
118
118
  }
119
119
 
@@ -127,11 +127,11 @@ export default class Router {
127
127
 
128
128
  const params = {};
129
129
  const urlParams = new URLSearchParams(queryString);
130
-
130
+
131
131
  for (const [key, value] of urlParams) {
132
132
  params[key] = value;
133
133
  }
134
-
134
+
135
135
  return params;
136
136
  }
137
137
 
@@ -179,13 +179,16 @@ export default class Router {
179
179
  if (typeof arg === 'object' && arg.path) {
180
180
  redirectPath = arg.path;
181
181
  redirectOptions = {
182
- replace: arg.replace || false
182
+ replace: arg.replace || false,
183
183
  };
184
184
  return;
185
185
  }
186
186
 
187
187
  // Argumento inválido
188
- slice.logger.logError('Router', 'Invalid argument passed to next(). Expected string, object with path, false, or undefined.');
188
+ slice.logger.logError(
189
+ 'Router',
190
+ 'Invalid argument passed to next(). Expected string, object with path, false, or undefined.'
191
+ );
189
192
  };
190
193
 
191
194
  try {
@@ -193,10 +196,7 @@ export default class Router {
193
196
 
194
197
  // Si no se llamó next(), loguear advertencia pero continuar
195
198
  if (!nextCalled) {
196
- slice.logger.logWarning(
197
- 'Router',
198
- 'beforeEach guard did not call next(). Navigation will continue.'
199
- );
199
+ slice.logger.logWarning('Router', 'beforeEach guard did not call next(). Navigation will continue.');
200
200
  }
201
201
 
202
202
  // Retornar tanto el path como las opciones
@@ -228,28 +228,21 @@ export default class Router {
228
228
  // ROUTING CORE (MODIFICADO CON GUARDS)
229
229
  // ============================================
230
230
 
231
- async navigate(path, _redirectChain = [], _options = {}) {
231
+ async navigate(path, _redirectChain = [], _options = {}) {
232
232
  const currentPath = window.location.pathname;
233
-
234
-
233
+
235
234
  // Detectar loops infinitos: si ya visitamos esta ruta en la cadena de redirecciones
236
235
  if (_redirectChain.includes(path)) {
237
- slice.logger.logError(
238
- 'Router',
239
- `Guard redirection loop detected: ${_redirectChain.join(' → ')} → ${path}`
240
- );
236
+ slice.logger.logError('Router', `Guard redirection loop detected: ${_redirectChain.join(' → ')} → ${path}`);
241
237
  return;
242
238
  }
243
-
239
+
244
240
  // Límite de seguridad: máximo 10 redirecciones
245
241
  if (_redirectChain.length >= 10) {
246
- slice.logger.logError(
247
- 'Router',
248
- `Too many redirections: ${_redirectChain.join(' → ')} → ${path}`
249
- );
242
+ slice.logger.logError('Router', `Too many redirections: ${_redirectChain.join(' → ')} → ${path}`);
250
243
  return;
251
244
  }
252
-
245
+
253
246
  // Obtener información de ruta actual
254
247
  const { route: fromRoute, params: fromParams } = this.matchRoute(currentPath);
255
248
  const from = this._createRouteInfo(fromRoute, fromParams, currentPath);
@@ -258,7 +251,6 @@ export default class Router {
258
251
  const { route: toRoute, params: toParams } = this.matchRoute(path);
259
252
  const to = this._createRouteInfo(toRoute, toParams, path);
260
253
 
261
-
262
254
  // EJECUTAR BEFORE EACH GUARD
263
255
  const guardResult = await this._executeBeforeEachGuard(to, from);
264
256
 
@@ -281,7 +273,7 @@ export default class Router {
281
273
  } else {
282
274
  window.history.pushState({}, path, window.location.origin + path);
283
275
  }
284
-
276
+
285
277
  await this._performNavigation(to, from);
286
278
  }
287
279
 
@@ -296,6 +288,9 @@ export default class Router {
296
288
 
297
289
  // EJECUTAR AFTER EACH GUARD
298
290
  this._executeAfterEachGuard(to, from);
291
+
292
+ // Emitir evento de cambio de ruta
293
+ this._emitRouteChange(to, from);
299
294
  }
300
295
 
301
296
  async onRouteChange() {
@@ -322,10 +317,10 @@ export default class Router {
322
317
 
323
318
  async handleRoute(route, params) {
324
319
  const targetElement = document.querySelector('#app');
325
-
320
+
326
321
  const componentName = route.parentRoute ? route.parentRoute.component : route.component;
327
322
  const sliceId = `route-${componentName}`;
328
-
323
+
329
324
  const existingComponent = slice.controller.getComponent(sliceId);
330
325
 
331
326
  if (slice.loading) {
@@ -348,7 +343,7 @@ export default class Router {
348
343
 
349
344
  targetElement.innerHTML = '';
350
345
  targetElement.appendChild(component);
351
-
346
+
352
347
  await this.renderRoutesInComponent(component);
353
348
  }
354
349
 
@@ -362,7 +357,7 @@ export default class Router {
362
357
  slice.router.activeRoute = route;
363
358
  }
364
359
 
365
- async loadInitialRoute() {
360
+ async loadInitialRoute() {
366
361
  const path = window.location.pathname;
367
362
  const { route, params } = this.matchRoute(path);
368
363
 
@@ -387,6 +382,25 @@ export default class Router {
387
382
 
388
383
  // EJECUTAR AFTER EACH GUARD en carga inicial
389
384
  this._executeAfterEachGuard(to, from);
385
+
386
+ // Emitir evento de cambio de ruta
387
+ this._emitRouteChange(to, from);
388
+ }
389
+
390
+ /**
391
+ * Emitir evento de cambio de ruta
392
+ * @param {Object} to
393
+ * @param {Object} from
394
+ */
395
+ _emitRouteChange(to, from) {
396
+ const payload = { to, from };
397
+
398
+ if (slice.eventsConfig?.enabled && slice.events && typeof slice.events.emit === 'function') {
399
+ slice.events.emit('router:change', payload);
400
+ return;
401
+ }
402
+
403
+ window.dispatchEvent(new CustomEvent('router:change', { detail: payload }));
390
404
  }
391
405
 
392
406
  // ============================================
@@ -397,33 +411,34 @@ export default class Router {
397
411
  if (typeof MutationObserver !== 'undefined') {
398
412
  this.observer = new MutationObserver((mutations) => {
399
413
  let shouldInvalidateCache = false;
400
-
414
+
401
415
  mutations.forEach((mutation) => {
402
416
  if (mutation.type === 'childList') {
403
417
  const addedNodes = Array.from(mutation.addedNodes);
404
418
  const removedNodes = Array.from(mutation.removedNodes);
405
-
406
- const hasRouteNodes = [...addedNodes, ...removedNodes].some(node =>
407
- node.nodeType === Node.ELEMENT_NODE &&
408
- (node.tagName === 'SLICE-ROUTE' ||
409
- node.tagName === 'SLICE-MULTI-ROUTE' ||
410
- node.querySelector?.('slice-route, slice-multi-route'))
419
+
420
+ const hasRouteNodes = [...addedNodes, ...removedNodes].some(
421
+ (node) =>
422
+ node.nodeType === Node.ELEMENT_NODE &&
423
+ (node.tagName === 'SLICE-ROUTE' ||
424
+ node.tagName === 'SLICE-MULTI-ROUTE' ||
425
+ node.querySelector?.('slice-route, slice-multi-route'))
411
426
  );
412
-
427
+
413
428
  if (hasRouteNodes) {
414
429
  shouldInvalidateCache = true;
415
430
  }
416
431
  }
417
432
  });
418
-
433
+
419
434
  if (shouldInvalidateCache) {
420
435
  this.invalidateCache();
421
436
  }
422
437
  });
423
-
438
+
424
439
  this.observer.observe(document.body, {
425
440
  childList: true,
426
- subtree: true
441
+ subtree: true,
427
442
  });
428
443
  }
429
444
  }
@@ -435,32 +450,28 @@ export default class Router {
435
450
 
436
451
  createPathToRouteMap(routes, basePath = '', parentRoute = null) {
437
452
  const pathToRouteMap = new Map();
438
-
453
+
439
454
  for (const route of routes) {
440
- const fullPath = `${basePath}${route.path}`.replace(/\/+/g, '/');
441
-
442
- const routeWithParent = {
443
- ...route,
444
- fullPath,
445
- parentPath: parentRoute ? parentRoute.fullPath : null,
446
- parentRoute: parentRoute
447
- };
448
-
449
- pathToRouteMap.set(fullPath, routeWithParent);
450
-
451
- if (route.children) {
452
- const childPathToRouteMap = this.createPathToRouteMap(
453
- route.children,
454
- fullPath,
455
- routeWithParent
456
- );
457
-
458
- for (const [childPath, childRoute] of childPathToRouteMap.entries()) {
459
- pathToRouteMap.set(childPath, childRoute);
460
- }
461
- }
455
+ const fullPath = `${basePath}${route.path}`.replace(/\/+/g, '/');
456
+
457
+ const routeWithParent = {
458
+ ...route,
459
+ fullPath,
460
+ parentPath: parentRoute ? parentRoute.fullPath : null,
461
+ parentRoute: parentRoute,
462
+ };
463
+
464
+ pathToRouteMap.set(fullPath, routeWithParent);
465
+
466
+ if (route.children) {
467
+ const childPathToRouteMap = this.createPathToRouteMap(route.children, fullPath, routeWithParent);
468
+
469
+ for (const [childPath, childRoute] of childPathToRouteMap.entries()) {
470
+ pathToRouteMap.set(childPath, childRoute);
471
+ }
472
+ }
462
473
  }
463
-
474
+
464
475
  return pathToRouteMap;
465
476
  }
466
477
 
@@ -491,37 +502,32 @@ export default class Router {
491
502
  getCachedRouteContainers(container) {
492
503
  const containerKey = container === document ? 'document' : container.sliceId || 'anonymous';
493
504
  const now = Date.now();
494
-
495
- if (this.routeContainersCache.has(containerKey) &&
496
- (now - this.lastCacheUpdate) < this.CACHE_DURATION) {
505
+
506
+ if (this.routeContainersCache.has(containerKey) && now - this.lastCacheUpdate < this.CACHE_DURATION) {
497
507
  return this.routeContainersCache.get(containerKey);
498
508
  }
499
509
 
500
510
  const routeContainers = this.findAllRouteContainersOptimized(container);
501
511
  this.routeContainersCache.set(containerKey, routeContainers);
502
512
  this.lastCacheUpdate = now;
503
-
513
+
504
514
  return routeContainers;
505
515
  }
506
516
 
507
517
  findAllRouteContainersOptimized(container) {
508
518
  const routeContainers = [];
509
-
510
- const walker = document.createTreeWalker(
511
- container,
512
- NodeFilter.SHOW_ELEMENT,
513
- {
514
- acceptNode: (node) => {
515
- if (node.tagName === 'SLICE-ROUTE' || node.tagName === 'SLICE-MULTI-ROUTE') {
516
- return NodeFilter.FILTER_ACCEPT;
517
- }
518
- return NodeFilter.FILTER_SKIP;
519
+
520
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
521
+ acceptNode: (node) => {
522
+ if (node.tagName === 'SLICE-ROUTE' || node.tagName === 'SLICE-MULTI-ROUTE') {
523
+ return NodeFilter.FILTER_ACCEPT;
519
524
  }
520
- }
521
- );
525
+ return NodeFilter.FILTER_SKIP;
526
+ },
527
+ });
522
528
 
523
529
  let node;
524
- while (node = walker.nextNode()) {
530
+ while ((node = walker.nextNode())) {
525
531
  routeContainers.push(node);
526
532
  }
527
533
 
@@ -541,15 +547,15 @@ export default class Router {
541
547
  const exactMatch = this.pathToRouteMap.get(path);
542
548
  if (exactMatch) {
543
549
  if (exactMatch.parentRoute) {
544
- return {
545
- route: exactMatch.parentRoute,
550
+ return {
551
+ route: exactMatch.parentRoute,
546
552
  params: {},
547
- childRoute: exactMatch
553
+ childRoute: exactMatch,
548
554
  };
549
555
  }
550
556
  return { route: exactMatch, params: {} };
551
557
  }
552
-
558
+
553
559
  for (const [routePattern, route] of this.pathToRouteMap.entries()) {
554
560
  if (routePattern.includes('${')) {
555
561
  const { regex, paramNames } = this.compilePathPattern(routePattern);
@@ -559,31 +565,34 @@ export default class Router {
559
565
  paramNames.forEach((name, i) => {
560
566
  params[name] = match[i + 1];
561
567
  });
562
-
568
+
563
569
  if (route.parentRoute) {
564
- return {
565
- route: route.parentRoute,
570
+ return {
571
+ route: route.parentRoute,
566
572
  params: params,
567
- childRoute: route
573
+ childRoute: route,
568
574
  };
569
575
  }
570
-
576
+
571
577
  return { route, params };
572
578
  }
573
579
  }
574
580
  }
575
-
581
+
576
582
  const notFoundRoute = this.pathToRouteMap.get('/404');
577
583
  return { route: notFoundRoute, params: {} };
578
584
  }
579
585
 
580
586
  compilePathPattern(pattern) {
581
587
  const paramNames = [];
582
- const regexPattern = '^' + pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
583
- paramNames.push(paramName);
584
- return '([^/]+)';
585
- }) + '$';
588
+ const regexPattern =
589
+ '^' +
590
+ pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
591
+ paramNames.push(paramName);
592
+ return '([^/]+)';
593
+ }) +
594
+ '$';
586
595
 
587
596
  return { regex: new RegExp(regexPattern), paramNames };
588
597
  }
589
- }
598
+ }
package/Slice/Slice.js CHANGED
@@ -11,6 +11,7 @@ export default class Slice {
11
11
  this.loggerConfig = sliceConfig.logger;
12
12
  this.debuggerConfig = sliceConfig.debugger;
13
13
  this.loadingConfig = sliceConfig.loading;
14
+ this.eventsConfig = sliceConfig.events;
14
15
 
15
16
  // 📦 Bundle system is initialized automatically via import in index.js
16
17
  }
@@ -24,7 +25,7 @@ export default class Slice {
24
25
  }
25
26
  }
26
27
 
27
- isProduction(){
28
+ isProduction() {
28
29
  return true;
29
30
  }
30
31
 
@@ -68,23 +69,26 @@ export default class Slice {
68
69
  return null;
69
70
  }
70
71
 
71
- let isVisual = slice.paths.components[componentCategory].type === "Visual";
72
+ let isVisual = slice.paths.components[componentCategory].type === 'Visual';
72
73
  let modulePath = `${slice.paths.components[componentCategory].path}/${componentName}/${componentName}.js`;
73
74
 
74
75
  // Load template, class, and CSS concurrently if needed
75
76
  try {
76
77
  // 📦 Skip individual loading if component is available from bundles
77
- const loadTemplate = (isFromBundle || !isVisual || this.controller.templates.has(componentName))
78
- ? Promise.resolve(null)
79
- : this.controller.fetchText(componentName, 'html', componentCategory);
78
+ const loadTemplate =
79
+ isFromBundle || !isVisual || this.controller.templates.has(componentName)
80
+ ? Promise.resolve(null)
81
+ : this.controller.fetchText(componentName, 'html', componentCategory);
80
82
 
81
- const loadClass = (isFromBundle || this.controller.classes.has(componentName))
82
- ? Promise.resolve(null)
83
- : this.getClass(modulePath);
83
+ const loadClass =
84
+ isFromBundle || this.controller.classes.has(componentName)
85
+ ? Promise.resolve(null)
86
+ : this.getClass(modulePath);
84
87
 
85
- const loadCSS = (isFromBundle || !isVisual || this.controller.requestedStyles.has(componentName))
86
- ? Promise.resolve(null)
87
- : this.controller.fetchText(componentName, 'css', componentCategory);
88
+ const loadCSS =
89
+ isFromBundle || !isVisual || this.controller.requestedStyles.has(componentName)
90
+ ? Promise.resolve(null)
91
+ : this.controller.fetchText(componentName, 'css', componentCategory);
88
92
 
89
93
  const [html, ComponentClass, css] = await Promise.all([loadTemplate, loadClass, loadCSS]);
90
94
 
@@ -142,7 +146,7 @@ export default class Slice {
142
146
  }
143
147
 
144
148
  this.controller.registerComponent(componentInstance);
145
- if(isVisual){
149
+ if (isVisual) {
146
150
  this.controller.registerComponentsRecursively(componentInstance);
147
151
  }
148
152
 
@@ -166,26 +170,22 @@ export default class Slice {
166
170
  attachTemplate(componentInstance) {
167
171
  this.controller.loadTemplateToComponent(componentInstance);
168
172
  }
169
-
170
-
171
173
  }
172
174
 
173
-
174
- async function loadConfig(){
175
+ async function loadConfig() {
175
176
  try {
176
177
  const response = await fetch('/sliceConfig.json'); // 🔹 Express lo sirve desde `src/`
177
178
  if (!response.ok) throw new Error('Error loading sliceConfig.json');
178
179
  const json = await response.json();
179
- console.log(json)
180
+ console.log(json);
180
181
  return json;
181
- } catch (error) {
182
+ } catch (error) {
182
183
  console.error(`Error loading config file: ${error.message}`);
183
184
  return null;
184
- }
185
+ }
185
186
  }
186
187
 
187
188
  async function init() {
188
-
189
189
  const sliceConfig = await loadConfig();
190
190
  if (!sliceConfig) {
191
191
  //Display error message in console with colors and alert in english
@@ -193,11 +193,10 @@ async function init() {
193
193
  alert('Error loading Slice configuration');
194
194
  return;
195
195
  }
196
-
197
196
 
198
197
  window.slice = new Slice(sliceConfig);
199
198
 
200
- slice.paths.structuralComponentFolderPath = "/Slice/Components/Structural";
199
+ slice.paths.structuralComponentFolderPath = '/Slice/Components/Structural';
201
200
 
202
201
  if (sliceConfig.logger.enabled) {
203
202
  const LoggerModule = await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Logger/Logger.js`);
@@ -219,31 +218,80 @@ async function init() {
219
218
  document.body.appendChild(window.slice.debugger);
220
219
  }
221
220
 
221
+ if (sliceConfig.events?.enabled) {
222
+ const EventManagerModule = await window.slice.getClass(
223
+ `${slice.paths.structuralComponentFolderPath}/EventManager/EventManager.js`
224
+ );
225
+ window.slice.events = new EventManagerModule();
226
+ if (typeof window.slice.events.init === 'function') {
227
+ await window.slice.events.init();
228
+ }
229
+ window.slice.logger.logError('Slice', 'EventManager enabled');
230
+ } else {
231
+ window.slice.events = {
232
+ subscribe: () => null,
233
+ subscribeOnce: () => null,
234
+ unsubscribe: () => false,
235
+ emit: () => {},
236
+ bind: () => ({
237
+ subscribe: () => null,
238
+ subscribeOnce: () => null,
239
+ emit: () => {},
240
+ }),
241
+ cleanupComponent: () => 0,
242
+ hasSubscribers: () => false,
243
+ subscriberCount: () => 0,
244
+ clear: () => {},
245
+ };
246
+ window.slice.logger.logError('Slice', 'EventManager disabled');
247
+ }
248
+
249
+ if (sliceConfig.context?.enabled) {
250
+ const ContextManagerModule = await window.slice.getClass(
251
+ `${slice.paths.structuralComponentFolderPath}/ContextManager/ContextManager.js`
252
+ );
253
+ window.slice.context = new ContextManagerModule();
254
+ if (typeof window.slice.context.init === 'function') {
255
+ await window.slice.context.init();
256
+ }
257
+ window.slice.logger.logError('Slice', 'ContextManager enabled');
258
+ } else {
259
+ window.slice.context = {
260
+ create: () => false,
261
+ getState: () => null,
262
+ setState: () => {},
263
+ watch: () => null,
264
+ has: () => false,
265
+ destroy: () => false,
266
+ list: () => [],
267
+ };
268
+ window.slice.logger.logError('Slice', 'ContextManager disabled');
269
+ }
270
+
222
271
  if (sliceConfig.loading.enabled) {
223
272
  const loading = await window.slice.build('Loading', {});
224
273
  window.slice.loading = loading;
225
274
  }
226
- await window.slice.stylesManager.init();
227
275
 
276
+ await window.slice.stylesManager.init();
228
277
 
229
278
  const routesModule = await import(slice.paths.routesFile);
230
279
  const routes = routesModule.default;
231
280
  const RouterModule = await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Router/Router.js`);
232
281
  window.slice.router = new RouterModule(routes);
233
282
  await window.slice.router.init();
234
-
235
283
  }
236
284
 
237
- await init();
285
+ await init();
238
286
 
239
- // Initialize bundles if available
240
- try {
241
- const { initializeBundles } = await import('/bundles/bundle.config.js');
242
- if (initializeBundles) {
243
- await initializeBundles(window.slice);
244
- console.log('📦 Bundles initialized automatically');
245
- }
246
- } catch (error) {
247
- // Bundles not available, continue with individual component loading
248
- console.log('📄 Using individual component loading (no bundles found)');
287
+ // Initialize bundles if available
288
+ try {
289
+ const { initializeBundles } = await import('/bundles/bundle.config.js');
290
+ if (initializeBundles) {
291
+ await initializeBundles(window.slice);
292
+ console.log('📦 Bundles initialized automatically');
249
293
  }
294
+ } catch (error) {
295
+ // Bundles not available, continue with individual component loading
296
+ console.log('📄 Using individual component loading (no bundles found)');
297
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-web-framework",
3
- "version": "2.2.13",
3
+ "version": "2.3.1",
4
4
  "description": "",
5
5
  "engines": {
6
6
  "node": ">=20"