rune-lab 0.2.2 → 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.
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/money/index.d.ts +2 -0
- package/dist/core/money/index.d.ts.map +1 -0
- package/dist/core/money/index.js +2 -0
- package/dist/core/money/money.d.ts +37 -0
- package/dist/core/money/money.d.ts.map +1 -0
- package/dist/core/money/money.js +79 -0
- package/dist/index.d.ts +0 -6
- package/dist/index.js +1 -7
- package/dist/state/auth/index.d.ts +2 -0
- package/dist/state/auth/index.js +2 -0
- package/dist/state/auth/session.svelte.d.ts +40 -0
- package/dist/state/auth/session.svelte.js +57 -0
- package/dist/state/auth/types.d.ts +30 -0
- package/dist/state/auth/types.js +3 -0
- package/dist/state/cart.svelte.d.ts +64 -0
- package/dist/state/cart.svelte.js +122 -0
- package/dist/state/composables/useMoney.d.ts +15 -0
- package/dist/state/composables/useMoney.js +48 -0
- package/dist/state/composables/useRuneLab.d.ts +3 -1
- package/dist/state/composables/useRuneLab.js +11 -0
- package/dist/state/context.d.ts +2 -0
- package/dist/state/context.js +2 -0
- package/dist/state/createConfigStore.svelte.d.ts +8 -3
- package/dist/state/createConfigStore.svelte.js +10 -0
- package/dist/state/currency.svelte.d.ts +9 -1
- package/dist/state/currency.svelte.js +28 -7
- package/dist/state/index.d.ts +7 -4
- package/dist/state/index.js +6 -2
- package/dist/state/language.svelte.d.ts +1 -0
- package/dist/state/persistence/drivers.d.ts +3 -1
- package/dist/state/persistence/drivers.js +5 -1
- package/dist/state/theme.svelte.d.ts +9 -1
- package/dist/state/theme.svelte.js +34 -8
- package/dist/ui/components/Icon.svelte +5 -2
- package/dist/ui/components/RuneProvider.svelte +59 -4
- package/dist/ui/components/RuneProvider.svelte.d.ts +19 -0
- package/dist/ui/components/money/MoneyDisplay.svelte +59 -0
- package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +13 -0
- package/dist/ui/components/money/MoneyInput.svelte +106 -0
- package/dist/ui/components/money/MoneyInput.svelte.d.ts +19 -0
- package/dist/ui/components/money/index.d.ts +2 -0
- package/dist/ui/components/money/index.js +3 -0
- package/dist/ui/components/user/UserAvatar.svelte +66 -0
- package/dist/ui/components/user/UserAvatar.svelte.d.ts +13 -0
- package/dist/ui/components/user/UserProfile.svelte +79 -0
- package/dist/ui/components/user/UserProfile.svelte.d.ts +17 -0
- package/dist/ui/components/user/index.d.ts +2 -0
- package/dist/ui/components/user/index.js +3 -0
- package/dist/ui/features/notifications/NotificationBell.svelte +89 -0
- package/dist/ui/features/notifications/NotificationBell.svelte.d.ts +11 -0
- package/dist/ui/features/notifications/index.d.ts +1 -0
- package/dist/ui/features/notifications/index.js +2 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +10 -0
- package/dist/ui/layout/WorkspaceLayout.svelte +9 -0
- package/dist/ui/paraglide/messages/_index.d.ts +4 -0
- package/dist/ui/paraglide/messages/_index.js +4 -0
- package/dist/ui/paraglide/messages/brl3.d.ts +17 -0
- package/dist/ui/paraglide/messages/brl3.js +85 -0
- package/dist/ui/paraglide/messages/cad3.d.ts +17 -0
- package/dist/ui/paraglide/messages/cad3.js +85 -0
- package/dist/ui/paraglide/messages/gbp3.d.ts +17 -0
- package/dist/ui/paraglide/messages/gbp3.js +85 -0
- package/dist/ui/paraglide/messages/inr3.d.ts +17 -0
- package/dist/ui/paraglide/messages/inr3.js +85 -0
- package/dist/ui/primitives/DatePicker.svelte +257 -0
- package/dist/ui/primitives/DatePicker.svelte.d.ts +17 -0
- package/dist/ui/primitives/index.d.ts +1 -0
- package/dist/ui/primitives/index.js +3 -0
- package/package.json +28 -18
- package/dist/state/config.d.ts +0 -4
- package/dist/state/config.js +0 -8
|
@@ -46,6 +46,16 @@ export function createConfigStore(options) {
|
|
|
46
46
|
const targetId = id ?? this.current;
|
|
47
47
|
return this.get(targetId)?.[prop];
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Append additional items (deduplicates by idKey)
|
|
51
|
+
*/
|
|
52
|
+
addItems(newItems) {
|
|
53
|
+
for (const item of newItems) {
|
|
54
|
+
if (!this.get(item[idKey])) {
|
|
55
|
+
this.available.push(item);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
49
59
|
}
|
|
50
60
|
return new ConfigStore();
|
|
51
61
|
}
|
|
@@ -9,11 +9,19 @@ export interface Currency {
|
|
|
9
9
|
decimals: number;
|
|
10
10
|
}
|
|
11
11
|
import type { PersistenceDriver } from "rune-lab/core";
|
|
12
|
-
export
|
|
12
|
+
export interface CurrencyStoreOptions {
|
|
13
|
+
driver?: PersistenceDriver | (() => PersistenceDriver | undefined);
|
|
14
|
+
/** Additional custom currencies to append to the built-in set */
|
|
15
|
+
customCurrencies?: Currency[];
|
|
16
|
+
/** Default currency code if no persisted value exists */
|
|
17
|
+
defaultCurrency?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function createCurrencyStore(driverOrOptions?: PersistenceDriver | (() => PersistenceDriver | undefined) | CurrencyStoreOptions): {
|
|
13
20
|
current: string | number;
|
|
14
21
|
available: Currency[];
|
|
15
22
|
set(id: string | number): void;
|
|
16
23
|
get(id: string | number): Currency | undefined;
|
|
17
24
|
getProp<K extends keyof Currency>(prop: K, id?: string | number | undefined): Currency[K] | undefined;
|
|
25
|
+
addItems(newItems: Currency[]): void;
|
|
18
26
|
};
|
|
19
27
|
export declare function getCurrencyStore(): ConfigStore<Currency>;
|
|
@@ -4,26 +4,47 @@ import { RUNE_LAB_CONTEXT } from "./context";
|
|
|
4
4
|
const CURRENCIES = [
|
|
5
5
|
{ code: "USD", symbol: "$", decimals: 2 },
|
|
6
6
|
{ code: "EUR", symbol: "€", decimals: 2 },
|
|
7
|
+
{ code: "GBP", symbol: "£", decimals: 2 },
|
|
7
8
|
{ code: "MXN", symbol: "$", decimals: 2 },
|
|
9
|
+
{ code: "CAD", symbol: "C$", decimals: 2 },
|
|
10
|
+
{ code: "BRL", symbol: "R$", decimals: 2 },
|
|
11
|
+
{ code: "INR", symbol: "₹", decimals: 2 },
|
|
8
12
|
{ code: "CNY", symbol: "¥", decimals: 2 },
|
|
9
13
|
{ code: "JPY", symbol: "¥", decimals: 0 },
|
|
10
14
|
{ code: "KRW", symbol: "₩", decimals: 0 },
|
|
11
15
|
{ code: "AED", symbol: "د.إ", decimals: 2 },
|
|
12
16
|
];
|
|
13
|
-
export function createCurrencyStore(
|
|
14
|
-
|
|
17
|
+
export function createCurrencyStore(driverOrOptions) {
|
|
18
|
+
// Normalize overloaded argument
|
|
19
|
+
const opts = driverOrOptions && typeof driverOrOptions === "object" &&
|
|
20
|
+
"driver" in driverOrOptions
|
|
21
|
+
? driverOrOptions
|
|
22
|
+
: {
|
|
23
|
+
driver: driverOrOptions,
|
|
24
|
+
};
|
|
25
|
+
const resolvedDriver = typeof opts.driver === "function"
|
|
26
|
+
? opts.driver()
|
|
27
|
+
: opts.driver;
|
|
28
|
+
const store = createConfigStore({
|
|
15
29
|
items: CURRENCIES,
|
|
16
30
|
storageKey: "currency",
|
|
17
31
|
displayName: "Currency",
|
|
18
32
|
idKey: "code",
|
|
19
33
|
icon: "💰",
|
|
20
|
-
driver:
|
|
34
|
+
driver: resolvedDriver,
|
|
21
35
|
});
|
|
36
|
+
// Append custom currencies if provided
|
|
37
|
+
if (opts.customCurrencies?.length) {
|
|
38
|
+
store.addItems(opts.customCurrencies);
|
|
39
|
+
}
|
|
40
|
+
// Set default currency if provided and no persisted value found
|
|
41
|
+
if (!resolvedDriver?.get("currency") && opts.defaultCurrency) {
|
|
42
|
+
if (store.get(opts.defaultCurrency)) {
|
|
43
|
+
store.set(opts.defaultCurrency);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return store;
|
|
22
47
|
}
|
|
23
48
|
export function getCurrencyStore() {
|
|
24
49
|
return getContext(RUNE_LAB_CONTEXT.currency);
|
|
25
50
|
}
|
|
26
|
-
// Usage:
|
|
27
|
-
// currencyStore.set("USD")
|
|
28
|
-
// currencyStore.getProp("symbol") // "$"
|
|
29
|
-
// currencyStore.getProp("decimals") // 2
|
package/dist/state/index.d.ts
CHANGED
|
@@ -2,15 +2,18 @@ export { RUNE_LAB_CONTEXT } from "./context";
|
|
|
2
2
|
export { useRuneLab } from "./composables/useRuneLab";
|
|
3
3
|
export type { RuneLabContext } from "./composables/useRuneLab";
|
|
4
4
|
export { usePersistence } from "./composables/usePersistence";
|
|
5
|
-
export {
|
|
5
|
+
export { useMoney } from "./composables/useMoney";
|
|
6
|
+
export { cookieDriver, createCookieDriver, createInMemoryDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
|
|
6
7
|
export { type AppData, AppStore, createAppStore, getAppStore, } from "./app.svelte";
|
|
7
8
|
export { createLayoutStore, getLayoutStore, LayoutStore, type NavigationItem, type NavigationSection, type WorkspaceItem, } from "./layout.svelte";
|
|
8
9
|
export { ApiStore, type ConnectionState, createApiStore, getApiStore, } from "./api.svelte";
|
|
9
10
|
export { createLanguageStore, getLanguageStore, type Language, } from "./language.svelte";
|
|
10
|
-
export { createCurrencyStore, type Currency, getCurrencyStore, } from "./currency.svelte";
|
|
11
|
+
export { createCurrencyStore, type Currency, type CurrencyStoreOptions, getCurrencyStore, } from "./currency.svelte";
|
|
11
12
|
export { createToastStore, getToastStore, ToastStore } from "./toast.svelte";
|
|
12
13
|
export { type Command, CommandStore, createCommandStore, getCommandStore, } from "./commands.svelte";
|
|
13
14
|
export { createShortcutStore, getShortcutStore, LAYOUT_SHORTCUTS, type ShortcutEntry, shortcutListener, type ShortcutMeta, ShortcutStore, } from "./shortcuts.svelte";
|
|
14
|
-
export { createThemeStore, getThemeStore, type Theme } from "./theme.svelte";
|
|
15
|
-
export { type ConfigStore, createConfigStore, } from "./createConfigStore.svelte";
|
|
15
|
+
export { createThemeStore, getThemeStore, type Theme, type ThemeStoreOptions, } from "./theme.svelte";
|
|
16
|
+
export { type ConfigItem, type ConfigStore, type ConfigStoreOptions, createConfigStore, } from "./createConfigStore.svelte";
|
|
16
17
|
export { createToastBridge, notify } from "./toast-bridge";
|
|
18
|
+
export { type CartEntry, type CartStore, type CartStoreConfig, createCartStore, getCartStore, } from "./cart.svelte";
|
|
19
|
+
export { type AuthConfig, createSessionStore, getSessionStore, type Session, SessionStore, type User, } from "./auth/index";
|
package/dist/state/index.js
CHANGED
|
@@ -5,8 +5,9 @@ export { RUNE_LAB_CONTEXT } from "./context";
|
|
|
5
5
|
// ── Composables ───────────────────────────────────────────────────────────────
|
|
6
6
|
export { useRuneLab } from "./composables/useRuneLab";
|
|
7
7
|
export { usePersistence } from "./composables/usePersistence";
|
|
8
|
+
export { useMoney } from "./composables/useMoney";
|
|
8
9
|
// ── Persistence Drivers ───────────────────────────────────────────────────────
|
|
9
|
-
export { cookieDriver, createInMemoryDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
|
|
10
|
+
export { cookieDriver, createCookieDriver, createInMemoryDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
|
|
10
11
|
// Stores
|
|
11
12
|
export { AppStore, createAppStore, getAppStore, } from "./app.svelte";
|
|
12
13
|
export { createLayoutStore, getLayoutStore, LayoutStore, } from "./layout.svelte";
|
|
@@ -16,6 +17,9 @@ export { createCurrencyStore, getCurrencyStore, } from "./currency.svelte";
|
|
|
16
17
|
export { createToastStore, getToastStore, ToastStore } from "./toast.svelte";
|
|
17
18
|
export { CommandStore, createCommandStore, getCommandStore, } from "./commands.svelte";
|
|
18
19
|
export { createShortcutStore, getShortcutStore, LAYOUT_SHORTCUTS, shortcutListener, ShortcutStore, } from "./shortcuts.svelte";
|
|
19
|
-
export { createThemeStore, getThemeStore } from "./theme.svelte";
|
|
20
|
+
export { createThemeStore, getThemeStore, } from "./theme.svelte";
|
|
20
21
|
export { createConfigStore, } from "./createConfigStore.svelte";
|
|
21
22
|
export { createToastBridge, notify } from "./toast-bridge";
|
|
23
|
+
export { createCartStore, getCartStore, } from "./cart.svelte";
|
|
24
|
+
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
25
|
+
export { createSessionStore, getSessionStore, SessionStore, } from "./auth/index";
|
|
@@ -59,5 +59,6 @@ export declare function createLanguageStore(options?: LanguageStoreOptions): {
|
|
|
59
59
|
set(id: string | undefined): void;
|
|
60
60
|
get(id: string | undefined): Language | undefined;
|
|
61
61
|
getProp<K extends keyof Language>(prop: K, id?: string | undefined): Language[K] | undefined;
|
|
62
|
+
addItems(newItems: Language[]): void;
|
|
62
63
|
};
|
|
63
64
|
export declare function getLanguageStore(): ConfigStore<Language>;
|
|
@@ -3,8 +3,10 @@ export declare function createInMemoryDriver(): PersistenceDriver;
|
|
|
3
3
|
export declare const inMemoryDriver: PersistenceDriver;
|
|
4
4
|
export declare const localStorageDriver: PersistenceDriver;
|
|
5
5
|
export declare const sessionStorageDriver: PersistenceDriver;
|
|
6
|
-
export declare const
|
|
6
|
+
export declare const createCookieDriver: (options?: {
|
|
7
7
|
path?: string;
|
|
8
8
|
maxAge?: number;
|
|
9
9
|
sameSite?: "Lax" | "Strict" | "None";
|
|
10
10
|
}) => PersistenceDriver;
|
|
11
|
+
/** Default cookie driver singleton (path='/') */
|
|
12
|
+
export declare const cookieDriver: PersistenceDriver;
|
|
@@ -42,7 +42,7 @@ export const sessionStorageDriver = {
|
|
|
42
42
|
window.sessionStorage.removeItem(key);
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
|
-
export const
|
|
45
|
+
export const createCookieDriver = (options = {}) => ({
|
|
46
46
|
get: (key) => {
|
|
47
47
|
if (!browser)
|
|
48
48
|
return null;
|
|
@@ -67,3 +67,7 @@ export const cookieDriver = (options = {}) => ({
|
|
|
67
67
|
document.cookie = `${key}=; max-age=0; path=${options.path || "/"}`;
|
|
68
68
|
},
|
|
69
69
|
});
|
|
70
|
+
/** Default cookie driver singleton (path='/') */
|
|
71
|
+
export const cookieDriver = createCookieDriver({
|
|
72
|
+
path: "/",
|
|
73
|
+
});
|
|
@@ -4,11 +4,19 @@ export interface Theme {
|
|
|
4
4
|
icon: string;
|
|
5
5
|
}
|
|
6
6
|
import type { PersistenceDriver } from "rune-lab/core";
|
|
7
|
-
export
|
|
7
|
+
export interface ThemeStoreOptions {
|
|
8
|
+
driver?: PersistenceDriver | (() => PersistenceDriver | undefined);
|
|
9
|
+
/** Additional custom themes to append to the built-in DaisyUI set */
|
|
10
|
+
customThemes?: Theme[];
|
|
11
|
+
/** Fallback theme if no persisted value exists (after system preference check) */
|
|
12
|
+
defaultTheme?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function createThemeStore(driverOrOptions?: PersistenceDriver | (() => PersistenceDriver | undefined) | ThemeStoreOptions): {
|
|
8
15
|
current: string;
|
|
9
16
|
available: Theme[];
|
|
10
17
|
set(id: string): void;
|
|
11
18
|
get(id: string): Theme | undefined;
|
|
12
19
|
getProp<K extends keyof Theme>(prop: K, id?: string | undefined): Theme[K] | undefined;
|
|
20
|
+
addItems(newItems: Theme[]): void;
|
|
13
21
|
};
|
|
14
22
|
export declare function getThemeStore(): ConfigStore<Theme>;
|
|
@@ -46,21 +46,47 @@ const THEMES = themeOrder.map((name) => ({
|
|
|
46
46
|
icon: THEME_ICONS[name] ?? "🎨",
|
|
47
47
|
}));
|
|
48
48
|
import themeOrder from "daisyui/functions/themeOrder.js";
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
import { BROWSER } from "esm-env";
|
|
50
|
+
export function createThemeStore(driverOrOptions) {
|
|
51
|
+
// Normalize overloaded argument
|
|
52
|
+
const opts = driverOrOptions && typeof driverOrOptions === "object" &&
|
|
53
|
+
"driver" in driverOrOptions
|
|
54
|
+
? driverOrOptions
|
|
55
|
+
: {
|
|
56
|
+
driver: driverOrOptions,
|
|
57
|
+
};
|
|
58
|
+
const resolvedDriver = typeof opts.driver === "function"
|
|
59
|
+
? opts.driver()
|
|
60
|
+
: opts.driver;
|
|
61
|
+
const store = createConfigStore({
|
|
51
62
|
items: THEMES,
|
|
52
63
|
storageKey: "theme",
|
|
53
64
|
displayName: "Theme",
|
|
54
65
|
idKey: "name",
|
|
55
66
|
icon: "🎨",
|
|
56
|
-
driver:
|
|
67
|
+
driver: resolvedDriver,
|
|
57
68
|
});
|
|
69
|
+
// Append custom themes if provided
|
|
70
|
+
if (opts.customThemes?.length) {
|
|
71
|
+
store.addItems(opts.customThemes);
|
|
72
|
+
}
|
|
73
|
+
// System preference detection — only if no persisted value was loaded
|
|
74
|
+
if (!resolvedDriver?.get("theme") && BROWSER) {
|
|
75
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
76
|
+
const systemDefault = prefersDark ? "dark" : "light";
|
|
77
|
+
const chosen = opts.defaultTheme ?? systemDefault;
|
|
78
|
+
if (store.get(chosen)) {
|
|
79
|
+
store.set(chosen);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (!resolvedDriver?.get("theme") && opts.defaultTheme) {
|
|
83
|
+
// SSR / non-browser: use defaultTheme if provided
|
|
84
|
+
if (store.get(opts.defaultTheme)) {
|
|
85
|
+
store.set(opts.defaultTheme);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return store;
|
|
58
89
|
}
|
|
59
90
|
export function getThemeStore() {
|
|
60
91
|
return getContext(RUNE_LAB_CONTEXT.theme);
|
|
61
92
|
}
|
|
62
|
-
// Usage:
|
|
63
|
-
// themeStore.set("dark")
|
|
64
|
-
// themeStore.get("dark")
|
|
65
|
-
// themeStore.getProp("icon") // gets icon of current theme
|
|
66
|
-
// themeStore.getProp("icon", "dark") // gets icon of specific theme
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
import { DEV } from "esm-env";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Simple Icon component for Rune Lab
|
|
7
|
-
* Centralizes SVG paths to reduce boilerplate
|
|
6
|
+
* Simple Icon component for Rune Lab.
|
|
7
|
+
* Centralizes SVG paths to reduce boilerplate.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ SECURITY: `customIcons` values (from `appStore.customIcons` or the `icons` prop)
|
|
10
|
+
* are rendered as **unsanitized HTML** via `{@html}`. Only pass trusted SVG path strings.
|
|
8
11
|
*/
|
|
9
12
|
let {
|
|
10
13
|
name,
|
|
@@ -10,11 +10,16 @@
|
|
|
10
10
|
createLanguageStore,
|
|
11
11
|
createCurrencyStore,
|
|
12
12
|
createShortcutStore,
|
|
13
|
+
createCartStore,
|
|
14
|
+
createSessionStore,
|
|
15
|
+
type CartStoreConfig,
|
|
13
16
|
} from "rune-lab/state";
|
|
14
17
|
import type { PersistenceDriver } from "rune-lab/core";
|
|
15
18
|
import { localStorageDriver } from "rune-lab/state";
|
|
16
19
|
import { RUNE_LAB_CONTEXT } from "rune-lab/state";
|
|
17
20
|
import type { AppData } from "rune-lab/state";
|
|
21
|
+
import type { Theme } from "rune-lab/state";
|
|
22
|
+
import type { Currency } from "rune-lab/state";
|
|
18
23
|
import { CommandPalette, ShortcutPalette, Toaster } from "rune-lab/ui";
|
|
19
24
|
|
|
20
25
|
export interface RuneLabConfig {
|
|
@@ -28,6 +33,30 @@
|
|
|
28
33
|
locales?: readonly string[];
|
|
29
34
|
onLanguageChange?: (code: string) => void;
|
|
30
35
|
onThemeChange?: (name: string) => void;
|
|
36
|
+
|
|
37
|
+
// Theming (DaisyUI)
|
|
38
|
+
/** Additional custom themes to register alongside the built-in DaisyUI set */
|
|
39
|
+
customThemes?: Theme[];
|
|
40
|
+
/** Theme to use when no persisted value exists (after system preference detection) */
|
|
41
|
+
defaultTheme?: string;
|
|
42
|
+
|
|
43
|
+
// Currencies (Dinero.js)
|
|
44
|
+
/** Additional custom currencies to register */
|
|
45
|
+
currencies?: Currency[];
|
|
46
|
+
/** Default currency code when no persisted value exists */
|
|
47
|
+
defaultCurrency?: string;
|
|
48
|
+
|
|
49
|
+
// Cart (opt-in)
|
|
50
|
+
/** CartStore configuration — when provided, a CartStore is created and registered in context */
|
|
51
|
+
cart?: CartStoreConfig<any>;
|
|
52
|
+
|
|
53
|
+
// Auth (opt-in)
|
|
54
|
+
/** When true, creates and registers a SessionStore in context */
|
|
55
|
+
auth?: {
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
/** Callback when session changes (login, logout, expiry) */
|
|
58
|
+
onSessionChange?: (session: any) => void;
|
|
59
|
+
};
|
|
31
60
|
}
|
|
32
61
|
|
|
33
62
|
let { children, config = {} } = $props<{
|
|
@@ -46,13 +75,27 @@
|
|
|
46
75
|
() => config.persistence ?? localStorageDriver,
|
|
47
76
|
);
|
|
48
77
|
const initialLocales = untrack(() => config.locales);
|
|
49
|
-
|
|
50
|
-
const
|
|
78
|
+
const initialCustomThemes = untrack(() => config.customThemes);
|
|
79
|
+
const initialDefaultTheme = untrack(() => config.defaultTheme);
|
|
80
|
+
const initialCustomCurrencies = untrack(() => config.currencies);
|
|
81
|
+
const initialDefaultCurrency = untrack(() => config.defaultCurrency);
|
|
82
|
+
const initialCartConfig = untrack(() => config.cart);
|
|
83
|
+
const initialAuthConfig = untrack(() => config.auth);
|
|
84
|
+
|
|
85
|
+
const themeStore = createThemeStore({
|
|
86
|
+
driver: initialPersistence,
|
|
87
|
+
customThemes: initialCustomThemes,
|
|
88
|
+
defaultTheme: initialDefaultTheme,
|
|
89
|
+
});
|
|
51
90
|
const languageStore = createLanguageStore({
|
|
52
91
|
driver: initialPersistence,
|
|
53
92
|
locales: initialLocales,
|
|
54
93
|
});
|
|
55
|
-
const currencyStore = createCurrencyStore(
|
|
94
|
+
const currencyStore = createCurrencyStore({
|
|
95
|
+
driver: initialPersistence,
|
|
96
|
+
customCurrencies: initialCustomCurrencies,
|
|
97
|
+
defaultCurrency: initialDefaultCurrency,
|
|
98
|
+
});
|
|
56
99
|
const shortcutStore = createShortcutStore();
|
|
57
100
|
|
|
58
101
|
// 2. Initialize Complex Stores (Dependency Injection)
|
|
@@ -66,7 +109,7 @@
|
|
|
66
109
|
currencyStore,
|
|
67
110
|
});
|
|
68
111
|
|
|
69
|
-
// 3. Provide Contexts
|
|
112
|
+
// 3. Provide Core Contexts
|
|
70
113
|
setContext(RUNE_LAB_CONTEXT.app, appStore);
|
|
71
114
|
setContext(RUNE_LAB_CONTEXT.api, apiStore);
|
|
72
115
|
setContext(RUNE_LAB_CONTEXT.toast, toastStore);
|
|
@@ -78,6 +121,18 @@
|
|
|
78
121
|
setContext(RUNE_LAB_CONTEXT.commands, commandStore);
|
|
79
122
|
setContext(RUNE_LAB_CONTEXT.persistence, initialPersistence);
|
|
80
123
|
|
|
124
|
+
// 4. Opt-in: CartStore (C-01 FIX)
|
|
125
|
+
if (initialCartConfig) {
|
|
126
|
+
const cartStore = createCartStore(initialCartConfig);
|
|
127
|
+
setContext(RUNE_LAB_CONTEXT.cart, cartStore);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 5. Opt-in: SessionStore (C-02 FIX)
|
|
131
|
+
if (initialAuthConfig && initialAuthConfig.enabled !== false) {
|
|
132
|
+
const sessionStore = createSessionStore();
|
|
133
|
+
setContext(RUNE_LAB_CONTEXT.session, sessionStore);
|
|
134
|
+
}
|
|
135
|
+
|
|
81
136
|
const initialDictionary = untrack(() => config.dictionary);
|
|
82
137
|
if (initialDictionary) {
|
|
83
138
|
setContext("rl:dictionary", initialDictionary);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type Snippet } from "svelte";
|
|
2
|
+
import { type CartStoreConfig } from "rune-lab/state";
|
|
2
3
|
import type { PersistenceDriver } from "rune-lab/core";
|
|
3
4
|
import type { AppData } from "rune-lab/state";
|
|
5
|
+
import type { Theme } from "rune-lab/state";
|
|
6
|
+
import type { Currency } from "rune-lab/state";
|
|
4
7
|
export interface RuneLabConfig {
|
|
5
8
|
persistence?: PersistenceDriver;
|
|
6
9
|
app?: Partial<AppData>;
|
|
@@ -12,6 +15,22 @@ export interface RuneLabConfig {
|
|
|
12
15
|
locales?: readonly string[];
|
|
13
16
|
onLanguageChange?: (code: string) => void;
|
|
14
17
|
onThemeChange?: (name: string) => void;
|
|
18
|
+
/** Additional custom themes to register alongside the built-in DaisyUI set */
|
|
19
|
+
customThemes?: Theme[];
|
|
20
|
+
/** Theme to use when no persisted value exists (after system preference detection) */
|
|
21
|
+
defaultTheme?: string;
|
|
22
|
+
/** Additional custom currencies to register */
|
|
23
|
+
currencies?: Currency[];
|
|
24
|
+
/** Default currency code when no persisted value exists */
|
|
25
|
+
defaultCurrency?: string;
|
|
26
|
+
/** CartStore configuration — when provided, a CartStore is created and registered in context */
|
|
27
|
+
cart?: CartStoreConfig<any>;
|
|
28
|
+
/** When true, creates and registers a SessionStore in context */
|
|
29
|
+
auth?: {
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
/** Callback when session changes (login, logout, expiry) */
|
|
32
|
+
onSessionChange?: (session: any) => void;
|
|
33
|
+
};
|
|
15
34
|
}
|
|
16
35
|
type $$ComponentProps = {
|
|
17
36
|
children: Snippet;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
MoneyDisplay — Renders a monetary amount with locale-aware formatting.
|
|
3
|
+
Uses CurrencyStore + LanguageStore from context for defaults.
|
|
4
|
+
Zero domain knowledge.
|
|
5
|
+
-->
|
|
6
|
+
<script module lang="ts">
|
|
7
|
+
export interface MoneyDisplayProps {
|
|
8
|
+
/** Amount in minor units (e.g., 15000 = $150.00 for USD) */
|
|
9
|
+
amount: number;
|
|
10
|
+
/** Override currency code (defaults to CurrencyStore.current) */
|
|
11
|
+
currency?: string;
|
|
12
|
+
/** Override locale (defaults to LanguageStore.current) */
|
|
13
|
+
locale?: string;
|
|
14
|
+
/** Use compact notation (e.g., $150K) */
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import { getLanguageStore, getCurrencyStore } from "rune-lab/state";
|
|
21
|
+
import { formatAmount } from "rune-lab/core";
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
amount,
|
|
25
|
+
currency,
|
|
26
|
+
locale,
|
|
27
|
+
compact = false,
|
|
28
|
+
}: MoneyDisplayProps = $props();
|
|
29
|
+
|
|
30
|
+
const currencyStore = getCurrencyStore();
|
|
31
|
+
const languageStore = getLanguageStore();
|
|
32
|
+
|
|
33
|
+
const resolvedCurrency = $derived(
|
|
34
|
+
currency ?? String(currencyStore.current),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const resolvedLocale = $derived(
|
|
38
|
+
locale ?? (String(languageStore.current) || "en"),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// M-02 FIX: Derive decimals from the RESOLVED currency, not the store's current
|
|
42
|
+
const currencyMeta = $derived(currencyStore.get(resolvedCurrency));
|
|
43
|
+
const decimals = $derived(currencyMeta?.decimals ?? 2);
|
|
44
|
+
|
|
45
|
+
const formatted = $derived.by(() => {
|
|
46
|
+
if (compact) {
|
|
47
|
+
const majorUnits = amount / Math.pow(10, decimals);
|
|
48
|
+
return new Intl.NumberFormat(resolvedLocale, {
|
|
49
|
+
style: "currency",
|
|
50
|
+
currency: resolvedCurrency,
|
|
51
|
+
notation: "compact",
|
|
52
|
+
maximumFractionDigits: 1,
|
|
53
|
+
}).format(majorUnits);
|
|
54
|
+
}
|
|
55
|
+
return formatAmount(amount, resolvedCurrency, resolvedLocale);
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<data value={amount} class="rl-money-display tabular-nums">{formatted}</data>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MoneyDisplayProps {
|
|
2
|
+
/** Amount in minor units (e.g., 15000 = $150.00 for USD) */
|
|
3
|
+
amount: number;
|
|
4
|
+
/** Override currency code (defaults to CurrencyStore.current) */
|
|
5
|
+
currency?: string;
|
|
6
|
+
/** Override locale (defaults to LanguageStore.current) */
|
|
7
|
+
locale?: string;
|
|
8
|
+
/** Use compact notation (e.g., $150K) */
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare const MoneyDisplay: import("svelte").Component<MoneyDisplayProps, {}, "">;
|
|
12
|
+
type MoneyDisplay = ReturnType<typeof MoneyDisplay>;
|
|
13
|
+
export default MoneyDisplay;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
MoneyInput — Monetary value input that works in minor units (cents).
|
|
3
|
+
No floats cross the boundary — values are always integer minor units.
|
|
4
|
+
-->
|
|
5
|
+
<script module lang="ts">
|
|
6
|
+
export interface MoneyInputProps {
|
|
7
|
+
/** Current value in minor units (e.g., 15000 = $150.00) */
|
|
8
|
+
value?: number;
|
|
9
|
+
/** Override currency code (defaults to CurrencyStore.current) */
|
|
10
|
+
currency?: string;
|
|
11
|
+
/** Minimum value in minor units */
|
|
12
|
+
min?: number;
|
|
13
|
+
/** Maximum value in minor units */
|
|
14
|
+
max?: number;
|
|
15
|
+
/** Placeholder text */
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
/** Fired when the value changes (in minor units) */
|
|
18
|
+
oninput?: (cents: number) => void;
|
|
19
|
+
/** Input disabled state */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
import { getCurrencyStore } from "rune-lab/state";
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
value = 0,
|
|
29
|
+
currency,
|
|
30
|
+
min,
|
|
31
|
+
max,
|
|
32
|
+
placeholder = "0.00",
|
|
33
|
+
oninput,
|
|
34
|
+
disabled = false,
|
|
35
|
+
}: MoneyInputProps = $props();
|
|
36
|
+
|
|
37
|
+
const currencyStore = getCurrencyStore();
|
|
38
|
+
|
|
39
|
+
const resolvedCurrency = $derived(
|
|
40
|
+
currency ?? String(currencyStore.current),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// M-02 parallel: derive symbol/decimals from resolved currency, not store current
|
|
44
|
+
const currencyMeta = $derived(currencyStore.get(resolvedCurrency));
|
|
45
|
+
const symbol = $derived(currencyMeta?.symbol ?? "$");
|
|
46
|
+
const decimals = $derived(currencyMeta?.decimals ?? 2);
|
|
47
|
+
|
|
48
|
+
// Convert minor units → display string
|
|
49
|
+
const displayValue = $derived.by(() => {
|
|
50
|
+
if (decimals === 0) return String(value);
|
|
51
|
+
const divisor = Math.pow(10, decimals);
|
|
52
|
+
return (value / divisor).toFixed(decimals);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* M-03 FIX: Parse user input to minor units using string-based integer parsing
|
|
57
|
+
* to avoid floating-point precision traps (e.g., 1.005 * 100 = 100.49999...)
|
|
58
|
+
*/
|
|
59
|
+
function handleInput(e: Event) {
|
|
60
|
+
const target = e.target as HTMLInputElement;
|
|
61
|
+
const raw = target.value.replace(/[^0-9.,-]/g, "");
|
|
62
|
+
|
|
63
|
+
if (!raw) return;
|
|
64
|
+
|
|
65
|
+
// String-based parsing: split on decimal point
|
|
66
|
+
const parts = raw.split(".");
|
|
67
|
+
const integerPart = parts[0] || "0";
|
|
68
|
+
|
|
69
|
+
// Pad or truncate the fractional part to exactly `decimals` digits
|
|
70
|
+
let fractionalPart = (parts[1] || "").slice(0, decimals);
|
|
71
|
+
fractionalPart = fractionalPart.padEnd(decimals, "0");
|
|
72
|
+
|
|
73
|
+
// Combine as integer minor units — no floats involved
|
|
74
|
+
const combined =
|
|
75
|
+
decimals === 0 ? integerPart : integerPart + fractionalPart;
|
|
76
|
+
|
|
77
|
+
let cents = parseInt(combined, 10);
|
|
78
|
+
if (isNaN(cents)) return;
|
|
79
|
+
|
|
80
|
+
if (min !== undefined) cents = Math.max(cents, min);
|
|
81
|
+
if (max !== undefined) cents = Math.min(cents, max);
|
|
82
|
+
|
|
83
|
+
value = cents;
|
|
84
|
+
oninput?.(cents);
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<label
|
|
89
|
+
class="input input-bordered flex items-center gap-1"
|
|
90
|
+
class:input-disabled={disabled}
|
|
91
|
+
>
|
|
92
|
+
<span class="text-base-content/50 font-medium select-none">{symbol}</span>
|
|
93
|
+
<input
|
|
94
|
+
type="text"
|
|
95
|
+
inputmode="decimal"
|
|
96
|
+
class="grow bg-transparent outline-none tabular-nums"
|
|
97
|
+
value={displayValue}
|
|
98
|
+
{placeholder}
|
|
99
|
+
{disabled}
|
|
100
|
+
oninput={handleInput}
|
|
101
|
+
aria-label={`Amount in ${resolvedCurrency}`}
|
|
102
|
+
/>
|
|
103
|
+
<span class="text-base-content/30 text-xs font-mono select-none"
|
|
104
|
+
>{resolvedCurrency}</span
|
|
105
|
+
>
|
|
106
|
+
</label>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface MoneyInputProps {
|
|
2
|
+
/** Current value in minor units (e.g., 15000 = $150.00) */
|
|
3
|
+
value?: number;
|
|
4
|
+
/** Override currency code (defaults to CurrencyStore.current) */
|
|
5
|
+
currency?: string;
|
|
6
|
+
/** Minimum value in minor units */
|
|
7
|
+
min?: number;
|
|
8
|
+
/** Maximum value in minor units */
|
|
9
|
+
max?: number;
|
|
10
|
+
/** Placeholder text */
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
/** Fired when the value changes (in minor units) */
|
|
13
|
+
oninput?: (cents: number) => void;
|
|
14
|
+
/** Input disabled state */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare const MoneyInput: import("svelte").Component<MoneyInputProps, {}, "">;
|
|
18
|
+
type MoneyInput = ReturnType<typeof MoneyInput>;
|
|
19
|
+
export default MoneyInput;
|