rune-lab 0.4.4 → 0.4.5

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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <h1 align="center">
2
- <img src="https://raw.githubusercontent.com/Yrrrrrf/rune-lab/main/static/img/rune.png" alt="Rune Lab Icon" width="128" height="128" descripti``on="Icon representing the Svelte Runes system">
2
+ <img src="https://raw.githubusercontent.com/Yrrrrrf/rune-lab/main/static/img/rune.png" alt="Rune Lab Icon" width="128" height="128" description="Icon representing the Svelte Runes system">
3
3
  <div align="center">Rune Lab</div>
4
4
  </h1>
5
5
 
@@ -14,72 +14,125 @@
14
14
 
15
15
  ## Overview
16
16
 
17
- **Rune Lab** is your modern toolkit for crafting stunning, reactive web
18
- applications with **Svelte 5**. Harnessing the power of Svelte's new **Runes**
19
- system, Rune Lab offers a suite of elegant UI components designed for seamless
20
- data handling and beautiful theming.
17
+ **Rune Lab** is a modern, extensible **plugin-based UI shell** for **Svelte 5**
18
+ applications. Harnessing the power of Svelte's new **Runes** system, it provides
19
+ a complete application skeleton with built-in layout management, dynamic
20
+ theming, i18n, keyboard shortcuts, a command palette, toast notifications, and a
21
+ highly precise money/currency subsystem.
22
+
23
+ Everything is wired through a centralized **Provider + Registry + Context**
24
+ architecture, ensuring clean dependency injection and state isolation across
25
+ your app.
21
26
 
22
27
  ## Key Features
23
28
 
24
- - **✨ Svelte 5 Runes Core:** Experience fine-grained reactivity and cleaner
25
- component logic.
26
- - **🎨 Dynamic Theming:** Powered by DaisyUI & Tailwind CSS for extensive
27
- customization and out-of-the-box themes.
28
- - **🔒 TypeScript First:** Robust type-safety for a confident and productive
29
- development workflow.
30
- - **📊 Data-Aware Components:** Tools and components built to handle and
31
- visualize complex data.
29
+ - **✨ Svelte 5 Runes-First:** Built from the ground up using `$state`,
30
+ `$derived`, and `$effect`. No legacy Svelte 4 stores.
31
+ - **🧩 Extensible Plugin Architecture:** Features are isolated into plugins
32
+ (`LayoutPlugin`, `PalettesPlugin`, `MoneyPlugin`). Only load what you need.
33
+ - **🎨 Dynamic Theming & i18n:** 32 DaisyUI themes and 13 pre-configured locales
34
+ powered by Paraglide JS, with zero-flash SSR persistence.
35
+ - **🔗 Declarative Callbacks:** Bridge store changes (theme, language, currency)
36
+ to your own system hooks via `RuneProvider` props.
37
+ - **💾 Abstract Persistence Layer:** Swap between cookies, localStorage,
38
+ sessionStorage, or in-memory state seamlessly via generic drivers.
39
+ - **💸 Robust Money Subsystem:** Backed by Dinero.js for floating-point-safe
40
+ precision arithmetic, complete with exchange rate strategies and masked
41
+ currency inputs.
42
+ - **⌨️ Developer & Power-User Friendly:** Out-of-the-box Command Palette
43
+ (`Ctrl+K`) and interactive Shortcuts Palette (`Ctrl+/`).
32
44
 
33
- <!-- - **🛰️ Interactive Schema Explorer:** A standout feature! Visually explore and
34
- interact with database schemas exposed by `prism-py` APIs directly within your
35
- Svelte application. Test CRUD operations, execute functions, and understand
36
- your data structure like never before.
37
- - **🌐 Smart API Integration:** Includes `apiStore` (using `prism-ts`) for easy
38
- and type-safe connection to backend APIs. -->
45
+ ## Installation
39
46
 
40
- - **📦 Lightweight Core:** Designed to be lean, with optional integrations.
47
+ ### Using NPM / Bun
41
48
 
42
- <!-- - **🦕 Universal Access:** Available on JSR (for Deno) and NPM (for
43
- Node.js/Bun/Yarn). -->
49
+ ```bash
50
+ npm install rune-lab
51
+ # or
52
+ bun install rune-lab
53
+ ```
44
54
 
45
- ## Installation
55
+ ## Project Configuration (Required)
46
56
 
47
- <!-- ### Using Deno / [JSR](https://jsr.io/@yrrrrrf/rune-lab)
57
+ After installing, two configuration steps are required to ensure the framework's
58
+ components are compiled and styled correctly in your consuming project.
48
59
 
49
- ```bash
50
- # Add to your Deno project
51
- deno add @yrrrrrf/rune-lab
52
- ``` -->
60
+ ### Step 1 — Vite: Process `rune-lab` through the Svelte compiler
53
61
 
54
- ### Using [NPM](https://www.npmjs.com/package/rune-lab) / Bun
62
+ By default, Vite externalizes `node_modules` during SSR, bypassing the Svelte
63
+ compiler. Add the following to your `vite.config.ts` to process `rune-lab`
64
+ properly:
55
65
 
56
- ```bash
57
- npm install rune-lab
58
- bun install rune-lab
66
+ ```ts
67
+ // vite.config.ts
68
+ import { defineConfig } from "vite";
69
+ import { sveltekit } from "@sveltejs/kit/vite";
70
+
71
+ export default defineConfig({
72
+ plugins: [sveltekit()],
73
+ ssr: {
74
+ noExternal: ["rune-lab"], // 👈 CRITICAL for Svelte 5 components in node_modules
75
+ },
76
+ });
77
+ ```
78
+
79
+ ### Step 2 — Tailwind CSS v4: Scan `rune-lab` for utility classes
80
+
81
+ Tailwind only generates CSS for classes it finds by scanning your source files.
82
+ Add a `@source` directive to your project's main CSS file so Tailwind compiles
83
+ the DaisyUI classes used internally by `rune-lab`:
84
+
85
+ ```css
86
+ /* app.css / layout.css / global.css */
87
+ @import "tailwindcss";
88
+ @source "../node_modules/rune-lab/dist"; /* 👈 add this */
59
89
  ```
60
90
 
61
91
  ## Quick Start
62
92
 
63
- Get your application shell running in less than 20 lines. Inside your
93
+ Get your application shell running in less than 40 lines. Inside your
64
94
  `+layout.svelte`:
65
95
 
66
96
  ```svelte
67
97
  <script lang="ts">
68
- import { RuneProvider, WorkspaceLayout, ConnectedNavigationPanel } from "rune-lab";
69
- import { cookieDriver } from "rune-lab";
98
+ import {
99
+ RuneProvider,
100
+ WorkspaceLayout,
101
+ ConnectedNavigationPanel,
102
+ LayoutPlugin,
103
+ PalettesPlugin,
104
+ cookieDriver
105
+ } from "rune-lab";
106
+ import type { NavigationSection } from "rune-lab";
107
+ import { setLocale } from "$lib/i18n/paraglide/runtime.js";
70
108
 
71
109
  let { children } = $props();
72
110
 
73
- // Example navigation
74
- const sections = [{ id: "main", title: "Main", items: [{ id: "home", label: "Home" }] }];
111
+ const sections: NavigationSection[] = [
112
+ {
113
+ id: "main",
114
+ title: "Main Menu",
115
+ items: [
116
+ { id: "home", label: "Dashboard", icon: "🏠" },
117
+ { id: "settings", label: "Settings", icon: "⚙️" }
118
+ ]
119
+ }
120
+ ];
75
121
  </script>
76
122
 
123
+ <!-- Initialize the system with your required plugins and callbacks -->
77
124
  <RuneProvider
78
- app={{ name: "My App", version: "1.0.0" }}
79
- persistence={cookieDriver}
125
+ config={{
126
+ app: { name: "My Startup", version: "1.0.0" },
127
+ persistence: cookieDriver,
128
+ }}
129
+ plugins={[LayoutPlugin, PalettesPlugin]}
130
+ onLanguageChange={(l) => setLocale(l.code)}
131
+ onThemeChange={(t) => console.log(`Theme changed to ${t.name}`)}
80
132
  >
81
133
  <WorkspaceLayout>
82
134
  {#snippet navigationPanel()}
135
+ <!-- Auto-wires to LayoutStore state -->
83
136
  <ConnectedNavigationPanel {sections} />
84
137
  {/snippet}
85
138
 
@@ -92,115 +145,130 @@ Get your application shell running in less than 20 lines. Inside your
92
145
  </RuneProvider>
93
146
  ```
94
147
 
95
- You now have a fully functional reactive layout, keyboard command palette, toast
96
- notification system, and theme switcher ready to go.
97
-
98
- ## Project Configuration
148
+ You now have a fully reactive layout, a keyboard command palette, a toast
149
+ notification system, and theme/language switchers.
99
150
 
100
- After installing, two configuration steps are required to ensure components are
101
- compiled and styled correctly in your consuming project.
151
+ ## Money & Currency Plugin
102
152
 
103
- ### Step 1 Vite: process `rune-lab` through the Svelte compiler
153
+ Rune Lab provides a robust money layer that handles precision arithmetic,
154
+ formatting, and live exchange-rate triangulation. To use it, simply register the
155
+ `MoneyPlugin`:
104
156
 
105
- Vite's SSR module runner externalizes `node_modules` by default, which means
106
- `.svelte` files from this package would be loaded as raw ES modules, bypassing
107
- the Svelte compiler entirely. Add the following to your `vite.config.ts` to
108
- force Vite to process `rune-lab` through its plugin pipeline:
157
+ ```svelte
158
+ <script lang="ts">
159
+ import { MoneyPlugin } from "rune-lab";
160
+ </script>
109
161
 
110
- ```ts
111
- // vite.config.ts
112
- export default defineConfig({
113
- plugins: [sveltekit()],
114
- ssr: {
115
- noExternal: ["rune-lab"],
116
- },
117
- });
162
+ <RuneProvider
163
+ config={{
164
+ "rune-lab.money": {
165
+ defaultCurrency: "USD",
166
+ exchangeRates: {
167
+ base: "USD",
168
+ rates: { MXN: 17.23, EUR: 0.91 }
169
+ },
170
+ }
171
+ }}
172
+ plugins={[LayoutPlugin, PalettesPlugin, MoneyPlugin]}
173
+ >
174
+ <!-- App Content -->
175
+ </RuneProvider>
118
176
  ```
119
177
 
120
- This ensures the Svelte plugin transforms the components correctly during SSR,
121
- just as it would for your own source files.
178
+ ### Displaying & Inputting Money
179
+
180
+ ```svelte
181
+ <script lang="ts">
182
+ import {
183
+ MoneyDisplay,
184
+ MoneyInput,
185
+ CurrencySelector,
186
+ useMoney
187
+ } from "rune-lab";
188
+
189
+ let price = $state(15000); // Minor units (e.g., cents) -> $150.00
190
+ const { convert, format } = useMoney();
191
+ </script>
122
192
 
123
- ### Step 2 Tailwind CSS: scan `rune-lab` for utility classes
193
+ <!-- Select from available currencies -->
194
+ <CurrencySelector />
124
195
 
125
- Tailwind only generates CSS for the classes it can find by scanning your source
126
- files. Because `rune-lab` lives in `node_modules`, its DaisyUI classes are not
127
- scanned by default and the components will appear unstyled.
196
+ <!-- Formats safely and localizes based on the active LanguageStore -->
197
+ <MoneyDisplay amount={price} currency="USD" />
128
198
 
129
- Add a `@source` directive to your project's main CSS file to tell Tailwind to
130
- also scan the `rune-lab` dist output:
199
+ <!-- Compact notation ($1.5M) -->
200
+ <MoneyDisplay amount={150000000} currency="USD" compact />
131
201
 
132
- ```css
133
- /* app.css / layout.css / global.css — wherever you import Tailwind */
134
- @import "tailwindcss";
135
- @source "../node_modules/rune-lab/dist"; /* 👈 add this */
202
+ <!-- Integer-backed masked input (prevents floating point errors) -->
203
+ <MoneyInput bind:amount={price} currency="USD" />
136
204
  ```
137
205
 
138
- > **Note:** Adjust the relative path to `node_modules` if your CSS file lives at
139
- > a different depth in your project tree. With both steps in place, all DaisyUI
140
- > component classes used by `rune-lab` will be included in your build and theme
141
- > switching will work across library components and your own code alike.
142
-
143
- ## Money & Currency
206
+ ## Persistence Drivers
144
207
 
145
- Rune Lab provides a robust, Dinero.js-backed money layer that handles precision
146
- arithmetic and locale-aware formatting.
208
+ Rune Lab provides generic drivers to remember user preferences across reloads.
209
+ Pass one to `config.persistence` on `<RuneProvider>`:
147
210
 
148
- ### MoneyDisplay
211
+ - `cookieDriver`: Best for SSR applications (like SvelteKit) to prevent "theme
212
+ flash" on initial load.
213
+ - `localStorageDriver`: Best for client-only applications (SPAs).
214
+ - `sessionStorageDriver`: For preferences that should clear when the browser tab
215
+ closes.
149
216
 
150
217
  ```svelte
151
- <script>
152
- import { MoneyDisplay } from "rune-lab";
218
+ <script lang="ts">
219
+ import { cookieDriver } from "rune-lab";
153
220
  </script>
154
221
 
155
- <!-- Minor units (default): $150.00 -->
156
- <MoneyDisplay amount={15000} currency="USD" />
222
+ <RuneProvider config={{ persistence: cookieDriver }} plugins={[...]}>
223
+ ```
157
224
 
158
- <!-- Major units: $150.00 -->
159
- <MoneyDisplay amount={150} unit="major" currency="USD" />
225
+ ## Advanced Patterns
160
226
 
161
- <!-- Compact notation: $1.2M -->
162
- <MoneyDisplay amount={1200000} unit="major" compact />
227
+ ### Store Observers (`onChange`)
163
228
 
164
- <!-- Null handling with fallback: "—" -->
165
- <MoneyDisplay amount={null} fallback="N/A" />
166
- ```
229
+ Beyond the `RuneProvider` props, you can imperatively subscribe to any
230
+ `ConfigStore` (theme, language, currency) from your own services or components.
231
+ Callbacks include `try/catch` protection so they never crash the store.
167
232
 
168
- ### MoneyInput
233
+ ```ts
234
+ import { getThemeStore } from "rune-lab";
169
235
 
170
- A masked input that prevents floating-point precision errors by working
171
- exclusively with integers.
236
+ const themeStore = getThemeStore();
172
237
 
173
- ```svelte
174
- <script>
175
- import { MoneyInput } from "rune-lab";
176
- let price = $state(150.00);
177
- </script>
178
-
179
- <MoneyInput bind:amount={price} unit="major" currency="USD" />
238
+ // Returns an unsubscribe function
239
+ const unsub = themeStore.onChange((newId, oldId) => {
240
+ console.log(`Theme swapped from ${oldId} to ${newId}`);
241
+ });
180
242
  ```
181
243
 
182
- ## Persistence Drivers
244
+ ### Keyboard Shortcuts (Auto-Cleanup)
183
245
 
184
- Rune Lab provides built-in drivers to remember user preferences (like theme,
185
- layout state, or language) across reloads. Pass one of these to the
186
- `persistence` prop on `<RuneProvider>`:
187
-
188
- - `cookieDriver`: Best for SSR applications (like SvelteKit) because the server
189
- can read the cookie and prevent a "theme flash" on initial load.
190
- - `localStorageDriver`: Best for client-only applications (SPA) looking for
191
- long-term persistence.
192
- - `sessionStorageDriver`: For preferences that should clear when the browser tab
193
- closes.
246
+ Any component deep in your tree can register its own keyboard shortcuts using
247
+ the `useShortcuts` composable. It handles Svelte's `$effect` lifecycle
248
+ internally, ensuring shortcuts unregister when the component unmounts:
194
249
 
195
250
  ```svelte
196
251
  <script lang="ts">
197
- import { cookieDriver } from "rune-lab";
198
- // Then pass directly: <RuneProvider persistence={cookieDriver}>
252
+ import { useShortcuts, getToastStore } from "rune-lab";
253
+
254
+ const toasts = getToastStore();
255
+
256
+ useShortcuts([
257
+ {
258
+ id: "feature.save",
259
+ keys: "ctrl+s, cmd+s", // Comma-separated alternative keys
260
+ label: "Save Document",
261
+ category: "Editor",
262
+ scope: "global",
263
+ handler: (e) => {
264
+ e.preventDefault();
265
+ toasts.success("Document Saved!");
266
+ }
267
+ }
268
+ ]);
199
269
  </script>
200
270
  ```
201
271
 
202
- ## Advanced Patterns
203
-
204
272
  ### SvelteKit Route Syncing
205
273
 
206
274
  To keep your layout's active navigation state synchronized with the SvelteKit
@@ -214,39 +282,31 @@ router, use an `$effect` inside your `+layout.svelte` right after the provider:
214
282
  const layoutStore = getLayoutStore();
215
283
 
216
284
  $effect(() => {
217
- // Example: Use the first path segment as the active nav item
285
+ // Automatically open the correct nav tree branch
218
286
  const segment = page.url.pathname.split("/")[1] || "home";
219
287
  layoutStore.navigate(segment);
220
288
  });
221
289
  </script>
222
290
  ```
223
291
 
224
- _(Note: Use `$app/state`, not the older Svelte 4 `$app/stores`)_
292
+ ### Calling Toasts from Outside Svelte Components
225
293
 
226
- ### Keyboard Shortcuts
294
+ If you need to trigger a toast from a pure `.ts` file (like a fetching utility
295
+ or global error handler), you can use the Toast Bridge:
227
296
 
228
- Any component deep in your tree can register its own keyboard shortcuts
229
- dynamically. To ensure they clean up when the component unmounts, **always
230
- register them inside an `$effect` returning a cleanup function**:
231
-
232
- ```svelte
233
- <script lang="ts">
234
- import { getShortcutStore, getToastStore } from "rune-lab";
235
-
236
- const shortcuts = getShortcutStore();
237
- const toasts = getToastStore();
238
-
239
- $effect(() => {
240
- shortcuts.register({
241
- id: "feature.save",
242
- keys: "ctrl s",
243
- label: "Save Document",
244
- handler: () => toasts.success("Document Saved!")
245
- });
246
-
247
- return () => shortcuts.unregister("feature.save"); // Important!
248
- });
249
- </script>
297
+ ```ts
298
+ import { createToastBridge } from "rune-lab";
299
+
300
+ const { notify } = createToastBridge();
301
+
302
+ export async function fetchUser() {
303
+ try {
304
+ // ...
305
+ } catch (err) {
306
+ // Safely queues the toast if the UI hasn't mounted yet
307
+ notify("Failed to fetch user data", "error");
308
+ }
309
+ }
250
310
  ```
251
311
 
252
312
  ## License
@@ -26,10 +26,20 @@
26
26
  [pluginId: string]: unknown;
27
27
  }
28
28
 
29
- let { children, config = {}, plugins = [] } = $props<{
29
+ let {
30
+ children,
31
+ config = {},
32
+ plugins = [],
33
+ onThemeChange,
34
+ onLanguageChange,
35
+ onCurrencyChange,
36
+ } = $props<{
30
37
  children: Snippet;
31
38
  config?: RuneLabConfig;
32
39
  plugins?: RunePlugin[];
40
+ onThemeChange?: (newTheme: any, oldTheme: any) => void;
41
+ onLanguageChange?: (newLang: any, oldLang: any) => void;
42
+ onCurrencyChange?: (newCurrency: any, oldCurrency: any) => void;
33
43
  }>();
34
44
 
35
45
  const initialPlugins = untrack(() => plugins);
@@ -78,9 +88,28 @@
78
88
 
79
89
  // ── Initialization for layout ──────────────────────────
80
90
  const layoutStore = stores.get("layout") as any;
91
+ const themeStore = stores.get("theme") as any;
92
+ const languageStore = stores.get("language") as any;
93
+ const currencyStore = stores.get("currency") as any;
81
94
 
82
95
  onMount(() => {
83
96
  if (layoutStore) layoutStore.init();
97
+
98
+ const unsubs: (() => void)[] = [];
99
+
100
+ if (onThemeChange && themeStore) {
101
+ unsubs.push(themeStore.onChange(onThemeChange));
102
+ }
103
+ if (onLanguageChange && languageStore) {
104
+ unsubs.push(languageStore.onChange(onLanguageChange));
105
+ }
106
+ if (onCurrencyChange && currencyStore) {
107
+ unsubs.push(currencyStore.onChange(onCurrencyChange));
108
+ }
109
+
110
+ return () => {
111
+ unsubs.forEach((unsub) => unsub());
112
+ };
84
113
  });
85
114
 
86
115
  // Meta tags derived from app store state
@@ -6,6 +6,11 @@ export type ConfigStore<T = unknown> = {
6
6
  get: (id: unknown) => T | undefined;
7
7
  getProp: <K extends keyof T>(prop: K, id?: unknown) => T[K] | undefined;
8
8
  addItems: (newItems: T[]) => void;
9
+ /**
10
+ * Register a callback to fire when the current item changes.
11
+ * Returns an unsubscribe function.
12
+ */
13
+ onChange: (cb: (newId: unknown, oldId: unknown) => void) => () => void;
9
14
  /**
10
15
  * Inject (or replace) the persistence driver at runtime.
11
16
  * Call this inside your plugin factory so the real driver
@@ -9,6 +9,7 @@ class ConfigStoreImpl {
9
9
  available = $state([]);
10
10
  #options;
11
11
  #driver;
12
+ #callbacks = new Set();
12
13
  constructor(options) {
13
14
  this.#options = options;
14
15
  const { items, idKey, storageKey, driver = createInMemoryDriver() } =
@@ -55,8 +56,29 @@ class ConfigStoreImpl {
55
56
  console.warn(`${this.#options.displayName} "${id}" not found`);
56
57
  return;
57
58
  }
59
+ const old = this.current;
58
60
  this.current = id;
59
61
  this.#driver.set(this.#options.storageKey, String(id));
62
+ // Call callbacks after state update and persistence
63
+ this.#callbacks.forEach((cb) => {
64
+ try {
65
+ cb(id, old);
66
+ } catch (err) {
67
+ if (DEV) {
68
+ console.error(
69
+ `[rune-lab] Error in ${this.#options.displayName} onChange callback:`,
70
+ err,
71
+ );
72
+ }
73
+ }
74
+ });
75
+ }
76
+ /**
77
+ * Register a change callback
78
+ */
79
+ onChange(cb) {
80
+ this.#callbacks.add(cb);
81
+ return () => this.#callbacks.delete(cb);
60
82
  }
61
83
  /**
62
84
  * Get item by id
package/dist/mod.js CHANGED
@@ -4,4 +4,4 @@ export * from "./kernel/src/mod.js";
4
4
  export * from "./runes/layout/src/mod.js";
5
5
  export * from "./runes/palettes/src/mod.js";
6
6
  export * from "./runes/plugins/money/src/mod.js";
7
- export const version = () => "0.4.4";
7
+ export const version = () => "0.4.5";
@@ -1,7 +1,20 @@
1
- <script lang="ts">
2
- import { portal } from "../../../kernel/src/mod.js";
1
+ <script lang="ts" module>
3
2
  import { type Snippet } from "svelte";
4
3
 
4
+ export interface AppSettingSelectorProps<T> {
5
+ options: T[];
6
+ value: T;
7
+ item: Snippet<[T]>;
8
+ triggerLabel: Snippet<[T]>;
9
+ tooltip?: string;
10
+ direction?: "top" | "bottom" | "left" | "right" | "end" | "auto";
11
+ responsive?: boolean;
12
+ }
13
+ </script>
14
+
15
+ <script lang="ts" generics="T">
16
+ import { portal } from "../../../kernel/src/mod.js";
17
+
5
18
  let {
6
19
  options,
7
20
  value,
@@ -11,10 +24,10 @@
11
24
  direction = "bottom",
12
25
  responsive = true,
13
26
  } = $props<{
14
- options: any[];
15
- value: any;
16
- item: Snippet<[any]>;
17
- triggerLabel: Snippet<[any]>;
27
+ options: T[];
28
+ value: T;
29
+ item: Snippet<[T]>;
30
+ triggerLabel: Snippet<[T]>;
18
31
  tooltip?: string;
19
32
  direction?: "top" | "bottom" | "left" | "right" | "end" | "auto";
20
33
  responsive?: boolean;
@@ -131,12 +144,20 @@
131
144
  class="border-b border-base-100 last:border-0"
132
145
  role="menuitem"
133
146
  >
134
- <button
135
- class="w-full text-left py-3"
147
+ <div
148
+ role="button"
149
+ tabindex="0"
150
+ class="w-full text-left py-3 cursor-pointer hover:bg-base-200 px-4 transition-colors"
136
151
  onclick={() => modal?.close()}
152
+ onkeydown={(e) => {
153
+ if (e.key === "Enter" || e.key === " ") {
154
+ e.preventDefault();
155
+ modal?.close();
156
+ }
157
+ }}
137
158
  >
138
159
  {@render item(option)}
139
- </button>
160
+ </div>
140
161
  </li>
141
162
  {/each}
142
163
  </ul>
@@ -1,20 +1,34 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
- declare const __propDef: {
3
- props: Record<string, never>;
4
- events: {
2
+ import { type Snippet } from "svelte";
3
+ export interface AppSettingSelectorProps<T> {
4
+ options: T[];
5
+ value: T;
6
+ item: Snippet<[T]>;
7
+ triggerLabel: Snippet<[T]>;
8
+ tooltip?: string;
9
+ direction?: "top" | "bottom" | "left" | "right" | "end" | "auto";
10
+ responsive?: boolean;
11
+ }
12
+ declare class __sveltets_Render<T> {
13
+ props(): Record<string, never>;
14
+ events(): {} & {
5
15
  [evt: string]: CustomEvent<any>;
6
16
  };
7
- slots: {};
8
- exports?: {};
9
- bindings?: string;
10
- };
11
- export type AppSettingSelectorProps = typeof __propDef.props;
12
- export type AppSettingSelectorEvents = typeof __propDef.events;
13
- export type AppSettingSelectorSlots = typeof __propDef.slots;
14
- export default class AppSettingSelector extends SvelteComponentTyped<
15
- AppSettingSelectorProps,
16
- AppSettingSelectorEvents,
17
- AppSettingSelectorSlots
17
+ slots(): {};
18
+ }
19
+ export type AppSettingSelectorProps_<T> = ReturnType<
20
+ __sveltets_Render<T>["props"]
21
+ >;
22
+ export type AppSettingSelectorEvents<T> = ReturnType<
23
+ __sveltets_Render<T>["events"]
24
+ >;
25
+ export type AppSettingSelectorSlots<T> = ReturnType<
26
+ __sveltets_Render<T>["slots"]
27
+ >;
28
+ export default class AppSettingSelector<T> extends SvelteComponentTyped<
29
+ AppSettingSelectorProps_<T>,
30
+ AppSettingSelectorEvents<T>,
31
+ AppSettingSelectorSlots<T>
18
32
  > {
19
33
  }
20
34
  export {};
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import ResourceSelector from "./ResourceSelector.svelte";
3
3
  import { getLanguageStore } from "../../../kernel/src/mod.js";
4
- import { setLocale } from "../../../i18n/paraglide/runtime.js";
5
4
 
6
5
  const languageStore = getLanguageStore();
7
6
 
@@ -33,7 +32,6 @@
33
32
  class="flex items-center gap-3 w-full"
34
33
  onclick={() => {
35
34
  languageStore.set(l.code);
36
- setLocale(l.code as any);
37
35
  current = l.code;
38
36
  onchange?.(l.code);
39
37
  }}
@@ -61,18 +61,20 @@
61
61
  );
62
62
  </script>
63
63
 
64
+ {#snippet _triggerLabel(v: any)}
65
+ {@render triggerLabel(v)}
66
+ {/snippet}
67
+
68
+ {#snippet _item(option: any)}
69
+ {@render item(option)}
70
+ {/snippet}
71
+
64
72
  <AppSettingSelector
65
73
  value={active}
66
74
  options={available}
67
75
  tooltip={resolveLabel(active)}
68
76
  {direction}
69
77
  {responsive}
70
- >
71
- {#snippet triggerLabel(value)}
72
- {@render triggerLabel(value)}
73
- {/snippet}
74
-
75
- {#snippet item(option)}
76
- {@render item(option)}
77
- {/snippet}
78
- </AppSettingSelector>
78
+ triggerLabel={_triggerLabel}
79
+ item={_item}
80
+ />
@@ -7,6 +7,9 @@ export * from "./strategies.js";
7
7
  export * from "./money-primitive.js";
8
8
  export * from "./useMoney.js";
9
9
  export * from "./useMoneyFilter.svelte.js";
10
+ export { default as CurrencySelector } from "./CurrencySelector.svelte";
11
+ export { default as MoneyDisplay } from "./MoneyDisplay.svelte";
12
+ export { default as MoneyInput } from "./MoneyInput.svelte";
10
13
  /**
11
14
  * Money Plugin — provides currency management and exchange rate conversion.
12
15
  */
@@ -9,6 +9,9 @@ export * from "./strategies.js";
9
9
  export * from "./money-primitive.js";
10
10
  export * from "./useMoney.js";
11
11
  export * from "./useMoneyFilter.svelte.js";
12
+ export { default as CurrencySelector } from "./CurrencySelector.svelte";
13
+ export { default as MoneyDisplay } from "./MoneyDisplay.svelte";
14
+ export { default as MoneyInput } from "./MoneyInput.svelte";
12
15
  /**
13
16
  * Money Plugin — provides currency management and exchange rate conversion.
14
17
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rune-lab",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Modern toolkit for Svelte 5 Runes applications.",
5
5
  "type": "module",
6
6
  "readme": "./README.md",