slicejs-cli 3.4.1 → 3.5.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.
Files changed (47) hide show
  1. package/.github/workflows/ci.yml +43 -0
  2. package/commands/createComponent/createComponent.js +6 -2
  3. package/commands/deleteComponent/deleteComponent.js +4 -0
  4. package/commands/doctor/doctor.js +9 -0
  5. package/commands/init/init.js +53 -6
  6. package/commands/utils/bundling/BundleGenerator.js +271 -38
  7. package/package.json +5 -2
  8. package/playwright.config.js +51 -0
  9. package/tests/build-command-integration.test.js +87 -0
  10. package/tests/build-production-e2e.test.js +140 -0
  11. package/tests/builder-edge-cases.test.js +322 -0
  12. package/tests/bundle-generate-e2e.test.js +115 -0
  13. package/tests/bundling-dependency-edges.test.js +127 -0
  14. package/tests/bundling-imports-unit.test.js +267 -0
  15. package/tests/commands-component-crud.test.js +102 -0
  16. package/tests/commands-doctor.test.js +80 -0
  17. package/tests/commands-version-checker.test.js +37 -0
  18. package/tests/component-registry-parse.test.js +1 -1
  19. package/tests/e2e/bundles.spec.js +91 -0
  20. package/tests/e2e/dependency-scenarios.spec.js +56 -0
  21. package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +136 -0
  22. package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +149 -0
  23. package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +45 -0
  24. package/tests/e2e/fixtures/components/Visual/Button/Button.css +106 -0
  25. package/tests/e2e/fixtures/components/Visual/Button/Button.html +5 -0
  26. package/tests/e2e/fixtures/components/Visual/Button/Button.js +158 -0
  27. package/tests/e2e/fixtures/components/Visual/Link/Link.js +33 -0
  28. package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +56 -0
  29. package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +83 -0
  30. package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +164 -0
  31. package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +167 -0
  32. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +116 -0
  33. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +44 -0
  34. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +180 -0
  35. package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +20 -0
  36. package/tests/e2e/fixtures/components/Visual/Route/Route.js +181 -0
  37. package/tests/e2e/fixtures/components/registry.json +12 -0
  38. package/tests/e2e/fixtures/vendor-components.mjs +65 -0
  39. package/tests/e2e/navigation.spec.js +44 -0
  40. package/tests/e2e/render.spec.js +34 -0
  41. package/tests/e2e/serve.mjs +264 -0
  42. package/tests/e2e/shared-deps.spec.js +61 -0
  43. package/tests/e2e/unminified.spec.js +33 -0
  44. package/tests/e2e-serve.test.js +148 -0
  45. package/tests/helpers/setup.js +6 -1
  46. package/tests/perf-budget.test.js +86 -0
  47. package/tests/types-generator.test.js +2 -0
@@ -0,0 +1,167 @@
1
+ export default class MultiRoute extends HTMLElement {
2
+ constructor(props) {
3
+ super();
4
+ this.props = props;
5
+ this.renderedComponents = new Map(); // Cache para componentes renderizados
6
+ }
7
+
8
+ init() {
9
+ if (!this.props.routes || !Array.isArray(this.props.routes)) {
10
+ slice.logger.logError('MultiRoute', 'No valid routes array provided in props.');
11
+ return;
12
+ }
13
+ // NOTE: MultiRoute does NOT register its routes in the Router. `routes.js` is the single
14
+ // source of truth for what the Router knows. The Router resolves the URL on first load /
15
+ // refresh / deep-link BEFORE this component mounts, so a path that only lived inside a
16
+ // MultiRoute would 404 on a direct load — incoherent. Declare every section path in
17
+ // `routes.js` (pointing at the shell); MultiRoute just chooses which one to show.
18
+ }
19
+
20
+ /**
21
+ * Encuentra una ruta que coincida con el path actual
22
+ * Soporta rutas estáticas y dinámicas con parámetros ${param}
23
+ */
24
+ matchRoute(currentPath) {
25
+ // Normalize trailing slash so '/about/' behaves like '/about' (keep root '/').
26
+ currentPath = currentPath.length > 1 ? currentPath.replace(/\/+$/, '') : currentPath;
27
+
28
+ // 1. Match exacto, case-insensitive ('/About' coincide con '/about')
29
+ const lowerPath = currentPath.toLowerCase();
30
+ const exactMatch = this.props.routes.find(
31
+ (route) => (route.path.length > 1 ? route.path.replace(/\/+$/, '') : route.path).toLowerCase() === lowerPath
32
+ );
33
+ if (exactMatch) {
34
+ return { route: exactMatch, params: {} };
35
+ }
36
+
37
+ // 2. Si no hay match exacto, buscar rutas dinámicas
38
+ for (const route of this.props.routes) {
39
+ if (route.path.includes('${')) {
40
+ const { regex, paramNames } = this.compilePathPattern(route.path);
41
+ const match = currentPath.match(regex);
42
+
43
+ if (match) {
44
+ // Extraer parámetros de la URL
45
+ const params = {};
46
+ paramNames.forEach((name, i) => {
47
+ params[name] = match[i + 1];
48
+ });
49
+
50
+ return { route, params };
51
+ }
52
+ }
53
+ }
54
+
55
+ // 3. No se encontró ninguna ruta
56
+ return { route: null, params: {} };
57
+ }
58
+
59
+ /**
60
+ * Convierte un patrón de ruta con ${param} en una expresión regular
61
+ * Ejemplo: "/user/${id}" -> /^\/user\/([^/]+)$/
62
+ */
63
+ compilePathPattern(pattern) {
64
+ const paramNames = [];
65
+ const regexPattern = '^' + pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
66
+ paramNames.push(paramName);
67
+ return '([^/]+)'; // Captura cualquier caracter excepto /
68
+ }) + '$';
69
+
70
+ return {
71
+ // 'i': case-insensitive path matching. Captured param values keep their original case.
72
+ regex: new RegExp(regexPattern, 'i'),
73
+ paramNames
74
+ };
75
+ }
76
+
77
+ async render() {
78
+ const currentPath = window.location.pathname;
79
+ const { route: routeMatch, params } = this.matchRoute(currentPath);
80
+
81
+ if (routeMatch) {
82
+ const { component, metadata } = routeMatch;
83
+
84
+ if (this.renderedComponents.has(component)) {
85
+ const cachedComponent = this.renderedComponents.get(component);
86
+ this.innerHTML = '';
87
+
88
+ // Actualizar props del componente cacheado
89
+ if (cachedComponent.props) {
90
+ cachedComponent.props = {
91
+ ...cachedComponent.props,
92
+ params: params,
93
+ metadata: metadata || {} // ✅ Incluir metadata
94
+ };
95
+ }
96
+
97
+ // Si el componente en caché tiene un método update, lo ejecutamos
98
+ if (cachedComponent.update) {
99
+ await cachedComponent.update();
100
+ }
101
+
102
+ this.appendChild(cachedComponent);
103
+ } else {
104
+ if (!slice.controller.componentCategories.has(component)) {
105
+ slice.logger.logError(`${this.sliceId}`, `Component ${component} not found`);
106
+ return;
107
+ }
108
+
109
+ // Crear el componente con los parámetros y metadata de la ruta
110
+ const newComponent = await slice.build(component, {
111
+ params: params,
112
+ metadata: metadata || {}
113
+ });
114
+
115
+ this.innerHTML = '';
116
+ this.appendChild(newComponent);
117
+ this.renderedComponents.set(component, newComponent);
118
+ }
119
+
120
+ // Emitir evento personalizado cuando el renderizado está completo
121
+ this.dispatchEvent(new CustomEvent('route-rendered', {
122
+ bubbles: true,
123
+ detail: {
124
+ component,
125
+ path: currentPath,
126
+ params: params,
127
+ metadata: metadata || {} // ✅ Incluir metadata en el evento
128
+ }
129
+ }));
130
+ } else {
131
+ // Limpiamos el contenido si no hay una coincidencia de ruta
132
+ this.innerHTML = '';
133
+ }
134
+ }
135
+
136
+ async renderIfCurrentRoute() {
137
+ const currentPath = window.location.pathname;
138
+ const { route: routeMatch } = this.matchRoute(currentPath);
139
+
140
+ if (routeMatch) {
141
+ await this.render();
142
+ return true;
143
+ }
144
+ return false;
145
+ }
146
+
147
+ removeComponent() {
148
+ const currentPath = window.location.pathname;
149
+ const { route: routeMatch } = this.matchRoute(currentPath);
150
+
151
+ if (routeMatch) {
152
+ const { component } = routeMatch;
153
+ this.renderedComponents.delete(component);
154
+ this.innerHTML = '';
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Cleanup cuando el componente se destruye
160
+ */
161
+ destroy() {
162
+ this.renderedComponents.clear();
163
+ this.innerHTML = '';
164
+ }
165
+ }
166
+
167
+ customElements.define('slice-multi-route', MultiRoute);
@@ -0,0 +1,116 @@
1
+ .slice_nav_header {
2
+ font-family: var(--font-family);
3
+ width: 100%;
4
+ z-index: 1;
5
+ box-shadow: 0px 0px 10px #00000050;
6
+ background-color: var(--primary-color);
7
+ display: flex;
8
+ justify-content: space-around;
9
+ align-items: center;
10
+ }
11
+ .direction-row-reverse {
12
+ flex-direction: row-reverse;
13
+ }
14
+ .nav_bar_fixed {
15
+ z-index: 100;
16
+ width: 100%;
17
+ left: 0;
18
+ top: 0;
19
+ position: fixed;
20
+ }
21
+ .slice_nav_bar {
22
+ transition: transform 0.3s ease;
23
+ width: 100%;
24
+ justify-content: center;
25
+ display: flex;
26
+ align-items: center;
27
+ left: 0;
28
+ top: 0;
29
+ }
30
+ .nav_bar_menu {
31
+ width: 100%;
32
+ overflow-y: hidden;
33
+ overflow-x: auto;
34
+ -webkit-overflow-scrolling: touch;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ list-style: none;
39
+ }
40
+ .slice_nav_header .item {
41
+ cursor: pointer;
42
+ font-weight: bold;
43
+ text-decoration: none;
44
+ color: var(--primary-color-contrast);
45
+ border-radius: var(--border-radius-slice);
46
+ }
47
+ .anim-item {
48
+ position: absolute;
49
+ top: 100%;
50
+ left: 50%;
51
+ width: 0%;
52
+ height: 2.5px;
53
+ border-radius: 5px;
54
+ background-color: var(--primary-color-contrast);
55
+ transition: width 0.3s ease, left 0.3s ease;
56
+ }
57
+ .slice_nav_header li {
58
+ margin: 25px;
59
+ display: flex;
60
+ justify-content: center;
61
+ position: relative;
62
+ }
63
+
64
+ .slice_nav_header li:hover .anim-item {
65
+ width: 100%;
66
+ left: 0;
67
+ }
68
+ .slice_nav_header .logo_container {
69
+ padding: 10px;
70
+ }
71
+ .logo_container img {
72
+ pointer-events: none;
73
+ user-select: none;
74
+ max-height: 50px;
75
+ max-width: 200px;
76
+ }
77
+ .nav_bar_buttons {
78
+ justify-content: flex-end;
79
+ display: flex;
80
+ }
81
+
82
+ .mobile_menu_button,
83
+ .mobile_close_menu {
84
+ visibility: hidden;
85
+ position: absolute;
86
+ right: 0px;
87
+ margin-right: 25px;
88
+ }
89
+ .mobile_close_menu {
90
+ top: 25px;
91
+ }
92
+ @media only screen and (max-width: 1020px) {
93
+ .slice_nav_bar {
94
+ z-index: 1;
95
+ list-style: none;
96
+ width: 100%;
97
+ height: 100%;
98
+ position: fixed;
99
+ top: 0;
100
+ left: 0;
101
+ display: flex;
102
+ justify-content: center;
103
+ align-items: center;
104
+ flex-direction: column;
105
+ background-color: var(--primary-color);
106
+ transform: translateX(100%);
107
+ }
108
+ .nav_bar_menu,
109
+ .nav_bar_buttons {
110
+ flex-direction: column;
111
+ }
112
+ .mobile_menu_button,
113
+ .mobile_close_menu {
114
+ visibility: visible;
115
+ }
116
+ }
@@ -0,0 +1,44 @@
1
+ <header class="slice_nav_header">
2
+ <a class="logo_container"></a>
3
+ <nav class="slice_nav_bar">
4
+ <div class="nav_bar_menu"></div>
5
+ <div class="nav_bar_buttons"></div>
6
+ <div class="mobile_close_menu">
7
+ <svg
8
+ class="w-6 h-6 text-gray-800 dark:text-white"
9
+ aria-hidden="true"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ width="24"
12
+ height="24"
13
+ fill="none"
14
+ viewBox="0 0 24 24"
15
+ >
16
+ <path
17
+ stroke="var(--primary-color-contrast)"
18
+ stroke-linecap="round"
19
+ stroke-linejoin="round"
20
+ stroke-width="2"
21
+ d="M6 18 17.94 6M18 18 6.06 6"
22
+ />
23
+ </svg>
24
+ </div>
25
+ </nav>
26
+ <div class="mobile_menu_button">
27
+ <svg
28
+ class="w-6 h-6 text-gray-800 dark:text-white"
29
+ aria-hidden="true"
30
+ xmlns="http://www.w3.org/2000/svg"
31
+ width="24"
32
+ height="24"
33
+ fill="none"
34
+ viewBox="0 0 24 24"
35
+ >
36
+ <path
37
+ stroke="var(--primary-color-contrast)"
38
+ stroke-linecap="round"
39
+ stroke-width="3"
40
+ d="M12 6h.01M12 12h.01M12 18h.01"
41
+ />
42
+ </svg>
43
+ </div>
44
+ </header>
@@ -0,0 +1,180 @@
1
+ export default class Navbar extends HTMLElement {
2
+
3
+ static props = {
4
+ logo: {
5
+ type: 'object',
6
+ default: null,
7
+ required: false
8
+ },
9
+ items: {
10
+ type: 'array',
11
+ default: [],
12
+ required: false
13
+ },
14
+ buttons: {
15
+ type: 'array',
16
+ default: [],
17
+ required: false
18
+ },
19
+ position: {
20
+ type: 'string',
21
+ default: 'static',
22
+ required: false
23
+ },
24
+ direction: {
25
+ type: 'string',
26
+ default: 'normal',
27
+ required: false
28
+ }
29
+ };
30
+
31
+ constructor(props) {
32
+ super();
33
+ slice.attachTemplate(this);
34
+
35
+ this.$header = this.querySelector('.slice_nav_header');
36
+ this.$navBar = this.querySelector('.slice_nav_bar');
37
+ this.$menu = this.querySelector('.nav_bar_menu');
38
+ this.$buttonsContainer = this.querySelector('.nav_bar_buttons');
39
+ this.$logoContainer = this.querySelector('.logo_container');
40
+ this.$mobileMenu = this.querySelector('.slice_mobile_menu');
41
+ this.$mobileButton = this.querySelector('.mobile_menu_button');
42
+ this.$closeMenu = this.querySelector('.mobile_close_menu');
43
+
44
+ this.$mobileButton.addEventListener('click', () => {
45
+ this.$navBar.style.transform = 'translateX(0%)';
46
+ });
47
+
48
+ this.$closeMenu.addEventListener('click', () => {
49
+ this.$navBar.style.transform = 'translateX(100%)';
50
+ });
51
+
52
+ slice.controller.setComponentProps(this, props);
53
+ }
54
+
55
+ async init() {
56
+ if (this.items && this.items.length > 0) {
57
+ await this.addItems(this.items);
58
+ }
59
+ if (this.buttons && this.buttons.length > 0) {
60
+ this.buttons.forEach(async (item) => {
61
+ await this.addButton(item, this.$buttonsContainer);
62
+ });
63
+ }
64
+
65
+ if (window.screen.width >= 1020 && this.items && this.logo && this.buttons) {
66
+ this.$menu.style.maxWidth = '60%';
67
+ }
68
+ }
69
+
70
+ async addItems(items) {
71
+ for (let i = 0; i < items.length; i++) {
72
+ await this.addItem(items[i], this.$menu);
73
+ }
74
+ }
75
+
76
+ get position() {
77
+ return this._position;
78
+ }
79
+
80
+ set position(value) {
81
+ this._position = value;
82
+ if (value === 'fixed') {
83
+ this.classList.add('nav_bar_fixed');
84
+ }
85
+ }
86
+
87
+ get logo() {
88
+ return this._logo;
89
+ }
90
+
91
+ set logo(value) {
92
+ this._logo = value;
93
+ // ✅ CORREGIDO: Validar que value no sea null antes de usarlo
94
+ if (!value) return;
95
+
96
+ const img = document.createElement('img');
97
+ img.src = value.src;
98
+ this.$logoContainer.appendChild(img);
99
+ this.$logoContainer.href = value.path;
100
+ }
101
+
102
+ get items() {
103
+ return this._items;
104
+ }
105
+
106
+ set items(values) {
107
+ this._items = values;
108
+ }
109
+
110
+ get buttons() {
111
+ return this._buttons;
112
+ }
113
+
114
+ set buttons(values) {
115
+ this._buttons = values;
116
+ }
117
+
118
+ get direction() {
119
+ return this._direction;
120
+ }
121
+
122
+ set direction(value) {
123
+ this._direction = value;
124
+ // ✅ MEJORADO: Validar valor antes de aplicar clase
125
+ if (value === 'reverse') {
126
+ this.$header.classList.add('direction-row-reverse');
127
+ }
128
+ }
129
+
130
+ async addItem(value, addTo) {
131
+ const item = document.createElement('li');
132
+ const hover = document.createElement('div');
133
+ hover.classList.add('anim-item');
134
+
135
+ if (!value.type) {
136
+ value.type = 'text';
137
+ }
138
+
139
+ if (value.type === 'text') {
140
+ const link = await slice.build('Link', {
141
+ text: value.text,
142
+ path: value.path,
143
+ classes: 'item',
144
+ });
145
+ item.appendChild(link);
146
+ }
147
+
148
+ if (value.type === 'dropdown') {
149
+ const d = await slice.build('DropDown', {
150
+ label: value.text,
151
+ options: value.options,
152
+ });
153
+ d.classList.add('item');
154
+ item.appendChild(d);
155
+ }
156
+
157
+ item.appendChild(hover);
158
+ addTo.appendChild(item);
159
+ }
160
+
161
+ async addButton(value, addTo) {
162
+ if (!value.color) {
163
+ value.color = {
164
+ text: 'var(--primary-color-rgb)',
165
+ background: 'var(--primary-background-color)',
166
+ };
167
+ }
168
+
169
+ const button = await slice.build('Button', {
170
+ value: value.value,
171
+ customColor: value.color,
172
+ icon: value.icon,
173
+ onClick: value.onClick ?? value.onClickCallback,
174
+ });
175
+
176
+ addTo.appendChild(button);
177
+ }
178
+ }
179
+
180
+ window.customElements.define('slice-nav-bar', Navbar);
@@ -0,0 +1,20 @@
1
+ export default class NotFound extends HTMLElement {
2
+
3
+ static props = {
4
+ // No props needed for this component
5
+ };
6
+
7
+ constructor(props) {
8
+ super();
9
+ slice.attachTemplate(this);
10
+
11
+ slice.controller.setComponentProps(this, props);
12
+ }
13
+
14
+ init() {
15
+ //change title of the page
16
+ document.title = '404 - Not Found';
17
+ }
18
+ }
19
+
20
+ customElements.define('slice-notfound', NotFound);
@@ -0,0 +1,181 @@
1
+ export default class Route extends HTMLElement {
2
+ constructor(props) {
3
+ super();
4
+ this.props = props;
5
+ this.rendered = false;
6
+ }
7
+
8
+ init() {
9
+ if (!this.props.path) {
10
+ this.props.path = ' ';
11
+ }
12
+
13
+ // If no component is given, derive it from routes.js (the Router's pathToRouteMap).
14
+ if (!this.props.component) {
15
+ this.props.component = slice.router.pathToRouteMap.get(this.props.path)?.component || ' ';
16
+ }
17
+ // NOTE: Route does NOT register itself in the Router. `routes.js` is the single source of
18
+ // truth for what the Router knows. The Router resolves the URL on first load / refresh /
19
+ // deep-link BEFORE this component mounts, so a path that only lived in a Route would 404
20
+ // on a direct load. Declare every path in `routes.js`.
21
+ }
22
+
23
+ get path() {
24
+ return this.props.path;
25
+ }
26
+
27
+ set path(value) {
28
+ this.props.path = value;
29
+ }
30
+
31
+ get component() {
32
+ return this.props.component;
33
+ }
34
+
35
+ set component(value) {
36
+ this.props.component = value;
37
+ }
38
+
39
+ /**
40
+ * Verifica si el path actual coincide con el path del Route
41
+ * Soporta rutas estáticas y dinámicas con parámetros ${param}
42
+ */
43
+ matchesCurrentPath() {
44
+ // Normalize trailing slash so '/about/' behaves like '/about' (keep root '/').
45
+ const raw = window.location.pathname;
46
+ const currentPath = raw.length > 1 ? raw.replace(/\/+$/, '') : raw;
47
+ const routePath = this.props.path.length > 1 ? this.props.path.replace(/\/+$/, '') : this.props.path;
48
+
49
+ // 1. Match exacto, case-insensitive ('/About' coincide con '/about')
50
+ if (routePath.toLowerCase() === currentPath.toLowerCase()) {
51
+ return { matches: true, params: {} };
52
+ }
53
+
54
+ // 2. Si la ruta tiene parámetros dinámicos ${param}
55
+ if (this.props.path.includes('${')) {
56
+ const { regex, paramNames } = this.compilePathPattern(this.props.path);
57
+ const match = currentPath.match(regex);
58
+
59
+ if (match) {
60
+ // Extraer parámetros de la URL
61
+ const params = {};
62
+ paramNames.forEach((name, i) => {
63
+ params[name] = match[i + 1];
64
+ });
65
+
66
+ return { matches: true, params };
67
+ }
68
+ }
69
+
70
+ return { matches: false, params: {} };
71
+ }
72
+
73
+ /**
74
+ * Convierte un patrón de ruta con ${param} en una expresión regular
75
+ * Ejemplo: "/user/${id}" -> /^\/user\/([^/]+)$/
76
+ */
77
+ compilePathPattern(pattern) {
78
+ const paramNames = [];
79
+ const regexPattern = '^' + pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
80
+ paramNames.push(paramName);
81
+ return '([^/]+)'; // Captura cualquier caracter excepto /
82
+ }) + '$';
83
+
84
+ return {
85
+ // 'i': case-insensitive path matching. Captured param values keep their original case.
86
+ regex: new RegExp(regexPattern, 'i'),
87
+ paramNames
88
+ };
89
+ }
90
+
91
+ async render(params = {}) {
92
+ const metadata = this.props.metadata || {};
93
+
94
+ if (Route.componentCache[this.props.component]) {
95
+ const cachedComponent = Route.componentCache[this.props.component];
96
+ this.innerHTML = '';
97
+
98
+ // Actualizar props del componente cacheado
99
+ if (cachedComponent.props) {
100
+ cachedComponent.props = {
101
+ ...cachedComponent.props,
102
+ params: params,
103
+ metadata: metadata // ✅ Incluir metadata
104
+ };
105
+ }
106
+
107
+ if (cachedComponent.update) {
108
+ await cachedComponent.update();
109
+ }
110
+
111
+ this.appendChild(cachedComponent);
112
+ } else {
113
+ if (!this.props.component) {
114
+ return;
115
+ }
116
+
117
+ if (!slice.controller.componentCategories.has(this.props.component)) {
118
+ slice.logger.logError(`${this.sliceId}`, `Component ${this.props.component} not found`);
119
+ return;
120
+ }
121
+
122
+ // Crear el componente con los parámetros y metadata de la ruta
123
+ const component = await slice.build(this.props.component, {
124
+ sliceId: this.props.component,
125
+ params: params, // ✅ Pasar los parámetros al componente
126
+ metadata: metadata // ✅ Pasar metadata al componente
127
+ });
128
+
129
+ this.innerHTML = '';
130
+ this.appendChild(component);
131
+ Route.componentCache[this.props.component] = component;
132
+ }
133
+ this.rendered = true;
134
+ }
135
+
136
+ async renderIfCurrentRoute() {
137
+ const { matches, params } = this.matchesCurrentPath();
138
+
139
+ if (matches) {
140
+ if (this.rendered) {
141
+ if (Route.componentCache[this.props.component]) {
142
+ const cachedComponent = Route.componentCache[this.props.component];
143
+
144
+ // Actualizar params y metadata en el componente cacheado
145
+ if (cachedComponent.props) {
146
+ cachedComponent.props = {
147
+ ...cachedComponent.props,
148
+ params: params,
149
+ metadata: this.props.metadata || {}
150
+ };
151
+ }
152
+
153
+ if (cachedComponent.update) {
154
+ await cachedComponent.update();
155
+ }
156
+ return true;
157
+ }
158
+ }
159
+ await this.render(params);
160
+ return true;
161
+ }
162
+ return false;
163
+ }
164
+
165
+ removeComponent() {
166
+ delete Route.componentCache[this.props.component];
167
+ this.innerHTML = '';
168
+ this.rendered = false;
169
+ }
170
+
171
+ /**
172
+ * Cleanup cuando el componente se destruye
173
+ */
174
+ destroy() {
175
+ this.removeComponent();
176
+ }
177
+ }
178
+
179
+ Route.componentCache = {};
180
+
181
+ customElements.define('slice-route', Route);