@hamak/ui-shell-impl 0.1.0

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 (71) hide show
  1. package/.turbo/turbo-build.log +1 -0
  2. package/dist/core/DefaultFeatureManager.d.ts +24 -0
  3. package/dist/core/DefaultFeatureManager.d.ts.map +1 -0
  4. package/dist/core/DefaultFeatureManager.js +80 -0
  5. package/dist/core/DefaultLayoutManager.d.ts +19 -0
  6. package/dist/core/DefaultLayoutManager.d.ts.map +1 -0
  7. package/dist/core/DefaultLayoutManager.js +59 -0
  8. package/dist/core/DefaultRouter.d.ts +30 -0
  9. package/dist/core/DefaultRouter.d.ts.map +1 -0
  10. package/dist/core/DefaultRouter.js +111 -0
  11. package/dist/core/DefaultShell.d.ts +30 -0
  12. package/dist/core/DefaultShell.d.ts.map +1 -0
  13. package/dist/core/DefaultShell.js +147 -0
  14. package/dist/core/DefaultThemeManager.d.ts +27 -0
  15. package/dist/core/DefaultThemeManager.d.ts.map +1 -0
  16. package/dist/core/DefaultThemeManager.js +76 -0
  17. package/dist/core/index.d.ts +10 -0
  18. package/dist/core/index.d.ts.map +1 -0
  19. package/dist/core/index.js +9 -0
  20. package/dist/index.d.ts +20 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +39 -0
  23. package/dist/plugin/ShellPluginFactory.d.ts +11 -0
  24. package/dist/plugin/ShellPluginFactory.d.ts.map +1 -0
  25. package/dist/plugin/ShellPluginFactory.js +73 -0
  26. package/dist/plugin/index.d.ts +6 -0
  27. package/dist/plugin/index.d.ts.map +1 -0
  28. package/dist/plugin/index.js +5 -0
  29. package/dist/providers/CSSVariablesThemeProvider.d.ts +16 -0
  30. package/dist/providers/CSSVariablesThemeProvider.d.ts.map +1 -0
  31. package/dist/providers/CSSVariablesThemeProvider.js +47 -0
  32. package/dist/providers/HashRouterStrategy.d.ts +20 -0
  33. package/dist/providers/HashRouterStrategy.d.ts.map +1 -0
  34. package/dist/providers/HashRouterStrategy.js +57 -0
  35. package/dist/providers/HistoryRouterStrategy.d.ts +21 -0
  36. package/dist/providers/HistoryRouterStrategy.d.ts.map +1 -0
  37. package/dist/providers/HistoryRouterStrategy.js +64 -0
  38. package/dist/providers/LocalStorageProvider.d.ts +13 -0
  39. package/dist/providers/LocalStorageProvider.d.ts.map +1 -0
  40. package/dist/providers/LocalStorageProvider.js +50 -0
  41. package/dist/providers/MemoryStorageProvider.d.ts +14 -0
  42. package/dist/providers/MemoryStorageProvider.d.ts.map +1 -0
  43. package/dist/providers/MemoryStorageProvider.js +22 -0
  44. package/dist/providers/index.d.ts +10 -0
  45. package/dist/providers/index.d.ts.map +1 -0
  46. package/dist/providers/index.js +9 -0
  47. package/dist/utils/index.d.ts +6 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/index.js +5 -0
  50. package/dist/utils/viewport-utils.d.ts +16 -0
  51. package/dist/utils/viewport-utils.d.ts.map +1 -0
  52. package/dist/utils/viewport-utils.js +52 -0
  53. package/package.json +37 -0
  54. package/src/core/DefaultFeatureManager.ts +101 -0
  55. package/src/core/DefaultLayoutManager.ts +74 -0
  56. package/src/core/DefaultRouter.ts +135 -0
  57. package/src/core/DefaultShell.ts +176 -0
  58. package/src/core/DefaultThemeManager.ts +99 -0
  59. package/src/core/index.ts +10 -0
  60. package/src/index.ts +51 -0
  61. package/src/plugin/ShellPluginFactory.ts +98 -0
  62. package/src/plugin/index.ts +6 -0
  63. package/src/providers/CSSVariablesThemeProvider.ts +60 -0
  64. package/src/providers/HashRouterStrategy.ts +71 -0
  65. package/src/providers/HistoryRouterStrategy.ts +78 -0
  66. package/src/providers/LocalStorageProvider.ts +53 -0
  67. package/src/providers/MemoryStorageProvider.ts +30 -0
  68. package/src/providers/index.ts +10 -0
  69. package/src/utils/index.ts +6 -0
  70. package/src/utils/viewport-utils.ts +64 -0
  71. package/tsconfig.json +20 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Shell Plugin Factory
3
+ * Creates microkernel plugin for UI Shell integration
4
+ */
5
+
6
+ import type { PluginModule } from '@amk/microkernel-spi';
7
+ import type { ActivateContext } from '@amk/microkernel-api';
8
+ import type { ShellConfig } from '@amk/ui-shell-api';
9
+ import {
10
+ SHELL_TOKEN,
11
+ THEME_MANAGER_TOKEN,
12
+ FEATURE_MANAGER_TOKEN,
13
+ LAYOUT_MANAGER_TOKEN,
14
+ ShellCommands,
15
+ ShellEvents,
16
+ } from '@amk/ui-shell-api';
17
+ import { DefaultShell } from '../core/DefaultShell';
18
+ import { DefaultLayoutManager } from '../core/DefaultLayoutManager';
19
+
20
+ export function createShellPlugin(config?: ShellConfig): PluginModule {
21
+ let shell: DefaultShell;
22
+ let layoutManager: DefaultLayoutManager;
23
+
24
+ return {
25
+ initialize(ctx) {
26
+ shell = new DefaultShell(config);
27
+ layoutManager = new DefaultLayoutManager();
28
+
29
+ // Provide shell services
30
+ ctx.provide({ provide: SHELL_TOKEN, useValue: shell });
31
+ ctx.provide({ provide: THEME_MANAGER_TOKEN, useValue: shell.getThemeManager() });
32
+ ctx.provide({ provide: FEATURE_MANAGER_TOKEN, useValue: shell.getFeatureManager() });
33
+ ctx.provide({ provide: LAYOUT_MANAGER_TOKEN, useValue: layoutManager });
34
+
35
+ // Register commands
36
+ ctx.commands.register(ShellCommands.SET_THEME, (mode: 'light' | 'dark' | 'system') => {
37
+ shell.getThemeManager().setTheme(mode);
38
+ });
39
+
40
+ ctx.commands.register(ShellCommands.TOGGLE_THEME, () => {
41
+ shell.getThemeManager().toggleTheme();
42
+ });
43
+
44
+ ctx.commands.register(ShellCommands.ENABLE_FEATURE, (key: string) => {
45
+ shell.getFeatureManager().enable(key);
46
+ });
47
+
48
+ ctx.commands.register(ShellCommands.DISABLE_FEATURE, (key: string) => {
49
+ shell.getFeatureManager().disable(key);
50
+ });
51
+
52
+ ctx.commands.register(ShellCommands.TOGGLE_FEATURE, (key: string) => {
53
+ shell.getFeatureManager().toggle(key);
54
+ });
55
+
56
+ ctx.commands.register(ShellCommands.NAVIGATE, (path: string) => {
57
+ const router = shell.getRouter();
58
+ if (!router) {
59
+ console.warn('Router not initialized');
60
+ return;
61
+ }
62
+ return router.push(path);
63
+ });
64
+
65
+ ctx.commands.register(ShellCommands.NAVIGATE_BACK, () => {
66
+ const router = shell.getRouter();
67
+ if (!router) {
68
+ console.warn('Router not initialized');
69
+ return;
70
+ }
71
+ router.back();
72
+ });
73
+
74
+ // Forward shell events to microkernel hooks
75
+ shell.on('shell:ready', () => ctx.hooks.emit(ShellEvents.READY, {}));
76
+ shell.on('theme:changed', (event) => ctx.hooks.emit(ShellEvents.THEME_CHANGED, event.payload));
77
+ shell.on('feature:toggled', (event) => ctx.hooks.emit(ShellEvents.FEATURE_TOGGLED, event.payload));
78
+ shell.on('route:changed', (event) => ctx.hooks.emit(ShellEvents.ROUTE_CHANGED, event.payload));
79
+ shell.on('viewport:resized', (event) => ctx.hooks.emit(ShellEvents.VIEWPORT_RESIZED, event.payload));
80
+ },
81
+
82
+ async activate(ctx: ActivateContext) {
83
+ await shell.initialize();
84
+ const shellContext = shell.getContext();
85
+ ctx.hooks.emit('shell:activated', { context: shellContext });
86
+ },
87
+
88
+ deactivate() {
89
+ shell.destroy();
90
+ layoutManager.destroy();
91
+ },
92
+ };
93
+ }
94
+
95
+ // Helper functions
96
+ export function getShellFromContext(ctx: ActivateContext): DefaultShell {
97
+ return ctx.resolve<DefaultShell>(SHELL_TOKEN);
98
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Plugin Integration
3
+ * Export plugin factory and helpers
4
+ */
5
+
6
+ export * from './ShellPluginFactory';
@@ -0,0 +1,60 @@
1
+ /**
2
+ * CSS Variables Theme Provider
3
+ * Implements theming using CSS custom properties
4
+ */
5
+
6
+ import type { IThemeProvider } from '@amk/ui-shell-spi';
7
+ import type { ThemeMode, ThemeConfig } from '@amk/ui-shell-api';
8
+
9
+ export class CSSVariablesThemeProvider implements IThemeProvider {
10
+ private mediaQuery?: MediaQueryList;
11
+ private listeners: Set<(theme: 'light' | 'dark') => void> = new Set();
12
+
13
+ applyTheme(mode: ThemeMode, config: ThemeConfig): void {
14
+ if (typeof document === 'undefined') return;
15
+
16
+ const resolved = mode === 'system' ? this.getSystemPreference() : mode;
17
+
18
+ document.documentElement.setAttribute('data-theme', resolved);
19
+ document.documentElement.style.colorScheme = resolved;
20
+
21
+ // Apply custom CSS variables
22
+ if (config.cssVariables) {
23
+ Object.entries(config.cssVariables).forEach(([key, value]) => {
24
+ document.documentElement.style.setProperty(key, value);
25
+ });
26
+ }
27
+ }
28
+
29
+ getSystemPreference(): 'light' | 'dark' {
30
+ if (typeof window === 'undefined') return 'light';
31
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
32
+ }
33
+
34
+ onSystemThemeChange(callback: (theme: 'light' | 'dark') => void): () => void {
35
+ if (typeof window === 'undefined') {
36
+ return () => {};
37
+ }
38
+
39
+ this.listeners.add(callback);
40
+
41
+ if (!this.mediaQuery) {
42
+ this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
43
+ this.mediaQuery.addEventListener('change', this.handleMediaQueryChange);
44
+ }
45
+
46
+ return () => {
47
+ this.listeners.delete(callback);
48
+ };
49
+ }
50
+
51
+ destroy(): void {
52
+ this.mediaQuery?.removeEventListener('change', this.handleMediaQueryChange);
53
+ this.listeners.clear();
54
+ }
55
+
56
+ private handleMediaQueryChange = (): void => {
57
+ const theme = this.getSystemPreference();
58
+ this.listeners.forEach(listener => listener(theme));
59
+ };
60
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Hash Router Strategy
3
+ * Uses URL hash for routing
4
+ */
5
+
6
+ import type { IRouterStrategy } from '@amk/ui-shell-spi';
7
+
8
+ export class HashRouterStrategy implements IRouterStrategy {
9
+ private listeners: Set<(path: string) => void> = new Set();
10
+
11
+ constructor() {
12
+ this.setupListener();
13
+ }
14
+
15
+ push(path: string): void {
16
+ if (typeof window === 'undefined') return;
17
+
18
+ window.location.hash = path;
19
+ }
20
+
21
+ replace(path: string): void {
22
+ if (typeof window === 'undefined') return;
23
+
24
+ window.location.replace(`#${path}`);
25
+ }
26
+
27
+ back(): void {
28
+ if (typeof window !== 'undefined') {
29
+ window.history.back();
30
+ }
31
+ }
32
+
33
+ forward(): void {
34
+ if (typeof window !== 'undefined') {
35
+ window.history.forward();
36
+ }
37
+ }
38
+
39
+ getCurrentPath(): string {
40
+ if (typeof window === 'undefined') return '/';
41
+
42
+ return window.location.hash.slice(1) || '/';
43
+ }
44
+
45
+ listen(callback: (path: string) => void): () => void {
46
+ this.listeners.add(callback);
47
+ return () => this.listeners.delete(callback);
48
+ }
49
+
50
+ destroy(): void {
51
+ if (typeof window !== 'undefined') {
52
+ window.removeEventListener('hashchange', this.handleHashChange);
53
+ }
54
+ this.listeners.clear();
55
+ }
56
+
57
+ private setupListener(): void {
58
+ if (typeof window !== 'undefined') {
59
+ window.addEventListener('hashchange', this.handleHashChange);
60
+ }
61
+ }
62
+
63
+ private handleHashChange = (): void => {
64
+ const path = this.getCurrentPath();
65
+ this.notifyListeners(path);
66
+ };
67
+
68
+ private notifyListeners(path: string): void {
69
+ this.listeners.forEach(listener => listener(path));
70
+ }
71
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * History Router Strategy
3
+ * Uses browser History API for routing
4
+ */
5
+
6
+ import type { IRouterStrategy } from '@amk/ui-shell-spi';
7
+
8
+ export class HistoryRouterStrategy implements IRouterStrategy {
9
+ private base: string;
10
+ private listeners: Set<(path: string) => void> = new Set();
11
+
12
+ constructor(base: string = '/') {
13
+ this.base = base;
14
+ this.setupListener();
15
+ }
16
+
17
+ push(path: string): void {
18
+ if (typeof window === 'undefined') return;
19
+
20
+ const fullPath = this.base + path;
21
+ window.history.pushState({}, '', fullPath);
22
+ this.notifyListeners(path);
23
+ }
24
+
25
+ replace(path: string): void {
26
+ if (typeof window === 'undefined') return;
27
+
28
+ const fullPath = this.base + path;
29
+ window.history.replaceState({}, '', fullPath);
30
+ this.notifyListeners(path);
31
+ }
32
+
33
+ back(): void {
34
+ if (typeof window !== 'undefined') {
35
+ window.history.back();
36
+ }
37
+ }
38
+
39
+ forward(): void {
40
+ if (typeof window !== 'undefined') {
41
+ window.history.forward();
42
+ }
43
+ }
44
+
45
+ getCurrentPath(): string {
46
+ if (typeof window === 'undefined') return '/';
47
+
48
+ const path = window.location.pathname;
49
+ return path.startsWith(this.base) ? path.slice(this.base.length) : path;
50
+ }
51
+
52
+ listen(callback: (path: string) => void): () => void {
53
+ this.listeners.add(callback);
54
+ return () => this.listeners.delete(callback);
55
+ }
56
+
57
+ destroy(): void {
58
+ if (typeof window !== 'undefined') {
59
+ window.removeEventListener('popstate', this.handlePopState);
60
+ }
61
+ this.listeners.clear();
62
+ }
63
+
64
+ private setupListener(): void {
65
+ if (typeof window !== 'undefined') {
66
+ window.addEventListener('popstate', this.handlePopState);
67
+ }
68
+ }
69
+
70
+ private handlePopState = (): void => {
71
+ const path = this.getCurrentPath();
72
+ this.notifyListeners(path);
73
+ };
74
+
75
+ private notifyListeners(path: string): void {
76
+ this.listeners.forEach(listener => listener(path));
77
+ }
78
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * LocalStorage Provider
3
+ * Implementation using browser localStorage
4
+ */
5
+
6
+ import type { IStorageProvider } from '@amk/ui-shell-spi';
7
+
8
+ export class LocalStorageProvider implements IStorageProvider {
9
+ getItem(key: string): string | null {
10
+ if (!this.isAvailable()) return null;
11
+
12
+ try {
13
+ return localStorage.getItem(key);
14
+ } catch (e) {
15
+ console.warn('Failed to get item from localStorage:', e);
16
+ return null;
17
+ }
18
+ }
19
+
20
+ setItem(key: string, value: string): void {
21
+ if (!this.isAvailable()) return;
22
+
23
+ try {
24
+ localStorage.setItem(key, value);
25
+ } catch (e) {
26
+ console.warn('Failed to set item in localStorage:', e);
27
+ }
28
+ }
29
+
30
+ removeItem(key: string): void {
31
+ if (!this.isAvailable()) return;
32
+
33
+ try {
34
+ localStorage.removeItem(key);
35
+ } catch (e) {
36
+ console.warn('Failed to remove item from localStorage:', e);
37
+ }
38
+ }
39
+
40
+ clear(): void {
41
+ if (!this.isAvailable()) return;
42
+
43
+ try {
44
+ localStorage.clear();
45
+ } catch (e) {
46
+ console.warn('Failed to clear localStorage:', e);
47
+ }
48
+ }
49
+
50
+ isAvailable(): boolean {
51
+ return typeof localStorage !== 'undefined';
52
+ }
53
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Memory Storage Provider
3
+ * In-memory storage for SSR or testing
4
+ */
5
+
6
+ import type { IStorageProvider } from '@amk/ui-shell-spi';
7
+
8
+ export class MemoryStorageProvider implements IStorageProvider {
9
+ private storage: Map<string, string> = new Map();
10
+
11
+ getItem(key: string): string | null {
12
+ return this.storage.get(key) || null;
13
+ }
14
+
15
+ setItem(key: string, value: string): void {
16
+ this.storage.set(key, value);
17
+ }
18
+
19
+ removeItem(key: string): void {
20
+ this.storage.delete(key);
21
+ }
22
+
23
+ clear(): void {
24
+ this.storage.clear();
25
+ }
26
+
27
+ isAvailable(): boolean {
28
+ return true;
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Providers
3
+ * Export all provider implementations
4
+ */
5
+
6
+ export * from './LocalStorageProvider';
7
+ export * from './MemoryStorageProvider';
8
+ export * from './CSSVariablesThemeProvider';
9
+ export * from './HistoryRouterStrategy';
10
+ export * from './HashRouterStrategy';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Utilities
3
+ * Export all utility classes
4
+ */
5
+
6
+ export * from './viewport-utils';
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Viewport Utilities
3
+ */
4
+
5
+ import type { BreakpointName, Breakpoint } from '@amk/ui-shell-api';
6
+
7
+ export const BREAKPOINTS: Record<BreakpointName, Breakpoint> = {
8
+ xs: { name: 'xs', minWidth: 0, maxWidth: 639 },
9
+ sm: { name: 'sm', minWidth: 640, maxWidth: 767 },
10
+ md: { name: 'md', minWidth: 768, maxWidth: 1023 },
11
+ lg: { name: 'lg', minWidth: 1024, maxWidth: 1279 },
12
+ xl: { name: 'xl', minWidth: 1280, maxWidth: 1535 },
13
+ '2xl': { name: '2xl', minWidth: 1536 },
14
+ };
15
+
16
+ export class ViewportUtils {
17
+ static getCurrentBreakpoint(): BreakpointName {
18
+ if (typeof window === 'undefined') return 'md';
19
+
20
+ const width = window.innerWidth;
21
+
22
+ for (const [name, breakpoint] of Object.entries(BREAKPOINTS)) {
23
+ if (width >= breakpoint.minWidth && (!breakpoint.maxWidth || width <= breakpoint.maxWidth)) {
24
+ return name as BreakpointName;
25
+ }
26
+ }
27
+
28
+ return 'md';
29
+ }
30
+
31
+ static matchesBreakpoint(breakpoint: BreakpointName): boolean {
32
+ if (typeof window === 'undefined') return false;
33
+
34
+ const width = window.innerWidth;
35
+ const bp = BREAKPOINTS[breakpoint];
36
+
37
+ return width >= bp.minWidth && (!bp.maxWidth || width <= bp.maxWidth);
38
+ }
39
+
40
+ static isMinBreakpoint(breakpoint: BreakpointName): boolean {
41
+ if (typeof window === 'undefined') return false;
42
+
43
+ const width = window.innerWidth;
44
+ const bp = BREAKPOINTS[breakpoint];
45
+
46
+ return width >= bp.minWidth;
47
+ }
48
+
49
+ static getDimensions(): { width: number; height: number } {
50
+ if (typeof window === 'undefined') {
51
+ return { width: 0, height: 0 };
52
+ }
53
+
54
+ return {
55
+ width: window.innerWidth,
56
+ height: window.innerHeight,
57
+ };
58
+ }
59
+
60
+ static isTouchDevice(): boolean {
61
+ if (typeof window === 'undefined') return false;
62
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
63
+ }
64
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "outDir": "dist",
9
+ "rootDir": "src",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "moduleResolution": "bundler",
15
+ "resolveJsonModule": true,
16
+ "allowSyntheticDefaultImports": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }