slicejs-web-framework 2.3.0 → 2.3.2
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.
- package/README.md +155 -156
- package/Slice/Components/Structural/ContextManager/ContextManager.js +21 -13
- package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +258 -0
- package/Slice/Components/Structural/Controller/Controller.js +19 -0
- package/Slice/Components/Structural/Debugger/Debugger.js +1547 -1347
- package/Slice/Components/Structural/EventManager/EventManager.js +22 -13
- package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +238 -0
- package/Slice/Components/Structural/Logger/Logger.js +146 -104
- package/Slice/Components/Structural/Router/Router.js +130 -8
- package/Slice/Components/Structural/StylesManager/StylesManager.js +64 -49
- package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +84 -56
- package/Slice/Slice.js +92 -6
- package/package.json +1 -1
- package/src/Components/AppComponents/HomePage/HomePage.js +195 -195
- package/src/Components/Visual/CodeVisualizer/CodeVisualizer.js +3 -4
- package/src/Components/components.js +1 -2
- package/src/sliceConfig.json +60 -60
- package/opencode.json +0 -11
- package/src/Components/Service/Translator/Translator.js +0 -45
- package/src/Components/Service/Translator/messages.json +0 -58
|
@@ -1,4 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} RouteConfig
|
|
3
|
+
* @property {string} path
|
|
4
|
+
* @property {string} component
|
|
5
|
+
* @property {RouteConfig[]} [children]
|
|
6
|
+
* @property {Object} [metadata]
|
|
7
|
+
* @property {string} [fullPath]
|
|
8
|
+
* @property {string|null} [parentPath]
|
|
9
|
+
* @property {RouteConfig|null} [parentRoute]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} RouteInfo
|
|
14
|
+
* @property {string} path
|
|
15
|
+
* @property {string} component
|
|
16
|
+
* @property {Object} params
|
|
17
|
+
* @property {Object} query
|
|
18
|
+
* @property {Object} metadata
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} GuardRedirect
|
|
23
|
+
* @property {string} path
|
|
24
|
+
* @property {boolean} [replace]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} RouteMatch
|
|
29
|
+
* @property {RouteConfig|null} route
|
|
30
|
+
* @property {Object} params
|
|
31
|
+
* @property {RouteConfig} [childRoute]
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @callback RouterNext
|
|
36
|
+
* @param {void|false|string|{ path: string, replace?: boolean }} [arg]
|
|
37
|
+
* @returns {void}
|
|
38
|
+
*/
|
|
39
|
+
|
|
1
40
|
export default class Router {
|
|
41
|
+
/**
|
|
42
|
+
* @param {RouteConfig[]} routes
|
|
43
|
+
*/
|
|
2
44
|
constructor(routes) {
|
|
3
45
|
this.routes = routes;
|
|
4
46
|
this.activeRoute = null;
|
|
@@ -23,7 +65,8 @@ export default class Router {
|
|
|
23
65
|
|
|
24
66
|
/**
|
|
25
67
|
* Inicializa el router
|
|
26
|
-
* Si el usuario no llama start() manualmente, se auto-inicia
|
|
68
|
+
* Si el usuario no llama start() manualmente, se auto-inicia despues de un delay
|
|
69
|
+
* @returns {void}
|
|
27
70
|
*/
|
|
28
71
|
init() {
|
|
29
72
|
window.addEventListener('popstate', this.onRouteChange.bind(this));
|
|
@@ -41,7 +84,8 @@ export default class Router {
|
|
|
41
84
|
/**
|
|
42
85
|
* Inicia el router y carga la ruta inicial
|
|
43
86
|
* OPCIONAL: Solo necesario si usas guards (beforeEach/afterEach)
|
|
44
|
-
* Si no lo llamas, el router se auto-inicia
|
|
87
|
+
* Si no lo llamas, el router se auto-inicia despues de 50ms
|
|
88
|
+
* @returns {Promise<void>}
|
|
45
89
|
*/
|
|
46
90
|
async start() {
|
|
47
91
|
// Prevenir múltiples llamadas
|
|
@@ -65,9 +109,10 @@ export default class Router {
|
|
|
65
109
|
// ============================================
|
|
66
110
|
|
|
67
111
|
/**
|
|
68
|
-
* Registra un guard que se ejecuta ANTES de cada
|
|
69
|
-
* Puede bloquear o redirigir la
|
|
70
|
-
* @param {
|
|
112
|
+
* Registra un guard que se ejecuta ANTES de cada navegacion.
|
|
113
|
+
* Puede bloquear o redirigir la navegacion mediante next().
|
|
114
|
+
* @param {(to: RouteInfo, from: RouteInfo, next: RouterNext) => void|Promise<void>} guard
|
|
115
|
+
* @returns {void}
|
|
71
116
|
*/
|
|
72
117
|
beforeEach(guard) {
|
|
73
118
|
if (typeof guard !== 'function') {
|
|
@@ -78,9 +123,10 @@ export default class Router {
|
|
|
78
123
|
}
|
|
79
124
|
|
|
80
125
|
/**
|
|
81
|
-
* Registra un guard que se ejecuta
|
|
82
|
-
* No puede bloquear la
|
|
83
|
-
* @param {
|
|
126
|
+
* Registra un guard que se ejecuta DESPUES de cada navegacion.
|
|
127
|
+
* No puede bloquear la navegacion.
|
|
128
|
+
* @param {(to: RouteInfo, from: RouteInfo) => void} guard
|
|
129
|
+
* @returns {void}
|
|
84
130
|
*/
|
|
85
131
|
afterEach(guard) {
|
|
86
132
|
if (typeof guard !== 'function') {
|
|
@@ -97,6 +143,13 @@ export default class Router {
|
|
|
97
143
|
* @param {String} requestedPath - Path original solicitado
|
|
98
144
|
* @returns {Object} Objeto con path, component, params, query, metadata
|
|
99
145
|
*/
|
|
146
|
+
/**
|
|
147
|
+
* Build route info used by guards and events.
|
|
148
|
+
* @param {RouteConfig|null} route
|
|
149
|
+
* @param {Object} [params]
|
|
150
|
+
* @param {string|null} [requestedPath]
|
|
151
|
+
* @returns {RouteInfo}
|
|
152
|
+
*/
|
|
100
153
|
_createRouteInfo(route, params = {}, requestedPath = null) {
|
|
101
154
|
if (!route) {
|
|
102
155
|
return {
|
|
@@ -121,6 +174,10 @@ export default class Router {
|
|
|
121
174
|
* Parsea los query parameters de la URL actual
|
|
122
175
|
* @returns {Object} Objeto con los query params
|
|
123
176
|
*/
|
|
177
|
+
/**
|
|
178
|
+
* Parse query params from current URL.
|
|
179
|
+
* @returns {Object}
|
|
180
|
+
*/
|
|
124
181
|
_parseQueryParams() {
|
|
125
182
|
const queryString = window.location.search;
|
|
126
183
|
if (!queryString) return {};
|
|
@@ -141,6 +198,12 @@ export default class Router {
|
|
|
141
198
|
* @param {Object} from - Información de ruta origen
|
|
142
199
|
* @returns {Object|null} Objeto con redirectPath y options, o null si continúa
|
|
143
200
|
*/
|
|
201
|
+
/**
|
|
202
|
+
* Execute beforeEach guard if defined.
|
|
203
|
+
* @param {RouteInfo} to
|
|
204
|
+
* @param {RouteInfo} from
|
|
205
|
+
* @returns {Promise<{ path: string|false, options: { replace?: boolean } }|null>}
|
|
206
|
+
*/
|
|
144
207
|
async _executeBeforeEachGuard(to, from) {
|
|
145
208
|
if (!this._beforeEachGuard) {
|
|
146
209
|
return null;
|
|
@@ -212,6 +275,12 @@ export default class Router {
|
|
|
212
275
|
* @param {Object} to - Información de ruta destino
|
|
213
276
|
* @param {Object} from - Información de ruta origen
|
|
214
277
|
*/
|
|
278
|
+
/**
|
|
279
|
+
* Execute afterEach guard if defined.
|
|
280
|
+
* @param {RouteInfo} to
|
|
281
|
+
* @param {RouteInfo} from
|
|
282
|
+
* @returns {void}
|
|
283
|
+
*/
|
|
215
284
|
_executeAfterEachGuard(to, from) {
|
|
216
285
|
if (!this._afterEachGuard) {
|
|
217
286
|
return;
|
|
@@ -228,6 +297,13 @@ export default class Router {
|
|
|
228
297
|
// ROUTING CORE (MODIFICADO CON GUARDS)
|
|
229
298
|
// ============================================
|
|
230
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Navigate to a route path with guards support. Add replace to do router.replace() instead of push.
|
|
302
|
+
* @param {string} path
|
|
303
|
+
* @param {string[]} [_redirectChain]
|
|
304
|
+
* @param {{ replace?: boolean }} [_options]
|
|
305
|
+
* @returns {Promise<void>}
|
|
306
|
+
*/
|
|
231
307
|
async navigate(path, _redirectChain = [], _options = {}) {
|
|
232
308
|
const currentPath = window.location.pathname;
|
|
233
309
|
|
|
@@ -282,6 +358,12 @@ export default class Router {
|
|
|
282
358
|
* @param {Object} to - Información de ruta destino
|
|
283
359
|
* @param {Object} from - Información de ruta origen
|
|
284
360
|
*/
|
|
361
|
+
/**
|
|
362
|
+
* Perform navigation after guards.
|
|
363
|
+
* @param {RouteInfo} to
|
|
364
|
+
* @param {RouteInfo} from
|
|
365
|
+
* @returns {Promise<void>}
|
|
366
|
+
*/
|
|
285
367
|
async _performNavigation(to, from) {
|
|
286
368
|
// Renderizar la nueva ruta
|
|
287
369
|
await this.onRouteChange();
|
|
@@ -293,6 +375,10 @@ export default class Router {
|
|
|
293
375
|
this._emitRouteChange(to, from);
|
|
294
376
|
}
|
|
295
377
|
|
|
378
|
+
/**
|
|
379
|
+
* React to URL changes and render routes.
|
|
380
|
+
* @returns {Promise<void>}
|
|
381
|
+
*/
|
|
296
382
|
async onRouteChange() {
|
|
297
383
|
// Cancelar el timeout anterior si existe
|
|
298
384
|
if (this.routeChangeTimeout) {
|
|
@@ -315,6 +401,12 @@ export default class Router {
|
|
|
315
401
|
}, 10);
|
|
316
402
|
}
|
|
317
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Build or update the active route component.
|
|
406
|
+
* @param {RouteConfig} route
|
|
407
|
+
* @param {Object} params
|
|
408
|
+
* @returns {Promise<void>}
|
|
409
|
+
*/
|
|
318
410
|
async handleRoute(route, params) {
|
|
319
411
|
const targetElement = document.querySelector('#app');
|
|
320
412
|
|
|
@@ -357,6 +449,10 @@ export default class Router {
|
|
|
357
449
|
slice.router.activeRoute = route;
|
|
358
450
|
}
|
|
359
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Load initial route and run guards.
|
|
454
|
+
* @returns {Promise<void>}
|
|
455
|
+
*/
|
|
360
456
|
async loadInitialRoute() {
|
|
361
457
|
const path = window.location.pathname;
|
|
362
458
|
const { route, params } = this.matchRoute(path);
|
|
@@ -392,6 +488,12 @@ export default class Router {
|
|
|
392
488
|
* @param {Object} to
|
|
393
489
|
* @param {Object} from
|
|
394
490
|
*/
|
|
491
|
+
/**
|
|
492
|
+
* Emit route change event.
|
|
493
|
+
* @param {RouteInfo} to
|
|
494
|
+
* @param {RouteInfo} from
|
|
495
|
+
* @returns {void}
|
|
496
|
+
*/
|
|
395
497
|
_emitRouteChange(to, from) {
|
|
396
498
|
const payload = { to, from };
|
|
397
499
|
|
|
@@ -475,6 +577,11 @@ export default class Router {
|
|
|
475
577
|
return pathToRouteMap;
|
|
476
578
|
}
|
|
477
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Render any Route/MultiRoute components in a container.
|
|
582
|
+
* @param {Document|HTMLElement} [searchContainer]
|
|
583
|
+
* @returns {Promise<boolean>}
|
|
584
|
+
*/
|
|
478
585
|
async renderRoutesComponentsInPage(searchContainer = document) {
|
|
479
586
|
let routerContainersFlag = false;
|
|
480
587
|
const routeContainers = this.getCachedRouteContainers(searchContainer);
|
|
@@ -534,6 +641,11 @@ export default class Router {
|
|
|
534
641
|
return routeContainers;
|
|
535
642
|
}
|
|
536
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Render route containers inside a component.
|
|
646
|
+
* @param {HTMLElement} component
|
|
647
|
+
* @returns {Promise<boolean>}
|
|
648
|
+
*/
|
|
537
649
|
async renderRoutesInComponent(component) {
|
|
538
650
|
if (!component) {
|
|
539
651
|
slice.logger.logWarning('Router', 'No component provided for route rendering');
|
|
@@ -543,6 +655,11 @@ export default class Router {
|
|
|
543
655
|
return await this.renderRoutesComponentsInPage(component);
|
|
544
656
|
}
|
|
545
657
|
|
|
658
|
+
/**
|
|
659
|
+
* Match a path to a configured route.
|
|
660
|
+
* @param {string} path
|
|
661
|
+
* @returns {RouteMatch}
|
|
662
|
+
*/
|
|
546
663
|
matchRoute(path) {
|
|
547
664
|
const exactMatch = this.pathToRouteMap.get(path);
|
|
548
665
|
if (exactMatch) {
|
|
@@ -583,6 +700,11 @@ export default class Router {
|
|
|
583
700
|
return { route: notFoundRoute, params: {} };
|
|
584
701
|
}
|
|
585
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Compile a path pattern with ${param} segments.
|
|
705
|
+
* @param {string} pattern
|
|
706
|
+
* @returns {{ regex: RegExp, paramNames: string[] }}
|
|
707
|
+
*/
|
|
586
708
|
compilePathPattern(pattern) {
|
|
587
709
|
const paramNames = [];
|
|
588
710
|
const regexPattern =
|
|
@@ -1,49 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
export default class StylesManager {
|
|
3
|
-
constructor() {
|
|
4
|
-
this.componentStyles = document.createElement('style');
|
|
5
|
-
this.componentStyles.id = 'slice-component-styles';
|
|
6
|
-
document.head.appendChild(this.componentStyles);
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
theme =
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
|
|
2
|
+
export default class StylesManager {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.componentStyles = document.createElement('style');
|
|
5
|
+
this.componentStyles.id = 'slice-component-styles';
|
|
6
|
+
document.head.appendChild(this.componentStyles);
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load global styles and initialize ThemeManager if enabled.
|
|
12
|
+
* @returns {Promise<void>}
|
|
13
|
+
*/
|
|
14
|
+
async init() {
|
|
15
|
+
for (let i = 0; i < slice.stylesConfig.requestedStyles.length; i++) {
|
|
16
|
+
const styles = await slice.controller.fetchText(slice.stylesConfig.requestedStyles[i], 'styles');
|
|
17
|
+
this.componentStyles.innerText += styles;
|
|
18
|
+
slice.logger.logInfo('StylesManager', `${slice.stylesConfig.requestedStyles[i]} styles loaded`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (slice.themeConfig.enabled) {
|
|
22
|
+
const module = await import(`${slice.paths.structuralComponentFolderPath}/StylesManager/ThemeManager/ThemeManager.js`);
|
|
23
|
+
|
|
24
|
+
this.themeManager = new module.default();
|
|
25
|
+
let theme;
|
|
26
|
+
|
|
27
|
+
if (slice.themeConfig.saveThemeLocally) {
|
|
28
|
+
theme = localStorage.getItem('sliceTheme');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!theme) {
|
|
32
|
+
theme = slice.themeConfig.defaultTheme;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (slice.themeConfig.useBrowserTheme) {
|
|
36
|
+
const browserTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'Dark' : 'Light';
|
|
37
|
+
theme = browserTheme;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await this.themeManager.applyTheme(theme);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//add a method that will add css as text to the componentStyles element
|
|
45
|
+
/**
|
|
46
|
+
* Append raw CSS to the global component style tag.
|
|
47
|
+
* @param {string} cssText
|
|
48
|
+
* @returns {void}
|
|
49
|
+
*/
|
|
50
|
+
appendComponentStyles(cssText) {
|
|
51
|
+
this.componentStyles.appendChild(document.createTextNode(cssText));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register CSS for a component.
|
|
56
|
+
* @param {string} componentName
|
|
57
|
+
* @param {string} cssText
|
|
58
|
+
* @returns {void}
|
|
59
|
+
*/
|
|
60
|
+
registerComponentStyles(componentName, cssText) {
|
|
61
|
+
slice.controller.requestedStyles.add(componentName);
|
|
62
|
+
this.appendComponentStyles(cssText);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,56 +1,84 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
slice.logger.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Manages theme CSS loading and persistence.
|
|
3
|
+
*/
|
|
4
|
+
export default class ThemeManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.themeStyles = new Map();
|
|
7
|
+
this.currentTheme = null;
|
|
8
|
+
this.themeStyle = document.createElement('style');
|
|
9
|
+
document.head.appendChild(this.themeStyle);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Apply a theme by name.
|
|
14
|
+
* @param {string} themeName
|
|
15
|
+
* @returns {Promise<void>}
|
|
16
|
+
*/
|
|
17
|
+
async applyTheme(themeName) {
|
|
18
|
+
if (!themeName) {
|
|
19
|
+
slice.logger.logError('ThemeManager', 'Invalid theme name');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!this.themeStyles.has(themeName)) {
|
|
24
|
+
await this.loadThemeCSS(themeName);
|
|
25
|
+
} else {
|
|
26
|
+
this.setThemeStyle(themeName);
|
|
27
|
+
this.saveThemeLocally(themeName, this.themeStyles.get(themeName));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Load theme CSS and cache it.
|
|
33
|
+
* @param {string} themeName
|
|
34
|
+
* @returns {Promise<void>}
|
|
35
|
+
*/
|
|
36
|
+
async loadThemeCSS(themeName) {
|
|
37
|
+
let themeContent =
|
|
38
|
+
localStorage.getItem(`sliceTheme-${themeName}`) || (await slice.controller.fetchText(themeName, 'theme'));
|
|
39
|
+
|
|
40
|
+
if (!themeContent) {
|
|
41
|
+
slice.logger.logError('ThemeManager', `Failed to load theme: ${themeName}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.themeStyles.set(themeName, themeContent);
|
|
46
|
+
this.setThemeStyle(themeName);
|
|
47
|
+
this.saveThemeLocally(themeName, themeContent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Persist a theme in localStorage when enabled.
|
|
52
|
+
* @param {string} themeName
|
|
53
|
+
* @param {string} themeContent
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
saveThemeLocally(themeName, themeContent) {
|
|
57
|
+
if (slice.themeConfig.saveThemeLocally) {
|
|
58
|
+
localStorage.setItem('sliceTheme', themeName);
|
|
59
|
+
localStorage.setItem(`sliceTheme-${themeName}`, themeContent);
|
|
60
|
+
slice.logger.logInfo('ThemeManager', `Theme ${themeName} saved locally`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Clear currently applied theme styles.
|
|
66
|
+
* @returns {void}
|
|
67
|
+
*/
|
|
68
|
+
removeCurrentTheme() {
|
|
69
|
+
if (this.currentTheme) {
|
|
70
|
+
this.themeStyle.textContent = '';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Set theme style text and mark current theme.
|
|
76
|
+
* @param {string} themeName
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
setThemeStyle(themeName) {
|
|
80
|
+
this.themeStyle.textContent = this.themeStyles.get(themeName);
|
|
81
|
+
this.currentTheme = themeName;
|
|
82
|
+
slice.logger.logInfo('ThemeManager', `Theme ${themeName} applied`);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/Slice/Slice.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import Controller from './Components/Structural/Controller/Controller.js';
|
|
2
2
|
import StylesManager from './Components/Structural/StylesManager/StylesManager.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Main Slice.js runtime.
|
|
6
|
+
*/
|
|
4
7
|
export default class Slice {
|
|
8
|
+
/**
|
|
9
|
+
* @param {Object} sliceConfig
|
|
10
|
+
*/
|
|
5
11
|
constructor(sliceConfig) {
|
|
6
12
|
this.controller = new Controller();
|
|
7
13
|
this.stylesManager = new StylesManager();
|
|
@@ -16,6 +22,11 @@ export default class Slice {
|
|
|
16
22
|
// 📦 Bundle system is initialized automatically via import in index.js
|
|
17
23
|
}
|
|
18
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Dynamically import a module and return its default export.
|
|
27
|
+
* @param {string} module
|
|
28
|
+
* @returns {Promise<any>}
|
|
29
|
+
*/
|
|
19
30
|
async getClass(module) {
|
|
20
31
|
try {
|
|
21
32
|
const { default: myClass } = await import(module);
|
|
@@ -25,14 +36,29 @@ export default class Slice {
|
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Flag for production behavior (override in builds).
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
28
43
|
isProduction() {
|
|
29
44
|
return true;
|
|
30
45
|
}
|
|
31
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Get a component instance by sliceId.
|
|
49
|
+
* @param {string} componentSliceId
|
|
50
|
+
* @returns {HTMLElement|undefined}
|
|
51
|
+
*/
|
|
32
52
|
getComponent(componentSliceId) {
|
|
33
53
|
return this.controller.activeComponents.get(componentSliceId);
|
|
34
54
|
}
|
|
35
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Build a component instance and run init.
|
|
58
|
+
* @param {string} componentName
|
|
59
|
+
* @param {Object} [props]
|
|
60
|
+
* @returns {Promise<HTMLElement|Object|null>}
|
|
61
|
+
*/
|
|
36
62
|
async build(componentName, props = {}) {
|
|
37
63
|
if (!componentName) {
|
|
38
64
|
this.logger.logError('Slice', null, `Component name is required to build a component`);
|
|
@@ -159,14 +185,28 @@ export default class Slice {
|
|
|
159
185
|
}
|
|
160
186
|
}
|
|
161
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Apply a theme by name.
|
|
190
|
+
* @param {string} themeName
|
|
191
|
+
* @returns {Promise<void>}
|
|
192
|
+
*/
|
|
162
193
|
async setTheme(themeName) {
|
|
163
194
|
await this.stylesManager.themeManager.applyTheme(themeName);
|
|
164
195
|
}
|
|
165
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Current theme name.
|
|
199
|
+
* @returns {string|null}
|
|
200
|
+
*/
|
|
166
201
|
get theme() {
|
|
167
202
|
return this.stylesManager.themeManager.currentTheme;
|
|
168
203
|
}
|
|
169
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Attach HTML template to a component instance.
|
|
207
|
+
* @param {HTMLElement} componentInstance
|
|
208
|
+
* @returns {void}
|
|
209
|
+
*/
|
|
170
210
|
attachTemplate(componentInstance) {
|
|
171
211
|
this.controller.loadTemplateToComponent(componentInstance);
|
|
172
212
|
}
|
|
@@ -210,14 +250,32 @@ async function init() {
|
|
|
210
250
|
}
|
|
211
251
|
|
|
212
252
|
if (sliceConfig.debugger.enabled) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
253
|
+
const DebuggerModule = await window.slice.getClass(
|
|
254
|
+
`${slice.paths.structuralComponentFolderPath}/Debugger/Debugger.js`
|
|
255
|
+
);
|
|
256
|
+
window.slice.debugger = new DebuggerModule();
|
|
257
|
+
await window.slice.debugger.enableDebugMode();
|
|
258
|
+
document.body.appendChild(window.slice.debugger);
|
|
219
259
|
}
|
|
220
260
|
|
|
261
|
+
if (sliceConfig.events?.ui?.enabled) {
|
|
262
|
+
const EventsDebuggerModule = await window.slice.getClass(
|
|
263
|
+
`${slice.paths.structuralComponentFolderPath}/EventManager/EventManagerDebugger.js`
|
|
264
|
+
);
|
|
265
|
+
window.slice.eventsDebugger = new EventsDebuggerModule();
|
|
266
|
+
await window.slice.eventsDebugger.init();
|
|
267
|
+
document.body.appendChild(window.slice.eventsDebugger);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (sliceConfig.context?.ui?.enabled) {
|
|
271
|
+
const ContextDebuggerModule = await window.slice.getClass(
|
|
272
|
+
`${slice.paths.structuralComponentFolderPath}/ContextManager/ContextManagerDebugger.js`
|
|
273
|
+
);
|
|
274
|
+
window.slice.contextDebugger = new ContextDebuggerModule();
|
|
275
|
+
await window.slice.contextDebugger.init();
|
|
276
|
+
document.body.appendChild(window.slice.contextDebugger);
|
|
277
|
+
}
|
|
278
|
+
|
|
221
279
|
if (sliceConfig.events?.enabled) {
|
|
222
280
|
const EventManagerModule = await window.slice.getClass(
|
|
223
281
|
`${slice.paths.structuralComponentFolderPath}/EventManager/EventManager.js`
|
|
@@ -275,6 +333,34 @@ async function init() {
|
|
|
275
333
|
|
|
276
334
|
await window.slice.stylesManager.init();
|
|
277
335
|
|
|
336
|
+
if (sliceConfig.events?.ui?.shortcut || sliceConfig.context?.ui?.shortcut) {
|
|
337
|
+
const normalize = (value) => (typeof value === 'string' ? value.toLowerCase() : '');
|
|
338
|
+
const toKey = (event) => {
|
|
339
|
+
const parts = [];
|
|
340
|
+
if (event.ctrlKey) parts.push('ctrl');
|
|
341
|
+
if (event.shiftKey) parts.push('shift');
|
|
342
|
+
if (event.altKey) parts.push('alt');
|
|
343
|
+
if (event.metaKey) parts.push('meta');
|
|
344
|
+
const key = event.key?.toLowerCase();
|
|
345
|
+
if (key && !['control', 'shift', 'alt', 'meta'].includes(key)) {
|
|
346
|
+
parts.push(key);
|
|
347
|
+
}
|
|
348
|
+
return parts.join('+');
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const handlers = {
|
|
352
|
+
[normalize(sliceConfig.events?.ui?.shortcut)]: () => window.slice.eventsDebugger?.toggle?.(),
|
|
353
|
+
[normalize(sliceConfig.context?.ui?.shortcut)]: () => window.slice.contextDebugger?.toggle?.(),
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
document.addEventListener('keydown', (event) => {
|
|
357
|
+
const key = toKey(event);
|
|
358
|
+
if (!key || !handlers[key]) return;
|
|
359
|
+
event.preventDefault();
|
|
360
|
+
handlers[key]();
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
278
364
|
const routesModule = await import(slice.paths.routesFile);
|
|
279
365
|
const routes = routesModule.default;
|
|
280
366
|
const RouterModule = await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Router/Router.js`);
|