import type { JSX } from 'solid-js'; import { createComponent, createEffect, createMemo, createUniqueId, mergeProps, } from 'solid-js'; import { omitProps } from 'solid-use/props'; import type { DisclosureStateControlledOptions, DisclosureStateUncontrolledOptions, } from '../../states/create-disclosure-state'; import { DisclosureStateProvider, createDisclosureState, } from '../../states/create-disclosure-state'; import type { MultipleSelectStateControlledOptions, MultipleSelectStateUncontrolledOptions, SingleSelectStateControlledOptions, SingleSelectStateUncontrolledOptions, } from '../../states/create-select-state'; import { SelectStateProvider, createMultipleSelectState, createSingleSelectState, } from '../../states/create-select-state'; import createDynamic from '../../utils/create-dynamic'; import type { DynamicProps, HeadlessProps, ValidConstructor, } from '../../utils/dynamic-prop'; import { createARIADisabledState, createDisabledState, createExpandedState, createHasActiveState, createHasSelectedState, } from '../../utils/state-props'; import type { Prettify } from '../../utils/types'; import useFocusStartPoint from '../../utils/use-focus-start-point'; import { ListboxContext } from './ListboxContext'; import { LISTBOX_TAG } from './tags'; export interface ListboxBaseProps { horizontal?: boolean; onDisclosureChange?: (value: boolean) => void; } export interface ListboxMultipleBaseProps { onSelectChange?: (value: V[]) => void; } export interface ListboxSingleBaseProps { onSelectChange?: (value?: V) => void; } // SCSCD = Single, Controlled Select, Controlled Disclosure export type ListboxSCSCDBaseProps = Prettify< ListboxBaseProps & ListboxSingleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxSCSCDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; // SCSCD = Single, Controlled Select, Uncontrolled Disclosure export type ListboxSCSUDBaseProps = Prettify< ListboxBaseProps & ListboxSingleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxSCSUDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; // SCSCD = Single, Uncontrolled Select, Controlled Disclosure export type ListboxSUSCDBaseProps = Prettify< ListboxBaseProps & ListboxSingleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxSUSCDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; // SCSCD = Single, Uncontrolled Select, Uncontrolled Disclosure export type ListboxSUSUDBaseProps = Prettify< ListboxBaseProps & ListboxSingleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxSUSUDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; export type ListboxSingleProps = | ListboxSCSCDProps | ListboxSCSUDProps | ListboxSUSCDProps | ListboxSUSUDProps; // MCSCD = Multiple, Controlled Select, Controlled Disclosure export type ListboxMCSCDBaseProps = Prettify< ListboxBaseProps & ListboxMultipleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxMCSCDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; // MCSCD = Multiple, Controlled Select, Uncontrolled Disclosure export type ListboxMCSUDBaseProps = Prettify< ListboxBaseProps & ListboxMultipleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxMCSUDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; // MCSCD = Multiple, Uncontrolled Select, Controlled Disclosure export type ListboxMUSCDBaseProps = Prettify< ListboxBaseProps & ListboxMultipleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxMUSCDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; // MCSCD = Multiple, Uncontrolled Select, Uncontrolled Disclosure export type ListboxMUSUDBaseProps = Prettify< ListboxBaseProps & ListboxMultipleBaseProps & Omit, 'onChange'> & Omit & { children?: JSX.Element; } >; export type ListboxMUSUDProps< V, T extends ValidConstructor = 'div', > = HeadlessProps>; export type ListboxMultipleProps = | ListboxMCSCDProps | ListboxMCSUDProps | ListboxMUSCDProps | ListboxMUSUDProps; type ListboxSelectUncontrolledProps = | ListboxMUSCDProps | ListboxMUSUDProps | ListboxSUSCDProps | ListboxSUSUDProps; type ListboxDisclosureUncontrolledProps = | ListboxMCSUDProps | ListboxMUSUDProps | ListboxSCSUDProps | ListboxSUSUDProps; export type ListboxProps = | ListboxMultipleProps | ListboxSingleProps; function isListboxMultiple( props: ListboxProps, ): props is ListboxMultipleProps { return !!props.multiple; } function isListboxSelectUncontrolled( props: ListboxProps, ): props is ListboxSelectUncontrolledProps { return 'defaultValue' in props; } function isListboxDisclosureUncontrolled( props: ListboxProps, ): props is ListboxDisclosureUncontrolledProps { return 'defaultOpen' in props; } function getProps( props: ListboxProps, ): DynamicProps { if (isListboxSelectUncontrolled(props)) { if (isListboxDisclosureUncontrolled(props)) { return omitProps(props, [ 'as', 'by', 'children', 'defaultOpen', 'defaultValue', 'disabled', 'horizontal', 'multiple', 'onClose', 'onDisclosureChange', 'onOpen', 'onSelectChange', 'toggleable', ]) as DynamicProps; } return omitProps(props, [ 'as', 'by', 'children', 'isOpen', 'defaultValue', 'disabled', 'horizontal', 'multiple', 'onClose', 'onDisclosureChange', 'onOpen', 'onSelectChange', 'toggleable', ]) as DynamicProps; } if (isListboxDisclosureUncontrolled(props)) { return omitProps(props, [ 'as', 'by', 'children', 'defaultOpen', 'value', 'disabled', 'horizontal', 'multiple', 'onClose', 'onDisclosureChange', 'onOpen', 'onSelectChange', 'toggleable', ]) as DynamicProps; } return omitProps(props, [ 'as', 'by', 'children', 'isOpen', 'value', 'disabled', 'horizontal', 'multiple', 'onClose', 'onDisclosureChange', 'onOpen', 'onSelectChange', 'toggleable', ]) as DynamicProps; } export function Listbox( props: ListboxProps, ): JSX.Element { return createMemo(() => { const ownerID = createUniqueId(); const labelID = createUniqueId(); const buttonID = createUniqueId(); const optionsID = createUniqueId(); const disclosureState = createDisclosureState( mergeProps(props, { onChange(value: boolean) { if (props.onDisclosureChange) { props.onDisclosureChange(value); } }, }), ); const selectState = isListboxMultiple(props) ? createMultipleSelectState( mergeProps(props, { onChange(value: V[]) { if (props.onSelectChange) { props.onSelectChange(value); } }, }), ) : createSingleSelectState( mergeProps(props, { onChange(value?: V) { if (props.onSelectChange) { props.onSelectChange(value); } }, }), ); const fsp = useFocusStartPoint(); createEffect(() => { if (disclosureState.isOpen()) { fsp.save(); } else { fsp.load(); } }); return createComponent(ListboxContext.Provider, { value: { get multiple() { return props.multiple; }, ownerID, labelID, buttonID, optionsID, get horizontal() { return props.horizontal; }, buttonHovering: false, optionsHovering: false, }, get children() { return createComponent(SelectStateProvider, { state: selectState, get children() { return createComponent(DisclosureStateProvider, { state: disclosureState, get children() { return createDynamic( () => props.as || 'div', mergeProps( LISTBOX_TAG, { id: ownerID, 'aria-labelledby': labelID, }, createDisabledState(() => selectState.disabled()), createARIADisabledState(() => selectState.disabled()), createHasSelectedState(() => selectState.hasSelected()), createHasActiveState(() => selectState.hasActive()), createExpandedState(() => disclosureState.isOpen()), getProps(props), { get children() { return props.children; }, }, ) as DynamicProps, ); }, }); }, }); }, }); }) as unknown as JSX.Element; }