slicejs-web-framework 3.2.3 → 3.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.
Files changed (124) 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 +1129 -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 +145 -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 +530 -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/components.js +8 -27
  41. package/src/Styles/sliceStyles.css +34 -34
  42. package/src/Themes/Dark.css +42 -42
  43. package/src/Themes/Light.css +31 -31
  44. package/src/Themes/Slice.css +47 -47
  45. package/src/routes.js +9 -15
  46. package/src/sliceConfig.json +74 -73
  47. package/types/index.d.ts +207 -207
  48. package/Slice/Components/Structural/Debugger/Debugger.css +0 -620
  49. package/src/Components/AppComponents/HomePage/HomePage.css +0 -201
  50. package/src/Components/AppComponents/HomePage/HomePage.html +0 -37
  51. package/src/Components/AppComponents/HomePage/HomePage.js +0 -210
  52. package/src/Components/AppComponents/Playground/Playground.css +0 -12
  53. package/src/Components/AppComponents/Playground/Playground.html +0 -0
  54. package/src/Components/AppComponents/Playground/Playground.js +0 -111
  55. package/src/Components/Service/FetchManager/FetchManager.js +0 -133
  56. package/src/Components/Service/IndexedDbManager/IndexedDbManager.js +0 -141
  57. package/src/Components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  58. package/src/Components/Visual/Button/Button.css +0 -47
  59. package/src/Components/Visual/Button/Button.html +0 -5
  60. package/src/Components/Visual/Button/Button.js +0 -93
  61. package/src/Components/Visual/Card/Card.css +0 -68
  62. package/src/Components/Visual/Card/Card.html +0 -7
  63. package/src/Components/Visual/Card/Card.js +0 -107
  64. package/src/Components/Visual/Checkbox/Checkbox.css +0 -87
  65. package/src/Components/Visual/Checkbox/Checkbox.html +0 -8
  66. package/src/Components/Visual/Checkbox/Checkbox.js +0 -86
  67. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.css +0 -130
  68. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.html +0 -4
  69. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.js +0 -262
  70. package/src/Components/Visual/Details/Details.css +0 -70
  71. package/src/Components/Visual/Details/Details.html +0 -9
  72. package/src/Components/Visual/Details/Details.js +0 -76
  73. package/src/Components/Visual/DropDown/DropDown.css +0 -60
  74. package/src/Components/Visual/DropDown/DropDown.html +0 -5
  75. package/src/Components/Visual/DropDown/DropDown.js +0 -63
  76. package/src/Components/Visual/Grid/Grid.css +0 -7
  77. package/src/Components/Visual/Grid/Grid.html +0 -1
  78. package/src/Components/Visual/Grid/Grid.js +0 -57
  79. package/src/Components/Visual/Icon/Icon.css +0 -510
  80. package/src/Components/Visual/Icon/Icon.html +0 -1
  81. package/src/Components/Visual/Icon/Icon.js +0 -89
  82. package/src/Components/Visual/Icon/slc.eot +0 -0
  83. package/src/Components/Visual/Icon/slc.json +0 -555
  84. package/src/Components/Visual/Icon/slc.styl +0 -507
  85. package/src/Components/Visual/Icon/slc.svg +0 -1485
  86. package/src/Components/Visual/Icon/slc.symbol.svg +0 -1059
  87. package/src/Components/Visual/Icon/slc.ttf +0 -0
  88. package/src/Components/Visual/Icon/slc.woff +0 -0
  89. package/src/Components/Visual/Icon/slc.woff2 +0 -0
  90. package/src/Components/Visual/Input/Input.css +0 -91
  91. package/src/Components/Visual/Input/Input.html +0 -4
  92. package/src/Components/Visual/Input/Input.js +0 -215
  93. package/src/Components/Visual/Layout/Layout.css +0 -0
  94. package/src/Components/Visual/Layout/Layout.html +0 -0
  95. package/src/Components/Visual/Layout/Layout.js +0 -49
  96. package/src/Components/Visual/Link/Link.css +0 -8
  97. package/src/Components/Visual/Link/Link.html +0 -1
  98. package/src/Components/Visual/Link/Link.js +0 -63
  99. package/src/Components/Visual/Loading/Loading.css +0 -56
  100. package/src/Components/Visual/Loading/Loading.html +0 -83
  101. package/src/Components/Visual/Loading/Loading.js +0 -38
  102. package/src/Components/Visual/MultiRoute/MultiRoute.js +0 -93
  103. package/src/Components/Visual/Navbar/Navbar.css +0 -115
  104. package/src/Components/Visual/Navbar/Navbar.html +0 -44
  105. package/src/Components/Visual/Navbar/Navbar.js +0 -141
  106. package/src/Components/Visual/NotFound/NotFound.css +0 -117
  107. package/src/Components/Visual/NotFound/NotFound.html +0 -24
  108. package/src/Components/Visual/NotFound/NotFound.js +0 -16
  109. package/src/Components/Visual/Route/Route.js +0 -93
  110. package/src/Components/Visual/Select/Select.css +0 -84
  111. package/src/Components/Visual/Select/Select.html +0 -8
  112. package/src/Components/Visual/Select/Select.js +0 -195
  113. package/src/Components/Visual/Switch/Switch.css +0 -76
  114. package/src/Components/Visual/Switch/Switch.html +0 -8
  115. package/src/Components/Visual/Switch/Switch.js +0 -102
  116. package/src/Components/Visual/TreeItem/TreeItem.css +0 -36
  117. package/src/Components/Visual/TreeItem/TreeItem.html +0 -1
  118. package/src/Components/Visual/TreeItem/TreeItem.js +0 -126
  119. package/src/Components/Visual/TreeView/TreeView.css +0 -8
  120. package/src/Components/Visual/TreeView/TreeView.html +0 -1
  121. package/src/Components/Visual/TreeView/TreeView.js +0 -48
  122. package/src/images/Slice.js-logo.png +0 -0
  123. package/src/images/im2/Slice.js-logo.png +0 -0
  124. package/src/testing.js +0 -888
package/Slice/Slice.js CHANGED
@@ -1,542 +1,530 @@
1
-
2
- /**
3
- * Main Slice.js runtime.
4
- */
5
- export default class Slice {
6
- /**
7
- * @param {Object} sliceConfig
8
- */
9
- constructor(sliceConfig, frameworkClasses = null) {
10
- this.frameworkClasses = frameworkClasses;
11
- const ControllerClass = frameworkClasses?.Controller;
12
- const StylesManagerClass = frameworkClasses?.StylesManager;
13
-
14
- this.controller = ControllerClass ? new ControllerClass() : null;
15
- this.stylesManager = StylesManagerClass ? new StylesManagerClass() : null;
16
- this.paths = sliceConfig.paths;
17
- this.themeConfig = sliceConfig.themeManager;
18
- this.stylesConfig = sliceConfig.stylesManager;
19
- this.loggerConfig = sliceConfig.logger;
20
- this.debuggerConfig = sliceConfig.debugger;
21
- this.loadingConfig = sliceConfig.loading;
22
- this.eventsConfig = sliceConfig.events;
23
-
24
- // Default to production until init() resolves the actual mode.
25
- // Safe to call isProduction() before init() completes.
26
- this._mode = 'production';
27
- this._publicEnv = {};
28
-
29
- // 📦 Bundle system is initialized automatically via import in index.js
30
- }
31
-
32
- /**
33
- * Dynamically import a module and return its default export.
34
- * @param {string} module
35
- * @returns {Promise<any>}
36
- */
37
- async getClass(module) {
38
- try {
39
- const { default: myClass } = await import(module);
40
- return await myClass;
41
- } catch (error) {
42
- this.logger.logError('Slice', `Error loading class ${module}`, error);
43
- }
44
- }
45
-
46
- /**
47
- * Returns true when running in production mode.
48
- * Reliable after init() has completed.
49
- * @returns {boolean}
50
- */
51
- isProduction() {
52
- return this._mode === 'production';
53
- }
54
-
55
- setPublicEnv(envPayload = {}) {
56
- const normalized = {};
57
-
58
- for (const [key, value] of Object.entries(envPayload || {})) {
59
- if (!key.startsWith('SLICE_PUBLIC_')) continue;
60
- normalized[key] = String(value ?? '');
61
- }
62
-
63
- this._publicEnv = normalized;
64
- }
65
-
66
- getEnv(name, fallbackValue = undefined) {
67
- if (!name || typeof name !== 'string') {
68
- return fallbackValue;
69
- }
70
-
71
- if (Object.prototype.hasOwnProperty.call(this._publicEnv, name)) {
72
- return this._publicEnv[name];
73
- }
74
-
75
- return fallbackValue;
76
- }
77
-
78
- getPublicEnv() {
79
- return { ...this._publicEnv };
80
- }
81
-
82
- /**
83
- * Get a component instance by sliceId.
84
- * @param {string} componentSliceId
85
- * @returns {HTMLElement|undefined}
86
- */
87
- getComponent(componentSliceId) {
88
- return this.controller.activeComponents.get(componentSliceId);
89
- }
90
-
91
- /**
92
- * Build a component instance and run init.
93
- * @param {string} componentName
94
- * @param {Object} [props]
95
- * @returns {Promise<HTMLElement|Object|null>}
96
- */
97
- async build(componentName, props = {}) {
98
- if (!componentName) {
99
- this.logger.logError('Slice', null, `Component name is required to build a component`);
100
- return null;
101
- }
102
-
103
- if (typeof componentName !== 'string') {
104
- this.logger.logError('Slice', null, `Component name must be a string`);
105
- return null;
106
- }
107
-
108
- // 📦 Try to load from bundles first
109
- const bundleName = this.controller.getBundleForComponent(componentName);
110
- if (bundleName && !this.controller.loadedBundles.has(bundleName)) {
111
- await this.controller.loadBundle(bundleName);
112
- }
113
-
114
- // After bundle loading attempt, allow build when class is already available
115
- // even if components map has no category entry (stale/components.js mismatch).
116
- if (!this.controller.componentCategories.has(componentName) && !this.controller.classes.has(componentName)) {
117
- this.logger.logError('Slice', null, `Component ${componentName} not found in components.js file`);
118
- return null;
119
- }
120
-
121
- let componentCategory = this.controller.componentCategories.get(componentName);
122
- if (!componentCategory && this.controller.classes.has(componentName)) {
123
- componentCategory = 'AppComponents';
124
- }
125
-
126
- // 📦 Check if component is already available from loaded bundles
127
- const isFromBundle = this.controller.isComponentFromBundle(componentName);
128
-
129
- if (componentCategory === 'Structural') {
130
- this.logger.logError(
131
- 'Slice',
132
- null,
133
- `Component ${componentName} is a Structural component and cannot be built`
134
- );
135
- return null;
136
- }
137
-
138
- let isVisual = slice.paths.components[componentCategory]?.type === 'Visual';
139
- let modulePath = `${slice.paths.components[componentCategory].path}/${componentName}/${componentName}.js`;
140
- const isJsOnlyVisualComponent = isVisual && (componentName === 'MultiRoute' || componentName === 'Route');
141
-
142
- // Load template, class, and CSS concurrently if needed
143
- try {
144
- // 📦 Skip individual loading if component is available from bundles
145
- const loadTemplate =
146
- isFromBundle || !isVisual || isJsOnlyVisualComponent || this.controller.templates.has(componentName)
147
- ? Promise.resolve(null)
148
- : this.controller.fetchText(componentName, 'html', componentCategory);
149
-
150
- const loadClass =
151
- isFromBundle || this.controller.classes.has(componentName)
152
- ? Promise.resolve(null)
153
- : this.getClass(modulePath);
154
-
155
- const loadCSS =
156
- isFromBundle || !isVisual || isJsOnlyVisualComponent || this.controller.requestedStyles.has(componentName)
157
- ? Promise.resolve(null)
158
- : this.controller.fetchText(componentName, 'css', componentCategory);
159
-
160
- const [html, ComponentClass, css] = await Promise.all([loadTemplate, loadClass, loadCSS]);
161
-
162
- // 📦 If component is from bundle but not in cache, it should have been registered by registerBundle
163
- if (isFromBundle) {
164
- console.log(`📦 Using bundled component: ${componentName}`);
165
- }
166
-
167
- if (html || html === '') {
168
- const template = document.createElement('template');
169
- template.innerHTML = html;
170
- this.controller.templates.set(componentName, template);
171
- this.logger.logInfo('Slice', `Template ${componentName} loaded`);
172
- }
173
-
174
- if (ComponentClass) {
175
- this.controller.classes.set(componentName, ComponentClass);
176
- this.logger.logInfo('Slice', `Class ${componentName} loaded`);
177
- }
178
-
179
- if (css) {
180
- this.stylesManager.registerComponentStyles(componentName, css);
181
- this.logger.logInfo('Slice', `CSS ${componentName} loaded`);
182
- }
183
- } catch (error) {
184
- console.log(error);
185
- this.logger.logError('Slice', `Error loading resources for ${componentName}`, error);
186
- return null;
187
- }
188
-
189
- // Create instance
190
- try {
191
- let componentIds = {};
192
- if (props.id) componentIds.id = props.id;
193
- if (props.sliceId) componentIds.sliceId = props.sliceId;
194
-
195
- delete props.id;
196
- delete props.sliceId;
197
-
198
- const ComponentClass = this.controller.classes.get(componentName);
199
- if (componentName === 'Loading') {
200
- console.log('🔎 Build component: Loading', {
201
- classType: typeof ComponentClass,
202
- isFunction: typeof ComponentClass === 'function',
203
- classValue: ComponentClass
204
- });
205
- }
206
- if (componentName === 'InputSearchDocs' || componentName === 'MainMenu') {
207
- console.log(`🔎 Build component: ${componentName}`, {
208
- classType: typeof ComponentClass,
209
- isFunction: typeof ComponentClass === 'function',
210
- classValue: ComponentClass
211
- });
212
- }
213
- const componentInstance = new ComponentClass(props);
214
-
215
- if (componentIds.id && isVisual) componentInstance.id = componentIds.id;
216
- if (componentIds.sliceId) componentInstance.sliceId = componentIds.sliceId;
217
-
218
- if (!this.controller.verifyComponentIds(componentInstance)) {
219
- this.logger.logError('Slice', `Error registering instance ${componentName} ${componentInstance.sliceId}`);
220
- return null;
221
- }
222
-
223
- if (componentInstance.init) await componentInstance.init();
224
-
225
- if (slice.debuggerConfig.enabled && isVisual) {
226
- this.debugger.attachDebugMode(componentInstance);
227
- }
228
-
229
- this.controller.registerComponent(componentInstance);
230
- if (isVisual) {
231
- this.controller.registerComponentsRecursively(componentInstance);
232
- }
233
-
234
- this.logger.logInfo('Slice', `Instance ${componentInstance.sliceId} created`);
235
- return componentInstance;
236
- } catch (error) {
237
- console.log(error);
238
- this.logger.logError('Slice', `Error creating instance ${componentName}`, error);
239
- return null;
240
- }
241
- }
242
-
243
- /**
244
- * Apply a theme by name.
245
- * @param {string} themeName
246
- * @returns {Promise<void>}
247
- */
248
- async setTheme(themeName) {
249
- await this.stylesManager.themeManager.applyTheme(themeName);
250
- }
251
-
252
- /**
253
- * Current theme name.
254
- * @returns {string|null}
255
- */
256
- get theme() {
257
- return this.stylesManager.themeManager.currentTheme;
258
- }
259
-
260
- /**
261
- * Attach HTML template to a component instance.
262
- * @param {HTMLElement} componentInstance
263
- * @returns {void}
264
- */
265
- attachTemplate(componentInstance) {
266
- this.controller.loadTemplateToComponent(componentInstance);
267
- }
268
- }
269
-
270
- async function loadConfig() {
271
- try {
272
- const response = await fetch('/sliceConfig.json'); // 🔹 Express lo sirve desde `src/`
273
- if (!response.ok) throw new Error('Error loading sliceConfig.json');
274
- const json = await response.json();
275
- return json;
276
- } catch (error) {
277
- console.error(`Error loading config file: ${error.message}`);
278
- return null;
279
- }
280
- }
281
-
282
- async function init() {
283
- const sliceConfig = await loadConfig();
284
- if (!sliceConfig) {
285
- //Display error message in console with colors and alert in english
286
- console.error('%c⛔️ Error loading Slice configuration ⛔️', 'color: red; font-size: 20px;');
287
- alert('Error loading Slice configuration');
288
- return;
289
- }
290
-
291
- // 1+2. Fetch mode endpoint and bundle config in parallel — both are independent.
292
- // In production, /slice-env.json returns 404 (catch is expected and normal).
293
- // bundleConfigJson.production serves as a mode fallback when env endpoint is absent.
294
- let frameworkClasses = null;
295
- const [envResult, configResult] = await Promise.all([
296
- fetch('/slice-env.json', { cache: 'no-store' })
297
- .then(r => r.ok ? r.json() : null)
298
- .catch(() => null),
299
- fetch('/bundles/bundle.config.json', { cache: 'no-store' })
300
- .then(r => r.ok ? r.json() : null)
301
- .catch(() => null)
302
- ]);
303
- const envMode = envResult?.mode ?? null;
304
- const bundleConfigJson = configResult;
305
-
306
- // 3. Determine canonical mode: env endpoint takes precedence, then bundle config
307
- let resolvedMode;
308
- if (envMode) {
309
- resolvedMode = envMode;
310
- } else if (bundleConfigJson?.production) {
311
- resolvedMode = 'production';
312
- } else {
313
- resolvedMode = 'development';
314
- }
315
-
316
- // 4. Load framework classes.
317
- // In production the bundler generates slice-bundle.framework.js which
318
- // sets window.SLICE_FRAMEWORK_CLASSES. In dev mode always use individual
319
- // imports so the live /Slice/ source is served directly without bundles.
320
- if (resolvedMode === 'production' && bundleConfigJson?.bundles?.framework?.file) {
321
- try {
322
- await import(`/bundles/${bundleConfigJson.bundles.framework.file}`);
323
- if (window.SLICE_FRAMEWORK_CLASSES) {
324
- frameworkClasses = window.SLICE_FRAMEWORK_CLASSES;
325
- }
326
- } catch (e) {
327
- // framework bundle failed — fall through to individual imports
328
- console.error('[Slice.js] framework bundle import failed:', e?.message || e);
329
- }
330
- }
331
-
332
- if (!frameworkClasses) {
333
- try {
334
- const imports = await Promise.all([
335
- import('./Components/Structural/Controller/Controller.js'),
336
- import('./Components/Structural/StylesManager/StylesManager.js')
337
- ]);
338
- frameworkClasses = {
339
- Controller: imports[0].default,
340
- StylesManager: imports[1].default
341
- };
342
- } catch (e) {
343
- console.error('[Slice.js] individual imports fallback failed:', e?.message || e);
344
- throw e;
345
- }
346
- }
347
-
348
- // 5. Create Slice instance and set resolved mode
349
- window.slice = new Slice(sliceConfig, frameworkClasses);
350
- window.slice._mode = resolvedMode;
351
- window.slice.setPublicEnv(envResult?.env || {});
352
-
353
- const createBundlingInitError = (step, error) => {
354
- const detail = error instanceof Error ? error.message : String(error);
355
- return new Error(`Bundling V2 initialization failed (${step}): ${detail}`, { cause: error });
356
- };
357
-
358
- // Initialize bundles before building components.
359
- // Only in production — dev mode loads each component individually from source.
360
- // bundleConfigJson was already fetched above (step 2); reuse it.
361
- if (resolvedMode === 'production' && bundleConfigJson) {
362
- window.slice.controller.bundleConfig = bundleConfigJson;
363
- }
364
-
365
- if (resolvedMode === 'production' && window.slice.controller.bundleConfig) {
366
- const config = window.slice.controller.bundleConfig;
367
- if (!window.__SLICE_SHARED_DEPS__ || typeof window.__SLICE_SHARED_DEPS__ !== 'object') {
368
- window.__SLICE_SHARED_DEPS__ = {};
369
- }
370
- const criticalFile = config?.bundles?.critical?.file;
371
- if (criticalFile) {
372
- try {
373
- await window.slice.controller.loadBundle('critical');
374
- } catch (error) {
375
- throw createBundlingInitError(`critical bundle "${criticalFile}"`, error);
376
- }
377
- }
378
-
379
- const routeBundles = config?.routeBundles || {};
380
- const initialPath = window.location.pathname || '/';
381
- const bundlesForRoute = routeBundles[initialPath] || [];
382
-
383
- const loadRouteBundles = async () => {
384
- for (const bundleName of bundlesForRoute) {
385
- if (bundleName === 'critical') continue;
386
- const bundleInfo = config?.bundles?.routes?.[bundleName];
387
- if (!bundleInfo?.file) continue;
388
- await window.slice.controller.loadBundle(bundleName);
389
- }
390
- };
391
-
392
- const preloadRouteBundles = () => {
393
- loadRouteBundles().catch((error) => {
394
- const bundlingError = createBundlingInitError(
395
- `idle route preload "${initialPath}"`,
396
- error
397
- );
398
- queueMicrotask(() => {
399
- throw bundlingError;
400
- });
401
- });
402
- };
403
-
404
- if (typeof requestIdleCallback === 'function') {
405
- requestIdleCallback(() => preloadRouteBundles());
406
- } else {
407
- setTimeout(() => preloadRouteBundles(), 0);
408
- }
409
- }
410
-
411
- slice.paths.structuralComponentFolderPath = '/Slice/Components/Structural';
412
-
413
- if (sliceConfig.logger.enabled) {
414
- const LoggerModule = window.slice.frameworkClasses?.Logger
415
- || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Logger/Logger.js`);
416
- window.slice.logger = new LoggerModule();
417
- } else {
418
- window.slice.logger = {
419
- logError: () => {},
420
- logWarning: () => {},
421
- logInfo: () => {},
422
- };
423
- }
424
-
425
- if (sliceConfig.debugger.enabled) {
426
- const DebuggerModule = window.slice.frameworkClasses?.Debugger
427
- || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Debugger/Debugger.js`);
428
- window.slice.debugger = new DebuggerModule();
429
- await window.slice.debugger.enableDebugMode();
430
- document.body.appendChild(window.slice.debugger);
431
- }
432
-
433
- if (sliceConfig.events?.ui?.enabled) {
434
- const EventsDebuggerModule = window.slice.frameworkClasses?.EventManagerDebugger
435
- || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/EventManager/EventManagerDebugger.js`);
436
- window.slice.eventsDebugger = new EventsDebuggerModule();
437
- await window.slice.eventsDebugger.init();
438
- document.body.appendChild(window.slice.eventsDebugger);
439
- }
440
-
441
- if (sliceConfig.context?.ui?.enabled) {
442
- const ContextDebuggerModule = window.slice.frameworkClasses?.ContextManagerDebugger
443
- || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/ContextManager/ContextManagerDebugger.js`);
444
- window.slice.contextDebugger = new ContextDebuggerModule();
445
- await window.slice.contextDebugger.init();
446
- document.body.appendChild(window.slice.contextDebugger);
447
- }
448
-
449
- if (sliceConfig.events?.enabled) {
450
- const EventManagerModule = window.slice.frameworkClasses?.EventManager
451
- || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/EventManager/EventManager.js`);
452
- window.slice.events = new EventManagerModule();
453
- if (typeof window.slice.events.init === 'function') {
454
- await window.slice.events.init();
455
- }
456
- } else {
457
- window.slice.events = {
458
- subscribe: () => null,
459
- subscribeOnce: () => null,
460
- unsubscribe: () => false,
461
- emit: () => {},
462
- bind: () => ({
463
- subscribe: () => null,
464
- subscribeOnce: () => null,
465
- emit: () => {},
466
- }),
467
- cleanupComponent: () => 0,
468
- hasSubscribers: () => false,
469
- subscriberCount: () => 0,
470
- clear: () => {},
471
- };
472
- window.slice.logger.logError('Slice', 'EventManager disabled');
473
- }
474
-
475
- if (sliceConfig.context?.enabled) {
476
- const ContextManagerModule = window.slice.frameworkClasses?.ContextManager
477
- || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/ContextManager/ContextManager.js`);
478
- window.slice.context = new ContextManagerModule();
479
- if (typeof window.slice.context.init === 'function') {
480
- await window.slice.context.init();
481
- }
482
- } else {
483
- window.slice.context = {
484
- create: () => false,
485
- getState: () => null,
486
- setState: () => {},
487
- watch: () => null,
488
- has: () => false,
489
- destroy: () => false,
490
- list: () => [],
491
- };
492
- window.slice.logger.logError('Slice', 'ContextManager disabled');
493
- }
494
-
495
- if (sliceConfig.loading.enabled) {
496
- const loading = await window.slice.build('Loading', {});
497
- window.slice.loading = loading;
498
- if (typeof loading?.start === 'function') {
499
- loading.start();
500
- }
501
- }
502
-
503
- const stylesInitPromise = window.slice.stylesManager.init();
504
- const routesModulePromise = import(slice.paths.routesFile);
505
-
506
- if (sliceConfig.events?.ui?.shortcut || sliceConfig.context?.ui?.shortcut) {
507
- const normalize = (value) => (typeof value === 'string' ? value.toLowerCase() : '');
508
- const toKey = (event) => {
509
- const parts = [];
510
- if (event.ctrlKey) parts.push('ctrl');
511
- if (event.shiftKey) parts.push('shift');
512
- if (event.altKey) parts.push('alt');
513
- if (event.metaKey) parts.push('meta');
514
- const key = event.key?.toLowerCase();
515
- if (key && !['control', 'shift', 'alt', 'meta'].includes(key)) {
516
- parts.push(key);
517
- }
518
- return parts.join('+');
519
- };
520
-
521
- const handlers = {
522
- [normalize(sliceConfig.events?.ui?.shortcut)]: () => window.slice.eventsDebugger?.toggle?.(),
523
- [normalize(sliceConfig.context?.ui?.shortcut)]: () => window.slice.contextDebugger?.toggle?.(),
524
- };
525
-
526
- document.addEventListener('keydown', (event) => {
527
- const key = toKey(event);
528
- if (!key || !handlers[key]) return;
529
- event.preventDefault();
530
- handlers[key]();
531
- });
532
- }
533
-
534
- const [, routesModule] = await Promise.all([stylesInitPromise, routesModulePromise]);
535
- const routes = routesModule.default;
536
- const RouterModule = window.slice.frameworkClasses?.Router
537
- || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Router/Router.js`);
538
- window.slice.router = new RouterModule(routes);
539
- await window.slice.router.init();
540
- }
541
-
542
- await init();
1
+
2
+ /**
3
+ * Main Slice.js runtime.
4
+ */
5
+ export default class Slice {
6
+ /**
7
+ * @param {Object} sliceConfig
8
+ */
9
+ constructor(sliceConfig, frameworkClasses = null) {
10
+ this.frameworkClasses = frameworkClasses;
11
+ const ControllerClass = frameworkClasses?.Controller;
12
+ const StylesManagerClass = frameworkClasses?.StylesManager;
13
+
14
+ this.controller = ControllerClass ? new ControllerClass() : null;
15
+ this.stylesManager = StylesManagerClass ? new StylesManagerClass() : null;
16
+ this.paths = sliceConfig.paths;
17
+ this.themeConfig = sliceConfig.themeManager;
18
+ this.stylesConfig = sliceConfig.stylesManager;
19
+ this.loggerConfig = sliceConfig.logger;
20
+ this.debuggerConfig = sliceConfig.debugger;
21
+ this.loadingConfig = sliceConfig.loading;
22
+ this.eventsConfig = sliceConfig.events;
23
+
24
+ // Default to production until init() resolves the actual mode.
25
+ // Safe to call isProduction() before init() completes.
26
+ this._mode = 'production';
27
+ this._publicEnv = {};
28
+
29
+ // 📦 Bundle system is initialized automatically via import in index.js
30
+ }
31
+
32
+ /**
33
+ * Dynamically import a module and return its default export.
34
+ * @param {string} module
35
+ * @returns {Promise<any>}
36
+ */
37
+ async getClass(module) {
38
+ try {
39
+ const { default: myClass } = await import(module);
40
+ return await myClass;
41
+ } catch (error) {
42
+ this.logger.logError('Slice', `Error loading class ${module}`, error);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Returns true when running in production mode.
48
+ * Reliable after init() has completed.
49
+ * @returns {boolean}
50
+ */
51
+ isProduction() {
52
+ return this._mode === 'production';
53
+ }
54
+
55
+ setPublicEnv(envPayload = {}) {
56
+ const normalized = {};
57
+
58
+ for (const [key, value] of Object.entries(envPayload || {})) {
59
+ if (!key.startsWith('SLICE_PUBLIC_')) continue;
60
+ normalized[key] = String(value ?? '');
61
+ }
62
+
63
+ this._publicEnv = normalized;
64
+ }
65
+
66
+ getEnv(name, fallbackValue = undefined) {
67
+ if (!name || typeof name !== 'string') {
68
+ return fallbackValue;
69
+ }
70
+
71
+ if (Object.prototype.hasOwnProperty.call(this._publicEnv, name)) {
72
+ return this._publicEnv[name];
73
+ }
74
+
75
+ return fallbackValue;
76
+ }
77
+
78
+ getPublicEnv() {
79
+ return { ...this._publicEnv };
80
+ }
81
+
82
+ /**
83
+ * Get a component instance by sliceId.
84
+ * @param {string} componentSliceId
85
+ * @returns {HTMLElement|undefined}
86
+ */
87
+ getComponent(componentSliceId) {
88
+ return this.controller.activeComponents.get(componentSliceId);
89
+ }
90
+
91
+ /**
92
+ * Build a component instance and run init.
93
+ * @param {string} componentName
94
+ * @param {Object} [props]
95
+ * @returns {Promise<HTMLElement|Object|null>}
96
+ */
97
+ async build(componentName, props = {}) {
98
+ if (!componentName) {
99
+ this.logger.logError('Slice', null, `Component name is required to build a component`);
100
+ return null;
101
+ }
102
+
103
+ if (typeof componentName !== 'string') {
104
+ this.logger.logError('Slice', null, `Component name must be a string`);
105
+ return null;
106
+ }
107
+
108
+ // 📦 Try to load from bundles first
109
+ const bundleName = this.controller.getBundleForComponent(componentName);
110
+ if (bundleName && !this.controller.loadedBundles.has(bundleName)) {
111
+ await this.controller.loadBundle(bundleName);
112
+ }
113
+
114
+ // After bundle loading attempt, allow build when class is already available
115
+ // even if components map has no category entry (stale/components.js mismatch).
116
+ if (!this.controller.componentCategories.has(componentName) && !this.controller.classes.has(componentName)) {
117
+ this.logger.logError('Slice', null, `Component ${componentName} not found in components.js file`);
118
+ return null;
119
+ }
120
+
121
+ let componentCategory = this.controller.componentCategories.get(componentName);
122
+ if (!componentCategory && this.controller.classes.has(componentName)) {
123
+ componentCategory = 'AppComponents';
124
+ }
125
+
126
+ // 📦 Check if component is already available from loaded bundles
127
+ const isFromBundle = this.controller.isComponentFromBundle(componentName);
128
+
129
+ if (componentCategory === 'Structural') {
130
+ this.logger.logError(
131
+ 'Slice',
132
+ null,
133
+ `Component ${componentName} is a Structural component and cannot be built`
134
+ );
135
+ return null;
136
+ }
137
+
138
+ let isVisual = slice.paths.components[componentCategory]?.type === 'Visual';
139
+ let modulePath = `${slice.paths.components[componentCategory].path}/${componentName}/${componentName}.js`;
140
+ const isJsOnlyVisualComponent = isVisual && (componentName === 'MultiRoute' || componentName === 'Route');
141
+
142
+ // Load template, class, and CSS concurrently if needed
143
+ try {
144
+ // 📦 Skip individual loading if component is available from bundles
145
+ const loadTemplate =
146
+ isFromBundle || !isVisual || isJsOnlyVisualComponent || this.controller.templates.has(componentName)
147
+ ? Promise.resolve(null)
148
+ : this.controller.fetchText(componentName, 'html', componentCategory);
149
+
150
+ const loadClass =
151
+ isFromBundle || this.controller.classes.has(componentName)
152
+ ? Promise.resolve(null)
153
+ : this.getClass(modulePath);
154
+
155
+ const loadCSS =
156
+ isFromBundle || !isVisual || isJsOnlyVisualComponent || this.controller.requestedStyles.has(componentName)
157
+ ? Promise.resolve(null)
158
+ : this.controller.fetchText(componentName, 'css', componentCategory);
159
+
160
+ const [html, ComponentClass, css] = await Promise.all([loadTemplate, loadClass, loadCSS]);
161
+
162
+ // 📦 If component is from bundle but not in cache, it should have been registered by registerBundle
163
+ if (isFromBundle) {
164
+ this.logger.logInfo('Slice', `📦 Using bundled component: ${componentName}`);
165
+ }
166
+
167
+ if (html || html === '') {
168
+ const template = document.createElement('template');
169
+ template.innerHTML = html;
170
+ this.controller.templates.set(componentName, template);
171
+ this.logger.logInfo('Slice', `Template ${componentName} loaded`);
172
+ }
173
+
174
+ if (ComponentClass) {
175
+ this.controller.classes.set(componentName, ComponentClass);
176
+ this.logger.logInfo('Slice', `Class ${componentName} loaded`);
177
+ }
178
+
179
+ if (css) {
180
+ this.stylesManager.registerComponentStyles(componentName, css);
181
+ this.logger.logInfo('Slice', `CSS ${componentName} loaded`);
182
+ }
183
+ } catch (error) {
184
+ this.logger.logError('Slice', `Error loading resources for ${componentName}`, error);
185
+ return null;
186
+ }
187
+
188
+ // Create instance
189
+ try {
190
+ let componentIds = {};
191
+ if (props.id) componentIds.id = props.id;
192
+ if (props.sliceId) componentIds.sliceId = props.sliceId;
193
+
194
+ delete props.id;
195
+ delete props.sliceId;
196
+
197
+ const ComponentClass = this.controller.classes.get(componentName);
198
+ this.logger.logInfo(
199
+ 'Slice',
200
+ `🔎 Build component: ${componentName} (classType: ${typeof ComponentClass}, isFunction: ${typeof ComponentClass === 'function'})`
201
+ );
202
+ const componentInstance = new ComponentClass(props);
203
+
204
+ if (componentIds.id && isVisual) componentInstance.id = componentIds.id;
205
+ if (componentIds.sliceId) componentInstance.sliceId = componentIds.sliceId;
206
+
207
+ if (!this.controller.verifyComponentIds(componentInstance)) {
208
+ this.logger.logError('Slice', `Error registering instance ${componentName} ${componentInstance.sliceId}`);
209
+ return null;
210
+ }
211
+
212
+ if (componentInstance.init) await componentInstance.init();
213
+
214
+ if (slice.debuggerConfig.enabled && isVisual) {
215
+ this.debugger.attachDebugMode(componentInstance);
216
+ }
217
+
218
+ this.controller.registerComponent(componentInstance);
219
+ if (isVisual) {
220
+ this.controller.registerComponentsRecursively(componentInstance);
221
+ }
222
+
223
+ this.logger.logInfo('Slice', `Instance ${componentInstance.sliceId} created`);
224
+ return componentInstance;
225
+ } catch (error) {
226
+ this.logger.logError('Slice', `Error creating instance ${componentName}`, error);
227
+ return null;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Apply a theme by name.
233
+ * @param {string} themeName
234
+ * @returns {Promise<void>}
235
+ */
236
+ async setTheme(themeName) {
237
+ await this.stylesManager.themeManager.applyTheme(themeName);
238
+ }
239
+
240
+ /**
241
+ * Current theme name.
242
+ * @returns {string|null}
243
+ */
244
+ get theme() {
245
+ return this.stylesManager.themeManager.currentTheme;
246
+ }
247
+
248
+ /**
249
+ * Attach HTML template to a component instance.
250
+ * @param {HTMLElement} componentInstance
251
+ * @returns {void}
252
+ */
253
+ attachTemplate(componentInstance) {
254
+ this.controller.loadTemplateToComponent(componentInstance);
255
+ }
256
+ }
257
+
258
+ async function loadConfig() {
259
+ try {
260
+ const response = await fetch('/sliceConfig.json'); // 🔹 Express lo sirve desde `src/`
261
+ if (!response.ok) throw new Error('Error loading sliceConfig.json');
262
+ const json = await response.json();
263
+ return json;
264
+ } catch (error) {
265
+ console.error(`Error loading config file: ${error.message}`);
266
+ return null;
267
+ }
268
+ }
269
+
270
+ async function init() {
271
+ const sliceConfig = await loadConfig();
272
+ if (!sliceConfig) {
273
+ //Display error message in console with colors and alert in english
274
+ console.error('%c⛔️ Error loading Slice configuration ⛔️', 'color: red; font-size: 20px;');
275
+ alert('Error loading Slice configuration');
276
+ return;
277
+ }
278
+
279
+ // 1+2. Fetch mode endpoint and bundle config in parallel — both are independent.
280
+ // In production, /slice-env.json returns 404 (catch is expected and normal).
281
+ // bundleConfigJson.production serves as a mode fallback when env endpoint is absent.
282
+ let frameworkClasses = null;
283
+ const [envResult, configResult] = await Promise.all([
284
+ fetch('/slice-env.json', { cache: 'no-store' })
285
+ .then(r => r.ok ? r.json() : null)
286
+ .catch(() => null),
287
+ fetch('/bundles/bundle.config.json', { cache: 'no-store' })
288
+ .then(r => r.ok ? r.json() : null)
289
+ .catch(() => null)
290
+ ]);
291
+ const envMode = envResult?.mode ?? null;
292
+ const bundleConfigJson = configResult;
293
+
294
+ // 3. Determine canonical mode: env endpoint takes precedence, then bundle config
295
+ let resolvedMode;
296
+ if (envMode) {
297
+ resolvedMode = envMode;
298
+ } else if (bundleConfigJson?.production) {
299
+ resolvedMode = 'production';
300
+ } else {
301
+ resolvedMode = 'development';
302
+ }
303
+
304
+ // 4. Load framework classes.
305
+ // In production the bundler generates slice-bundle.framework.js which
306
+ // sets window.SLICE_FRAMEWORK_CLASSES. In dev mode always use individual
307
+ // imports so the live /Slice/ source is served directly without bundles.
308
+ if (resolvedMode === 'production' && bundleConfigJson?.bundles?.framework?.file) {
309
+ try {
310
+ await import(`/bundles/${bundleConfigJson.bundles.framework.file}`);
311
+ if (window.SLICE_FRAMEWORK_CLASSES) {
312
+ frameworkClasses = window.SLICE_FRAMEWORK_CLASSES;
313
+ }
314
+ } catch (e) {
315
+ // framework bundle failed — fall through to individual imports
316
+ console.error('[Slice.js] framework bundle import failed:', e?.message || e);
317
+ }
318
+ }
319
+
320
+ if (!frameworkClasses) {
321
+ try {
322
+ const imports = await Promise.all([
323
+ import('./Components/Structural/Controller/Controller.js'),
324
+ import('./Components/Structural/StylesManager/StylesManager.js')
325
+ ]);
326
+ frameworkClasses = {
327
+ Controller: imports[0].default,
328
+ StylesManager: imports[1].default
329
+ };
330
+ } catch (e) {
331
+ console.error('[Slice.js] individual imports fallback failed:', e?.message || e);
332
+ throw e;
333
+ }
334
+ }
335
+
336
+ // 5. Create Slice instance and set resolved mode
337
+ window.slice = new Slice(sliceConfig, frameworkClasses);
338
+ window.slice._mode = resolvedMode;
339
+ window.slice.setPublicEnv(envResult?.env || {});
340
+
341
+ const createBundlingInitError = (step, error) => {
342
+ const detail = error instanceof Error ? error.message : String(error);
343
+ return new Error(`Bundling V2 initialization failed (${step}): ${detail}`, { cause: error });
344
+ };
345
+
346
+ // Initialize bundles before building components.
347
+ // Only in production — dev mode loads each component individually from source.
348
+ // bundleConfigJson was already fetched above (step 2); reuse it.
349
+ if (resolvedMode === 'production' && bundleConfigJson) {
350
+ window.slice.controller.bundleConfig = bundleConfigJson;
351
+ }
352
+
353
+ if (resolvedMode === 'production' && window.slice.controller.bundleConfig) {
354
+ const config = window.slice.controller.bundleConfig;
355
+ if (!window.__SLICE_SHARED_DEPS__ || typeof window.__SLICE_SHARED_DEPS__ !== 'object') {
356
+ window.__SLICE_SHARED_DEPS__ = {};
357
+ }
358
+ const criticalFile = config?.bundles?.critical?.file;
359
+ if (criticalFile) {
360
+ try {
361
+ await window.slice.controller.loadBundle('critical');
362
+ } catch (error) {
363
+ throw createBundlingInitError(`critical bundle "${criticalFile}"`, error);
364
+ }
365
+ }
366
+
367
+ const routeBundles = config?.routeBundles || {};
368
+ const initialPath = window.location.pathname || '/';
369
+ const bundlesForRoute = routeBundles[initialPath] || [];
370
+
371
+ const loadRouteBundles = async () => {
372
+ for (const bundleName of bundlesForRoute) {
373
+ if (bundleName === 'critical') continue;
374
+ const bundleInfo = config?.bundles?.routes?.[bundleName];
375
+ if (!bundleInfo?.file) continue;
376
+ await window.slice.controller.loadBundle(bundleName);
377
+ }
378
+ };
379
+
380
+ const preloadRouteBundles = () => {
381
+ loadRouteBundles().catch((error) => {
382
+ const bundlingError = createBundlingInitError(
383
+ `idle route preload "${initialPath}"`,
384
+ error
385
+ );
386
+ queueMicrotask(() => {
387
+ throw bundlingError;
388
+ });
389
+ });
390
+ };
391
+
392
+ if (typeof requestIdleCallback === 'function') {
393
+ requestIdleCallback(() => preloadRouteBundles());
394
+ } else {
395
+ setTimeout(() => preloadRouteBundles(), 0);
396
+ }
397
+ }
398
+
399
+ slice.paths.structuralComponentFolderPath = '/Slice/Components/Structural';
400
+
401
+ if (sliceConfig.logger.enabled) {
402
+ const LoggerModule = window.slice.frameworkClasses?.Logger
403
+ || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Logger/Logger.js`);
404
+ window.slice.logger = new LoggerModule();
405
+ } else {
406
+ window.slice.logger = {
407
+ logError: () => {},
408
+ logWarning: () => {},
409
+ logInfo: () => {},
410
+ };
411
+ }
412
+
413
+ if (sliceConfig.debugger.enabled) {
414
+ const DebuggerModule = window.slice.frameworkClasses?.Debugger
415
+ || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Debugger/Debugger.js`);
416
+ window.slice.debugger = new DebuggerModule();
417
+ await window.slice.debugger.enableDebugMode();
418
+ document.body.appendChild(window.slice.debugger);
419
+ }
420
+
421
+ if (sliceConfig.events?.ui?.enabled) {
422
+ const EventsDebuggerModule = window.slice.frameworkClasses?.EventManagerDebugger
423
+ || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/EventManager/EventManagerDebugger.js`);
424
+ window.slice.eventsDebugger = new EventsDebuggerModule();
425
+ await window.slice.eventsDebugger.init();
426
+ document.body.appendChild(window.slice.eventsDebugger);
427
+ }
428
+
429
+ if (sliceConfig.context?.ui?.enabled) {
430
+ const ContextDebuggerModule = window.slice.frameworkClasses?.ContextManagerDebugger
431
+ || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/ContextManager/ContextManagerDebugger.js`);
432
+ window.slice.contextDebugger = new ContextDebuggerModule();
433
+ await window.slice.contextDebugger.init();
434
+ document.body.appendChild(window.slice.contextDebugger);
435
+ }
436
+
437
+ if (sliceConfig.events?.enabled) {
438
+ const EventManagerModule = window.slice.frameworkClasses?.EventManager
439
+ || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/EventManager/EventManager.js`);
440
+ window.slice.events = new EventManagerModule();
441
+ if (typeof window.slice.events.init === 'function') {
442
+ await window.slice.events.init();
443
+ }
444
+ } else {
445
+ window.slice.events = {
446
+ subscribe: () => null,
447
+ subscribeOnce: () => null,
448
+ unsubscribe: () => false,
449
+ emit: () => {},
450
+ bind: () => ({
451
+ subscribe: () => null,
452
+ subscribeOnce: () => null,
453
+ emit: () => {},
454
+ }),
455
+ cleanupComponent: () => 0,
456
+ hasSubscribers: () => false,
457
+ subscriberCount: () => 0,
458
+ clear: () => {},
459
+ };
460
+ window.slice.logger.logError('Slice', 'EventManager disabled');
461
+ }
462
+
463
+ if (sliceConfig.context?.enabled) {
464
+ const ContextManagerModule = window.slice.frameworkClasses?.ContextManager
465
+ || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/ContextManager/ContextManager.js`);
466
+ window.slice.context = new ContextManagerModule();
467
+ if (typeof window.slice.context.init === 'function') {
468
+ await window.slice.context.init();
469
+ }
470
+ } else {
471
+ window.slice.context = {
472
+ create: () => false,
473
+ getState: () => null,
474
+ setState: () => {},
475
+ watch: () => null,
476
+ has: () => false,
477
+ destroy: () => false,
478
+ list: () => [],
479
+ };
480
+ window.slice.logger.logError('Slice', 'ContextManager disabled');
481
+ }
482
+
483
+ if (sliceConfig.loading.enabled) {
484
+ const loading = await window.slice.build('Loading', {});
485
+ window.slice.loading = loading;
486
+ if (typeof loading?.start === 'function') {
487
+ loading.start();
488
+ }
489
+ }
490
+
491
+ const stylesInitPromise = window.slice.stylesManager.init();
492
+ const routesModulePromise = import(slice.paths.routesFile);
493
+
494
+ if (sliceConfig.events?.ui?.shortcut || sliceConfig.context?.ui?.shortcut) {
495
+ const normalize = (value) => (typeof value === 'string' ? value.toLowerCase() : '');
496
+ const toKey = (event) => {
497
+ const parts = [];
498
+ if (event.ctrlKey) parts.push('ctrl');
499
+ if (event.shiftKey) parts.push('shift');
500
+ if (event.altKey) parts.push('alt');
501
+ if (event.metaKey) parts.push('meta');
502
+ const key = event.key?.toLowerCase();
503
+ if (key && !['control', 'shift', 'alt', 'meta'].includes(key)) {
504
+ parts.push(key);
505
+ }
506
+ return parts.join('+');
507
+ };
508
+
509
+ const handlers = {
510
+ [normalize(sliceConfig.events?.ui?.shortcut)]: () => window.slice.eventsDebugger?.toggle?.(),
511
+ [normalize(sliceConfig.context?.ui?.shortcut)]: () => window.slice.contextDebugger?.toggle?.(),
512
+ };
513
+
514
+ document.addEventListener('keydown', (event) => {
515
+ const key = toKey(event);
516
+ if (!key || !handlers[key]) return;
517
+ event.preventDefault();
518
+ handlers[key]();
519
+ });
520
+ }
521
+
522
+ const [, routesModule] = await Promise.all([stylesInitPromise, routesModulePromise]);
523
+ const routes = routesModule.default;
524
+ const RouterModule = window.slice.frameworkClasses?.Router
525
+ || await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Router/Router.js`);
526
+ window.slice.router = new RouterModule(routes);
527
+ await window.slice.router.init();
528
+ }
529
+
530
+ await init();