@svadmin/core 0.50.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/package.json +21 -4
  2. package/src/access-control-contract.ts +250 -0
  3. package/src/adapters/casbin.ts +20 -14
  4. package/src/adapters/casl.ts +22 -19
  5. package/src/admin-tool.ts +7 -7
  6. package/src/audit-contract.ts +105 -0
  7. package/src/audit.ts +88 -128
  8. package/src/auth-hooks.svelte.ts +593 -261
  9. package/src/auth-mutation-contract.ts +143 -0
  10. package/src/auth-query-contract.ts +97 -0
  11. package/src/captured-mutation.svelte.ts +45 -0
  12. package/src/chatProvider.svelte.ts +1 -1
  13. package/src/command-contract.ts +195 -0
  14. package/src/context.svelte.ts +217 -205
  15. package/src/contract-context.svelte.ts +37 -0
  16. package/src/contract-form.svelte.ts +566 -0
  17. package/src/create-contract.ts +56 -0
  18. package/src/create-hooks.svelte.ts +347 -0
  19. package/src/create-many-contract.ts +90 -0
  20. package/src/create-many-hooks.svelte.ts +396 -0
  21. package/src/data-transfer.svelte.ts +1 -215
  22. package/src/defined-options.ts +50 -0
  23. package/src/delete-contract.ts +50 -0
  24. package/src/delete-hooks.svelte.ts +403 -0
  25. package/src/delete-many-contract.ts +60 -0
  26. package/src/delete-many-hooks.svelte.ts +402 -0
  27. package/src/enterprise.ts +4 -4
  28. package/src/export-format.ts +16 -14
  29. package/src/export-hooks.svelte.ts +191 -0
  30. package/src/feature-gate-contract.ts +53 -0
  31. package/src/hook-utils.svelte.ts +53 -105
  32. package/src/hooks.svelte.ts +19 -776
  33. package/src/http.ts +88 -87
  34. package/src/i18n.svelte.ts +64 -2
  35. package/src/import-contract.ts +106 -0
  36. package/src/import-hooks.svelte.ts +310 -0
  37. package/src/index.ts +77 -30
  38. package/src/inferencer-graphql.ts +7 -6
  39. package/src/inferencer.ts +9 -0
  40. package/src/infinite-query.svelte.ts +228 -0
  41. package/src/invalidation-contract.ts +26 -0
  42. package/src/live-sse.ts +91 -127
  43. package/src/live-subscription.svelte.ts +130 -0
  44. package/src/live-transport.ts +127 -0
  45. package/src/live-websocket.ts +85 -118
  46. package/src/live.svelte.ts +66 -68
  47. package/src/notification.svelte.ts +17 -15
  48. package/src/permission-catalog.ts +121 -0
  49. package/src/permission-hints-contract.ts +48 -0
  50. package/src/permissions.svelte.ts +112 -59
  51. package/src/plain-data.ts +61 -0
  52. package/src/provider-bundle.ts +85 -79
  53. package/src/provider-response.ts +35 -0
  54. package/src/query-hooks.svelte.ts +241 -288
  55. package/src/query-invalidation.ts +172 -0
  56. package/src/query-keys.ts +221 -110
  57. package/src/query-session.svelte.ts +220 -0
  58. package/src/query-snapshot.ts +144 -0
  59. package/src/reactive-projection.ts +53 -0
  60. package/src/record-decoder.ts +63 -0
  61. package/src/resource-binding.svelte.ts +19 -0
  62. package/src/resource-contract.ts +595 -0
  63. package/src/resource-mutation-types.ts +30 -0
  64. package/src/resource-schemas.ts +371 -0
  65. package/src/resource-transport-config.ts +156 -0
  66. package/src/route-parsing.ts +11 -11
  67. package/src/router-provider.ts +64 -59
  68. package/src/router.ts +8 -3
  69. package/src/routing-hooks.svelte.ts +90 -62
  70. package/src/schema-validation.ts +92 -0
  71. package/src/schema.ts +13 -0
  72. package/src/select-options.ts +95 -0
  73. package/src/select-query.svelte.ts +191 -0
  74. package/src/session-query.svelte.ts +103 -0
  75. package/src/strict-command-hooks.svelte.ts +333 -0
  76. package/src/strict-hooks.svelte.ts +198 -0
  77. package/src/table-hooks.svelte.ts +180 -174
  78. package/src/table-state.svelte.ts +117 -117
  79. package/src/task-contract.ts +87 -0
  80. package/src/task-hooks.svelte.ts +190 -180
  81. package/src/task-provider.ts +115 -0
  82. package/src/task-subscription.ts +132 -0
  83. package/src/theme.svelte.ts +155 -154
  84. package/src/toast.svelte.ts +38 -37
  85. package/src/types.ts +68 -110
  86. package/src/unsafe.ts +7 -0
  87. package/src/update-contract.ts +65 -0
  88. package/src/update-hooks.svelte.ts +347 -0
  89. package/src/update-many-contract.ts +79 -0
  90. package/src/update-many-hooks.svelte.ts +373 -0
  91. package/src/url-sync.ts +11 -10
  92. package/src/useCan.ts +96 -31
  93. package/src/useParsed.svelte.ts +18 -14
  94. package/src/useStepsForm.svelte.ts +123 -63
  95. package/src/utility-hooks.svelte.ts +101 -158
  96. package/src/form-hooks.svelte.ts +0 -889
  97. package/src/mutation-hooks.svelte.ts +0 -561
  98. package/src/schema-form.ts +0 -31
@@ -0,0 +1,132 @@
1
+ import { TaskError } from './task-contract';
2
+
3
+ export function taskClientField(value: unknown, key: string): unknown {
4
+ try {
5
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
6
+ const field: unknown = Reflect.get(value, key);
7
+ return field;
8
+ }
9
+ } catch {
10
+ // Configuration accessors must not leak SDK credentials.
11
+ }
12
+ throw new TaskError('INVALID_TASK_INPUT');
13
+ }
14
+
15
+ export function taskClientMethod(value: unknown, key: string, optional = false): ((...args: unknown[]) => unknown) | undefined {
16
+ const callable = taskClientField(value, key);
17
+ if (callable === undefined && optional) return undefined;
18
+ if (typeof callable !== 'function') throw new TaskError('INVALID_TASK_INPUT');
19
+ return (...args) => {
20
+ const result: unknown = Reflect.apply(callable, value, args);
21
+ return result;
22
+ };
23
+ }
24
+
25
+ export function requiredTaskClientMethod(value: unknown, key: string): (...args: unknown[]) => unknown {
26
+ const method = taskClientMethod(value, key);
27
+ if (!method) throw new TaskError('INVALID_TASK_INPUT');
28
+ return method;
29
+ }
30
+
31
+ export function reportTaskError(error: TaskError, onError?: (error: TaskError) => void): void {
32
+ if (!onError) { console.error(error); return; }
33
+ try {
34
+ void Promise.resolve(onError(error)).catch(() => console.error(new TaskError('TASK_CALLBACK_FAILED')));
35
+ }
36
+ catch { console.error(new TaskError('TASK_CALLBACK_FAILED')); }
37
+ }
38
+
39
+ export function taskHandleField(value: unknown, key: string, write = false): unknown {
40
+ try {
41
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
42
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
43
+ if (!descriptor) return undefined;
44
+ if ('value' in descriptor) {
45
+ const field: unknown = descriptor.value;
46
+ if (field === undefined) throw new TaskError('INVALID_TASK_RESPONSE', write);
47
+ return field;
48
+ }
49
+ }
50
+ } catch {
51
+ // Handle accessors and reflection errors cannot carry trusted protocol values.
52
+ }
53
+ throw new TaskError('INVALID_TASK_RESPONSE', write);
54
+ }
55
+
56
+ export function taskHandleMethod(value: unknown, key: string, write = false): (...args: unknown[]) => unknown {
57
+ const callable = taskHandleField(value, key, write);
58
+ if (typeof callable !== 'function') throw new TaskError('INVALID_TASK_RESPONSE', write);
59
+ return (...args) => {
60
+ const result: unknown = Reflect.apply(callable, value, args);
61
+ return result;
62
+ };
63
+ }
64
+
65
+ /** Validate synchronous events before publishing, but wait for a valid cleanup handle. */
66
+ export function validatedTaskSubscription<T>(
67
+ start: (onUpdate: (value: unknown) => void, onFailure: (error: unknown) => void) => unknown,
68
+ decode: (value: unknown) => T,
69
+ callback: (task: T) => void,
70
+ onError?: (error: TaskError) => void,
71
+ ): () => void {
72
+ if (typeof callback !== 'function' || onError !== undefined && typeof onError !== 'function') {
73
+ throw new TaskError('INVALID_TASK_INPUT');
74
+ }
75
+ let stopped = false;
76
+ let ready = false;
77
+ let cleanup: (() => unknown) | undefined;
78
+ const buffered: T[] = [];
79
+ const release = () => {
80
+ try {
81
+ void Promise.resolve(cleanup?.()).catch(() => reportTaskError(new TaskError('TASK_SUBSCRIPTION_FAILED'), onError));
82
+ } catch { reportTaskError(new TaskError('TASK_SUBSCRIPTION_FAILED'), onError); }
83
+ };
84
+ const stop = () => {
85
+ if (stopped) return;
86
+ stopped = true;
87
+ buffered.length = 0;
88
+ release();
89
+ };
90
+ const emit = (task: T) => {
91
+ if (stopped) return;
92
+ try {
93
+ void Promise.resolve(callback(task)).catch(() => reportTaskError(new TaskError('TASK_CALLBACK_FAILED'), onError));
94
+ }
95
+ catch { reportTaskError(new TaskError('TASK_CALLBACK_FAILED'), onError); }
96
+ };
97
+ const fail = (error: TaskError) => {
98
+ if (stopped) return;
99
+ stop();
100
+ reportTaskError(error, onError);
101
+ };
102
+ try {
103
+ const subscription = start(value => {
104
+ if (stopped) return;
105
+ let task: T;
106
+ try { task = decode(value); }
107
+ catch (error) {
108
+ fail(new TaskError(error instanceof TaskError ? error.code : 'INVALID_TASK_RESPONSE'));
109
+ return;
110
+ }
111
+ if (ready) emit(task);
112
+ else buffered.push(task);
113
+ }, () => fail(new TaskError('TASK_SUBSCRIPTION_FAILED')));
114
+ if (subscription instanceof Promise) {
115
+ void Promise.resolve<unknown>(subscription).catch(() => {});
116
+ throw new TaskError('TASK_SUBSCRIPTION_FAILED');
117
+ }
118
+ cleanup = typeof subscription === 'function'
119
+ ? () => { const result: unknown = Reflect.apply(subscription, undefined, []); return result; }
120
+ : taskHandleMethod(subscription, 'unsubscribe');
121
+ if (stopped) release();
122
+ else {
123
+ ready = true;
124
+ for (const task of buffered) emit(task);
125
+ buffered.length = 0;
126
+ }
127
+ } catch {
128
+ stop();
129
+ throw new TaskError('TASK_SUBSCRIPTION_FAILED');
130
+ }
131
+ return stop;
132
+ }
@@ -1,14 +1,15 @@
1
+ import { definedOptions } from './defined-options';
1
2
  // Theme — dark/light/system mode + color theme management (Svelte 5 runes)
2
3
  //
3
4
  // Supports two class strategies:
4
5
  // - 'standard' (default): adds 'dark' class for dark mode (light-first)
5
6
  // - 'dark-first': adds 'light' class for light mode (dark-first)
6
7
 
7
- export type ThemeMode = 'light' | 'dark' | 'system';
8
- export type ColorTheme = 'blue' | 'green' | 'rose' | 'orange' | 'violet' | 'neutral';
8
+ export type ThemeMode='light'|'dark'|'system';
9
+ export type ColorTheme='blue'|'green'|'rose'|'orange'|'violet'|'neutral';
9
10
 
10
11
  /** Controls how the theme class is applied to <html> */
11
- export type ThemeStrategy = 'standard' | 'dark-first';
12
+ export type ThemeStrategy='standard'|'dark-first';
12
13
 
13
14
  // ── Color Preset System ──────────────────────────────────
14
15
 
@@ -21,13 +22,13 @@ export interface ColorPreset {
21
22
  /** Preview swatch color (hex) for UI pickers */
22
23
  color: string;
23
24
  /** CSS variable overrides for light mode */
24
- light: Record<string, string>;
25
+ light: Record<string,string>;
25
26
  /** CSS variable overrides for dark mode */
26
- dark: Record<string, string>;
27
+ dark: Record<string,string>;
27
28
  }
28
29
 
29
30
  /** Built-in color presets — consumers can extend via registerColorPreset(). */
30
- export const builtinPresets: Record<string, ColorPreset> = $state({
31
+ export const builtinPresets: Record<string,ColorPreset>=$state({
31
32
  neutral: {
32
33
  name: 'neutral',
33
34
  label: 'Neutral',
@@ -193,12 +194,12 @@ export const builtinPresets: Record<string, ColorPreset> = $state({
193
194
 
194
195
  /** Register a custom color preset. Overwrites any built-in preset with the same name. */
195
196
  export function registerColorPreset(preset: ColorPreset): void {
196
- builtinPresets[preset.name] = preset;
197
+ builtinPresets[preset.name]=preset;
197
198
  }
198
199
 
199
200
  /** Resolve a preset by name or return the preset object directly. */
200
- function resolvePreset(preset: ColorPreset | string): ColorPreset | undefined {
201
- if (typeof preset === 'string') return builtinPresets[preset];
201
+ function resolvePreset(preset: ColorPreset|string): ColorPreset|undefined {
202
+ if(typeof preset==='string') return builtinPresets[preset];
202
203
  return preset;
203
204
  }
204
205
 
@@ -211,13 +212,13 @@ export interface ThemeConfig {
211
212
  /** Class strategy: 'standard' toggles '.dark', 'dark-first' toggles '.light' */
212
213
  strategy?: ThemeStrategy;
213
214
  /** Custom CSS variables to inject as overrides on <html> */
214
- cssOverrides?: Record<string, string>;
215
+ cssOverrides?: Record<string,string>;
215
216
  /** Whether to disable the built-in color-scheme attribute */
216
217
  disableColorScheme?: boolean;
217
218
  /** Built-in color preset name (e.g. 'indigo', 'blue') or a custom ColorPreset object */
218
- colorPreset?: ColorPreset | string;
219
+ colorPreset?: ColorPreset|string;
219
220
  /** Layout style option: 'default' or 'clean-flat' for high-contrast flat panel design */
220
- layoutPreset?: 'default' | 'clean-flat';
221
+ layoutPreset?: 'default'|'clean-flat';
221
222
  }
222
223
 
223
224
  /** Explicit document-level values contributed by one mounted AdminApp. */
@@ -227,22 +228,22 @@ export interface ThemeOwnerOptions {
227
228
  }
228
229
 
229
230
  /** Opaque identity used to update or unregister a mounted theme owner. */
230
- export type ThemeOwnerToken = symbol;
231
+ export type ThemeOwnerToken=symbol;
231
232
 
232
- const STORAGE_KEY = 'svadmin-theme';
233
- const COLOR_STORAGE_KEY = 'svadmin-color-theme';
233
+ const STORAGE_KEY='svadmin-theme';
234
+ const COLOR_STORAGE_KEY='svadmin-color-theme';
234
235
 
235
236
  interface ThemeOwnerEntry {
236
237
  token: ThemeOwnerToken;
237
238
  options: ThemeOwnerOptions;
238
239
  }
239
240
 
240
- let legacyThemeConfig: ThemeConfig = {};
241
- let themeConfig: ThemeConfig = {};
242
- let themeOwners: ThemeOwnerEntry[] = [];
243
- let activePresetVars: string[] = [];
244
- let activeCssOverrideVars: string[] = [];
245
- let colorSelectionOverridesConfig = false;
241
+ let legacyThemeConfig: ThemeConfig={};
242
+ let themeConfig: ThemeConfig={};
243
+ let themeOwners: ThemeOwnerEntry[]=[];
244
+ let activePresetVars: string[]=[];
245
+ let activeCssOverrideVars: string[]=[];
246
+ let colorSelectionOverridesConfig=false;
246
247
 
247
248
  function cloneColorPreset(preset: ColorPreset): ColorPreset {
248
249
  return {
@@ -252,36 +253,36 @@ function cloneColorPreset(preset: ColorPreset): ColorPreset {
252
253
  };
253
254
  }
254
255
 
255
- function cloneThemeConfig(config: ThemeConfig = {}): ThemeConfig {
256
- const cloned = { ...config };
257
- if (config.cssOverrides) cloned.cssOverrides = { ...config.cssOverrides };
258
- if (typeof config.colorPreset === 'object') cloned.colorPreset = cloneColorPreset(config.colorPreset);
256
+ function cloneThemeConfig(config: ThemeConfig={}): ThemeConfig {
257
+ const cloned={ ...config };
258
+ if(config.cssOverrides) cloned.cssOverrides={ ...config.cssOverrides };
259
+ if(typeof config.colorPreset==='object') cloned.colorPreset=cloneColorPreset(config.colorPreset);
259
260
  return cloned;
260
261
  }
261
262
 
262
263
  function cloneOwnerOptions(options: ThemeOwnerOptions): ThemeOwnerOptions {
263
- return {
264
+ return definedOptions({
264
265
  defaultTheme: options.defaultTheme,
265
- themeConfig: options.themeConfig === undefined
266
+ themeConfig: options.themeConfig===undefined
266
267
  ? undefined
267
- : cloneThemeConfig(options.themeConfig),
268
- };
268
+ :cloneThemeConfig(options.themeConfig),
269
+ });
269
270
  }
270
271
 
271
272
  function isExplicitOwner(options: ThemeOwnerOptions): boolean {
272
- return options.themeConfig !== undefined || options.defaultTheme !== undefined;
273
+ return options.themeConfig!==undefined||options.defaultTheme!==undefined;
273
274
  }
274
275
 
275
- function getActiveOwner(): ThemeOwnerEntry | undefined {
276
- for (let index = themeOwners.length - 1; index >= 0; index -= 1) {
277
- const owner = themeOwners[index];
278
- if (isExplicitOwner(owner.options)) return owner;
276
+ function getActiveOwner(): ThemeOwnerEntry|undefined {
277
+ for(let index=themeOwners.length-1;index>=0;index-=1) {
278
+ const owner=themeOwners[index];
279
+ if(owner&&isExplicitOwner(owner.options)) return owner;
279
280
  }
280
281
  return undefined;
281
282
  }
282
283
 
283
- function getStorage(): Storage | undefined {
284
- if (typeof window === 'undefined') return undefined;
284
+ function getStorage(): Storage|undefined {
285
+ if(typeof window==='undefined') return undefined;
285
286
  try {
286
287
  return window.localStorage;
287
288
  } catch {
@@ -289,115 +290,115 @@ function getStorage(): Storage | undefined {
289
290
  }
290
291
  }
291
292
 
292
- function readStoredTheme(): ThemeMode | undefined {
293
- const stored = getStorage()?.getItem(STORAGE_KEY);
294
- return stored === 'light' || stored === 'dark' || stored === 'system' ? stored : undefined;
293
+ function readStoredTheme(): ThemeMode|undefined {
294
+ const stored=getStorage()?.getItem(STORAGE_KEY);
295
+ return stored==='light'||stored==='dark'||stored==='system'? stored:undefined;
295
296
  }
296
297
 
297
298
  function readStoredColorTheme(): ColorTheme {
298
- const stored = getStorage()?.getItem(COLOR_STORAGE_KEY);
299
- return stored && builtinPresets[stored] ? stored as ColorTheme : 'blue';
299
+ const stored=getStorage()?.getItem(COLOR_STORAGE_KEY);
300
+ return stored&&builtinPresets[stored]? stored as ColorTheme:'blue';
300
301
  }
301
302
 
302
- function persistSelection(key: string, value: string): void {
303
- const storage = getStorage();
304
- if (!storage || storage.getItem(key) === value) return;
305
- storage.setItem(key, value);
303
+ function persistSelection(key: string,value: string): void {
304
+ const storage=getStorage();
305
+ if(!storage||storage.getItem(key)===value) return;
306
+ storage.setItem(key,value);
306
307
  }
307
308
 
308
- let selectedMode = readStoredTheme();
309
- let mode = $state<ThemeMode>(selectedMode ?? 'system');
310
- const initialColorTheme = readStoredColorTheme();
311
- let selectedColorTheme = $state<ColorTheme>(initialColorTheme);
312
- let colorTheme = $state<ColorTheme>(initialColorTheme);
309
+ let selectedMode=readStoredTheme();
310
+ let mode=$state<ThemeMode>(selectedMode??'system');
311
+ const initialColorTheme=readStoredColorTheme();
312
+ let selectedColorTheme=$state<ColorTheme>(initialColorTheme);
313
+ let colorTheme=$state<ColorTheme>(initialColorTheme);
313
314
 
314
- function getSystemPreference(): 'light' | 'dark' {
315
- if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return 'light';
316
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
315
+ function getSystemPreference(): 'light'|'dark' {
316
+ if(typeof window==='undefined'||typeof window.matchMedia!=='function') return 'light';
317
+ return window.matchMedia('(prefers-color-scheme: dark)').matches? 'dark':'light';
317
318
  }
318
319
 
319
320
  function removeCssProperties(keys: string[]): void {
320
- if (typeof document === 'undefined') return;
321
- const root = document.documentElement;
322
- for (const key of keys) root.style.removeProperty(key);
321
+ if(typeof document==='undefined') return;
322
+ const root=document.documentElement;
323
+ for(const key of keys) root.style.removeProperty(key);
323
324
  }
324
325
 
325
326
  function clearAppliedArtifacts(): void {
326
- if (typeof document === 'undefined') return;
327
- const root = document.documentElement;
327
+ if(typeof document==='undefined') return;
328
+ const root=document.documentElement;
328
329
 
329
330
  removeCssProperties(activeCssOverrideVars);
330
331
  removeCssProperties(activePresetVars);
331
- activeCssOverrideVars = [];
332
- activePresetVars = [];
333
- root.classList.remove('layout-clean-flat', 'light', 'dark');
332
+ activeCssOverrideVars=[];
333
+ activePresetVars=[];
334
+ root.classList.remove('layout-clean-flat','light','dark');
334
335
  root.style.removeProperty('color-scheme');
335
336
  root.removeAttribute('data-theme');
336
337
  }
337
338
 
338
- function applyThemeClasses(themeMode: ThemeMode, config: ThemeConfig): void {
339
- if (typeof document === 'undefined') return;
340
- const resolved = themeMode === 'system' ? getSystemPreference() : themeMode;
341
- const strategy = config.strategy ?? 'standard';
342
- const root = document.documentElement;
339
+ function applyThemeClasses(themeMode: ThemeMode,config: ThemeConfig): void {
340
+ if(typeof document==='undefined') return;
341
+ const resolved=themeMode==='system'? getSystemPreference():themeMode;
342
+ const strategy=config.strategy??'standard';
343
+ const root=document.documentElement;
343
344
 
344
- root.classList.toggle('layout-clean-flat', config.layoutPreset === 'clean-flat');
345
- if (strategy === 'dark-first') {
346
- root.classList.toggle('light', resolved === 'light');
345
+ root.classList.toggle('layout-clean-flat',config.layoutPreset==='clean-flat');
346
+ if(strategy==='dark-first') {
347
+ root.classList.toggle('light',resolved==='light');
347
348
  root.classList.remove('dark');
348
349
  } else {
349
- root.classList.toggle('dark', resolved === 'dark');
350
+ root.classList.toggle('dark',resolved==='dark');
350
351
  root.classList.remove('light');
351
352
  }
352
353
 
353
- if (!config.disableColorScheme) root.style.colorScheme = resolved;
354
+ if(!config.disableColorScheme) root.style.colorScheme=resolved;
354
355
  }
355
356
 
356
- function applyColorPreset(preset: ColorPreset, themeMode: ThemeMode): void {
357
- if (typeof document === 'undefined') return;
358
- const resolved = themeMode === 'system' ? getSystemPreference() : themeMode;
359
- const variables = resolved === 'dark' ? preset.dark : preset.light;
360
- const root = document.documentElement;
357
+ function applyColorPreset(preset: ColorPreset,themeMode: ThemeMode): void {
358
+ if(typeof document==='undefined') return;
359
+ const resolved=themeMode==='system'? getSystemPreference():themeMode;
360
+ const variables=resolved==='dark'? preset.dark:preset.light;
361
+ const root=document.documentElement;
361
362
 
362
- for (const [key, value] of Object.entries(variables)) {
363
- const cssVar = key.startsWith('--') ? key : '--' + key;
364
- root.style.setProperty(cssVar, value);
363
+ for(const [key,value] of Object.entries(variables)) {
364
+ const cssVar=key.startsWith('--')? key:'--'+key;
365
+ root.style.setProperty(cssVar,value);
365
366
  activePresetVars.push(cssVar);
366
367
  }
367
368
  }
368
369
 
369
- function applyCssOverrides(overrides: Record<string, string>): void {
370
- if (typeof document === 'undefined') return;
371
- const root = document.documentElement;
370
+ function applyCssOverrides(overrides: Record<string,string>): void {
371
+ if(typeof document==='undefined') return;
372
+ const root=document.documentElement;
372
373
 
373
- for (const [key, value] of Object.entries(overrides)) {
374
- const cssVar = key.startsWith('--') ? key : '--' + key;
375
- root.style.setProperty(cssVar, value);
374
+ for(const [key,value] of Object.entries(overrides)) {
375
+ const cssVar=key.startsWith('--')? key:'--'+key;
376
+ root.style.setProperty(cssVar,value);
376
377
  activeCssOverrideVars.push(cssVar);
377
378
  }
378
379
  }
379
380
 
380
381
  function applyEffectiveTheme(): void {
381
- const owner = getActiveOwner();
382
- const nextConfig = owner
382
+ const owner=getActiveOwner();
383
+ const nextConfig=owner
383
384
  ? cloneThemeConfig(owner.options.themeConfig)
384
- : cloneThemeConfig(legacyThemeConfig);
385
- const nextMode = selectedMode ?? owner?.options.defaultTheme ?? 'system';
386
- const configuredPreset = !colorSelectionOverridesConfig && nextConfig.colorPreset
385
+ :cloneThemeConfig(legacyThemeConfig);
386
+ const nextMode=selectedMode??owner?.options.defaultTheme??'system';
387
+ const configuredPreset=!colorSelectionOverridesConfig&&nextConfig.colorPreset
387
388
  ? resolvePreset(nextConfig.colorPreset)
388
- : undefined;
389
- const preset = configuredPreset ?? builtinPresets[selectedColorTheme];
389
+ :undefined;
390
+ const preset=configuredPreset??builtinPresets[selectedColorTheme];
390
391
 
391
- themeConfig = nextConfig;
392
- mode = nextMode;
393
- colorTheme = (preset?.name ?? selectedColorTheme) as ColorTheme;
392
+ themeConfig=nextConfig;
393
+ mode=nextMode;
394
+ colorTheme=(preset?.name??selectedColorTheme) as ColorTheme;
394
395
 
395
- if (typeof document === 'undefined') return;
396
+ if(typeof document==='undefined') return;
396
397
  clearAppliedArtifacts();
397
- applyThemeClasses(mode, themeConfig);
398
- document.documentElement.setAttribute('data-theme', colorTheme);
399
- if (preset) applyColorPreset(preset, mode);
400
- if (themeConfig.cssOverrides) applyCssOverrides(themeConfig.cssOverrides);
398
+ applyThemeClasses(mode,themeConfig);
399
+ document.documentElement.setAttribute('data-theme',colorTheme);
400
+ if(preset) applyColorPreset(preset,mode);
401
+ if(themeConfig.cssOverrides) applyCssOverrides(themeConfig.cssOverrides);
401
402
  }
402
403
 
403
404
  /**
@@ -405,9 +406,9 @@ function applyEffectiveTheme(): void {
405
406
  * mounted explicit owners continue to take precedence until they unregister.
406
407
  */
407
408
  export function configureTheme(config: ThemeConfig): void {
408
- legacyThemeConfig = cloneThemeConfig(config);
409
- colorSelectionOverridesConfig = false;
410
- if (!getActiveOwner()) applyEffectiveTheme();
409
+ legacyThemeConfig=cloneThemeConfig(config);
410
+ colorSelectionOverridesConfig=false;
411
+ if(!getActiveOwner()) applyEffectiveTheme();
411
412
  }
412
413
 
413
414
  /** Get current effective theme configuration. */
@@ -417,18 +418,18 @@ export function getThemeConfig(): ThemeConfig {
417
418
 
418
419
  /** Remove previously applied CSS overrides. */
419
420
  export function clearCssOverrides(keys?: string[]): void {
420
- if (typeof document === 'undefined') return;
421
- const root = document.documentElement;
421
+ if(typeof document==='undefined') return;
422
+ const root=document.documentElement;
422
423
 
423
- if (keys) {
424
- for (const key of keys) {
425
- const cssVar = key.startsWith('--') ? key : '--' + key;
424
+ if(keys) {
425
+ for(const key of keys) {
426
+ const cssVar=key.startsWith('--')? key:'--'+key;
426
427
  root.style.removeProperty(cssVar);
427
- activeCssOverrideVars = activeCssOverrideVars.filter((activeKey) => activeKey !== cssVar);
428
+ activeCssOverrideVars=activeCssOverrideVars.filter((activeKey) => activeKey!==cssVar);
428
429
  }
429
430
  } else {
430
431
  removeCssProperties(activeCssOverrideVars);
431
- activeCssOverrideVars = [];
432
+ activeCssOverrideVars=[];
432
433
  }
433
434
  }
434
435
 
@@ -445,19 +446,19 @@ export function getTheme(): ThemeMode {
445
446
  }
446
447
 
447
448
  export function setTheme(newMode: ThemeMode): void {
448
- selectedMode = newMode;
449
- persistSelection(STORAGE_KEY, newMode);
449
+ selectedMode=newMode;
450
+ persistSelection(STORAGE_KEY,newMode);
450
451
  applyEffectiveTheme();
451
452
  }
452
453
 
453
454
  export function toggleTheme(): void {
454
- const resolved = mode === 'system' ? getSystemPreference() : mode;
455
- setTheme(resolved === 'dark' ? 'light' : 'dark');
455
+ const resolved=mode==='system'? getSystemPreference():mode;
456
+ setTheme(resolved==='dark'? 'light':'dark');
456
457
  }
457
458
 
458
459
  /** Resolved theme (always 'light' or 'dark', never 'system'). */
459
- export function getResolvedTheme(): 'light' | 'dark' {
460
- return mode === 'system' ? getSystemPreference() : mode;
460
+ export function getResolvedTheme(): 'light'|'dark' {
461
+ return mode==='system'? getSystemPreference():mode;
461
462
  }
462
463
 
463
464
  export function getColorTheme(): ColorTheme {
@@ -465,9 +466,9 @@ export function getColorTheme(): ColorTheme {
465
466
  }
466
467
 
467
468
  export function setColorTheme(nextColorTheme: ColorTheme): void {
468
- selectedColorTheme = nextColorTheme;
469
- colorSelectionOverridesConfig = true;
470
- persistSelection(COLOR_STORAGE_KEY, nextColorTheme);
469
+ selectedColorTheme=nextColorTheme;
470
+ colorSelectionOverridesConfig=true;
471
+ persistSelection(COLOR_STORAGE_KEY,nextColorTheme);
471
472
  applyEffectiveTheme();
472
473
  }
473
474
 
@@ -475,15 +476,15 @@ export function setColorTheme(nextColorTheme: ColorTheme): void {
475
476
  * Register a browser-only document owner. Owners with omitted values remain
476
477
  * inert, allowing a mounted AdminApp to activate the same token after prop updates.
477
478
  */
478
- export function registerThemeOwner(options: ThemeOwnerOptions): ThemeOwnerToken | undefined {
479
- if (typeof window === 'undefined' || typeof document === 'undefined') return undefined;
479
+ export function registerThemeOwner(options: ThemeOwnerOptions): ThemeOwnerToken|undefined {
480
+ if(typeof window==='undefined'||typeof document==='undefined') return undefined;
480
481
 
481
- const token = Symbol('svadmin-theme-owner');
482
- const entry = { token, options: cloneOwnerOptions(options) };
482
+ const token=Symbol('svadmin-theme-owner');
483
+ const entry={ token,options: cloneOwnerOptions(options) };
483
484
  themeOwners.push(entry);
484
485
 
485
- if (isExplicitOwner(entry.options)) {
486
- colorSelectionOverridesConfig = false;
486
+ if(isExplicitOwner(entry.options)) {
487
+ colorSelectionOverridesConfig=false;
487
488
  applyEffectiveTheme();
488
489
  }
489
490
 
@@ -492,52 +493,52 @@ export function registerThemeOwner(options: ThemeOwnerOptions): ThemeOwnerToken
492
493
 
493
494
  /** Update one mounted owner without changing its stack order. */
494
495
  export function updateThemeOwner(
495
- token: ThemeOwnerToken | undefined,
496
+ token: ThemeOwnerToken|undefined,
496
497
  options: ThemeOwnerOptions,
497
498
  ): void {
498
- if (!token || typeof window === 'undefined' || typeof document === 'undefined') return;
499
- const owner = themeOwners.find((entry) => entry.token === token);
500
- if (!owner) return;
501
-
502
- const wasExplicit = isExplicitOwner(owner.options);
503
- owner.options = cloneOwnerOptions(options);
504
- if (wasExplicit || isExplicitOwner(owner.options)) {
505
- colorSelectionOverridesConfig = false;
499
+ if(!token||typeof window==='undefined'||typeof document==='undefined') return;
500
+ const owner=themeOwners.find((entry) => entry.token===token);
501
+ if(!owner) return;
502
+
503
+ const wasExplicit=isExplicitOwner(owner.options);
504
+ owner.options=cloneOwnerOptions(options);
505
+ if(wasExplicit||isExplicitOwner(owner.options)) {
506
+ colorSelectionOverridesConfig=false;
506
507
  applyEffectiveTheme();
507
508
  }
508
509
  }
509
510
 
510
511
  /** Remove an owner safely regardless of its position in the stack. */
511
- export function unregisterThemeOwner(token: ThemeOwnerToken | undefined): void {
512
- if (!token || typeof window === 'undefined' || typeof document === 'undefined') return;
513
- const index = themeOwners.findIndex((entry) => entry.token === token);
514
- if (index === -1) return;
515
-
516
- const [removed] = themeOwners.splice(index, 1);
517
- if (isExplicitOwner(removed.options)) {
518
- colorSelectionOverridesConfig = false;
512
+ export function unregisterThemeOwner(token: ThemeOwnerToken|undefined): void {
513
+ if(!token||typeof window==='undefined'||typeof document==='undefined') return;
514
+ const index=themeOwners.findIndex((entry) => entry.token===token);
515
+ if(index===-1) return;
516
+
517
+ const [removed]=themeOwners.splice(index,1);
518
+ if(removed&&isExplicitOwner(removed.options)) {
519
+ colorSelectionOverridesConfig=false;
519
520
  applyEffectiveTheme();
520
521
  }
521
522
  }
522
523
 
523
524
  export function resetTheme(): void {
524
- selectedMode = readStoredTheme();
525
- selectedColorTheme = readStoredColorTheme();
526
- mode = selectedMode ?? 'system';
527
- colorTheme = selectedColorTheme;
528
- legacyThemeConfig = {};
529
- themeConfig = {};
530
- themeOwners = [];
531
- colorSelectionOverridesConfig = false;
525
+ selectedMode=readStoredTheme();
526
+ selectedColorTheme=readStoredColorTheme();
527
+ mode=selectedMode??'system';
528
+ colorTheme=selectedColorTheme;
529
+ legacyThemeConfig={};
530
+ themeConfig={};
531
+ themeOwners=[];
532
+ colorSelectionOverridesConfig=false;
532
533
  applyEffectiveTheme();
533
534
  }
534
535
 
535
- if (typeof window !== 'undefined' && typeof document !== 'undefined') {
536
+ if(typeof window!=='undefined'&&typeof document!=='undefined') {
536
537
  applyEffectiveTheme();
537
538
 
538
- if (typeof window.matchMedia === 'function') {
539
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
540
- if (mode === 'system') applyEffectiveTheme();
539
+ if(typeof window.matchMedia==='function') {
540
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',() => {
541
+ if(mode==='system') applyEffectiveTheme();
541
542
  });
542
543
  }
543
544
  }