PettyUI/packages/core/src/primitives/create-disclosure-state.ts
Mats Bosson 5bc9ac7b61 Listbox, Select, and list navigation
createListNavigation is the core primitive for all collection components
(Listbox, Select, Combobox, Menu). It provides value-based keyboard
navigation, selection/activation modes, typeahead, and aria-activedescendant
virtual focus.
Listbox: standalone selectable list with single/multiple selection.
Select: floating dropdown with trigger, keyboard navigation, form integration.
Also fixes exactOptionalPropertyTypes compatibility in createDisclosureState
and createListNavigation interfaces.
2026-03-29 19:12:05 +07:00

38 lines
1.2 KiB
TypeScript

import type { Accessor } from "solid-js";
import { createControllableSignal } from "./create-controllable-signal";
export interface CreateDisclosureStateOptions {
open?: boolean | undefined;
defaultOpen?: boolean | undefined;
onOpenChange?: ((open: boolean) => void) | undefined;
}
export interface DisclosureState {
isOpen: Accessor<boolean>;
open: () => void;
close: () => void;
toggle: () => void;
}
/**
* Shared open/close state for all disclosure components (Dialog, Popover,
* Tooltip, Collapsible, etc.). Wraps createControllableSignal with
* convenience open/close/toggle methods.
*/
export function createDisclosureState(options: CreateDisclosureStateOptions): DisclosureState {
const [isOpen, setIsOpen] = createControllableSignal<boolean>({
value: () => options.open,
defaultValue: () => options.defaultOpen ?? false,
...(options.onOpenChange !== undefined && { onChange: options.onOpenChange }),
});
return {
isOpen,
open: () => setIsOpen(true),
close: () => setIsOpen(false),
// Imperative-only: do not call toggle() inside a reactive computation (effect/memo),
// as isOpen() would create an unwanted reactive dependency there.
toggle: () => setIsOpen(!isOpen()),
};
}