@almadar/ui 1.0.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.
- package/LICENSE +72 -0
- package/README.md +335 -0
- package/dist/ThemeContext-lI5bo85E.d.ts +103 -0
- package/dist/components/index.d.ts +4789 -0
- package/dist/components/index.js +21566 -0
- package/dist/components/index.js.map +1 -0
- package/dist/context/index.d.ts +208 -0
- package/dist/context/index.js +443 -0
- package/dist/context/index.js.map +1 -0
- package/dist/event-bus-types-8-cjyMxw.d.ts +65 -0
- package/dist/hooks/index.d.ts +1006 -0
- package/dist/hooks/index.js +2262 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/lib/index.d.ts +291 -0
- package/dist/lib/index.js +431 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/offline-executor-CHr4uAhf.d.ts +401 -0
- package/dist/providers/index.d.ts +386 -0
- package/dist/providers/index.js +1111 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/renderer/index.d.ts +382 -0
- package/dist/renderer/index.js +808 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/stores/index.d.ts +151 -0
- package/dist/stores/index.js +196 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/useUISlots-mnggE9X9.d.ts +105 -0
- package/package.json +121 -0
- package/themes/almadar.css +196 -0
- package/themes/index.css +11 -0
- package/themes/minimalist.css +193 -0
- package/themes/wireframe.css +188 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
import { U as UISlotManager, a as UISlot, S as SlotContent } from '../useUISlots-mnggE9X9.js';
|
|
4
|
+
export { R as RenderUIConfig, b as SlotAnimation, c as SlotChangeCallback } from '../useUISlots-mnggE9X9.js';
|
|
5
|
+
import { T as ThemeProviderProps } from '../ThemeContext-lI5bo85E.js';
|
|
6
|
+
export { B as BUILT_IN_THEMES, C as ColorMode, D as DesignTheme, R as ResolvedMode, a as ThemeContext, b as ThemeDefinition, c as ThemeProvider, u as useTheme } from '../ThemeContext-lI5bo85E.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* UISlotContext
|
|
10
|
+
*
|
|
11
|
+
* React context for providing the UI Slot Manager throughout the application.
|
|
12
|
+
* Traits use this context to render content into slots via render_ui effects.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // In App.tsx or layout
|
|
17
|
+
* <UISlotProvider>
|
|
18
|
+
* <App />
|
|
19
|
+
* </UISlotProvider>
|
|
20
|
+
*
|
|
21
|
+
* // In trait hooks or components
|
|
22
|
+
* const { render, clear } = useUISlots();
|
|
23
|
+
* render({ target: 'modal', pattern: 'form-section', props: {...} });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @packageDocumentation
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Context for the UI Slot Manager
|
|
31
|
+
*/
|
|
32
|
+
declare const UISlotContext: React__default.Context<UISlotManager | null>;
|
|
33
|
+
interface UISlotProviderProps {
|
|
34
|
+
children: React__default.ReactNode;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Provider component that creates and provides the UI Slot Manager.
|
|
38
|
+
*
|
|
39
|
+
* Must wrap any components that use traits with render_ui effects.
|
|
40
|
+
*/
|
|
41
|
+
declare function UISlotProvider({ children }: UISlotProviderProps): React__default.ReactElement;
|
|
42
|
+
/**
|
|
43
|
+
* Hook to access the UI Slot Manager.
|
|
44
|
+
*
|
|
45
|
+
* Must be used within a UISlotProvider.
|
|
46
|
+
*
|
|
47
|
+
* @throws Error if used outside of UISlotProvider
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```tsx
|
|
51
|
+
* function MyTraitHook() {
|
|
52
|
+
* const { render, clear } = useUISlots();
|
|
53
|
+
*
|
|
54
|
+
* const showModal = () => {
|
|
55
|
+
* render({
|
|
56
|
+
* target: 'modal',
|
|
57
|
+
* pattern: 'form-section',
|
|
58
|
+
* props: { title: 'Create Item' },
|
|
59
|
+
* });
|
|
60
|
+
* };
|
|
61
|
+
*
|
|
62
|
+
* const closeModal = () => {
|
|
63
|
+
* clear('modal');
|
|
64
|
+
* };
|
|
65
|
+
*
|
|
66
|
+
* return { showModal, closeModal };
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function useUISlots(): UISlotManager;
|
|
71
|
+
/**
|
|
72
|
+
* Hook to get content for a specific slot.
|
|
73
|
+
*
|
|
74
|
+
* Useful for components that only need to read slot state.
|
|
75
|
+
*/
|
|
76
|
+
declare function useSlotContent(slot: UISlot): SlotContent | null;
|
|
77
|
+
/**
|
|
78
|
+
* Hook to check if a slot has content.
|
|
79
|
+
*/
|
|
80
|
+
declare function useSlotHasContent(slot: UISlot): boolean;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @deprecated Use ThemeProvider from ThemeContext instead
|
|
84
|
+
*/
|
|
85
|
+
declare const DesignThemeProvider: React.FC<ThemeProviderProps>;
|
|
86
|
+
/**
|
|
87
|
+
* @deprecated Use useTheme from ThemeContext instead
|
|
88
|
+
*
|
|
89
|
+
* This wrapper provides backward compatibility with the old API.
|
|
90
|
+
*/
|
|
91
|
+
declare function useDesignTheme(): {
|
|
92
|
+
designTheme: string;
|
|
93
|
+
setDesignTheme: (theme: string) => void;
|
|
94
|
+
availableThemes: string[];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* UserContext
|
|
99
|
+
*
|
|
100
|
+
* React context for providing user data throughout the application.
|
|
101
|
+
* Enables @user bindings in S-expressions for role-based UI and permissions.
|
|
102
|
+
*
|
|
103
|
+
* Usage:
|
|
104
|
+
* ```tsx
|
|
105
|
+
* // In App.tsx or layout
|
|
106
|
+
* <UserProvider user={{ id: '123', role: 'admin', permissions: ['read', 'write'] }}>
|
|
107
|
+
* <App />
|
|
108
|
+
* </UserProvider>
|
|
109
|
+
*
|
|
110
|
+
* // In components - access via hook
|
|
111
|
+
* const { user, hasRole, hasPermission } = useUser();
|
|
112
|
+
* if (hasRole('admin')) { ... }
|
|
113
|
+
* if (hasPermission('delete')) { ... }
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @packageDocumentation
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* User data for @user bindings.
|
|
121
|
+
* Matches UserContext type from evaluator/context.ts
|
|
122
|
+
*/
|
|
123
|
+
interface UserData {
|
|
124
|
+
/** User's unique ID */
|
|
125
|
+
id: string;
|
|
126
|
+
/** User's email */
|
|
127
|
+
email?: string;
|
|
128
|
+
/** User's display name */
|
|
129
|
+
name?: string;
|
|
130
|
+
/** User's role (for RBAC) */
|
|
131
|
+
role?: string;
|
|
132
|
+
/** User's permissions */
|
|
133
|
+
permissions?: string[];
|
|
134
|
+
/** Additional custom profile fields */
|
|
135
|
+
[key: string]: unknown;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* User context value.
|
|
139
|
+
*/
|
|
140
|
+
interface UserContextValue {
|
|
141
|
+
/** Current user data (null if not logged in) */
|
|
142
|
+
user: UserData | null;
|
|
143
|
+
/** Check if user is logged in */
|
|
144
|
+
isLoggedIn: boolean;
|
|
145
|
+
/** Check if user has a specific role */
|
|
146
|
+
hasRole: (role: string) => boolean;
|
|
147
|
+
/** Check if user has a specific permission */
|
|
148
|
+
hasPermission: (permission: string) => boolean;
|
|
149
|
+
/** Check if user has any of the specified roles */
|
|
150
|
+
hasAnyRole: (roles: string[]) => boolean;
|
|
151
|
+
/** Check if user has all of the specified permissions */
|
|
152
|
+
hasAllPermissions: (permissions: string[]) => boolean;
|
|
153
|
+
/** Get a user field by path (for @user.field bindings) */
|
|
154
|
+
getUserField: (path: string) => unknown;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Anonymous user for when no user is logged in.
|
|
158
|
+
*/
|
|
159
|
+
declare const ANONYMOUS_USER: UserData;
|
|
160
|
+
declare const UserContext: React__default.Context<UserContextValue | null>;
|
|
161
|
+
interface UserProviderProps {
|
|
162
|
+
/** User data (null if not logged in) */
|
|
163
|
+
user?: UserData | null;
|
|
164
|
+
/** Children to render */
|
|
165
|
+
children: React__default.ReactNode;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Provider component that provides user context to the application.
|
|
169
|
+
*
|
|
170
|
+
* Provides RBAC helpers and field access for @user bindings.
|
|
171
|
+
*/
|
|
172
|
+
declare function UserProvider({ user, children, }: UserProviderProps): React__default.ReactElement;
|
|
173
|
+
/**
|
|
174
|
+
* Hook to access the user context.
|
|
175
|
+
*
|
|
176
|
+
* Returns default values if used outside of UserProvider (for resilience).
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```tsx
|
|
180
|
+
* function AdminPanel() {
|
|
181
|
+
* const { user, hasRole, hasPermission } = useUser();
|
|
182
|
+
*
|
|
183
|
+
* if (!hasRole('admin') && !hasPermission('admin:access')) {
|
|
184
|
+
* return <AccessDenied />;
|
|
185
|
+
* }
|
|
186
|
+
*
|
|
187
|
+
* return <div>Welcome, {user?.name}</div>;
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
declare function useUser(): UserContextValue;
|
|
192
|
+
/**
|
|
193
|
+
* Hook to check if user has a specific role.
|
|
194
|
+
* Convenience wrapper around useUser().hasRole().
|
|
195
|
+
*/
|
|
196
|
+
declare function useHasRole(role: string): boolean;
|
|
197
|
+
/**
|
|
198
|
+
* Hook to check if user has a specific permission.
|
|
199
|
+
* Convenience wrapper around useUser().hasPermission().
|
|
200
|
+
*/
|
|
201
|
+
declare function useHasPermission(permission: string): boolean;
|
|
202
|
+
/**
|
|
203
|
+
* Hook to get user data for @user bindings in S-expressions.
|
|
204
|
+
* Returns the user data object compatible with EvaluationContext.user
|
|
205
|
+
*/
|
|
206
|
+
declare function useUserForEvaluation(): UserData | undefined;
|
|
207
|
+
|
|
208
|
+
export { ANONYMOUS_USER, DesignThemeProvider, SlotContent, ThemeProviderProps, UISlot, UISlotContext, UISlotManager, UISlotProvider, UserContext, type UserContextValue, type UserData, UserProvider, type UserProviderProps, useDesignTheme, useHasPermission, useHasRole, useSlotContent, useSlotHasContent, useUISlots, useUser, useUserForEvaluation };
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { createContext, useMemo, useContext, useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// context/UISlotContext.tsx
|
|
5
|
+
var DEFAULT_SLOTS = {
|
|
6
|
+
main: null,
|
|
7
|
+
sidebar: null,
|
|
8
|
+
modal: null,
|
|
9
|
+
drawer: null,
|
|
10
|
+
overlay: null,
|
|
11
|
+
center: null,
|
|
12
|
+
toast: null,
|
|
13
|
+
"hud-top": null,
|
|
14
|
+
"hud-bottom": null,
|
|
15
|
+
floating: null
|
|
16
|
+
};
|
|
17
|
+
var idCounter = 0;
|
|
18
|
+
function generateId() {
|
|
19
|
+
return `slot-content-${++idCounter}-${Date.now()}`;
|
|
20
|
+
}
|
|
21
|
+
function useUISlotManager() {
|
|
22
|
+
const [slots, setSlots] = useState(DEFAULT_SLOTS);
|
|
23
|
+
const subscribersRef = useRef(/* @__PURE__ */ new Set());
|
|
24
|
+
const timersRef = useRef(/* @__PURE__ */ new Map());
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
return () => {
|
|
27
|
+
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
28
|
+
timersRef.current.clear();
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
const notifySubscribers = useCallback((slot, content) => {
|
|
32
|
+
subscribersRef.current.forEach((callback) => {
|
|
33
|
+
try {
|
|
34
|
+
callback(slot, content);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("[UISlots] Subscriber error:", error);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}, []);
|
|
40
|
+
const render = useCallback((config) => {
|
|
41
|
+
const id = generateId();
|
|
42
|
+
const content = {
|
|
43
|
+
id,
|
|
44
|
+
pattern: config.pattern,
|
|
45
|
+
props: config.props ?? {},
|
|
46
|
+
priority: config.priority ?? 0,
|
|
47
|
+
animation: config.animation ?? "fade",
|
|
48
|
+
onDismiss: config.onDismiss,
|
|
49
|
+
sourceTrait: config.sourceTrait
|
|
50
|
+
};
|
|
51
|
+
if (config.autoDismissMs && config.autoDismissMs > 0) {
|
|
52
|
+
content.autoDismissAt = Date.now() + config.autoDismissMs;
|
|
53
|
+
const timer = setTimeout(() => {
|
|
54
|
+
setSlots((prev) => {
|
|
55
|
+
if (prev[config.target]?.id === id) {
|
|
56
|
+
content.onDismiss?.();
|
|
57
|
+
notifySubscribers(config.target, null);
|
|
58
|
+
return { ...prev, [config.target]: null };
|
|
59
|
+
}
|
|
60
|
+
return prev;
|
|
61
|
+
});
|
|
62
|
+
timersRef.current.delete(id);
|
|
63
|
+
}, config.autoDismissMs);
|
|
64
|
+
timersRef.current.set(id, timer);
|
|
65
|
+
}
|
|
66
|
+
setSlots((prev) => {
|
|
67
|
+
const existing = prev[config.target];
|
|
68
|
+
if (existing && existing.priority > content.priority) {
|
|
69
|
+
console.warn(
|
|
70
|
+
`[UISlots] Slot "${config.target}" already has higher priority content (${existing.priority} > ${content.priority})`
|
|
71
|
+
);
|
|
72
|
+
return prev;
|
|
73
|
+
}
|
|
74
|
+
notifySubscribers(config.target, content);
|
|
75
|
+
return { ...prev, [config.target]: content };
|
|
76
|
+
});
|
|
77
|
+
return id;
|
|
78
|
+
}, [notifySubscribers]);
|
|
79
|
+
const clear = useCallback((slot) => {
|
|
80
|
+
setSlots((prev) => {
|
|
81
|
+
const content = prev[slot];
|
|
82
|
+
if (content) {
|
|
83
|
+
const timer = timersRef.current.get(content.id);
|
|
84
|
+
if (timer) {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
timersRef.current.delete(content.id);
|
|
87
|
+
}
|
|
88
|
+
content.onDismiss?.();
|
|
89
|
+
notifySubscribers(slot, null);
|
|
90
|
+
}
|
|
91
|
+
return { ...prev, [slot]: null };
|
|
92
|
+
});
|
|
93
|
+
}, [notifySubscribers]);
|
|
94
|
+
const clearById = useCallback((id) => {
|
|
95
|
+
setSlots((prev) => {
|
|
96
|
+
const entry = Object.entries(prev).find(([, content]) => content?.id === id);
|
|
97
|
+
if (entry) {
|
|
98
|
+
const [slot, content] = entry;
|
|
99
|
+
const timer = timersRef.current.get(id);
|
|
100
|
+
if (timer) {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
timersRef.current.delete(id);
|
|
103
|
+
}
|
|
104
|
+
content.onDismiss?.();
|
|
105
|
+
notifySubscribers(slot, null);
|
|
106
|
+
return { ...prev, [slot]: null };
|
|
107
|
+
}
|
|
108
|
+
return prev;
|
|
109
|
+
});
|
|
110
|
+
}, [notifySubscribers]);
|
|
111
|
+
const clearAll = useCallback(() => {
|
|
112
|
+
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
113
|
+
timersRef.current.clear();
|
|
114
|
+
setSlots((prev) => {
|
|
115
|
+
Object.entries(prev).forEach(([slot, content]) => {
|
|
116
|
+
if (content) {
|
|
117
|
+
content.onDismiss?.();
|
|
118
|
+
notifySubscribers(slot, null);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return DEFAULT_SLOTS;
|
|
122
|
+
});
|
|
123
|
+
}, [notifySubscribers]);
|
|
124
|
+
const subscribe = useCallback((callback) => {
|
|
125
|
+
subscribersRef.current.add(callback);
|
|
126
|
+
return () => {
|
|
127
|
+
subscribersRef.current.delete(callback);
|
|
128
|
+
};
|
|
129
|
+
}, []);
|
|
130
|
+
const hasContent = useCallback((slot) => {
|
|
131
|
+
return slots[slot] !== null;
|
|
132
|
+
}, [slots]);
|
|
133
|
+
const getContent = useCallback((slot) => {
|
|
134
|
+
return slots[slot];
|
|
135
|
+
}, [slots]);
|
|
136
|
+
return {
|
|
137
|
+
slots,
|
|
138
|
+
render,
|
|
139
|
+
clear,
|
|
140
|
+
clearById,
|
|
141
|
+
clearAll,
|
|
142
|
+
subscribe,
|
|
143
|
+
hasContent,
|
|
144
|
+
getContent
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
var UISlotContext = createContext(null);
|
|
148
|
+
function UISlotProvider({ children }) {
|
|
149
|
+
const slotManager = useUISlotManager();
|
|
150
|
+
const contextValue = useMemo(() => slotManager, [slotManager]);
|
|
151
|
+
return /* @__PURE__ */ jsx(UISlotContext.Provider, { value: contextValue, children });
|
|
152
|
+
}
|
|
153
|
+
function useUISlots() {
|
|
154
|
+
const context = useContext(UISlotContext);
|
|
155
|
+
if (!context) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"useUISlots must be used within a UISlotProvider. Make sure your component tree is wrapped with <UISlotProvider>."
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return context;
|
|
161
|
+
}
|
|
162
|
+
function useSlotContent(slot) {
|
|
163
|
+
const { getContent } = useUISlots();
|
|
164
|
+
return getContent(slot);
|
|
165
|
+
}
|
|
166
|
+
function useSlotHasContent(slot) {
|
|
167
|
+
const { hasContent } = useUISlots();
|
|
168
|
+
return hasContent(slot);
|
|
169
|
+
}
|
|
170
|
+
var BUILT_IN_THEMES = [
|
|
171
|
+
{
|
|
172
|
+
name: "wireframe",
|
|
173
|
+
displayName: "Wireframe",
|
|
174
|
+
hasLightMode: true,
|
|
175
|
+
hasDarkMode: true
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "minimalist",
|
|
179
|
+
displayName: "Minimalist",
|
|
180
|
+
hasLightMode: true,
|
|
181
|
+
hasDarkMode: true
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "almadar",
|
|
185
|
+
displayName: "Almadar",
|
|
186
|
+
hasLightMode: true,
|
|
187
|
+
hasDarkMode: true
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
var ThemeContext = createContext(void 0);
|
|
191
|
+
var THEME_STORAGE_KEY = "theme";
|
|
192
|
+
var MODE_STORAGE_KEY = "theme-mode";
|
|
193
|
+
function getSystemMode() {
|
|
194
|
+
if (typeof window === "undefined") return "light";
|
|
195
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
196
|
+
}
|
|
197
|
+
function resolveMode(mode) {
|
|
198
|
+
if (mode === "system") {
|
|
199
|
+
return getSystemMode();
|
|
200
|
+
}
|
|
201
|
+
return mode;
|
|
202
|
+
}
|
|
203
|
+
var ThemeProvider = ({
|
|
204
|
+
children,
|
|
205
|
+
themes = [],
|
|
206
|
+
defaultTheme = "wireframe",
|
|
207
|
+
defaultMode = "system"
|
|
208
|
+
}) => {
|
|
209
|
+
const availableThemes = useMemo(() => {
|
|
210
|
+
const themeMap = /* @__PURE__ */ new Map();
|
|
211
|
+
BUILT_IN_THEMES.forEach((t) => themeMap.set(t.name, t));
|
|
212
|
+
themes.forEach((t) => themeMap.set(t.name, t));
|
|
213
|
+
return Array.from(themeMap.values());
|
|
214
|
+
}, [themes]);
|
|
215
|
+
const [theme, setThemeState] = useState(() => {
|
|
216
|
+
if (typeof window === "undefined") return defaultTheme;
|
|
217
|
+
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
|
218
|
+
const validThemes = [
|
|
219
|
+
...BUILT_IN_THEMES.map((t) => t.name),
|
|
220
|
+
...themes.map((t) => t.name)
|
|
221
|
+
];
|
|
222
|
+
if (stored && validThemes.includes(stored)) {
|
|
223
|
+
return stored;
|
|
224
|
+
}
|
|
225
|
+
return defaultTheme;
|
|
226
|
+
});
|
|
227
|
+
const [mode, setModeState] = useState(() => {
|
|
228
|
+
if (typeof window === "undefined") return defaultMode;
|
|
229
|
+
const stored = localStorage.getItem(MODE_STORAGE_KEY);
|
|
230
|
+
if (stored === "light" || stored === "dark" || stored === "system") {
|
|
231
|
+
return stored;
|
|
232
|
+
}
|
|
233
|
+
return defaultMode;
|
|
234
|
+
});
|
|
235
|
+
const [resolvedMode, setResolvedMode] = useState(
|
|
236
|
+
() => resolveMode(mode)
|
|
237
|
+
);
|
|
238
|
+
const appliedTheme = useMemo(
|
|
239
|
+
() => `${theme}-${resolvedMode}`,
|
|
240
|
+
[theme, resolvedMode]
|
|
241
|
+
);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
const updateResolved = () => {
|
|
244
|
+
setResolvedMode(resolveMode(mode));
|
|
245
|
+
};
|
|
246
|
+
updateResolved();
|
|
247
|
+
if (mode === "system") {
|
|
248
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
249
|
+
const handleChange = () => updateResolved();
|
|
250
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
251
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
252
|
+
}
|
|
253
|
+
return void 0;
|
|
254
|
+
}, [mode]);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
const root = document.documentElement;
|
|
257
|
+
root.setAttribute("data-theme", appliedTheme);
|
|
258
|
+
root.classList.remove("light", "dark");
|
|
259
|
+
root.classList.add(resolvedMode);
|
|
260
|
+
}, [appliedTheme, resolvedMode]);
|
|
261
|
+
const setTheme = useCallback(
|
|
262
|
+
(newTheme) => {
|
|
263
|
+
const validTheme = availableThemes.find((t) => t.name === newTheme);
|
|
264
|
+
if (validTheme) {
|
|
265
|
+
setThemeState(newTheme);
|
|
266
|
+
if (typeof window !== "undefined") {
|
|
267
|
+
localStorage.setItem(THEME_STORAGE_KEY, newTheme);
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
console.warn(
|
|
271
|
+
`Theme "${newTheme}" not found. Available: ${availableThemes.map((t) => t.name).join(", ")}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
[availableThemes]
|
|
276
|
+
);
|
|
277
|
+
const setMode = useCallback((newMode) => {
|
|
278
|
+
setModeState(newMode);
|
|
279
|
+
if (typeof window !== "undefined") {
|
|
280
|
+
localStorage.setItem(MODE_STORAGE_KEY, newMode);
|
|
281
|
+
}
|
|
282
|
+
}, []);
|
|
283
|
+
const toggleMode = useCallback(() => {
|
|
284
|
+
const newMode = resolvedMode === "dark" ? "light" : "dark";
|
|
285
|
+
setMode(newMode);
|
|
286
|
+
}, [resolvedMode, setMode]);
|
|
287
|
+
const contextValue = useMemo(
|
|
288
|
+
() => ({
|
|
289
|
+
theme,
|
|
290
|
+
mode,
|
|
291
|
+
resolvedMode,
|
|
292
|
+
setTheme,
|
|
293
|
+
setMode,
|
|
294
|
+
toggleMode,
|
|
295
|
+
availableThemes,
|
|
296
|
+
appliedTheme
|
|
297
|
+
}),
|
|
298
|
+
[
|
|
299
|
+
theme,
|
|
300
|
+
mode,
|
|
301
|
+
resolvedMode,
|
|
302
|
+
setTheme,
|
|
303
|
+
setMode,
|
|
304
|
+
toggleMode,
|
|
305
|
+
availableThemes,
|
|
306
|
+
appliedTheme
|
|
307
|
+
]
|
|
308
|
+
);
|
|
309
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
|
|
310
|
+
};
|
|
311
|
+
function useTheme() {
|
|
312
|
+
const context = useContext(ThemeContext);
|
|
313
|
+
if (context === void 0) {
|
|
314
|
+
return {
|
|
315
|
+
theme: "wireframe",
|
|
316
|
+
mode: "light",
|
|
317
|
+
resolvedMode: "light",
|
|
318
|
+
setTheme: () => {
|
|
319
|
+
},
|
|
320
|
+
setMode: () => {
|
|
321
|
+
},
|
|
322
|
+
toggleMode: () => {
|
|
323
|
+
},
|
|
324
|
+
availableThemes: BUILT_IN_THEMES,
|
|
325
|
+
appliedTheme: "wireframe-light"
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return context;
|
|
329
|
+
}
|
|
330
|
+
var ThemeContext_default = ThemeContext;
|
|
331
|
+
|
|
332
|
+
// context/DesignThemeContext.tsx
|
|
333
|
+
var DesignThemeProvider = ThemeProvider;
|
|
334
|
+
function useDesignTheme() {
|
|
335
|
+
const { theme, setTheme, availableThemes } = useTheme();
|
|
336
|
+
return {
|
|
337
|
+
designTheme: theme,
|
|
338
|
+
setDesignTheme: setTheme,
|
|
339
|
+
availableThemes: availableThemes.map((t) => t.name)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
var ANONYMOUS_USER = {
|
|
343
|
+
id: "anonymous",
|
|
344
|
+
role: "anonymous",
|
|
345
|
+
permissions: []
|
|
346
|
+
};
|
|
347
|
+
var UserContext = createContext(null);
|
|
348
|
+
function UserProvider({
|
|
349
|
+
user = null,
|
|
350
|
+
children
|
|
351
|
+
}) {
|
|
352
|
+
const hasRole = useCallback(
|
|
353
|
+
(role) => {
|
|
354
|
+
if (!user) return role === "anonymous";
|
|
355
|
+
return user.role === role;
|
|
356
|
+
},
|
|
357
|
+
[user]
|
|
358
|
+
);
|
|
359
|
+
const hasPermission = useCallback(
|
|
360
|
+
(permission) => {
|
|
361
|
+
if (!user) return false;
|
|
362
|
+
return user.permissions?.includes(permission) ?? false;
|
|
363
|
+
},
|
|
364
|
+
[user]
|
|
365
|
+
);
|
|
366
|
+
const hasAnyRole = useCallback(
|
|
367
|
+
(roles) => {
|
|
368
|
+
if (!user) return roles.includes("anonymous");
|
|
369
|
+
return user.role ? roles.includes(user.role) : false;
|
|
370
|
+
},
|
|
371
|
+
[user]
|
|
372
|
+
);
|
|
373
|
+
const hasAllPermissions = useCallback(
|
|
374
|
+
(permissions) => {
|
|
375
|
+
if (!user || !user.permissions) return false;
|
|
376
|
+
return permissions.every((p) => user.permissions?.includes(p));
|
|
377
|
+
},
|
|
378
|
+
[user]
|
|
379
|
+
);
|
|
380
|
+
const getUserField = useCallback(
|
|
381
|
+
(path) => {
|
|
382
|
+
const userData = user ?? ANONYMOUS_USER;
|
|
383
|
+
const parts = path.split(".");
|
|
384
|
+
let value = userData;
|
|
385
|
+
for (const segment of parts) {
|
|
386
|
+
if (value === null || value === void 0) {
|
|
387
|
+
return void 0;
|
|
388
|
+
}
|
|
389
|
+
if (typeof value === "object") {
|
|
390
|
+
value = value[segment];
|
|
391
|
+
} else {
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return value;
|
|
396
|
+
},
|
|
397
|
+
[user]
|
|
398
|
+
);
|
|
399
|
+
const contextValue = useMemo(
|
|
400
|
+
() => ({
|
|
401
|
+
user,
|
|
402
|
+
isLoggedIn: user !== null,
|
|
403
|
+
hasRole,
|
|
404
|
+
hasPermission,
|
|
405
|
+
hasAnyRole,
|
|
406
|
+
hasAllPermissions,
|
|
407
|
+
getUserField
|
|
408
|
+
}),
|
|
409
|
+
[user, hasRole, hasPermission, hasAnyRole, hasAllPermissions, getUserField]
|
|
410
|
+
);
|
|
411
|
+
return /* @__PURE__ */ jsx(UserContext.Provider, { value: contextValue, children });
|
|
412
|
+
}
|
|
413
|
+
function useUser() {
|
|
414
|
+
const context = useContext(UserContext);
|
|
415
|
+
if (!context) {
|
|
416
|
+
return {
|
|
417
|
+
user: null,
|
|
418
|
+
isLoggedIn: false,
|
|
419
|
+
hasRole: (role) => role === "anonymous",
|
|
420
|
+
hasPermission: () => false,
|
|
421
|
+
hasAnyRole: (roles) => roles.includes("anonymous"),
|
|
422
|
+
hasAllPermissions: () => false,
|
|
423
|
+
getUserField: () => void 0
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return context;
|
|
427
|
+
}
|
|
428
|
+
function useHasRole(role) {
|
|
429
|
+
const { hasRole } = useUser();
|
|
430
|
+
return hasRole(role);
|
|
431
|
+
}
|
|
432
|
+
function useHasPermission(permission) {
|
|
433
|
+
const { hasPermission } = useUser();
|
|
434
|
+
return hasPermission(permission);
|
|
435
|
+
}
|
|
436
|
+
function useUserForEvaluation() {
|
|
437
|
+
const { user, isLoggedIn } = useUser();
|
|
438
|
+
return isLoggedIn && user ? user : void 0;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export { ANONYMOUS_USER, BUILT_IN_THEMES, DesignThemeProvider, ThemeContext_default as ThemeContext, ThemeProvider, UISlotContext, UISlotProvider, UserContext, UserProvider, useDesignTheme, useHasPermission, useHasRole, useSlotContent, useSlotHasContent, useTheme, useUISlots, useUser, useUserForEvaluation };
|
|
442
|
+
//# sourceMappingURL=index.js.map
|
|
443
|
+
//# sourceMappingURL=index.js.map
|