@cloudvoyant/helical-svelte 0.20.1 → 0.21.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 (37) hide show
  1. package/dist/Kbd.svelte +86 -0
  2. package/dist/Kbd.svelte.d.ts +10 -0
  3. package/dist/PageContent.svelte +33 -4
  4. package/dist/PageContent.svelte.d.ts +2 -1
  5. package/dist/PageGutter.svelte +34 -3
  6. package/dist/PageGutter.svelte.d.ts +2 -1
  7. package/dist/PageSection.svelte +33 -4
  8. package/dist/PageSection.svelte.d.ts +2 -1
  9. package/dist/SplitterPanel.svelte +29 -3
  10. package/dist/SplitterPanel.svelte.d.ts +3 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +2 -0
  13. package/dist/listbox/ListboxItemGroup.svelte +17 -0
  14. package/dist/listbox/ListboxItemGroup.svelte.d.ts +9 -0
  15. package/dist/listbox/ListboxItemGroupLabel.svelte +17 -0
  16. package/dist/listbox/ListboxItemGroupLabel.svelte.d.ts +9 -0
  17. package/dist/listbox/index.d.ts +2 -0
  18. package/dist/listbox/index.js +2 -0
  19. package/dist/sidebar/Root.svelte +34 -9
  20. package/dist/sidebar/Root.svelte.d.ts +2 -1
  21. package/dist/which-key/Pane.svelte +49 -0
  22. package/dist/which-key/Pane.svelte.d.ts +10 -0
  23. package/dist/which-key/WhichKey.svelte +197 -0
  24. package/dist/which-key/WhichKey.svelte.d.ts +8 -0
  25. package/dist/which-key/WhichKeyExplorer.svelte +269 -0
  26. package/dist/which-key/WhichKeyExplorer.svelte.d.ts +8 -0
  27. package/dist/which-key/WhichKeyMode.svelte +44 -0
  28. package/dist/which-key/WhichKeyMode.svelte.d.ts +9 -0
  29. package/dist/which-key/WhichKeyProvider.svelte +136 -0
  30. package/dist/which-key/WhichKeyProvider.svelte.d.ts +9 -0
  31. package/dist/which-key/context.svelte.d.ts +57 -0
  32. package/dist/which-key/context.svelte.js +177 -0
  33. package/dist/which-key/index.d.ts +7 -0
  34. package/dist/which-key/index.js +7 -0
  35. package/dist/window/Window.svelte +30 -4
  36. package/dist/window/Window.svelte.d.ts +2 -1
  37. package/package.json +5 -2
@@ -0,0 +1,197 @@
1
+ <!-- libs/helical-svelte/src/which-key/WhichKey.svelte -->
2
+ <script lang="ts">
3
+ import { Portal } from '@ark-ui/svelte/portal';
4
+ import { Presence } from '@ark-ui/svelte/presence';
5
+ import {
6
+ cn,
7
+ jumpBadgeMatchedPrefixBase,
8
+ jumpBadgeVariants,
9
+ jumpOverlayRootBase,
10
+ whichKeyBodyVariants,
11
+ whichKeyBreadcrumbBase,
12
+ whichKeyFooterBase,
13
+ whichKeyHeaderBase,
14
+ whichKeyItemBase,
15
+ whichKeyItemLabelBase,
16
+ whichKeyTitleBase,
17
+ whichKeyVariants,
18
+ type WhichKeyProps as CoreWhichKeyProps,
19
+ } from '@cloudvoyant/helical-ui';
20
+ import Kbd from '../Kbd.svelte';
21
+ import Row from '../Row.svelte';
22
+ import Scroll from '../Scroll.svelte';
23
+ import { getOptionalWhichKeyContext } from './context.svelte';
24
+ import WhichKeyExplorer from './WhichKeyExplorer.svelte';
25
+ import WhichKeyMode from './WhichKeyMode.svelte';
26
+
27
+ type Props = Omit<CoreWhichKeyProps, 'className'> & {
28
+ class?: string;
29
+ className?: string;
30
+ };
31
+
32
+ let {
33
+ variant = 'helix',
34
+ showModeBadge = true,
35
+ showExplorer = true,
36
+ class: className = '',
37
+ className: reactClassName = '',
38
+ }: Props = $props();
39
+
40
+ const ctx = getOptionalWhichKeyContext();
41
+
42
+ let panelRef: HTMLDivElement | null = $state(null);
43
+ let jumpOverlayRef: HTMLDivElement | null = $state(null);
44
+
45
+ $effect(() => {
46
+ if (!ctx?.state.isOpen) return;
47
+ return ctx.controller.trackBranchElement(() => panelRef);
48
+ });
49
+
50
+ $effect(() => {
51
+ if (ctx?.state.mode !== 'jump') return;
52
+ return ctx.controller.trackBranchElement(() => jumpOverlayRef);
53
+ });
54
+ </script>
55
+
56
+ {#if ctx}
57
+ <Portal>
58
+ <Presence present={ctx.state.isOpen} lazyMount unmountOnExit>
59
+ <div
60
+ bind:this={panelRef}
61
+ role="region"
62
+ aria-label={ctx.state.title || 'WhichKey commands'}
63
+ data-which-key-root="true"
64
+ data-testid="which-key-panel"
65
+ data-variant={variant}
66
+ data-preset={ctx.state.preset}
67
+ data-mode={ctx.state.mode}
68
+ data-sticky={ctx.state.sticky ? 'true' : 'false'}
69
+ class={cn(whichKeyVariants({ variant }), className, reactClassName)}
70
+ >
71
+ <div class={whichKeyHeaderBase}>
72
+ <div class={whichKeyTitleBase}>
73
+ {#if showModeBadge}
74
+ <WhichKeyMode mode={ctx.state.mode} sticky={ctx.state.sticky} />
75
+ {/if}
76
+ <span data-testid="which-key-title">
77
+ {ctx.state.title === 'Space' ? 'Leader' : ctx.state.title}
78
+ </span>
79
+ {#if ctx.state.countBuffer}
80
+ <span
81
+ data-testid="which-key-count"
82
+ class="rounded bg-muted px-1 font-mono text-[10px]"
83
+ >
84
+ {ctx.state.countBuffer}×
85
+ </span>
86
+ {/if}
87
+ </div>
88
+ <div
89
+ data-testid="which-key-breadcrumb"
90
+ class={whichKeyBreadcrumbBase}
91
+ >
92
+ <span>{ctx.state.breadcrumb.join(' » ')}</span>
93
+ </div>
94
+ </div>
95
+
96
+ <Scroll class="max-h-72" viewportClass="max-h-72">
97
+ <div
98
+ data-testid="which-key-items"
99
+ class={whichKeyBodyVariants({ variant })}
100
+ >
101
+ {#each ctx.state.items as node (node.key)}
102
+ {@const isGroup = Boolean(
103
+ node.children || node.proxy || node.expand || node.label.startsWith('+'),
104
+ )}
105
+ <button
106
+ type="button"
107
+ data-testid="which-key-item"
108
+ data-key={node.key}
109
+ data-group={isGroup ? 'true' : 'false'}
110
+ onclick={(e: MouseEvent) => {
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ ctx.controller.triggerKey(node.key);
114
+ }}
115
+ class={whichKeyItemBase}
116
+ >
117
+ <span
118
+ data-group={isGroup ? 'true' : 'false'}
119
+ class={whichKeyItemLabelBase}
120
+ >
121
+ {node.label}
122
+ </span>
123
+ <Kbd
124
+ size="sm"
125
+ variant={isGroup ? 'subtle' : 'outline'}
126
+ hotkey={node.key}
127
+ />
128
+ </button>
129
+ {/each}
130
+ </div>
131
+ </Scroll>
132
+
133
+ <div class={whichKeyFooterBase}>
134
+ <Row class="items-center gap-2">
135
+ {#if ctx.state.breadcrumb.length > 1}
136
+ <button
137
+ type="button"
138
+ onclick={() => ctx.controller.stepBackPrefix()}
139
+ class="inline-flex items-center gap-1 hover:text-foreground"
140
+ >
141
+ <Kbd size="sm">⌫</Kbd>
142
+ <span>Back</span>
143
+ </button>
144
+ {/if}
145
+ <button
146
+ type="button"
147
+ onclick={() => ctx.controller.closePrefix()}
148
+ class="inline-flex items-center gap-1 hover:text-foreground"
149
+ >
150
+ <Kbd size="sm">Esc</Kbd>
151
+ <span>Close</span>
152
+ </button>
153
+ </Row>
154
+ <span class="font-mono uppercase opacity-75">
155
+ {ctx.state.preset}
156
+ </span>
157
+ </div>
158
+ </div>
159
+ </Presence>
160
+ </Portal>
161
+
162
+ <Portal>
163
+ <Presence
164
+ present={ctx.state.mode === 'jump' && ctx.state.jumpTargets.length > 0}
165
+ lazyMount
166
+ unmountOnExit
167
+ >
168
+ <div
169
+ bind:this={jumpOverlayRef}
170
+ data-which-key-jump-overlay="true"
171
+ data-testid="which-key-jump-overlay"
172
+ data-jump-action={ctx.state.jumpAction}
173
+ class={jumpOverlayRootBase}
174
+ >
175
+ {#each ctx.state.jumpTargets as target (target.id)}
176
+ <span
177
+ data-testid="which-key-jump-badge"
178
+ data-jump-label={target.label}
179
+ style={`top: ${Math.max(4, target.rect.top - 4)}px; left: ${Math.max(4, target.rect.left - 4)}px;`}
180
+ class={jumpBadgeVariants({ action: ctx.state.jumpAction })}
181
+ >
182
+ {#if target.matchedPrefix}
183
+ <span class={jumpBadgeMatchedPrefixBase}>
184
+ {target.matchedPrefix}
185
+ </span>
186
+ {/if}
187
+ <span>{target.remainingLabel}</span>
188
+ </span>
189
+ {/each}
190
+ </div>
191
+ </Presence>
192
+ </Portal>
193
+
194
+ {#if showExplorer}
195
+ <WhichKeyExplorer />
196
+ {/if}
197
+ {/if}
@@ -0,0 +1,8 @@
1
+ import { type WhichKeyProps as CoreWhichKeyProps } from '@cloudvoyant/helical-ui';
2
+ type Props = Omit<CoreWhichKeyProps, 'className'> & {
3
+ class?: string;
4
+ className?: string;
5
+ };
6
+ declare const WhichKey: import("svelte").Component<Props, {}, "">;
7
+ type WhichKey = ReturnType<typeof WhichKey>;
8
+ export default WhichKey;
@@ -0,0 +1,269 @@
1
+ <!-- libs/helical-svelte/src/which-key/WhichKeyExplorer.svelte -->
2
+ <script lang="ts">
3
+ import { Highlight } from '@ark-ui/svelte/highlight';
4
+ import { createListCollection } from '@ark-ui/svelte/listbox';
5
+ import {
6
+ cn,
7
+ whichKeyExplorerContentBase,
8
+ whichKeyExplorerHeaderBase,
9
+ whichKeyExplorerHighlightMarkBase,
10
+ whichKeyExplorerInputBase,
11
+ whichKeyExplorerItemBase,
12
+ whichKeyExplorerListboxBase,
13
+ whichKeyExplorerListContentBase,
14
+ whichKeyModeBadgeVariants,
15
+ type KeymapExplorerEntry,
16
+ type WhichKeyExplorerProps as CoreWhichKeyExplorerProps,
17
+ } from '@cloudvoyant/helical-ui';
18
+ import {
19
+ Dialog,
20
+ DialogBackdrop,
21
+ DialogContent,
22
+ DialogDismiss,
23
+ DialogHeader,
24
+ DialogTitle,
25
+ } from '../dialog';
26
+ import Kbd from '../Kbd.svelte';
27
+ import {
28
+ Listbox,
29
+ ListboxContent,
30
+ ListboxEmpty,
31
+ ListboxInput,
32
+ ListboxItem,
33
+ ListboxItemGroup,
34
+ ListboxItemGroupLabel,
35
+ ListboxItemText,
36
+ } from '../listbox';
37
+ import Row from '../Row.svelte';
38
+ import Scroll from '../Scroll.svelte';
39
+ import { getOptionalWhichKeyContext } from './context.svelte';
40
+
41
+ type Props = Omit<CoreWhichKeyExplorerProps, 'className'> & {
42
+ class?: string;
43
+ className?: string;
44
+ };
45
+
46
+ let {
47
+ open: controlledOpen,
48
+ onOpenChange,
49
+ placeholder = 'Search keybindings...',
50
+ class: className = '',
51
+ className: reactClassName = '',
52
+ }: Props = $props();
53
+
54
+ const ctx = getOptionalWhichKeyContext();
55
+
56
+ const isOpen = $derived(controlledOpen ?? Boolean(ctx?.state.explorerOpen));
57
+ const explorerEntries = $derived(ctx?.state.explorerEntries ?? []);
58
+ const explorerQuery = $derived(ctx?.state.explorerQuery ?? '');
59
+ const preset = $derived(ctx?.state.preset ?? 'helix');
60
+
61
+ let highlightedId = $state<string | null>(null);
62
+
63
+ const groupedEntries = $derived.by((): Array<[string, KeymapExplorerEntry[]]> => {
64
+ const groups = new Map<string, KeymapExplorerEntry[]>();
65
+ for (const entry of explorerEntries) {
66
+ const groupName = entry.group || 'General';
67
+ const list = groups.get(groupName) ?? [];
68
+ list.push(entry);
69
+ groups.set(groupName, list);
70
+ }
71
+ return Array.from(groups.entries());
72
+ });
73
+
74
+ const orderedEntries = $derived(groupedEntries.flatMap(([, entries]) => entries));
75
+
76
+ const effectiveHighlightedId = $derived.by((): string | null => {
77
+ if (orderedEntries.length === 0) return null;
78
+ if (highlightedId && orderedEntries.some((e) => e.id === highlightedId)) {
79
+ return highlightedId;
80
+ }
81
+ return orderedEntries[0]!.id;
82
+ });
83
+
84
+ const collection = $derived(
85
+ createListCollection({
86
+ items: orderedEntries.map((entry) => ({
87
+ value: entry.id,
88
+ label: entry.label,
89
+ entry,
90
+ })),
91
+ }),
92
+ );
93
+
94
+ $effect(() => {
95
+ if (!isOpen || typeof document === 'undefined') return;
96
+ const focusInput = () => {
97
+ const input = document.querySelector<HTMLInputElement>(
98
+ '[data-which-key-explorer] [data-testid="which-key-explorer-input"]',
99
+ );
100
+ input?.focus();
101
+ };
102
+ focusInput();
103
+ const raf = requestAnimationFrame(focusInput);
104
+ return () => cancelAnimationFrame(raf);
105
+ });
106
+
107
+ function scrollItemIntoView(id: string) {
108
+ requestAnimationFrame(() => {
109
+ if (typeof document === 'undefined') return;
110
+ const items = Array.from(
111
+ document.querySelectorAll<HTMLElement>(
112
+ '[data-which-key-explorer] [data-part="item"]',
113
+ ),
114
+ );
115
+ for (const el of items) {
116
+ if (el.getAttribute('data-value') === id) {
117
+ el.scrollIntoView({ block: 'nearest' });
118
+ break;
119
+ }
120
+ }
121
+ });
122
+ }
123
+
124
+ function handleOpenChange(details: { open: boolean }) {
125
+ if (!ctx) return;
126
+ if (details.open) {
127
+ ctx.controller.openExplorer();
128
+ } else {
129
+ ctx.controller.closeExplorer();
130
+ }
131
+ onOpenChange?.(details.open);
132
+ }
133
+ </script>
134
+
135
+ {#if ctx && isOpen}
136
+ <Dialog
137
+ open={isOpen}
138
+ lazyMount
139
+ unmountOnExit
140
+ initialFocusEl={() =>
141
+ typeof document !== 'undefined'
142
+ ? document.querySelector<HTMLElement>(
143
+ '[data-which-key-explorer] [data-testid="which-key-explorer-input"]',
144
+ )
145
+ : null}
146
+ onOpenChange={handleOpenChange}
147
+ >
148
+ <DialogBackdrop />
149
+ <DialogContent
150
+ data-which-key-explorer="true"
151
+ data-testid="which-key-explorer"
152
+ class={cn(whichKeyExplorerContentBase, className, reactClassName)}
153
+ >
154
+ <DialogHeader class={whichKeyExplorerHeaderBase}>
155
+ <Row class="items-center gap-2">
156
+ <DialogTitle class="text-sm font-semibold">Keybindings</DialogTitle>
157
+ <span class={whichKeyModeBadgeVariants({ mode: 'normal' })}>
158
+ {preset}
159
+ </span>
160
+ </Row>
161
+ <DialogDismiss
162
+ tabindex={-1}
163
+ class="static opacity-80 hover:opacity-100 focus:ring-0"
164
+ >
165
+ {#snippet children()}
166
+ <Kbd size="sm">Esc</Kbd>
167
+ {/snippet}
168
+ </DialogDismiss>
169
+ </DialogHeader>
170
+
171
+ <Listbox
172
+ {collection}
173
+ selectionMode="single"
174
+ highlightedValue={effectiveHighlightedId}
175
+ onHighlightChange={(details) => {
176
+ if (details.highlightedValue) {
177
+ highlightedId = details.highlightedValue;
178
+ }
179
+ }}
180
+ onSelect={(details) => {
181
+ ctx.controller.executeExplorerEntry(details.value);
182
+ }}
183
+ class={whichKeyExplorerListboxBase}
184
+ >
185
+ <ListboxInput
186
+ autofocus
187
+ autoHighlight
188
+ data-testid="which-key-explorer-input"
189
+ {placeholder}
190
+ value={explorerQuery}
191
+ oninput={(e: Event) => {
192
+ ctx.controller.setExplorerQuery((e.currentTarget as HTMLInputElement).value);
193
+ }}
194
+ onkeydown={(e: KeyboardEvent) => {
195
+ if (orderedEntries.length === 0) return;
196
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
197
+ e.preventDefault();
198
+ e.stopPropagation();
199
+ const delta = e.key === 'ArrowDown' ? 1 : -1;
200
+ const currentIdx = orderedEntries.findIndex(
201
+ (item) => item.id === effectiveHighlightedId,
202
+ );
203
+ const nextIdx =
204
+ currentIdx === -1
205
+ ? 0
206
+ : (currentIdx + delta + orderedEntries.length) %
207
+ orderedEntries.length;
208
+ const nextId = orderedEntries[nextIdx]!.id;
209
+ highlightedId = nextId;
210
+ scrollItemIntoView(nextId);
211
+ return;
212
+ }
213
+ if (e.key === 'Enter') {
214
+ const itemVal = effectiveHighlightedId ?? orderedEntries[0]?.id;
215
+ if (itemVal) {
216
+ e.preventDefault();
217
+ e.stopPropagation();
218
+ ctx.controller.executeExplorerEntry(itemVal);
219
+ }
220
+ }
221
+ }}
222
+ class={whichKeyExplorerInputBase}
223
+ />
224
+ <Scroll class="max-h-96 min-h-0 flex-1" viewportClass="max-h-96">
225
+ {#snippet children()}
226
+ <ListboxContent
227
+ data-testid="which-key-explorer-list"
228
+ class={whichKeyExplorerListContentBase}
229
+ >
230
+ {#each groupedEntries as [groupName, entries] (groupName)}
231
+ <ListboxItemGroup>
232
+ <ListboxItemGroupLabel>{groupName}</ListboxItemGroupLabel>
233
+ {#each entries as entry (entry.id)}
234
+ <ListboxItem
235
+ item={{
236
+ value: entry.id,
237
+ label: entry.label,
238
+ }}
239
+ data-testid="which-key-explorer-item"
240
+ data-command-id={entry.commandId}
241
+ onclick={() => ctx.controller.executeExplorerEntry(entry)}
242
+ class={whichKeyExplorerItemBase}
243
+ >
244
+ <ListboxItemText class="min-w-0 flex-1 truncate font-medium text-foreground">
245
+ <Highlight
246
+ text={entry.label}
247
+ query={explorerQuery}
248
+ ignoreCase
249
+ matchAll
250
+ class={whichKeyExplorerHighlightMarkBase}
251
+ />
252
+ </ListboxItemText>
253
+ <Row class="shrink-0 items-center gap-1">
254
+ <Kbd size="sm" hotkey={entry.chords} />
255
+ </Row>
256
+ </ListboxItem>
257
+ {/each}
258
+ </ListboxItemGroup>
259
+ {/each}
260
+ {#if groupedEntries.length === 0}
261
+ <ListboxEmpty>No matching keybindings found.</ListboxEmpty>
262
+ {/if}
263
+ </ListboxContent>
264
+ {/snippet}
265
+ </Scroll>
266
+ </Listbox>
267
+ </DialogContent>
268
+ </Dialog>
269
+ {/if}
@@ -0,0 +1,8 @@
1
+ import { type WhichKeyExplorerProps as CoreWhichKeyExplorerProps } from '@cloudvoyant/helical-ui';
2
+ type Props = Omit<CoreWhichKeyExplorerProps, 'className'> & {
3
+ class?: string;
4
+ className?: string;
5
+ };
6
+ declare const WhichKeyExplorer: import("svelte").Component<Props, {}, "">;
7
+ type WhichKeyExplorer = ReturnType<typeof WhichKeyExplorer>;
8
+ export default WhichKeyExplorer;
@@ -0,0 +1,44 @@
1
+ <!-- libs/helical-svelte/src/which-key/WhichKeyMode.svelte -->
2
+ <script lang="ts">
3
+ import {
4
+ cn,
5
+ whichKeyModeBadgeVariants,
6
+ type WhichKeyModeProps as CoreWhichKeyModeProps,
7
+ } from '@cloudvoyant/helical-ui';
8
+ import type { HTMLAttributes } from 'svelte/elements';
9
+ import { useWhichKey } from './context.svelte';
10
+
11
+ type Props = Omit<CoreWhichKeyModeProps, 'className'> &
12
+ Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
13
+ class?: string;
14
+ className?: string;
15
+ };
16
+
17
+ let {
18
+ mode: modeProp,
19
+ sticky: stickyProp,
20
+ showPreset = false,
21
+ size = 'sm',
22
+ class: className = '',
23
+ className: reactClassName = '',
24
+ ...rest
25
+ }: Props = $props();
26
+
27
+ const wk = useWhichKey();
28
+ const resolvedMode = $derived(modeProp ?? wk.mode);
29
+ const resolvedSticky = $derived(stickyProp ?? wk.sticky);
30
+ const modeLabel = $derived(resolvedSticky ? `${resolvedMode}·lock` : resolvedMode);
31
+ const label = $derived(showPreset ? `${wk.preset} · ${modeLabel}` : modeLabel);
32
+ </script>
33
+
34
+ <span
35
+ data-slot="which-key-mode"
36
+ data-testid="which-key-mode-badge"
37
+ data-mode={resolvedMode}
38
+ data-sticky={resolvedSticky ? 'true' : 'false'}
39
+ data-preset={wk.preset}
40
+ class={cn(whichKeyModeBadgeVariants({ mode: resolvedMode, size }), className, reactClassName)}
41
+ {...rest}
42
+ >
43
+ {label}
44
+ </span>
@@ -0,0 +1,9 @@
1
+ import { type WhichKeyModeProps as CoreWhichKeyModeProps } from '@cloudvoyant/helical-ui';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<CoreWhichKeyModeProps, 'className'> & Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
4
+ class?: string;
5
+ className?: string;
6
+ };
7
+ declare const WhichKeyMode: import("svelte").Component<Props, {}, "">;
8
+ type WhichKeyMode = ReturnType<typeof WhichKeyMode>;
9
+ export default WhichKeyMode;
@@ -0,0 +1,136 @@
1
+ <!-- libs/helical-svelte/src/which-key/WhichKeyProvider.svelte -->
2
+ <script lang="ts">
3
+ import { untrack, type Snippet } from 'svelte';
4
+ import { useEnvironmentContext } from '@ark-ui/svelte/environment';
5
+ import { HotkeysProvider } from '@tanstack/svelte-hotkeys';
6
+ import {
7
+ createWhichKeyController,
8
+ type WhichKeyControllerOptions,
9
+ type WhichKeyState,
10
+ } from '@cloudvoyant/helical-ui';
11
+ import { setWhichKeyContext } from './context.svelte';
12
+
13
+ type Props = WhichKeyControllerOptions & {
14
+ scoped?: boolean;
15
+ children?: Snippet;
16
+ };
17
+
18
+ let {
19
+ preset,
20
+ defaultMode = 'normal',
21
+ leaderKey = 'Space',
22
+ jumpAction,
23
+ jumpAlphabet,
24
+ storageKey,
25
+ keymap,
26
+ commands,
27
+ enabled = true,
28
+ disableOnMobile = true,
29
+ scoped = true,
30
+ onPresetChange,
31
+ onModeChange,
32
+ onJumpActionChange,
33
+ onCommand,
34
+ onYank,
35
+ children,
36
+ }: Props = $props();
37
+
38
+ const env = useEnvironmentContext();
39
+ let scopeEl: HTMLDivElement | null = $state(null);
40
+
41
+ const controller = untrack(() =>
42
+ createWhichKeyController({
43
+ preset,
44
+ defaultMode,
45
+ leaderKey,
46
+ jumpAction,
47
+ jumpAlphabet,
48
+ storageKey,
49
+ keymap,
50
+ commands,
51
+ enabled,
52
+ disableOnMobile,
53
+ getDocument: () => {
54
+ try {
55
+ return env?.().getDocument?.() ?? document;
56
+ } catch {
57
+ return document;
58
+ }
59
+ },
60
+ getScopeElement: () => (scoped ? scopeEl : null),
61
+ onPresetChange: (next) => onPresetChange?.(next),
62
+ onModeChange: (next) => onModeChange?.(next),
63
+ onJumpActionChange: (next) => onJumpActionChange?.(next),
64
+ onCommand: (id, ctx) => onCommand?.(id, ctx),
65
+ onYank: (text, ctx) => onYank?.(text, ctx),
66
+ }),
67
+ );
68
+
69
+ let reactiveState: WhichKeyState = $state(controller.getState());
70
+
71
+ setWhichKeyContext({
72
+ controller,
73
+ get state() {
74
+ return reactiveState;
75
+ },
76
+ });
77
+
78
+ $effect(() => {
79
+ const unsubscribe = controller.subscribe(() => {
80
+ reactiveState = controller.getState();
81
+ });
82
+ return () => {
83
+ unsubscribe();
84
+ };
85
+ });
86
+
87
+ $effect(() => {
88
+ if (preset !== undefined) {
89
+ controller.setPreset(preset);
90
+ }
91
+ });
92
+
93
+ $effect(() => {
94
+ if (jumpAction !== undefined) {
95
+ controller.setJumpAction(jumpAction);
96
+ }
97
+ });
98
+
99
+ $effect(() => {
100
+ if (!enabled) return;
101
+ let doc: Document | undefined;
102
+ try {
103
+ doc = env?.().getDocument?.();
104
+ } catch {
105
+ doc = typeof document !== 'undefined' ? document : undefined;
106
+ }
107
+ return controller.attach(doc);
108
+ });
109
+
110
+ $effect(() => {
111
+ if (!keymap) return;
112
+ return controller.registerKeymap('__provider_keymap__', keymap, {
113
+ enabled: true,
114
+ });
115
+ });
116
+
117
+ $effect(() => {
118
+ if (!commands) return;
119
+ const cleanups = Object.entries(commands).map(([id, handler]) =>
120
+ controller.registerCommand(id, handler),
121
+ );
122
+ return () => {
123
+ for (const fn of cleanups) fn();
124
+ };
125
+ });
126
+ </script>
127
+
128
+ <HotkeysProvider>
129
+ {#if scoped}
130
+ <div bind:this={scopeEl} data-which-key-scope class="contents">
131
+ {@render children?.()}
132
+ </div>
133
+ {:else}
134
+ {@render children?.()}
135
+ {/if}
136
+ </HotkeysProvider>
@@ -0,0 +1,9 @@
1
+ import { type Snippet } from 'svelte';
2
+ import { type WhichKeyControllerOptions } from '@cloudvoyant/helical-ui';
3
+ type Props = WhichKeyControllerOptions & {
4
+ scoped?: boolean;
5
+ children?: Snippet;
6
+ };
7
+ declare const WhichKeyProvider: import("svelte").Component<Props, {}, "">;
8
+ type WhichKeyProvider = ReturnType<typeof WhichKeyProvider>;
9
+ export default WhichKeyProvider;