rune-lab 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { setContext, type Snippet } from "svelte";
2
+ import { setContext, untrack, type Snippet } from "svelte";
3
3
  import {
4
4
  createAppStore,
5
5
  createLayoutStore,
@@ -25,15 +25,17 @@
25
25
  const appStore = createAppStore();
26
26
  const apiStore = createApiStore();
27
27
  const toastStore = createToastStore();
28
- // We use a closure approach (`() => persistence`) as supported by the updated config definitions,
29
- // to strictly respect Svelte 5 state capturing validations without disabling them globally.
30
- const themeStore = createThemeStore(() => persistence);
31
- const languageStore = createLanguageStore(() => persistence);
32
- const currencyStore = createCurrencyStore(() => persistence);
28
+
29
+ // Capture the initial persistence prop to avoid Svelte 5 reactive capture warnings
30
+ const initialPersistence = untrack(() => persistence);
31
+
32
+ const themeStore = createThemeStore(initialPersistence);
33
+ const languageStore = createLanguageStore(initialPersistence);
34
+ const currencyStore = createCurrencyStore(initialPersistence);
33
35
  const shortcutStore = createShortcutStore();
34
36
 
35
37
  // 2. Initialize Complex Stores (Dependency Injection)
36
- const layoutStore = createLayoutStore(() => persistence);
38
+ const layoutStore = createLayoutStore(initialPersistence);
37
39
  const commandStore = createCommandStore({
38
40
  appStore,
39
41
  apiStore,
@@ -53,6 +55,7 @@
53
55
  setContext(RUNE_LAB_CONTEXT.shortcut, shortcutStore);
54
56
  setContext(RUNE_LAB_CONTEXT.layout, layoutStore);
55
57
  setContext(RUNE_LAB_CONTEXT.commands, commandStore);
58
+ setContext(RUNE_LAB_CONTEXT.persistence, initialPersistence);
56
59
 
57
60
  // Meta tags derived from app store state
58
61
  const metaTags = $derived([
@@ -0,0 +1,2 @@
1
+ import type { PersistenceDriver } from "../persistence/types";
2
+ export declare function usePersistence(): PersistenceDriver | undefined;
@@ -0,0 +1,5 @@
1
+ import { getContext } from "svelte";
2
+ import { RUNE_LAB_CONTEXT } from "../context";
3
+ export function usePersistence() {
4
+ return getContext(RUNE_LAB_CONTEXT.persistence);
5
+ }
package/dist/context.d.ts CHANGED
@@ -8,4 +8,5 @@ export declare const RUNE_LAB_CONTEXT: {
8
8
  readonly shortcut: symbol;
9
9
  readonly layout: symbol;
10
10
  readonly commands: symbol;
11
+ readonly persistence: symbol;
11
12
  };
package/dist/context.js CHANGED
@@ -9,4 +9,5 @@ export const RUNE_LAB_CONTEXT = {
9
9
  shortcut: Symbol("rl:shortcut"),
10
10
  layout: Symbol("rl:layout"),
11
11
  commands: Symbol("rl:commands"),
12
+ persistence: Symbol("rl:persistence"),
12
13
  };
@@ -48,10 +48,7 @@
48
48
  {@const styles = typeDetails[toast.type] || typeDetails.info}
49
49
 
50
50
  <div
51
- animate:flip={{ duration: 300 }}
52
- in:fly={{ y: 20, duration: 300 }}
53
- out:fade={{ duration: 200 }}
54
- class="pointer-events-auto relative flex w-full max-w-sm items-start gap-4 overflow-hidden rounded-xl border p-4 shadow-lg backdrop-blur-xl transition-all sm:min-w-[320px] {styles.colors}"
51
+ class="pointer-events-auto relative flex w-full max-w-sm items-start gap-4 overflow-hidden rounded-xl border p-4 shadow-lg backdrop-blur-xl transition-all duration-300 sm:min-w-[320px] {styles.colors} animate-in fade-in slide-in-from-bottom-4 zoom-in-95 ease-out data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:slide-out-to-right-full"
55
52
  role="alert"
56
53
  >
57
54
  <!-- Icon -->
@@ -0,0 +1,30 @@
1
+ <!-- src/lib/features/layout/smart/ConnectedNavigationPanel.svelte -->
2
+ <script lang="ts">
3
+ import {
4
+ getLayoutStore,
5
+ type NavigationSection,
6
+ } from "../../../state/layout.svelte";
7
+ import NavigationPanel from "../../../layout/NavigationPanel.svelte";
8
+ import type { Snippet } from "svelte";
9
+
10
+ let { sections, header, footer } = $props<{
11
+ sections: NavigationSection[];
12
+ header?: Snippet;
13
+ footer?: Snippet;
14
+ }>();
15
+
16
+ const layoutStore = getLayoutStore();
17
+ </script>
18
+
19
+ <NavigationPanel
20
+ {sections}
21
+ {header}
22
+ {footer}
23
+ activeId={layoutStore.activeNavItemId}
24
+ collapsedIds={layoutStore.collapsedSections}
25
+ onSelect={(item) => layoutStore.navigate(item.id!)}
26
+ onToggle={(id, isOpen) =>
27
+ isOpen
28
+ ? layoutStore.expandSection(id)
29
+ : layoutStore.collapseSection(id)}
30
+ />
@@ -0,0 +1,10 @@
1
+ import { type NavigationSection } from "../../../state/layout.svelte";
2
+ import type { Snippet } from "svelte";
3
+ type $$ComponentProps = {
4
+ sections: NavigationSection[];
5
+ header?: Snippet;
6
+ footer?: Snippet;
7
+ };
8
+ declare const ConnectedNavigationPanel: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type ConnectedNavigationPanel = ReturnType<typeof ConnectedNavigationPanel>;
10
+ export default ConnectedNavigationPanel;
@@ -0,0 +1,23 @@
1
+ <!-- src/lib/features/layout/smart/ConnectedWorkspaceStrip.svelte -->
2
+ <script lang="ts">
3
+ import {
4
+ getLayoutStore,
5
+ type WorkspaceItem,
6
+ } from "../../../state/layout.svelte";
7
+ import WorkspaceStrip from "../../../layout/WorkspaceStrip.svelte";
8
+ import type { Snippet } from "svelte";
9
+
10
+ let { items, globalActions } = $props<{
11
+ items: WorkspaceItem[];
12
+ globalActions?: Snippet;
13
+ }>();
14
+
15
+ const layoutStore = getLayoutStore();
16
+ </script>
17
+
18
+ <WorkspaceStrip
19
+ {items}
20
+ {globalActions}
21
+ activeId={layoutStore.activeWorkspaceId}
22
+ onSelect={(id) => layoutStore.activateWorkspace(id)}
23
+ />
@@ -0,0 +1,9 @@
1
+ import { type WorkspaceItem } from "../../../state/layout.svelte";
2
+ import type { Snippet } from "svelte";
3
+ type $$ComponentProps = {
4
+ items: WorkspaceItem[];
5
+ globalActions?: Snippet;
6
+ };
7
+ declare const ConnectedWorkspaceStrip: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type ConnectedWorkspaceStrip = ReturnType<typeof ConnectedWorkspaceStrip>;
9
+ export default ConnectedWorkspaceStrip;
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ export { createMessageResolver } from "./devtools/message-resolver";
3
3
  export { RUNE_LAB_CONTEXT } from "./context";
4
4
  export type { PersistenceDriver } from "./persistence/types";
5
5
  export { cookieDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
6
- export { useRuneLab } from "./composables/useRuneLab";
7
- export type { RuneLabContext } from "./composables/useRuneLab";
6
+ export { type RuneLabContext, useRuneLab } from "./composables/useRuneLab.js";
7
+ export { usePersistence } from "./composables/usePersistence.js";
8
8
  export { portal } from "./actions/portal";
9
9
  export { default as RuneProvider } from "./components/RuneProvider.svelte";
10
10
  export { default as Icon } from "./components/Icon.svelte";
@@ -12,7 +12,13 @@ export { default as Toaster } from "./devtools/Toaster.svelte";
12
12
  export { default as ApiMonitor } from "./devtools/API_Monitor.svelte";
13
13
  export { default as CommandPalette } from "./features/command-palette/CommandPalette.svelte";
14
14
  export { default as ShortcutPalette } from "./features/shortcuts/ShortcutPalette.svelte";
15
- export * from "./layout/index";
15
+ export { default as WorkspaceLayout } from "./layout/WorkspaceLayout.svelte";
16
+ export { default as WorkspaceStrip } from "./layout/WorkspaceStrip.svelte";
17
+ export { default as NavigationPanel } from "./layout/NavigationPanel.svelte";
18
+ export { default as ContentArea } from "./layout/ContentArea.svelte";
19
+ export { default as DetailPanel } from "./layout/DetailPanel.svelte";
20
+ export { default as ConnectedNavigationPanel } from "./features/layout/smart/ConnectedNavigationPanel.svelte";
21
+ export { default as ConnectedWorkspaceStrip } from "./features/layout/smart/ConnectedWorkspaceStrip.svelte";
16
22
  export { default as AppSettingSelector } from "./features/config/components/AppSettingSelector.svelte";
17
23
  export { default as ThemeSelector } from "./features/config/components/ThemeSelector.svelte";
18
24
  export { default as LanguageSelector } from "./features/config/components/LanguageSelector.svelte";
package/dist/index.js CHANGED
@@ -6,7 +6,8 @@ export { createMessageResolver } from "./devtools/message-resolver";
6
6
  export { RUNE_LAB_CONTEXT } from "./context";
7
7
  export { cookieDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
8
8
  // ── Composables ───────────────────────────────────────────────────────────────
9
- export { useRuneLab } from "./composables/useRuneLab";
9
+ export { useRuneLab } from "./composables/useRuneLab.js";
10
+ export { usePersistence } from "./composables/usePersistence.js";
10
11
  // ── Actions ───────────────────────────────────────────────────────────────────
11
12
  export { portal } from "./actions/portal";
12
13
  // ── UI Components ─────────────────────────────────────────────────────────────
@@ -18,8 +19,15 @@ export { default as Toaster } from "./devtools/Toaster.svelte";
18
19
  export { default as ApiMonitor } from "./devtools/API_Monitor.svelte";
19
20
  export { default as CommandPalette } from "./features/command-palette/CommandPalette.svelte";
20
21
  export { default as ShortcutPalette } from "./features/shortcuts/ShortcutPalette.svelte";
21
- // Layout
22
- export * from "./layout/index";
22
+ // Layout - Dumb Primitives
23
+ export { default as WorkspaceLayout } from "./layout/WorkspaceLayout.svelte";
24
+ export { default as WorkspaceStrip } from "./layout/WorkspaceStrip.svelte";
25
+ export { default as NavigationPanel } from "./layout/NavigationPanel.svelte";
26
+ export { default as ContentArea } from "./layout/ContentArea.svelte";
27
+ export { default as DetailPanel } from "./layout/DetailPanel.svelte";
28
+ // Layout - Smart Connected Components
29
+ export { default as ConnectedNavigationPanel } from "./features/layout/smart/ConnectedNavigationPanel.svelte";
30
+ export { default as ConnectedWorkspaceStrip } from "./features/layout/smart/ConnectedWorkspaceStrip.svelte";
23
31
  // Setting selectors
24
32
  export { default as AppSettingSelector } from "./features/config/components/AppSettingSelector.svelte";
25
33
  export { default as ThemeSelector } from "./features/config/components/ThemeSelector.svelte";
@@ -1,31 +1,82 @@
1
1
  <!-- src/lib/layout/NavigationPanel.svelte -->
2
2
  <script lang="ts">
3
3
  import type { Snippet } from "svelte";
4
- import {
5
- getLayoutStore,
6
- type NavigationItem,
7
- type NavigationSection,
4
+ import type {
5
+ NavigationItem,
6
+ NavigationSection,
8
7
  } from "../state/layout.svelte";
9
- import { page } from "$app/stores"; // Check if this is needed, or use layoutStore for active item
10
8
 
11
9
  let {
12
10
  header,
13
11
  sections = [],
14
12
  footer,
13
+ activeId,
14
+ collapsedIds = new Set(),
15
+ onSelect,
16
+ onToggle,
15
17
  } = $props<{
16
18
  header?: Snippet;
17
19
  sections: NavigationSection[];
18
20
  footer?: Snippet;
21
+ activeId?: string | null;
22
+ collapsedIds?: Set<string>;
23
+ onSelect?: (item: NavigationItem) => void;
24
+ onToggle?: (id: string, isOpen: boolean) => void;
19
25
  }>();
20
26
 
21
- const layoutStore = getLayoutStore();
22
-
23
27
  function handleItemClick(item: NavigationItem) {
24
- if (item.id) layoutStore.navigate(item.id);
28
+ onSelect?.(item);
29
+ // Only trigger click handler, don't auto-navigate here (let parent handle it)
25
30
  item.onClick?.();
26
31
  }
27
32
  </script>
28
33
 
34
+ {#snippet navItem(item: NavigationItem)}
35
+ <li>
36
+ {#if item.children && item.children.length > 0}
37
+ <!-- Folder Item (Recursive) -->
38
+ <details
39
+ open={/* You might want a way to track open state for sub-folders too */ false}
40
+ >
41
+ <summary>
42
+ {#if item.icon}<span class="text-lg">{item.icon}</span>{/if}
43
+ <span class="flex-1">{item.label}</span>
44
+ </summary>
45
+ <ul>
46
+ {#each item.children as child (child.id)}
47
+ {@render navItem(child)}
48
+ <!-- â™ģī¸ RECURSION HAPPENS HERE -->
49
+ {/each}
50
+ </ul>
51
+ </details>
52
+ {:else}
53
+ <!-- Leaf Item -->
54
+ <button
55
+ class:active={item.isActive !== undefined
56
+ ? item.isActive
57
+ : activeId === item.id}
58
+ onclick={() => handleItemClick(item)}
59
+ >
60
+ {#if item.icon}
61
+ {#if typeof item.icon === "string" && (item.icon.startsWith("http") || item.icon.length > 2)}
62
+ <!-- Image or SVG string -->
63
+ <span class="text-lg">{item.icon}</span>
64
+ {:else}
65
+ <!-- Emoji or simple char -->
66
+ <span class="text-lg">{item.icon}</span>
67
+ {/if}
68
+ {/if}
69
+
70
+ <span class="flex-1">{item.label}</span>
71
+
72
+ {#if item.badge}
73
+ <span class="badge badge-sm badge-ghost">{item.badge}</span>
74
+ {/if}
75
+ </button>
76
+ {/if}
77
+ </li>
78
+ {/snippet}
79
+
29
80
  <div class="flex flex-col h-full bg-base-200 text-base-content">
30
81
  {#if header}
31
82
  <div class="p-4 border-b border-base-content/10">
@@ -38,12 +89,11 @@
38
89
  {#each sections as section (section.id)}
39
90
  <li>
40
91
  <details
41
- open={!layoutStore.collapsedSections.has(section.id)}
92
+ open={!collapsedIds.has(section.id)}
42
93
  ontoggle={(e) => {
43
94
  const open = (e.currentTarget as HTMLDetailsElement)
44
95
  .open;
45
- if (open) layoutStore.expandSection(section.id);
46
- else layoutStore.collapseSection(section.id);
96
+ onToggle?.(section.id, open);
47
97
  }}
48
98
  >
49
99
  <summary
@@ -53,27 +103,7 @@
53
103
  </summary>
54
104
  <ul>
55
105
  {#each section.items as item (item.id)}
56
- <!-- Recursive Render? Or simple list? Current structure suggests mostly 1 level deep inside section -->
57
- <li>
58
- <button
59
- class:active={layoutStore.activeNavItemId ===
60
- item.id}
61
- onclick={() => handleItemClick(item)}
62
- >
63
- {#if item.icon}
64
- <span class="text-lg"
65
- >{item.icon}</span
66
- >
67
- {/if}
68
- <span class="flex-1">{item.label}</span>
69
- {#if item.badge}
70
- <span
71
- class="badge badge-sm badge-ghost"
72
- >{item.badge}</span
73
- >
74
- {/if}
75
- </button>
76
- </li>
106
+ {@render navItem(item)}
77
107
  {/each}
78
108
  </ul>
79
109
  </details>
@@ -1,9 +1,13 @@
1
1
  import type { Snippet } from "svelte";
2
- import { type NavigationSection } from "../state/layout.svelte";
2
+ import type { NavigationItem, NavigationSection } from "../state/layout.svelte";
3
3
  type $$ComponentProps = {
4
4
  header?: Snippet;
5
5
  sections: NavigationSection[];
6
6
  footer?: Snippet;
7
+ activeId?: string | null;
8
+ collapsedIds?: Set<string>;
9
+ onSelect?: (item: NavigationItem) => void;
10
+ onToggle?: (id: string, isOpen: boolean) => void;
7
11
  };
8
12
  declare const NavigationPanel: import("svelte").Component<$$ComponentProps, {}, "">;
9
13
  type NavigationPanel = ReturnType<typeof NavigationPanel>;
@@ -1,16 +1,13 @@
1
1
  <!-- src/lib/layout/WorkspaceLayout.svelte -->
2
2
  <script lang="ts">
3
3
  import type { Snippet } from "svelte";
4
- import {
5
- createLayoutStore,
6
- getLayoutStore,
7
- } from "../state/layout.svelte";
4
+ import { getLayoutStore } from "../state/layout.svelte";
8
5
  import {
9
6
  getShortcutStore,
10
7
  shortcutListener,
11
8
  LAYOUT_SHORTCUTS,
12
9
  } from "../state/shortcuts.svelte";
13
- import { onMount, setContext } from "svelte";
10
+ import { onMount } from "svelte";
14
11
 
15
12
  let {
16
13
  workspaceStrip,
@@ -68,66 +65,104 @@
68
65
  </script>
69
66
 
70
67
  <div
71
- class="rl-layout h-screen w-screen grid overflow-hidden bg-base-100 text-base-content font-sans"
72
- style="
73
- grid-template-columns:
74
- {workspaceStrip ? 'var(--rl-strip-width, 72px)' : '0px'}
75
- {navigationPanel && layoutStore.navOpen
76
- ? 'var(--rl-nav-width, 240px)'
77
- : '0px'}
78
- 1fr
79
- {detailPanel && layoutStore.detailOpen
80
- ? 'var(--rl-detail-width, 320px)'
81
- : '0px'};
82
- grid-template-rows: 100%;
83
- transition: grid-template-columns 300ms ease-in-out;
84
- "
68
+ class="rl-layout flex h-[100dvh] w-screen overflow-hidden bg-base-100 text-base-content font-sans relative"
85
69
  use:shortcutListener={shortcutStore}
86
70
  data-rl-layout
87
71
  >
88
72
  <!-- Zone 1: Workspace Strip -->
89
- <aside
90
- class="rl-strip h-full overflow-y-auto overflow-x-hidden bg-base-300 flex flex-col items-center py-3 gap-2"
91
- class:hidden={!workspaceStrip}
92
- >
93
- {#if workspaceStrip}
73
+ {#if workspaceStrip}
74
+ <aside
75
+ class="rl-strip h-full w-[var(--rl-strip-width,72px)] shrink-0 overflow-y-auto overflow-x-hidden bg-base-300 flex flex-col items-center py-3 gap-2 z-[60]"
76
+ >
94
77
  {@render workspaceStrip()}
95
- {/if}
96
- </aside>
78
+ </aside>
79
+ {/if}
80
+
81
+ <!-- Mobile Nav Backdrop -->
82
+ {#if navigationPanel && layoutStore.navOpen}
83
+ <button
84
+ class="fixed inset-0 bg-black/50 z-[40] md:hidden cursor-default border-none"
85
+ onclick={() => (layoutStore.navOpen = false)}
86
+ aria-label="Close navigation"
87
+ ></button>
88
+ {/if}
97
89
 
98
90
  <!-- Zone 2: Navigation Panel -->
99
- <aside
100
- class="rl-nav h-full bg-base-200 border-base-content/5 overflow-hidden flex flex-col"
101
- class:hidden={!navigationPanel}
102
- class:border-r={layoutStore.navOpen}
103
- data-rl-panel="navigation"
104
- >
105
- {#if navigationPanel}
106
- {@render navigationPanel()}
107
- {/if}
108
- </aside>
91
+ {#if navigationPanel}
92
+ <aside
93
+ class="rl-nav h-full shrink-0 bg-base-200 overflow-hidden flex flex-col transition-all duration-300 ease-in-out z-[50]"
94
+ class:border-r={layoutStore.navOpen}
95
+ class:border-base-content={true}
96
+ style="
97
+ border-opacity: 0.05;
98
+ width: {layoutStore.navOpen
99
+ ? 'var(--rl-nav-width, 240px)'
100
+ : '0px'};
101
+ left: {workspaceStrip ? 'var(--rl-strip-width, 72px)' : '0px'};
102
+ "
103
+ class:max-md:!w-[var(--rl-nav-width,240px)]={true}
104
+ class:max-md:fixed={true}
105
+ class:max-md:-translate-x-[200%]={!layoutStore.navOpen}
106
+ data-rl-panel="navigation"
107
+ >
108
+ <div class="w-[var(--rl-nav-width,240px)] h-full flex flex-col">
109
+ {@render navigationPanel()}
110
+ </div>
111
+ </aside>
112
+ {/if}
109
113
 
110
114
  <!-- Zone 3: Main Content Area -->
111
115
  <main
112
- class="rl-content h-full min-w-0 bg-base-100 flex flex-col overflow-hidden relative"
116
+ class="rl-content h-full flex-1 min-w-0 bg-base-100 flex flex-col overflow-hidden relative"
113
117
  >
114
118
  {@render content()}
115
119
  </main>
116
120
 
121
+ <!-- Mobile Detail Backdrop -->
122
+ {#if detailPanel && layoutStore.detailOpen}
123
+ <button
124
+ class="fixed inset-0 bg-black/50 z-[40] md:hidden cursor-default border-none"
125
+ onclick={() => (layoutStore.detailOpen = false)}
126
+ aria-label="Close detail panel"
127
+ ></button>
128
+ {/if}
129
+
117
130
  <!-- Zone 4: Detail Panel -->
118
- <aside
119
- class="rl-detail h-full bg-base-100 border-base-content/5 overflow-y-auto"
120
- class:hidden={!detailPanel}
121
- class:border-l={layoutStore.detailOpen}
122
- data-rl-panel="detail"
123
- >
124
- {#if detailPanel}
125
- {@render detailPanel()}
126
- {/if}
127
- </aside>
131
+ {#if detailPanel}
132
+ <aside
133
+ class="rl-detail h-full shrink-0 bg-base-100 overflow-y-auto overflow-x-hidden transition-all duration-300 ease-in-out z-[50]"
134
+ class:border-l={layoutStore.detailOpen}
135
+ class:border-base-content={true}
136
+ style="
137
+ border-opacity: 0.05;
138
+ width: {layoutStore.detailOpen
139
+ ? 'var(--rl-detail-width, 320px)'
140
+ : '0px'};
141
+ "
142
+ class:max-md:!w-[var(--rl-detail-width,320px)]={true}
143
+ class:max-md:fixed={true}
144
+ class:max-md:right-0={true}
145
+ class:max-md:translate-x-[200%]={!layoutStore.detailOpen}
146
+ data-rl-panel="detail"
147
+ >
148
+ <div class="w-[var(--rl-detail-width,320px)] h-full flex flex-col">
149
+ {@render detailPanel()}
150
+ </div>
151
+ </aside>
152
+ {/if}
128
153
  </div>
129
154
 
130
155
  <style>
156
+ /* Global lock for entire screen, relying on WorkspaceLayout scroll areas */
157
+ :global(html),
158
+ :global(body) {
159
+ overflow: hidden;
160
+ height: 100%;
161
+ width: 100%;
162
+ margin: 0;
163
+ padding: 0;
164
+ }
165
+
131
166
  :global(.rl-layout) {
132
167
  --rl-strip-width: 72px;
133
168
  --rl-nav-width: 240px;
@@ -1,24 +1,22 @@
1
1
  <!-- src/lib/layout/WorkspaceStrip.svelte -->
2
2
  <script lang="ts">
3
3
  import type { Snippet } from "svelte";
4
- import {
5
- getLayoutStore,
6
- type WorkspaceItem,
7
- } from "../state/layout.svelte";
4
+ import type { WorkspaceItem } from "../state/layout.svelte";
8
5
 
9
- const layoutStore = getLayoutStore();
10
-
11
- let { items = [], globalActions } = $props<{
6
+ let {
7
+ items = [],
8
+ globalActions,
9
+ activeId,
10
+ onSelect,
11
+ } = $props<{
12
12
  items: WorkspaceItem[];
13
13
  globalActions?: Snippet;
14
+ activeId?: string | null;
15
+ onSelect?: (id: string, item: WorkspaceItem) => void;
14
16
  }>();
15
17
 
16
- $effect(() => {
17
- layoutStore.setWorkspaces(items);
18
- });
19
-
20
18
  function handleWorkspaceClick(item: WorkspaceItem) {
21
- layoutStore.activateWorkspace(item.id);
19
+ onSelect?.(item.id, item);
22
20
  item.onClick?.();
23
21
  }
24
22
  </script>
@@ -29,19 +27,18 @@
29
27
  <!-- Discord-style active indicator -->
30
28
  <div
31
29
  class="absolute left-0 w-1 bg-base-content rounded-r-full transition-all duration-200"
32
- class:h-10={layoutStore.activeWorkspaceId === item.id}
33
- class:h-2={layoutStore.activeWorkspaceId !== item.id}
34
- class:opacity-100={layoutStore.activeWorkspaceId === item.id}
35
- class:opacity-0={layoutStore.activeWorkspaceId !== item.id}
30
+ class:h-10={activeId === item.id}
31
+ class:h-2={activeId !== item.id}
32
+ class:opacity-100={activeId === item.id}
33
+ class:opacity-0={activeId !== item.id}
36
34
  ></div>
37
35
 
38
36
  <button
39
37
  onclick={() => handleWorkspaceClick(item)}
40
38
  class="w-12 h-12 flex items-center justify-center rounded-[24px] hover:rounded-[16px] transition-all duration-200 bg-base-100 group-hover:bg-primary group-hover:text-primary-content shadow-sm overflow-hidden"
41
- class:rounded-[16px]={layoutStore.activeWorkspaceId === item.id}
42
- class:bg-primary={layoutStore.activeWorkspaceId === item.id}
43
- class:text-primary-content={layoutStore.activeWorkspaceId ===
44
- item.id}
39
+ class:rounded-[16px]={activeId === item.id}
40
+ class:bg-primary={activeId === item.id}
41
+ class:text-primary-content={activeId === item.id}
45
42
  title={item.label}
46
43
  >
47
44
  {#if item.icon.startsWith("http") || item.icon.startsWith("/")}
@@ -1,8 +1,10 @@
1
1
  import type { Snippet } from "svelte";
2
- import { type WorkspaceItem } from "../state/layout.svelte";
2
+ import type { WorkspaceItem } from "../state/layout.svelte";
3
3
  type $$ComponentProps = {
4
4
  items: WorkspaceItem[];
5
5
  globalActions?: Snippet;
6
+ activeId?: string | null;
7
+ onSelect?: (id: string, item: WorkspaceItem) => void;
6
8
  };
7
9
  declare const WorkspaceStrip: import("svelte").Component<$$ComponentProps, {}, "">;
8
10
  type WorkspaceStrip = ReturnType<typeof WorkspaceStrip>;
@@ -9,83 +9,9 @@ export class CommandStore {
9
9
  }
10
10
  commands = $state([]);
11
11
  refreshDefaultCommands() {
12
- const { appStore, apiStore, toastStore, themeStore, languageStore, currencyStore, } = this.#services;
13
- this.commands = [
14
- {
15
- id: "send-toast",
16
- label: "Send Toast Notification",
17
- category: "System",
18
- icon: "🔔",
19
- children: [
20
- {
21
- id: "toast-success",
22
- label: "Success Toast",
23
- icon: "✨",
24
- action: () => toastStore.success("Operation completed successfully!"),
25
- },
26
- {
27
- id: "toast-info",
28
- label: "Info Toast",
29
- icon: "â„šī¸",
30
- action: () => toastStore.info("Here is some information."),
31
- },
32
- {
33
- id: "toast-warning",
34
- label: "Warning Toast",
35
- icon: "âš ī¸",
36
- action: () => toastStore.warn("Please be careful."),
37
- },
38
- {
39
- id: "toast-error",
40
- label: "Error Toast",
41
- icon: "đŸšĢ",
42
- action: () => toastStore.error("Something went wrong!"),
43
- },
44
- ],
45
- },
46
- {
47
- id: "log-app",
48
- label: "Log Current App State",
49
- category: "Debug",
50
- icon: "📋",
51
- action: () => {
52
- console.group("🚀 Rune Lab — Current State");
53
- console.table(appStore.info);
54
- console.log("🎨 Theme:", themeStore.current, themeStore.getProp("icon"));
55
- console.log("🌐 Language:", languageStore.current, languageStore.getProp("flag"));
56
- console.log("💰 Currency:", currencyStore.current, currencyStore.getProp("symbol"));
57
- console.log("🔌 API:", apiStore.connectionState, apiStore.URL);
58
- console.groupEnd();
59
- },
60
- },
61
- {
62
- id: "log-all-stores",
63
- label: "Log All Stores (Full Dump)",
64
- category: "Debug",
65
- icon: "đŸ—ƒī¸",
66
- action: () => {
67
- console.group("📚 Rune Lab — Full Store Dump");
68
- Object.entries(this.#services).forEach(([name, store]) => {
69
- console.group(`Store: ${name}`);
70
- console.log("Reactive Instance:", store);
71
- if (store && "available" in store) {
72
- console.log("Available:", store.available);
73
- }
74
- if (store && "current" in store) {
75
- console.log("Current:", store.current);
76
- }
77
- if (store && "toasts" in store) {
78
- console.log("Queue:", store.toasts);
79
- }
80
- if (store && "commands" in store) {
81
- console.log("Registry:", store.commands);
82
- }
83
- console.groupEnd();
84
- });
85
- console.groupEnd();
86
- },
87
- },
88
- ];
12
+ // Intentionally empty.
13
+ // Consumers (like the demo app) should inject their own default commands via `commandStore.register`.
14
+ this.commands = [];
89
15
  }
90
16
  /**
91
17
  * Register a new command
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rune-lab",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Rune Lab: Modern toolkit for Svelte 5 Runes applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",