@hamak/ui-shell-impl 0.4.7 → 0.4.16

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/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@hamak/ui-shell-impl",
3
- "version": "0.4.7",
3
+ "version": "0.4.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "UI Shell Implementation - Core UI shell functionality",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "sideEffects": false,
10
+ "files": [
11
+ "dist"
12
+ ],
10
13
  "repository": {
11
14
  "type": "git",
12
15
  "url": "https://github.com/amah/app-framework.git",
@@ -32,10 +35,10 @@
32
35
  }
33
36
  },
34
37
  "dependencies": {
35
- "@hamak/ui-shell-api": "0.4.7",
36
- "@hamak/ui-shell-spi": "0.4.7",
37
- "@hamak/microkernel-api": "0.4.7",
38
- "@hamak/microkernel-spi": "0.4.7"
38
+ "@hamak/ui-shell-api": "*",
39
+ "@hamak/ui-shell-spi": "*",
40
+ "@hamak/microkernel-api": "*",
41
+ "@hamak/microkernel-spi": "*"
39
42
  },
40
43
  "devDependencies": {
41
44
  "typescript": "~5.4.0"
@@ -1 +0,0 @@
1
- $ tsc -p tsconfig.json && tsc -p tsconfig.es2015.json
package/CHANGELOG.md DELETED
@@ -1,41 +0,0 @@
1
- ## 0.4.0 (2025-11-10)
2
-
3
- ### 🚀 Features
4
-
5
- - implement notification plugin with UI and backend components ([c19ffcf](https://github.com/amah/app-framework/commit/c19ffcf))
6
- - add ES2015 build support and fix TypeScript config for logging packages ([be5e45e](https://github.com/amah/app-framework/commit/be5e45e))
7
- - complete logging system build and add optional console interception ([f390bc6](https://github.com/amah/app-framework/commit/f390bc6))
8
- - implement core pluggable logging system (Phase 1) ([2abdc1a](https://github.com/amah/app-framework/commit/2abdc1a))
9
-
10
- ### 🩹 Fixes
11
-
12
- - add notification packages to workspaces ([97a234d](https://github.com/amah/app-framework/commit/97a234d))
13
-
14
- ### ❤️ Thank You
15
-
16
- - Amah
17
- - Claude
18
-
19
- ## 0.3.0 (2025-11-06)
20
-
21
- ### 🚀 Features
22
-
23
- - migrate from Turbo to Nx 22 with comprehensive monorepo setup ([e63801e](https://github.com/amah/app-framework/commit/e63801e))
24
- - add Nx Release for automated dependency management ([01d474f](https://github.com/amah/app-framework/commit/01d474f))
25
- - migrate from Turbo to Nx 22 monorepo orchestration ([d374271](https://github.com/amah/app-framework/commit/d374271))
26
- - add configurable main padding and resizable sidebar to DashboardLayout ([c1d25bf](https://github.com/amah/app-framework/commit/c1d25bf))
27
- - add debug logging and version management system ([ea514fc](https://github.com/amah/app-framework/commit/ea514fc))
28
- - **ui-store:** add STORE_EXTENSIONS_TOKEN for DI-based middleware/reducer registration ([e855bdd](https://github.com/amah/app-framework/commit/e855bdd))
29
- - Rename package scope from @amk to @hamak and configure npm publishing ([b6040b5](https://github.com/amah/app-framework/commit/b6040b5))
30
- - Add hybrid local/CI-CD development workflow with bun link ([d09f528](https://github.com/amah/app-framework/commit/d09f528))
31
- - Add Turborepo for intelligent build orchestration and fix test type errors ([ba41db8](https://github.com/amah/app-framework/commit/ba41db8))
32
- - Add Redux store integration with ui-store package and demo ([e5aafa8](https://github.com/amah/app-framework/commit/e5aafa8))
33
-
34
- ### 🩹 Fixes
35
-
36
- - move git config to top-level release.git in nx.json ([1bb2187](https://github.com/amah/app-framework/commit/1bb2187))
37
-
38
- ### ❤️ Thank You
39
-
40
- - Amah
41
- - Claude
package/project.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "name": "@hamak/ui-shell-impl",
3
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "packages/ui-shell/ui-shell-impl/src",
5
- "projectType": "library",
6
- "targets": {
7
- "build": {
8
- "executor": "nx:run-commands",
9
- "outputs": ["{projectRoot}/dist"],
10
- "options": {
11
- "command": "tsc -p tsconfig.json && tsc -p tsconfig.es2015.json",
12
- "cwd": "{projectRoot}"
13
- }
14
- },
15
- "clean": {
16
- "executor": "nx:run-commands",
17
- "options": {
18
- "command": "rm -rf dist",
19
- "cwd": "{projectRoot}"
20
- }
21
- }
22
- },
23
- "tags": ["type:library", "scope:ui-shell"]
24
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * Default Feature Manager Implementation
3
- */
4
-
5
- import type { IFeatureManager } from '@hamak/ui-shell-api';
6
- import type { FeatureFlags } from '@hamak/ui-shell-api';
7
-
8
- export class DefaultFeatureManager implements IFeatureManager {
9
- private features: FeatureFlags;
10
- private listeners: Map<string, Set<(value: any) => void>> = new Map();
11
-
12
- constructor(initialFeatures: FeatureFlags = {}) {
13
- this.features = { ...initialFeatures };
14
- }
15
-
16
- isEnabled(key: string): boolean {
17
- const value = this.features[key];
18
- return typeof value === 'boolean' ? value : Boolean(value);
19
- }
20
-
21
- get<T = any>(key: string, defaultValue?: T): T {
22
- const value = this.features[key];
23
- return (value !== undefined ? value : defaultValue) as T;
24
- }
25
-
26
- set(key: string, value: any): void {
27
- const oldValue = this.features[key];
28
- if (oldValue === value) return;
29
-
30
- this.features[key] = value;
31
- this.notifyListeners(key, value);
32
- }
33
-
34
- enable(key: string): void {
35
- this.set(key, true);
36
- }
37
-
38
- disable(key: string): void {
39
- this.set(key, false);
40
- }
41
-
42
- toggle(key: string): void {
43
- this.set(key, !this.isEnabled(key));
44
- }
45
-
46
- update(updates: FeatureFlags): void {
47
- Object.entries(updates).forEach(([key, value]) => {
48
- this.set(key, value);
49
- });
50
- }
51
-
52
- getAll(): Readonly<FeatureFlags> {
53
- return { ...this.features };
54
- }
55
-
56
- has(key: string): boolean {
57
- return key in this.features;
58
- }
59
-
60
- subscribe(key: string, listener: (value: any) => void): () => void {
61
- if (!this.listeners.has(key)) {
62
- this.listeners.set(key, new Set());
63
- }
64
- this.listeners.get(key)!.add(listener);
65
-
66
- return () => {
67
- const listeners = this.listeners.get(key);
68
- if (listeners) {
69
- listeners.delete(listener);
70
- if (listeners.size === 0) {
71
- this.listeners.delete(key);
72
- }
73
- }
74
- };
75
- }
76
-
77
- subscribeAll(listener: (key: string, value: any) => void): () => void {
78
- const unsubscribers: (() => void)[] = [];
79
- const currentKeys = Object.keys(this.features);
80
-
81
- currentKeys.forEach(key => {
82
- const unsub = this.subscribe(key, value => listener(key, value));
83
- unsubscribers.push(unsub);
84
- });
85
-
86
- return () => {
87
- unsubscribers.forEach(unsub => unsub());
88
- };
89
- }
90
-
91
- destroy(): void {
92
- this.listeners.clear();
93
- }
94
-
95
- private notifyListeners(key: string, value: any): void {
96
- const listeners = this.listeners.get(key);
97
- if (listeners) {
98
- listeners.forEach(listener => listener(value));
99
- }
100
- }
101
- }
@@ -1,74 +0,0 @@
1
- /**
2
- * Default Layout Manager Implementation
3
- */
4
-
5
- import type { ILayoutManager } from '@hamak/ui-shell-api';
6
- import type { LayoutSlot, LayoutArea } from '@hamak/ui-shell-api';
7
-
8
- export class DefaultLayoutManager implements ILayoutManager {
9
- private slots: Map<string, Set<LayoutSlot>> = new Map();
10
- private listeners: Set<() => void> = new Set();
11
-
12
- registerSlot(slot: LayoutSlot): () => void {
13
- const area = slot.area;
14
- if (!this.slots.has(area)) {
15
- this.slots.set(area, new Set());
16
- }
17
-
18
- this.slots.get(area)!.add(slot);
19
- this.notifyListeners();
20
-
21
- return () => this.unregisterSlot(slot);
22
- }
23
-
24
- unregisterSlot(slot: LayoutSlot): void {
25
- const area = slot.area;
26
- const slots = this.slots.get(area);
27
- if (slots) {
28
- slots.delete(slot);
29
- if (slots.size === 0) {
30
- this.slots.delete(area);
31
- }
32
- this.notifyListeners();
33
- }
34
- }
35
-
36
- getSlots(area: LayoutArea): LayoutSlot[] {
37
- const slots = this.slots.get(area);
38
- if (!slots) return [];
39
-
40
- return Array.from(slots).sort((a, b) => {
41
- const priorityA = a.priority || 0;
42
- const priorityB = b.priority || 0;
43
- return priorityB - priorityA; // Higher priority first
44
- });
45
- }
46
-
47
- getAreas(): LayoutArea[] {
48
- return Array.from(this.slots.keys()) as LayoutArea[];
49
- }
50
-
51
- hasSlots(area: LayoutArea): boolean {
52
- const slots = this.slots.get(area);
53
- return Boolean(slots && slots.size > 0);
54
- }
55
-
56
- subscribe(listener: () => void): () => void {
57
- this.listeners.add(listener);
58
- return () => this.listeners.delete(listener);
59
- }
60
-
61
- clear(): void {
62
- this.slots.clear();
63
- this.notifyListeners();
64
- }
65
-
66
- destroy(): void {
67
- this.slots.clear();
68
- this.listeners.clear();
69
- }
70
-
71
- private notifyListeners(): void {
72
- this.listeners.forEach(listener => listener());
73
- }
74
- }
@@ -1,135 +0,0 @@
1
- /**
2
- * Default Router Implementation
3
- */
4
-
5
- import type { IRouter } from '@hamak/ui-shell-api';
6
- import type { RouteConfig, RouterOptions } from '@hamak/ui-shell-api';
7
- import type { IRouterStrategy } from '@hamak/ui-shell-spi';
8
- import type { NavigationGuard } from '@hamak/ui-shell-spi';
9
-
10
- export class DefaultRouter implements IRouter {
11
- private routes: Map<string, RouteConfig> = new Map();
12
- private currentRoute: RouteConfig | null = null;
13
- private guards: NavigationGuard[] = [];
14
- private listeners: Set<(route: RouteConfig) => void> = new Set();
15
- private strategy: IRouterStrategy;
16
-
17
- constructor(options: RouterOptions, strategy: IRouterStrategy) {
18
- this.strategy = strategy;
19
- this.guards = [];
20
- this.registerRoutes(options.routes);
21
- this.setupNavigationListener();
22
- }
23
-
24
- private registerRoutes(routes: RouteConfig[]): void {
25
- routes.forEach(route => {
26
- this.routes.set(route.path, route);
27
- if (route.children) {
28
- this.registerRoutes(route.children);
29
- }
30
- });
31
- }
32
-
33
- async push(path: string): Promise<boolean> {
34
- const route = this.matchRoute(path);
35
- if (!route) {
36
- console.error(`Route not found: ${path}`);
37
- return false;
38
- }
39
-
40
- if (!(await this.runGuards(route))) {
41
- return false;
42
- }
43
-
44
- if (route.beforeEnter && !(await route.beforeEnter(route, this.currentRoute!))) {
45
- return false;
46
- }
47
-
48
- this.currentRoute = route;
49
- this.strategy.push(path);
50
- this.notifyListeners(route);
51
- return true;
52
- }
53
-
54
- async replace(path: string): Promise<boolean> {
55
- const route = this.matchRoute(path);
56
- if (!route) {
57
- console.error(`Route not found: ${path}`);
58
- return false;
59
- }
60
-
61
- if (!(await this.runGuards(route))) {
62
- return false;
63
- }
64
-
65
- this.currentRoute = route;
66
- this.strategy.replace(path);
67
- this.notifyListeners(route);
68
- return true;
69
- }
70
-
71
- back(): void {
72
- this.strategy.back();
73
- }
74
-
75
- forward(): void {
76
- this.strategy.forward();
77
- }
78
-
79
- getCurrentRoute(): RouteConfig | null {
80
- return this.currentRoute;
81
- }
82
-
83
- subscribe(listener: (route: RouteConfig) => void): () => void {
84
- this.listeners.add(listener);
85
- return () => this.listeners.delete(listener);
86
- }
87
-
88
- async loadRouteComponent(route: RouteConfig): Promise<any> {
89
- try {
90
- const component = await route.component();
91
- return component;
92
- } catch (error) {
93
- console.error(`Failed to load route component for ${route.path}:`, error);
94
- throw error;
95
- }
96
- }
97
-
98
- addGuard(guard: NavigationGuard): () => void {
99
- this.guards.push(guard);
100
- return () => {
101
- const index = this.guards.indexOf(guard);
102
- if (index > -1) {
103
- this.guards.splice(index, 1);
104
- }
105
- };
106
- }
107
-
108
- destroy(): void {
109
- this.strategy.destroy();
110
- this.listeners.clear();
111
- this.guards = [];
112
- }
113
-
114
- private matchRoute(path: string): RouteConfig | null {
115
- return this.routes.get(path) || null;
116
- }
117
-
118
- private async runGuards(to: RouteConfig): Promise<boolean> {
119
- for (const guard of this.guards) {
120
- const result = await guard(to, this.currentRoute);
121
- if (!result) return false;
122
- }
123
- return true;
124
- }
125
-
126
- private setupNavigationListener(): void {
127
- this.strategy.listen((path) => {
128
- this.push(path);
129
- });
130
- }
131
-
132
- private notifyListeners(route: RouteConfig): void {
133
- this.listeners.forEach(listener => listener(route));
134
- }
135
- }
@@ -1,176 +0,0 @@
1
- /**
2
- * Default Shell Implementation
3
- */
4
-
5
- import type { IShell, IThemeManager, IFeatureManager, IRouter } from '@hamak/ui-shell-api';
6
- import type { ShellConfig, ShellContext, ShellEvent, ShellEventListener, ShellEventType } from '@hamak/ui-shell-api';
7
- import { DefaultThemeManager } from './DefaultThemeManager';
8
- import { DefaultFeatureManager } from './DefaultFeatureManager';
9
- import { LocalStorageProvider } from '../providers/LocalStorageProvider';
10
- import { CSSVariablesThemeProvider } from '../providers/CSSVariablesThemeProvider';
11
-
12
- export class DefaultShell implements IShell {
13
- private themeManager: IThemeManager;
14
- private featureManager: IFeatureManager;
15
- private router: IRouter | null = null;
16
- private eventListeners: Map<string, Set<ShellEventListener>> = new Map();
17
- private viewportState = {
18
- width: typeof window !== 'undefined' ? window.innerWidth : 0,
19
- height: typeof window !== 'undefined' ? window.innerHeight : 0,
20
- };
21
- private config: ShellConfig;
22
- private isReady = false;
23
-
24
- constructor(config: ShellConfig = {}) {
25
- this.config = config;
26
-
27
- // Create theme manager with providers
28
- const storageProvider = new LocalStorageProvider();
29
- const themeProvider = new CSSVariablesThemeProvider();
30
- this.themeManager = new DefaultThemeManager(config.theme, storageProvider, themeProvider);
31
-
32
- // Create feature manager
33
- this.featureManager = new DefaultFeatureManager(config.features);
34
-
35
- this.setupViewportListener();
36
- }
37
-
38
- async initialize(): Promise<void> {
39
- if (this.isReady) {
40
- console.warn('Shell is already initialized');
41
- return;
42
- }
43
-
44
- // Apply initial theme
45
- this.themeManager.setTheme(this.themeManager.getTheme());
46
-
47
- // Subscribe to theme changes
48
- this.themeManager.subscribe(theme => {
49
- this.emit('theme:changed', { theme });
50
- });
51
-
52
- // Subscribe to feature changes
53
- this.featureManager.subscribeAll((key, value) => {
54
- this.emit('feature:toggled', { key, value });
55
- });
56
-
57
- this.isReady = true;
58
- this.emit('shell:ready', {});
59
- }
60
-
61
- getContext(): ShellContext {
62
- return {
63
- theme: this.themeManager.getTheme(),
64
- setTheme: (mode) => this.themeManager.setTheme(mode),
65
- features: this.featureManager.getAll(),
66
- isFeatureEnabled: (key) => this.featureManager.isEnabled(key),
67
- getFeature: (key, defaultValue) => this.featureManager.get(key, defaultValue),
68
- viewport: {
69
- width: this.viewportState.width,
70
- height: this.viewportState.height,
71
- isMobile: this.viewportState.width < 768,
72
- isTablet: this.viewportState.width >= 768 && this.viewportState.width < 1024,
73
- isDesktop: this.viewportState.width >= 1024,
74
- },
75
- };
76
- }
77
-
78
- getThemeManager(): IThemeManager {
79
- return this.themeManager;
80
- }
81
-
82
- getFeatureManager(): IFeatureManager {
83
- return this.featureManager;
84
- }
85
-
86
- getRouter(): IRouter | null {
87
- return this.router;
88
- }
89
-
90
- setRouter(router: IRouter): void {
91
- this.router = router;
92
- this.router.subscribe(route => {
93
- this.emit('route:changed', { route });
94
- });
95
- }
96
-
97
- emit(type: ShellEventType, payload?: any): void {
98
- const event: ShellEvent = {
99
- type,
100
- payload,
101
- timestamp: Date.now(),
102
- };
103
-
104
- const listeners = this.eventListeners.get(type);
105
- if (listeners) {
106
- listeners.forEach(listener => listener(event));
107
- }
108
-
109
- const wildcardListeners = this.eventListeners.get('*');
110
- if (wildcardListeners) {
111
- wildcardListeners.forEach(listener => listener(event));
112
- }
113
- }
114
-
115
- on(type: ShellEventType | '*', listener: ShellEventListener): () => void {
116
- if (!this.eventListeners.has(type)) {
117
- this.eventListeners.set(type, new Set());
118
- }
119
- this.eventListeners.get(type)!.add(listener);
120
-
121
- return () => {
122
- const listeners = this.eventListeners.get(type);
123
- if (listeners) {
124
- listeners.delete(listener);
125
- if (listeners.size === 0) {
126
- this.eventListeners.delete(type);
127
- }
128
- }
129
- };
130
- }
131
-
132
- off(type: ShellEventType | '*', listener: ShellEventListener): void {
133
- const listeners = this.eventListeners.get(type);
134
- if (listeners) {
135
- listeners.delete(listener);
136
- }
137
- }
138
-
139
- ready(): boolean {
140
- return this.isReady;
141
- }
142
-
143
- getConfig(): Readonly<ShellConfig> {
144
- return { ...this.config };
145
- }
146
-
147
- destroy(): void {
148
- this.themeManager.destroy();
149
- this.featureManager.destroy();
150
- if (this.router) {
151
- this.router.destroy();
152
- }
153
- if (typeof window !== 'undefined') {
154
- window.removeEventListener('resize', this.handleResize);
155
- }
156
- this.eventListeners.clear();
157
- this.isReady = false;
158
- }
159
-
160
- private setupViewportListener(): void {
161
- if (typeof window === 'undefined') return;
162
- window.addEventListener('resize', this.handleResize);
163
- }
164
-
165
- private handleResize = (): void => {
166
- if (typeof window === 'undefined') return;
167
-
168
- this.viewportState.width = window.innerWidth;
169
- this.viewportState.height = window.innerHeight;
170
-
171
- this.emit('viewport:resized', {
172
- width: this.viewportState.width,
173
- height: this.viewportState.height,
174
- });
175
- };
176
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * Default Theme Manager Implementation
3
- */
4
-
5
- import type { IThemeManager } from '@hamak/ui-shell-api';
6
- import type { ThemeMode, ThemeConfig } from '@hamak/ui-shell-api';
7
- import type { IStorageProvider, IThemeProvider } from '@hamak/ui-shell-spi';
8
-
9
- const THEME_STORAGE_KEY = 'ui-shell-theme';
10
-
11
- export class DefaultThemeManager implements IThemeManager {
12
- private currentTheme: ThemeMode = 'system';
13
- private config: ThemeConfig;
14
- private listeners: Set<(theme: ThemeMode) => void> = new Set();
15
- private storageProvider: IStorageProvider;
16
- private themeProvider: IThemeProvider;
17
-
18
- constructor(
19
- config: ThemeConfig = { mode: 'system' },
20
- storageProvider: IStorageProvider,
21
- themeProvider: IThemeProvider
22
- ) {
23
- this.config = config;
24
- this.storageProvider = storageProvider;
25
- this.themeProvider = themeProvider;
26
- this.currentTheme = config.mode || this.loadPersistedTheme();
27
- this.setupSystemThemeListener();
28
- this.applyTheme();
29
- }
30
-
31
- getTheme(): ThemeMode {
32
- return this.currentTheme;
33
- }
34
-
35
- getResolvedTheme(): 'light' | 'dark' {
36
- if (this.currentTheme === 'system') {
37
- return this.themeProvider.getSystemPreference();
38
- }
39
- return this.currentTheme;
40
- }
41
-
42
- setTheme(mode: ThemeMode): void {
43
- if (this.currentTheme === mode) return;
44
-
45
- this.currentTheme = mode;
46
- this.persistTheme(mode);
47
- this.applyTheme();
48
- this.notifyListeners();
49
- }
50
-
51
- toggleTheme(): void {
52
- const resolved = this.getResolvedTheme();
53
- this.setTheme(resolved === 'light' ? 'dark' : 'light');
54
- }
55
-
56
- subscribe(listener: (theme: ThemeMode) => void): () => void {
57
- this.listeners.add(listener);
58
- return () => this.listeners.delete(listener);
59
- }
60
-
61
- setCSSVariables(variables: Record<string, string>): void {
62
- this.config.cssVariables = { ...this.config.cssVariables, ...variables };
63
- this.applyTheme();
64
- }
65
-
66
- destroy(): void {
67
- this.themeProvider.destroy();
68
- this.listeners.clear();
69
- }
70
-
71
- private setupSystemThemeListener(): void {
72
- this.themeProvider.onSystemThemeChange(() => {
73
- if (this.currentTheme === 'system') {
74
- this.applyTheme();
75
- this.notifyListeners();
76
- }
77
- });
78
- }
79
-
80
- private applyTheme(): void {
81
- this.themeProvider.applyTheme(this.currentTheme, this.config);
82
- }
83
-
84
- private loadPersistedTheme(): ThemeMode {
85
- const stored = this.storageProvider.getItem(THEME_STORAGE_KEY);
86
- if (stored === 'light' || stored === 'dark' || stored === 'system') {
87
- return stored;
88
- }
89
- return 'system';
90
- }
91
-
92
- private persistTheme(mode: ThemeMode): void {
93
- this.storageProvider.setItem(THEME_STORAGE_KEY, mode);
94
- }
95
-
96
- private notifyListeners(): void {
97
- this.listeners.forEach(listener => listener(this.currentTheme));
98
- }
99
- }
package/src/core/index.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * Core Implementations
3
- * Export all core implementation classes
4
- */
5
-
6
- export * from './DefaultShell';
7
- export * from './DefaultThemeManager';
8
- export * from './DefaultFeatureManager';
9
- export * from './DefaultLayoutManager';
10
- export * from './DefaultRouter';
package/src/index.ts DELETED
@@ -1,51 +0,0 @@
1
- /**
2
- * UI Shell Implementation
3
- * Concrete implementations of UI Shell interfaces
4
- *
5
- * This package provides default implementations that can be used directly
6
- * or extended for custom behavior.
7
- */
8
-
9
- export const version = '0.2.2';
10
-
11
- // Export core implementations
12
- export * from './core';
13
-
14
- // Export providers
15
- export * from './providers';
16
-
17
- // Export utilities
18
- export * from './utils';
19
-
20
- // Export plugin integration
21
- export * from './plugin';
22
-
23
- // Factory functions
24
- import { DefaultShell } from './core/DefaultShell';
25
- import { DefaultLayoutManager } from './core/DefaultLayoutManager';
26
- import type { ShellConfig } from '@hamak/ui-shell-api';
27
-
28
- export function createShell(config?: ShellConfig): DefaultShell {
29
- return new DefaultShell(config);
30
- }
31
-
32
- export function createLayoutManager(): DefaultLayoutManager {
33
- return new DefaultLayoutManager();
34
- }
35
-
36
- // Singleton pattern support
37
- let globalShell: DefaultShell | null = null;
38
-
39
- export function getShell(config?: ShellConfig): DefaultShell {
40
- if (!globalShell) {
41
- globalShell = createShell(config);
42
- }
43
- return globalShell;
44
- }
45
-
46
- export function resetShell(): void {
47
- if (globalShell) {
48
- globalShell.destroy();
49
- globalShell = null;
50
- }
51
- }
@@ -1,98 +0,0 @@
1
- /**
2
- * Shell Plugin Factory
3
- * Creates microkernel plugin for UI Shell integration
4
- */
5
-
6
- import type { PluginModule } from '@hamak/microkernel-spi';
7
- import type { ActivateContext } from '@hamak/microkernel-api';
8
- import type { ShellConfig } from '@hamak/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 '@hamak/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
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Plugin Integration
3
- * Export plugin factory and helpers
4
- */
5
-
6
- export * from './ShellPluginFactory';
@@ -1,60 +0,0 @@
1
- /**
2
- * CSS Variables Theme Provider
3
- * Implements theming using CSS custom properties
4
- */
5
-
6
- import type { IThemeProvider } from '@hamak/ui-shell-spi';
7
- import type { ThemeMode, ThemeConfig } from '@hamak/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
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * Hash Router Strategy
3
- * Uses URL hash for routing
4
- */
5
-
6
- import type { IRouterStrategy } from '@hamak/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
- }
@@ -1,78 +0,0 @@
1
- /**
2
- * History Router Strategy
3
- * Uses browser History API for routing
4
- */
5
-
6
- import type { IRouterStrategy } from '@hamak/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
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * LocalStorage Provider
3
- * Implementation using browser localStorage
4
- */
5
-
6
- import type { IStorageProvider } from '@hamak/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
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Memory Storage Provider
3
- * In-memory storage for SSR or testing
4
- */
5
-
6
- import type { IStorageProvider } from '@hamak/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
- }
@@ -1,10 +0,0 @@
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';
@@ -1,6 +0,0 @@
1
- /**
2
- * Utilities
3
- * Export all utility classes
4
- */
5
-
6
- export * from './viewport-utils';
@@ -1,64 +0,0 @@
1
- /**
2
- * Viewport Utilities
3
- */
4
-
5
- import type { BreakpointName, Breakpoint } from '@hamak/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
- }
@@ -1,24 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "target": "ES2015",
5
- "lib": [
6
- "ES2015",
7
- "DOM",
8
- "DOM.Iterable"
9
- ],
10
- "outDir": "dist/es2015",
11
- "declaration": false,
12
- "declarationMap": false,
13
- "sourceMap": false,
14
- "downlevelIteration": true,
15
- "composite": false
16
- },
17
- "include": [
18
- "src/**/*"
19
- ],
20
- "exclude": [
21
- "node_modules",
22
- "dist"
23
- ]
24
- }
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
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
- }