@hamak/ui-shell-impl 0.2.1 → 0.2.3

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.
@@ -1 +1 @@
1
- $ tsc -p tsconfig.json
1
+ $ tsc -p tsconfig.json && tsc -p tsconfig.es2015.json
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Default Feature Manager Implementation
3
+ */
4
+ export class DefaultFeatureManager {
5
+ constructor(initialFeatures = {}) {
6
+ this.listeners = new Map();
7
+ this.features = Object.assign({}, initialFeatures);
8
+ }
9
+ isEnabled(key) {
10
+ const value = this.features[key];
11
+ return typeof value === 'boolean' ? value : Boolean(value);
12
+ }
13
+ get(key, defaultValue) {
14
+ const value = this.features[key];
15
+ return (value !== undefined ? value : defaultValue);
16
+ }
17
+ set(key, value) {
18
+ const oldValue = this.features[key];
19
+ if (oldValue === value)
20
+ return;
21
+ this.features[key] = value;
22
+ this.notifyListeners(key, value);
23
+ }
24
+ enable(key) {
25
+ this.set(key, true);
26
+ }
27
+ disable(key) {
28
+ this.set(key, false);
29
+ }
30
+ toggle(key) {
31
+ this.set(key, !this.isEnabled(key));
32
+ }
33
+ update(updates) {
34
+ Object.entries(updates).forEach(([key, value]) => {
35
+ this.set(key, value);
36
+ });
37
+ }
38
+ getAll() {
39
+ return Object.assign({}, this.features);
40
+ }
41
+ has(key) {
42
+ return key in this.features;
43
+ }
44
+ subscribe(key, listener) {
45
+ if (!this.listeners.has(key)) {
46
+ this.listeners.set(key, new Set());
47
+ }
48
+ this.listeners.get(key).add(listener);
49
+ return () => {
50
+ const listeners = this.listeners.get(key);
51
+ if (listeners) {
52
+ listeners.delete(listener);
53
+ if (listeners.size === 0) {
54
+ this.listeners.delete(key);
55
+ }
56
+ }
57
+ };
58
+ }
59
+ subscribeAll(listener) {
60
+ const unsubscribers = [];
61
+ const currentKeys = Object.keys(this.features);
62
+ currentKeys.forEach(key => {
63
+ const unsub = this.subscribe(key, value => listener(key, value));
64
+ unsubscribers.push(unsub);
65
+ });
66
+ return () => {
67
+ unsubscribers.forEach(unsub => unsub());
68
+ };
69
+ }
70
+ destroy() {
71
+ this.listeners.clear();
72
+ }
73
+ notifyListeners(key, value) {
74
+ const listeners = this.listeners.get(key);
75
+ if (listeners) {
76
+ listeners.forEach(listener => listener(value));
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Default Layout Manager Implementation
3
+ */
4
+ export class DefaultLayoutManager {
5
+ constructor() {
6
+ this.slots = new Map();
7
+ this.listeners = new Set();
8
+ }
9
+ registerSlot(slot) {
10
+ const area = slot.area;
11
+ if (!this.slots.has(area)) {
12
+ this.slots.set(area, new Set());
13
+ }
14
+ this.slots.get(area).add(slot);
15
+ this.notifyListeners();
16
+ return () => this.unregisterSlot(slot);
17
+ }
18
+ unregisterSlot(slot) {
19
+ const area = slot.area;
20
+ const slots = this.slots.get(area);
21
+ if (slots) {
22
+ slots.delete(slot);
23
+ if (slots.size === 0) {
24
+ this.slots.delete(area);
25
+ }
26
+ this.notifyListeners();
27
+ }
28
+ }
29
+ getSlots(area) {
30
+ const slots = this.slots.get(area);
31
+ if (!slots)
32
+ return [];
33
+ return Array.from(slots).sort((a, b) => {
34
+ const priorityA = a.priority || 0;
35
+ const priorityB = b.priority || 0;
36
+ return priorityB - priorityA; // Higher priority first
37
+ });
38
+ }
39
+ getAreas() {
40
+ return Array.from(this.slots.keys());
41
+ }
42
+ hasSlots(area) {
43
+ const slots = this.slots.get(area);
44
+ return Boolean(slots && slots.size > 0);
45
+ }
46
+ subscribe(listener) {
47
+ this.listeners.add(listener);
48
+ return () => this.listeners.delete(listener);
49
+ }
50
+ clear() {
51
+ this.slots.clear();
52
+ this.notifyListeners();
53
+ }
54
+ destroy() {
55
+ this.slots.clear();
56
+ this.listeners.clear();
57
+ }
58
+ notifyListeners() {
59
+ this.listeners.forEach(listener => listener());
60
+ }
61
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Default Router Implementation
3
+ */
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ export class DefaultRouter {
14
+ constructor(options, strategy) {
15
+ this.routes = new Map();
16
+ this.currentRoute = null;
17
+ this.guards = [];
18
+ this.listeners = new Set();
19
+ this.strategy = strategy;
20
+ this.guards = [];
21
+ this.registerRoutes(options.routes);
22
+ this.setupNavigationListener();
23
+ }
24
+ registerRoutes(routes) {
25
+ routes.forEach(route => {
26
+ this.routes.set(route.path, route);
27
+ if (route.children) {
28
+ this.registerRoutes(route.children);
29
+ }
30
+ });
31
+ }
32
+ push(path) {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ const route = this.matchRoute(path);
35
+ if (!route) {
36
+ console.error(`Route not found: ${path}`);
37
+ return false;
38
+ }
39
+ if (!(yield this.runGuards(route))) {
40
+ return false;
41
+ }
42
+ if (route.beforeEnter && !(yield route.beforeEnter(route, this.currentRoute))) {
43
+ return false;
44
+ }
45
+ this.currentRoute = route;
46
+ this.strategy.push(path);
47
+ this.notifyListeners(route);
48
+ return true;
49
+ });
50
+ }
51
+ replace(path) {
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ const route = this.matchRoute(path);
54
+ if (!route) {
55
+ console.error(`Route not found: ${path}`);
56
+ return false;
57
+ }
58
+ if (!(yield this.runGuards(route))) {
59
+ return false;
60
+ }
61
+ this.currentRoute = route;
62
+ this.strategy.replace(path);
63
+ this.notifyListeners(route);
64
+ return true;
65
+ });
66
+ }
67
+ back() {
68
+ this.strategy.back();
69
+ }
70
+ forward() {
71
+ this.strategy.forward();
72
+ }
73
+ getCurrentRoute() {
74
+ return this.currentRoute;
75
+ }
76
+ subscribe(listener) {
77
+ this.listeners.add(listener);
78
+ return () => this.listeners.delete(listener);
79
+ }
80
+ loadRouteComponent(route) {
81
+ return __awaiter(this, void 0, void 0, function* () {
82
+ try {
83
+ const component = yield route.component();
84
+ return component;
85
+ }
86
+ catch (error) {
87
+ console.error(`Failed to load route component for ${route.path}:`, error);
88
+ throw error;
89
+ }
90
+ });
91
+ }
92
+ addGuard(guard) {
93
+ this.guards.push(guard);
94
+ return () => {
95
+ const index = this.guards.indexOf(guard);
96
+ if (index > -1) {
97
+ this.guards.splice(index, 1);
98
+ }
99
+ };
100
+ }
101
+ destroy() {
102
+ this.strategy.destroy();
103
+ this.listeners.clear();
104
+ this.guards = [];
105
+ }
106
+ matchRoute(path) {
107
+ return this.routes.get(path) || null;
108
+ }
109
+ runGuards(to) {
110
+ return __awaiter(this, void 0, void 0, function* () {
111
+ for (const guard of this.guards) {
112
+ const result = yield guard(to, this.currentRoute);
113
+ if (!result)
114
+ return false;
115
+ }
116
+ return true;
117
+ });
118
+ }
119
+ setupNavigationListener() {
120
+ this.strategy.listen((path) => {
121
+ this.push(path);
122
+ });
123
+ }
124
+ notifyListeners(route) {
125
+ this.listeners.forEach(listener => listener(route));
126
+ }
127
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Default Shell Implementation
3
+ */
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ import { DefaultThemeManager } from './DefaultThemeManager';
14
+ import { DefaultFeatureManager } from './DefaultFeatureManager';
15
+ import { LocalStorageProvider } from '../providers/LocalStorageProvider';
16
+ import { CSSVariablesThemeProvider } from '../providers/CSSVariablesThemeProvider';
17
+ export class DefaultShell {
18
+ constructor(config = {}) {
19
+ this.router = null;
20
+ this.eventListeners = new Map();
21
+ this.viewportState = {
22
+ width: typeof window !== 'undefined' ? window.innerWidth : 0,
23
+ height: typeof window !== 'undefined' ? window.innerHeight : 0,
24
+ };
25
+ this.isReady = false;
26
+ this.handleResize = () => {
27
+ if (typeof window === 'undefined')
28
+ return;
29
+ this.viewportState.width = window.innerWidth;
30
+ this.viewportState.height = window.innerHeight;
31
+ this.emit('viewport:resized', {
32
+ width: this.viewportState.width,
33
+ height: this.viewportState.height,
34
+ });
35
+ };
36
+ this.config = config;
37
+ // Create theme manager with providers
38
+ const storageProvider = new LocalStorageProvider();
39
+ const themeProvider = new CSSVariablesThemeProvider();
40
+ this.themeManager = new DefaultThemeManager(config.theme, storageProvider, themeProvider);
41
+ // Create feature manager
42
+ this.featureManager = new DefaultFeatureManager(config.features);
43
+ this.setupViewportListener();
44
+ }
45
+ initialize() {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ if (this.isReady) {
48
+ console.warn('Shell is already initialized');
49
+ return;
50
+ }
51
+ // Apply initial theme
52
+ this.themeManager.setTheme(this.themeManager.getTheme());
53
+ // Subscribe to theme changes
54
+ this.themeManager.subscribe(theme => {
55
+ this.emit('theme:changed', { theme });
56
+ });
57
+ // Subscribe to feature changes
58
+ this.featureManager.subscribeAll((key, value) => {
59
+ this.emit('feature:toggled', { key, value });
60
+ });
61
+ this.isReady = true;
62
+ this.emit('shell:ready', {});
63
+ });
64
+ }
65
+ getContext() {
66
+ return {
67
+ theme: this.themeManager.getTheme(),
68
+ setTheme: (mode) => this.themeManager.setTheme(mode),
69
+ features: this.featureManager.getAll(),
70
+ isFeatureEnabled: (key) => this.featureManager.isEnabled(key),
71
+ getFeature: (key, defaultValue) => this.featureManager.get(key, defaultValue),
72
+ viewport: {
73
+ width: this.viewportState.width,
74
+ height: this.viewportState.height,
75
+ isMobile: this.viewportState.width < 768,
76
+ isTablet: this.viewportState.width >= 768 && this.viewportState.width < 1024,
77
+ isDesktop: this.viewportState.width >= 1024,
78
+ },
79
+ };
80
+ }
81
+ getThemeManager() {
82
+ return this.themeManager;
83
+ }
84
+ getFeatureManager() {
85
+ return this.featureManager;
86
+ }
87
+ getRouter() {
88
+ return this.router;
89
+ }
90
+ setRouter(router) {
91
+ this.router = router;
92
+ this.router.subscribe(route => {
93
+ this.emit('route:changed', { route });
94
+ });
95
+ }
96
+ emit(type, payload) {
97
+ const event = {
98
+ type,
99
+ payload,
100
+ timestamp: Date.now(),
101
+ };
102
+ const listeners = this.eventListeners.get(type);
103
+ if (listeners) {
104
+ listeners.forEach(listener => listener(event));
105
+ }
106
+ const wildcardListeners = this.eventListeners.get('*');
107
+ if (wildcardListeners) {
108
+ wildcardListeners.forEach(listener => listener(event));
109
+ }
110
+ }
111
+ on(type, listener) {
112
+ if (!this.eventListeners.has(type)) {
113
+ this.eventListeners.set(type, new Set());
114
+ }
115
+ this.eventListeners.get(type).add(listener);
116
+ return () => {
117
+ const listeners = this.eventListeners.get(type);
118
+ if (listeners) {
119
+ listeners.delete(listener);
120
+ if (listeners.size === 0) {
121
+ this.eventListeners.delete(type);
122
+ }
123
+ }
124
+ };
125
+ }
126
+ off(type, listener) {
127
+ const listeners = this.eventListeners.get(type);
128
+ if (listeners) {
129
+ listeners.delete(listener);
130
+ }
131
+ }
132
+ ready() {
133
+ return this.isReady;
134
+ }
135
+ getConfig() {
136
+ return Object.assign({}, this.config);
137
+ }
138
+ destroy() {
139
+ this.themeManager.destroy();
140
+ this.featureManager.destroy();
141
+ if (this.router) {
142
+ this.router.destroy();
143
+ }
144
+ if (typeof window !== 'undefined') {
145
+ window.removeEventListener('resize', this.handleResize);
146
+ }
147
+ this.eventListeners.clear();
148
+ this.isReady = false;
149
+ }
150
+ setupViewportListener() {
151
+ if (typeof window === 'undefined')
152
+ return;
153
+ window.addEventListener('resize', this.handleResize);
154
+ }
155
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Default Theme Manager Implementation
3
+ */
4
+ const THEME_STORAGE_KEY = 'ui-shell-theme';
5
+ export class DefaultThemeManager {
6
+ constructor(config = { mode: 'system' }, storageProvider, themeProvider) {
7
+ this.currentTheme = 'system';
8
+ this.listeners = new Set();
9
+ this.config = config;
10
+ this.storageProvider = storageProvider;
11
+ this.themeProvider = themeProvider;
12
+ this.currentTheme = config.mode || this.loadPersistedTheme();
13
+ this.setupSystemThemeListener();
14
+ this.applyTheme();
15
+ }
16
+ getTheme() {
17
+ return this.currentTheme;
18
+ }
19
+ getResolvedTheme() {
20
+ if (this.currentTheme === 'system') {
21
+ return this.themeProvider.getSystemPreference();
22
+ }
23
+ return this.currentTheme;
24
+ }
25
+ setTheme(mode) {
26
+ if (this.currentTheme === mode)
27
+ return;
28
+ this.currentTheme = mode;
29
+ this.persistTheme(mode);
30
+ this.applyTheme();
31
+ this.notifyListeners();
32
+ }
33
+ toggleTheme() {
34
+ const resolved = this.getResolvedTheme();
35
+ this.setTheme(resolved === 'light' ? 'dark' : 'light');
36
+ }
37
+ subscribe(listener) {
38
+ this.listeners.add(listener);
39
+ return () => this.listeners.delete(listener);
40
+ }
41
+ setCSSVariables(variables) {
42
+ this.config.cssVariables = Object.assign(Object.assign({}, this.config.cssVariables), variables);
43
+ this.applyTheme();
44
+ }
45
+ destroy() {
46
+ this.themeProvider.destroy();
47
+ this.listeners.clear();
48
+ }
49
+ setupSystemThemeListener() {
50
+ this.themeProvider.onSystemThemeChange(() => {
51
+ if (this.currentTheme === 'system') {
52
+ this.applyTheme();
53
+ this.notifyListeners();
54
+ }
55
+ });
56
+ }
57
+ applyTheme() {
58
+ this.themeProvider.applyTheme(this.currentTheme, this.config);
59
+ }
60
+ loadPersistedTheme() {
61
+ const stored = this.storageProvider.getItem(THEME_STORAGE_KEY);
62
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
63
+ return stored;
64
+ }
65
+ return 'system';
66
+ }
67
+ persistTheme(mode) {
68
+ this.storageProvider.setItem(THEME_STORAGE_KEY, mode);
69
+ }
70
+ notifyListeners() {
71
+ this.listeners.forEach(listener => listener(this.currentTheme));
72
+ }
73
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Core Implementations
3
+ * Export all core implementation classes
4
+ */
5
+ export * from './DefaultShell';
6
+ export * from './DefaultThemeManager';
7
+ export * from './DefaultFeatureManager';
8
+ export * from './DefaultLayoutManager';
9
+ export * from './DefaultRouter';
@@ -0,0 +1,39 @@
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
+ export const version = '0.2.2';
9
+ // Export core implementations
10
+ export * from './core';
11
+ // Export providers
12
+ export * from './providers';
13
+ // Export utilities
14
+ export * from './utils';
15
+ // Export plugin integration
16
+ export * from './plugin';
17
+ // Factory functions
18
+ import { DefaultShell } from './core/DefaultShell';
19
+ import { DefaultLayoutManager } from './core/DefaultLayoutManager';
20
+ export function createShell(config) {
21
+ return new DefaultShell(config);
22
+ }
23
+ export function createLayoutManager() {
24
+ return new DefaultLayoutManager();
25
+ }
26
+ // Singleton pattern support
27
+ let globalShell = null;
28
+ export function getShell(config) {
29
+ if (!globalShell) {
30
+ globalShell = createShell(config);
31
+ }
32
+ return globalShell;
33
+ }
34
+ export function resetShell() {
35
+ if (globalShell) {
36
+ globalShell.destroy();
37
+ globalShell = null;
38
+ }
39
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shell Plugin Factory
3
+ * Creates microkernel plugin for UI Shell integration
4
+ */
5
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
6
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
7
+ return new (P || (P = Promise))(function (resolve, reject) {
8
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
9
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
11
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
12
+ });
13
+ };
14
+ import { SHELL_TOKEN, THEME_MANAGER_TOKEN, FEATURE_MANAGER_TOKEN, LAYOUT_MANAGER_TOKEN, ShellCommands, ShellEvents, } from '@hamak/ui-shell-api';
15
+ import { DefaultShell } from '../core/DefaultShell';
16
+ import { DefaultLayoutManager } from '../core/DefaultLayoutManager';
17
+ export function createShellPlugin(config) {
18
+ let shell;
19
+ let layoutManager;
20
+ return {
21
+ initialize(ctx) {
22
+ shell = new DefaultShell(config);
23
+ layoutManager = new DefaultLayoutManager();
24
+ // Provide shell services
25
+ ctx.provide({ provide: SHELL_TOKEN, useValue: shell });
26
+ ctx.provide({ provide: THEME_MANAGER_TOKEN, useValue: shell.getThemeManager() });
27
+ ctx.provide({ provide: FEATURE_MANAGER_TOKEN, useValue: shell.getFeatureManager() });
28
+ ctx.provide({ provide: LAYOUT_MANAGER_TOKEN, useValue: layoutManager });
29
+ // Register commands
30
+ ctx.commands.register(ShellCommands.SET_THEME, (mode) => {
31
+ shell.getThemeManager().setTheme(mode);
32
+ });
33
+ ctx.commands.register(ShellCommands.TOGGLE_THEME, () => {
34
+ shell.getThemeManager().toggleTheme();
35
+ });
36
+ ctx.commands.register(ShellCommands.ENABLE_FEATURE, (key) => {
37
+ shell.getFeatureManager().enable(key);
38
+ });
39
+ ctx.commands.register(ShellCommands.DISABLE_FEATURE, (key) => {
40
+ shell.getFeatureManager().disable(key);
41
+ });
42
+ ctx.commands.register(ShellCommands.TOGGLE_FEATURE, (key) => {
43
+ shell.getFeatureManager().toggle(key);
44
+ });
45
+ ctx.commands.register(ShellCommands.NAVIGATE, (path) => {
46
+ const router = shell.getRouter();
47
+ if (!router) {
48
+ console.warn('Router not initialized');
49
+ return;
50
+ }
51
+ return router.push(path);
52
+ });
53
+ ctx.commands.register(ShellCommands.NAVIGATE_BACK, () => {
54
+ const router = shell.getRouter();
55
+ if (!router) {
56
+ console.warn('Router not initialized');
57
+ return;
58
+ }
59
+ router.back();
60
+ });
61
+ // Forward shell events to microkernel hooks
62
+ shell.on('shell:ready', () => ctx.hooks.emit(ShellEvents.READY, {}));
63
+ shell.on('theme:changed', (event) => ctx.hooks.emit(ShellEvents.THEME_CHANGED, event.payload));
64
+ shell.on('feature:toggled', (event) => ctx.hooks.emit(ShellEvents.FEATURE_TOGGLED, event.payload));
65
+ shell.on('route:changed', (event) => ctx.hooks.emit(ShellEvents.ROUTE_CHANGED, event.payload));
66
+ shell.on('viewport:resized', (event) => ctx.hooks.emit(ShellEvents.VIEWPORT_RESIZED, event.payload));
67
+ },
68
+ activate(ctx) {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ yield shell.initialize();
71
+ const shellContext = shell.getContext();
72
+ ctx.hooks.emit('shell:activated', { context: shellContext });
73
+ });
74
+ },
75
+ deactivate() {
76
+ shell.destroy();
77
+ layoutManager.destroy();
78
+ },
79
+ };
80
+ }
81
+ // Helper functions
82
+ export function getShellFromContext(ctx) {
83
+ return ctx.resolve(SHELL_TOKEN);
84
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Plugin Integration
3
+ * Export plugin factory and helpers
4
+ */
5
+ export * from './ShellPluginFactory';
@@ -0,0 +1,49 @@
1
+ /**
2
+ * CSS Variables Theme Provider
3
+ * Implements theming using CSS custom properties
4
+ */
5
+ export class CSSVariablesThemeProvider {
6
+ constructor() {
7
+ this.listeners = new Set();
8
+ this.handleMediaQueryChange = () => {
9
+ const theme = this.getSystemPreference();
10
+ this.listeners.forEach(listener => listener(theme));
11
+ };
12
+ }
13
+ applyTheme(mode, config) {
14
+ if (typeof document === 'undefined')
15
+ return;
16
+ const resolved = mode === 'system' ? this.getSystemPreference() : mode;
17
+ document.documentElement.setAttribute('data-theme', resolved);
18
+ document.documentElement.style.colorScheme = resolved;
19
+ // Apply custom CSS variables
20
+ if (config.cssVariables) {
21
+ Object.entries(config.cssVariables).forEach(([key, value]) => {
22
+ document.documentElement.style.setProperty(key, value);
23
+ });
24
+ }
25
+ }
26
+ getSystemPreference() {
27
+ if (typeof window === 'undefined')
28
+ return 'light';
29
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
30
+ }
31
+ onSystemThemeChange(callback) {
32
+ if (typeof window === 'undefined') {
33
+ return () => { };
34
+ }
35
+ this.listeners.add(callback);
36
+ if (!this.mediaQuery) {
37
+ this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
38
+ this.mediaQuery.addEventListener('change', this.handleMediaQueryChange);
39
+ }
40
+ return () => {
41
+ this.listeners.delete(callback);
42
+ };
43
+ }
44
+ destroy() {
45
+ var _a;
46
+ (_a = this.mediaQuery) === null || _a === void 0 ? void 0 : _a.removeEventListener('change', this.handleMediaQueryChange);
47
+ this.listeners.clear();
48
+ }
49
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Hash Router Strategy
3
+ * Uses URL hash for routing
4
+ */
5
+ export class HashRouterStrategy {
6
+ constructor() {
7
+ this.listeners = new Set();
8
+ this.handleHashChange = () => {
9
+ const path = this.getCurrentPath();
10
+ this.notifyListeners(path);
11
+ };
12
+ this.setupListener();
13
+ }
14
+ push(path) {
15
+ if (typeof window === 'undefined')
16
+ return;
17
+ window.location.hash = path;
18
+ }
19
+ replace(path) {
20
+ if (typeof window === 'undefined')
21
+ return;
22
+ window.location.replace(`#${path}`);
23
+ }
24
+ back() {
25
+ if (typeof window !== 'undefined') {
26
+ window.history.back();
27
+ }
28
+ }
29
+ forward() {
30
+ if (typeof window !== 'undefined') {
31
+ window.history.forward();
32
+ }
33
+ }
34
+ getCurrentPath() {
35
+ if (typeof window === 'undefined')
36
+ return '/';
37
+ return window.location.hash.slice(1) || '/';
38
+ }
39
+ listen(callback) {
40
+ this.listeners.add(callback);
41
+ return () => this.listeners.delete(callback);
42
+ }
43
+ destroy() {
44
+ if (typeof window !== 'undefined') {
45
+ window.removeEventListener('hashchange', this.handleHashChange);
46
+ }
47
+ this.listeners.clear();
48
+ }
49
+ setupListener() {
50
+ if (typeof window !== 'undefined') {
51
+ window.addEventListener('hashchange', this.handleHashChange);
52
+ }
53
+ }
54
+ notifyListeners(path) {
55
+ this.listeners.forEach(listener => listener(path));
56
+ }
57
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * History Router Strategy
3
+ * Uses browser History API for routing
4
+ */
5
+ export class HistoryRouterStrategy {
6
+ constructor(base = '/') {
7
+ this.listeners = new Set();
8
+ this.handlePopState = () => {
9
+ const path = this.getCurrentPath();
10
+ this.notifyListeners(path);
11
+ };
12
+ this.base = base;
13
+ this.setupListener();
14
+ }
15
+ push(path) {
16
+ if (typeof window === 'undefined')
17
+ return;
18
+ const fullPath = this.base + path;
19
+ window.history.pushState({}, '', fullPath);
20
+ this.notifyListeners(path);
21
+ }
22
+ replace(path) {
23
+ if (typeof window === 'undefined')
24
+ return;
25
+ const fullPath = this.base + path;
26
+ window.history.replaceState({}, '', fullPath);
27
+ this.notifyListeners(path);
28
+ }
29
+ back() {
30
+ if (typeof window !== 'undefined') {
31
+ window.history.back();
32
+ }
33
+ }
34
+ forward() {
35
+ if (typeof window !== 'undefined') {
36
+ window.history.forward();
37
+ }
38
+ }
39
+ getCurrentPath() {
40
+ if (typeof window === 'undefined')
41
+ return '/';
42
+ const path = window.location.pathname;
43
+ return path.startsWith(this.base) ? path.slice(this.base.length) : path;
44
+ }
45
+ listen(callback) {
46
+ this.listeners.add(callback);
47
+ return () => this.listeners.delete(callback);
48
+ }
49
+ destroy() {
50
+ if (typeof window !== 'undefined') {
51
+ window.removeEventListener('popstate', this.handlePopState);
52
+ }
53
+ this.listeners.clear();
54
+ }
55
+ setupListener() {
56
+ if (typeof window !== 'undefined') {
57
+ window.addEventListener('popstate', this.handlePopState);
58
+ }
59
+ }
60
+ notifyListeners(path) {
61
+ this.listeners.forEach(listener => listener(path));
62
+ }
63
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * LocalStorage Provider
3
+ * Implementation using browser localStorage
4
+ */
5
+ export class LocalStorageProvider {
6
+ getItem(key) {
7
+ if (!this.isAvailable())
8
+ return null;
9
+ try {
10
+ return localStorage.getItem(key);
11
+ }
12
+ catch (e) {
13
+ console.warn('Failed to get item from localStorage:', e);
14
+ return null;
15
+ }
16
+ }
17
+ setItem(key, value) {
18
+ if (!this.isAvailable())
19
+ return;
20
+ try {
21
+ localStorage.setItem(key, value);
22
+ }
23
+ catch (e) {
24
+ console.warn('Failed to set item in localStorage:', e);
25
+ }
26
+ }
27
+ removeItem(key) {
28
+ if (!this.isAvailable())
29
+ return;
30
+ try {
31
+ localStorage.removeItem(key);
32
+ }
33
+ catch (e) {
34
+ console.warn('Failed to remove item from localStorage:', e);
35
+ }
36
+ }
37
+ clear() {
38
+ if (!this.isAvailable())
39
+ return;
40
+ try {
41
+ localStorage.clear();
42
+ }
43
+ catch (e) {
44
+ console.warn('Failed to clear localStorage:', e);
45
+ }
46
+ }
47
+ isAvailable() {
48
+ return typeof localStorage !== 'undefined';
49
+ }
50
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Memory Storage Provider
3
+ * In-memory storage for SSR or testing
4
+ */
5
+ export class MemoryStorageProvider {
6
+ constructor() {
7
+ this.storage = new Map();
8
+ }
9
+ getItem(key) {
10
+ return this.storage.get(key) || null;
11
+ }
12
+ setItem(key, value) {
13
+ this.storage.set(key, value);
14
+ }
15
+ removeItem(key) {
16
+ this.storage.delete(key);
17
+ }
18
+ clear() {
19
+ this.storage.clear();
20
+ }
21
+ isAvailable() {
22
+ return true;
23
+ }
24
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Providers
3
+ * Export all provider implementations
4
+ */
5
+ export * from './LocalStorageProvider';
6
+ export * from './MemoryStorageProvider';
7
+ export * from './CSSVariablesThemeProvider';
8
+ export * from './HistoryRouterStrategy';
9
+ export * from './HashRouterStrategy';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Utilities
3
+ * Export all utility classes
4
+ */
5
+ export * from './viewport-utils';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Viewport Utilities
3
+ */
4
+ export const BREAKPOINTS = {
5
+ xs: { name: 'xs', minWidth: 0, maxWidth: 639 },
6
+ sm: { name: 'sm', minWidth: 640, maxWidth: 767 },
7
+ md: { name: 'md', minWidth: 768, maxWidth: 1023 },
8
+ lg: { name: 'lg', minWidth: 1024, maxWidth: 1279 },
9
+ xl: { name: 'xl', minWidth: 1280, maxWidth: 1535 },
10
+ '2xl': { name: '2xl', minWidth: 1536 },
11
+ };
12
+ export class ViewportUtils {
13
+ static getCurrentBreakpoint() {
14
+ if (typeof window === 'undefined')
15
+ return 'md';
16
+ const width = window.innerWidth;
17
+ for (const [name, breakpoint] of Object.entries(BREAKPOINTS)) {
18
+ if (width >= breakpoint.minWidth && (!breakpoint.maxWidth || width <= breakpoint.maxWidth)) {
19
+ return name;
20
+ }
21
+ }
22
+ return 'md';
23
+ }
24
+ static matchesBreakpoint(breakpoint) {
25
+ if (typeof window === 'undefined')
26
+ return false;
27
+ const width = window.innerWidth;
28
+ const bp = BREAKPOINTS[breakpoint];
29
+ return width >= bp.minWidth && (!bp.maxWidth || width <= bp.maxWidth);
30
+ }
31
+ static isMinBreakpoint(breakpoint) {
32
+ if (typeof window === 'undefined')
33
+ return false;
34
+ const width = window.innerWidth;
35
+ const bp = BREAKPOINTS[breakpoint];
36
+ return width >= bp.minWidth;
37
+ }
38
+ static getDimensions() {
39
+ if (typeof window === 'undefined') {
40
+ return { width: 0, height: 0 };
41
+ }
42
+ return {
43
+ width: window.innerWidth,
44
+ height: window.innerHeight,
45
+ };
46
+ }
47
+ static isTouchDevice() {
48
+ if (typeof window === 'undefined')
49
+ return false;
50
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
51
+ }
52
+ }
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * This package provides default implementations that can be used directly
6
6
  * or extended for custom behavior.
7
7
  */
8
- export declare const version = "0.2.1";
8
+ export declare const version = "0.2.2";
9
9
  export * from './core';
10
10
  export * from './providers';
11
11
  export * from './utils';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * This package provides default implementations that can be used directly
6
6
  * or extended for custom behavior.
7
7
  */
8
- export const version = '0.2.1';
8
+ export const version = '0.2.2';
9
9
  // Export core implementations
10
10
  export * from './core';
11
11
  // Export providers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hamak/ui-shell-impl",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "UI Shell Implementation - Core UI shell functionality",
@@ -16,20 +16,26 @@
16
16
  "access": "public"
17
17
  },
18
18
  "scripts": {
19
- "build": "tsc -p tsconfig.json",
19
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.es2015.json",
20
20
  "clean": "rm -rf dist"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
- "import": "./dist/index.js"
25
+ "import": "./dist/index.js",
26
+ "default": "./dist/index.js",
27
+ "legacy": "./dist/es2015/index.js"
28
+ },
29
+ "./es2015": {
30
+ "import": "./dist/es2015/index.js",
31
+ "default": "./dist/es2015/index.js"
26
32
  }
27
33
  },
28
34
  "dependencies": {
29
- "@hamak/ui-shell-api": "0.2.1",
30
- "@hamak/ui-shell-spi": "0.2.1",
31
- "@hamak/microkernel-api": "0.2.1",
32
- "@hamak/microkernel-spi": "0.2.1"
35
+ "@hamak/ui-shell-api": "0.2.2",
36
+ "@hamak/ui-shell-spi": "0.2.2",
37
+ "@hamak/microkernel-api": "0.2.2",
38
+ "@hamak/microkernel-spi": "0.2.2"
33
39
  },
34
40
  "devDependencies": {
35
41
  "typescript": "~5.4.0"
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * or extended for custom behavior.
7
7
  */
8
8
 
9
- export const version = '0.2.1';
9
+ export const version = '0.2.2';
10
10
 
11
11
  // Export core implementations
12
12
  export * from './core';
@@ -0,0 +1,24 @@
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
+ }