feat(command): Command.Dialog closes on item select

Selecting an item ran its onSelect but nothing closed the dialog. Add a
provider-level `onSelect` to the soma command (fires for ANY item, on BOTH
paths — `selectCurrent` for the keyboard + the item `onclick` for the mouse,
after the item's own onSelect). The eidos `<Command.Dialog>` hooks it → `open
= false`.

Verified in-browser: click → closes; Enter → closes. check 0; command provider
tests 2/2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 67d419f839
commit a60abef4bf

@ -65,6 +65,7 @@
<Dialog.Close position="top-right" variant="ghost" color="neutral" /> <Dialog.Close position="top-right" variant="ghost" color="neutral" />
<CommandRoot <CommandRoot
{label} {label}
onSelect={() => (open = false)}
bind:value bind:value
onValueChange={(v) => onValueChange?.(v)} onValueChange={(v) => onValueChange?.(v)}
bind:search bind:search

@ -86,7 +86,11 @@ interface CommandOpts
columns: number; columns: number;
disablePointerSelection: boolean; disablePointerSelection: boolean;
dir: 'ltr' | 'rtl'; dir: 'ltr' | 'rtl';
}> {} }> {
/** Provider-level select event — fires for ANY item (after its own
* onSelect). A host (Command.Dialog) hooks it to close on select. */
onSelect?: Active<((value: string) => void) | undefined>;
}
export class CommandProvider { export class CommandProvider {
readonly opts: CommandOpts; readonly opts: CommandOpts;
@ -380,6 +384,9 @@ export class CommandProvider {
const entry = this.findItem(value); const entry = this.findItem(value);
if (!entry || entry.disabled || !this.isVisible(value)) return; if (!entry || entry.disabled || !this.isVisible(value)) return;
this.onSelectHandlers.get(value)?.(value); this.onSelectHandlers.get(value)?.(value);
// Provider-level select event — fires for ANY item, after its own onSelect.
// A host (Command.Dialog) hooks it to close on select.
this.opts.onSelect?.current?.(value);
// Morfo: commit-submit-invoke on `item`. Look up the item element // Morfo: commit-submit-invoke on `item`. Look up the item element
// by data-value so the cascade matches this specific item (Enter // by data-value so the cascade matches this specific item (Enter
// key path — onclick handler emits independently for mouse path). // key path — onclick handler emits independently for mouse path).
@ -708,6 +715,9 @@ export class CommandItemProvider {
if (this.opts.disabled.current) return; if (this.opts.disabled.current) return;
this.provider.setValue(this.opts.value.current); this.provider.setValue(this.opts.value.current);
this.opts.onSelect.current?.(this.opts.value.current); this.opts.onSelect.current?.(this.opts.value.current);
// Provider-level select event (mouse path) — mirrors selectCurrent's fire
// on the keyboard path, so a host (Command.Dialog) closes on click too.
this.provider.opts.onSelect?.current?.(this.opts.value.current);
// Morfo: commit-submit-invoke on `item` (affirm). Fire with the // Morfo: commit-submit-invoke on `item` (affirm). Fire with the
// clicked element so the cascade matches this specific item. // clicked element so the cascade matches this specific item.
void this.provider.runtime.trigger('commit-submit-invoke', { void this.provider.runtime.trigger('commit-submit-invoke', {
@ -834,6 +844,9 @@ export class CommandLinkItemProvider {
return; return;
} }
this.opts.onSelect.current?.(this.opts.value.current); this.opts.onSelect.current?.(this.opts.value.current);
// Provider-level select event (mouse path) — so a host (Command.Dialog)
// closes on a link-item click too.
this.provider.opts.onSelect?.current?.(this.opts.value.current);
}; };
readonly onpointermove = () => { readonly onpointermove = () => {

@ -1,8 +1,5 @@
<script lang="ts"> <script lang="ts">
import { import { readableActive, writableActive } from '$libs/reactive';
readableActive,
writableActive
} from '$libs/reactive';
import { mergeProps } from '../../../props'; import { mergeProps } from '../../../props';
import { createId } from '$active-uix/id'; import { createId } from '$active-uix/id';
import { CommandProvider } from '../command-provider.svelte'; import { CommandProvider } from '../command-provider.svelte';
@ -21,6 +18,7 @@
onValueChange = () => {}, onValueChange = () => {},
search = $bindable(''), search = $bindable(''),
onSearchChange = () => {}, onSearchChange = () => {},
onSelect = () => {},
shouldFilter = true, shouldFilter = true,
filter = computeFuzzyScore, filter = computeFuzzyScore,
loop = false, loop = false,
@ -60,7 +58,8 @@
vimBindings: readableActive(() => vimBindings), vimBindings: readableActive(() => vimBindings),
columns: readableActive(() => columns), columns: readableActive(() => columns),
disablePointerSelection: readableActive(() => disablePointerSelection), disablePointerSelection: readableActive(() => disablePointerSelection),
dir: readableActive(() => dir ?? soma?.prefs.getDir() ?? 'ltr') dir: readableActive(() => dir ?? soma?.prefs.getDir() ?? 'ltr'),
onSelect: readableActive(() => onSelect)
}); });
const mergedProps = $derived(mergeProps(restProps, state.props)); const mergedProps = $derived(mergeProps(restProps, state.props));

@ -67,6 +67,12 @@ export type CommandProps = WithChild<
search?: string; search?: string;
/** Callback on search change. */ /** Callback on search change. */
onSearchChange?: OnChangeFn<string>; onSearchChange?: OnChangeFn<string>;
/**
* Fires when ANY item is activated (Enter or click), after the item's own
* `onSelect`. Lets a host react to selection — e.g. a `Command.Dialog`
* closing on select. Item-level `onSelect` stays per-item.
*/
onSelect?: (value: string) => void;
/** Whether to run the scoring filter over items. @default true */ /** Whether to run the scoring filter over items. @default true */
shouldFilter?: boolean; shouldFilter?: boolean;
/** Custom scoring function. @default built-in fuzzy matcher */ /** Custom scoring function. @default built-in fuzzy matcher */
@ -102,14 +108,11 @@ export type CommandProps = WithChild<
> & > &
Without<PrimitiveDivAttributes, {}>; Without<PrimitiveDivAttributes, {}>;
export type CommandInputProps = WithChild<{ id?: string }> & export type CommandInputProps = WithChild<{ id?: string }> & Without<PrimitiveInputAttributes, {}>;
Without<PrimitiveInputAttributes, {}>;
export type CommandListProps = WithChild<{ id?: string }> & export type CommandListProps = WithChild<{ id?: string }> & Without<PrimitiveDivAttributes, {}>;
Without<PrimitiveDivAttributes, {}>;
export type CommandViewportProps = WithChild<{ id?: string }> & export type CommandViewportProps = WithChild<{ id?: string }> & Without<PrimitiveDivAttributes, {}>;
Without<PrimitiveDivAttributes, {}>;
export type CommandItemProps = WithChild<{ export type CommandItemProps = WithChild<{
id?: string; id?: string;

Loading…
Cancel
Save

Powered by TurnKey Linux.