You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/components/command/command-dialog.svelte

81 lines
2.1 KiB

feat(eidos): port Command (⌘K palette) Closes #130. Eidos wrapper + 13 part wrappers + 2 new sub-parts + Dialog composition + recipe + canonical-depth demo. Soma owns scoring (`computeFuzzyScore`), keyboard nav (Arrow + vim Ctrl+N/P/J/K + typeahead + loop + grid columns + RTL), item visibility, and the imperative `CommandApi`. Eidos adds size + Item.Icon / Item.Shortcut slots + Dialog composition with optional shortcut binding. Reference parity: | Feature | cmdk | Mantine Spotlight | bits-ui Command | this port | |---|---|---|---|---| | Root provider w/ bind:value, bind:search | ✓ | ✓ | ✓ | ✓ | | Input, List, Viewport, Empty | ✓ | ✓ | ✓ | ✓ | | Group + GroupHeading + GroupItems | ✓ | ✓ | ✓ | ✓ | | Item value, keywords, disabled, onSelect | ✓ | ✓ | ✓ | ✓ | | LinkItem (anchor) | ✗ | ✗ | ✓ | ✓ | | Separator, Loading (progress 0-100) | ✓ | partial | ✓ | ✓ | | Custom filter / shouldFilter | ✓ | ✓ | ✓ | ✓ | | Loop, vim bindings, grid columns | ✓ | partial | ✓ | ✓ | | disablePointerSelection | ✓ | ✗ | ✓ | ✓ | | Modal Dialog composition | ✓ | ✓ (Root is modal) | ✓ | ✓ via eidos Dialog | | Built-in ⌘K shortcut binding | ✗ (BYO) | ✓ | ✗ (BYO) | ✓ optional | | Item.Icon / Item.Shortcut slots | BYO | leftSection/rightSection | BYO | ✓ NEW | Naming: `Command.Root` aliased as the default export (cmdk/bits-ui parity, eidos convention). Inner parts as named sub-properties: `Command.Input`, `Command.List`, `Command.Item`, `Command.Item.Icon`, `Command.Item.Shortcut`, `Command.Dialog`, … Variant set: only `size: 'sm' | 'md' | 'lg'` (drops xs/xl — palette UX needs comfortable hit targets, matches air baseline). Shortcut binding: cross-platform `mod` token (Cmd on macOS, Ctrl elsewhere). Parses `'mod+k'`, `'ctrl+shift+p'`, etc. Window keydown listener registered on mount, cleaned up on unmount. Pass `null`/ undefined to disable. Recipe: `command.css` paints palette container, input row, scrolling List/Viewport, item rows (with highlighted [data-selected] state), icon + shortcut slot layout, group heading, separator, empty state. Loading progress is a 2px bar under Input with `--_command-loading- progress` for the fill. Inside Command.Dialog the panel chrome comes from dialog.css. Demo: 6 tabs (Live/API/Morfo/Sema/Recipe/A11y) — Live wires every soma + eidos prop + Dialog mode with shortcut input. Two stages: inline palette + dialog opener with shortcut hint. Three code snippets (soma, eidos inline, eidos dialog). Gotcha note: command.svelte renames the children prop to `bodyContent` on destructure to avoid shadowing the inner `{#snippet children}` — same infinite-recursion trap noted in `feedback_button_consumer_pattern.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<script lang="ts">
/**
* Eidos `<Command.Dialog>` — modal palette built on top of the eidos
* `<Dialog>` wrapper (so panel chrome + overlay + sizing stay
* consistent with the rest of the system). Mirrors soma's
* CommandDialog API but routes through the eidos layer.
*
* <Command.Dialog bind:open shortcut="mod+k">
* <Command.Input placeholder="Search…" />
* <Command.List>…</Command.List>
* </Command.Dialog>
*
* Default `shortcut='mod+k'` opens the dialog on the canonical
* keyboard binding. Set `shortcut={null}` to disable.
*/
import { onMount } from 'svelte';
import { Dialog } from '$uix/eidos/components/dialog';
import CommandRoot from './command.svelte';
import type { CommandDialogProps } from './types';
import { bindCommandShortcut } from './command-shortcut.svelte';
let {
open = $bindable(false),
onOpenChange,
modal = true,
shortcut = 'mod+k',
size = 'md',
dialogSize = 'md',
// Command pass-through
label,
value = $bindable(''),
onValueChange,
search = $bindable(''),
onSearchChange,
shouldFilter = true,
filter,
loop = false,
vimBindings = true,
columns = 1,
disablePointerSelection = false,
overlay,
// Rename to avoid shadowing the inner snippet
children: bodyContent
}: CommandDialogProps = $props();
onMount(() => {
if (!shortcut) return undefined;
return bindCommandShortcut(shortcut, () => {
const next = !open;
open = next;
onOpenChange?.(next);
});
});
</script>
<Dialog bind:open {modal} onOpenChange={(v) => onOpenChange?.(v)}>
<Dialog.Portal>
{#if overlay}{@render overlay()}{:else}<Dialog.Overlay />{/if}
<Dialog.Content size={dialogSize}>
<CommandRoot
{label}
bind:value
onValueChange={(v) => onValueChange?.(v)}
bind:search
onSearchChange={(v) => onSearchChange?.(v)}
{shouldFilter}
{filter}
{loop}
{vimBindings}
{columns}
{disablePointerSelection}
{size}
>
{#snippet children(snippetProps)}
{@render bodyContent?.(snippetProps)}
{/snippet}
</CommandRoot>
</Dialog.Content>
</Dialog.Portal>
</Dialog>

Powered by TurnKey Linux.