@kobalte/core
Version:
Unstyled components and primitives for building accessible web apps and design systems with SolidJS.
840 lines (823 loc) • 23.9 kB
JSX
import {
HiddenSelectBase
} from "./3LKATPLK.jsx";
import {
ListboxItem,
ListboxItemDescription,
ListboxItemIndicator,
ListboxItemLabel,
ListboxRoot,
ListboxSection
} from "./2I3Q5YLM.jsx";
import {
Popper,
PopperArrow
} from "./2CTBMVJ4.jsx";
import {
ListKeyboardDelegate
} from "./N3GAC5SS.jsx";
import {
Selection,
createListState,
createTypeSelect
} from "./QZDH5R5B.jsx";
import {
createCollator
} from "./LR7LBJN3.jsx";
import {
createFocusScope
} from "./7A3GDF4Y.jsx";
import {
createHideOutside
} from "./FBCYWU27.jsx";
import {
DismissableLayer
} from "./3VFJM5NZ.jsx";
import {
createDisclosureState
} from "./E53DB7BS.jsx";
import {
ButtonRoot
} from "./UKTBL2JL.jsx";
import {
FORM_CONTROL_FIELD_PROP_NAMES,
createFormControlField
} from "./NGHEENNE.jsx";
import {
FormControlLabel
} from "./FOXVCQFV.jsx";
import {
createFormResetListener
} from "./QJIB6BDF.jsx";
import {
FormControlErrorMessage
} from "./ZZYKR3VO.jsx";
import {
FORM_CONTROL_PROP_NAMES,
FormControlContext,
FormControlDescription,
createFormControl,
useFormControlContext
} from "./XUUROM4M.jsx";
import {
createRegisterId
} from "./JNCCF6MP.jsx";
import {
Polymorphic
} from "./FLVHQV4A.jsx";
import {
__export
} from "./5WXHJDCZ.jsx";
// src/select/index.tsx
var select_exports = {};
__export(select_exports, {
Arrow: () => PopperArrow,
Content: () => SelectContent,
Description: () => FormControlDescription,
ErrorMessage: () => FormControlErrorMessage,
HiddenSelect: () => SelectHiddenSelect,
Icon: () => SelectIcon,
Item: () => ListboxItem,
ItemDescription: () => ListboxItemDescription,
ItemIndicator: () => ListboxItemIndicator,
ItemLabel: () => ListboxItemLabel,
Label: () => SelectLabel,
Listbox: () => SelectListbox,
Portal: () => SelectPortal,
Root: () => SelectRoot,
Section: () => ListboxSection,
Select: () => Select,
Trigger: () => SelectTrigger,
Value: () => SelectValue,
useSelectContext: () => useSelectContext
});
// src/select/select-content.tsx
import {
focusWithoutScrolling,
mergeRefs
} from "@kobalte/utils";
import {
Show,
splitProps
} from "solid-js";
import { combineStyle } from "@solid-primitives/props";
import createPreventScroll from "solid-prevent-scroll";
// src/select/select-context.tsx
import {
createContext,
useContext
} from "solid-js";
var SelectContext = createContext();
function useSelectContext() {
const context = useContext(SelectContext);
if (context === void 0) {
throw new Error(
"[kobalte]: `useSelectContext` must be used within a `Select` component"
);
}
return context;
}
// src/select/select-content.tsx
function SelectContent(props) {
let ref;
const context = useSelectContext();
const [local, others] = splitProps(props, [
"ref",
"style",
"onCloseAutoFocus",
"onFocusOutside"
]);
const onEscapeKeyDown = (e) => {
context.close();
};
const onFocusOutside = (e) => {
local.onFocusOutside?.(e);
if (context.isOpen() && context.isModal()) {
e.preventDefault();
}
};
createHideOutside({
isDisabled: () => !(context.isOpen() && context.isModal()),
targets: () => ref ? [ref] : []
});
createPreventScroll({
element: () => ref ?? null,
enabled: () => context.contentPresent() && context.preventScroll()
});
createFocusScope(
{
trapFocus: () => context.isOpen() && context.isModal(),
onMountAutoFocus: (e) => {
e.preventDefault();
},
onUnmountAutoFocus: (e) => {
local.onCloseAutoFocus?.(e);
if (!e.defaultPrevented) {
focusWithoutScrolling(context.triggerRef());
e.preventDefault();
}
}
},
() => ref
);
return <Show when={context.contentPresent()}><Popper.Positioner><DismissableLayer
ref={mergeRefs((el) => {
context.setContentRef(el);
ref = el;
}, local.ref)}
disableOutsidePointerEvents={context.isModal() && context.isOpen()}
excludedElements={[context.triggerRef]}
style={combineStyle(
{
"--kb-select-content-transform-origin": "var(--kb-popper-content-transform-origin)",
position: "relative"
},
local.style
)}
onEscapeKeyDown={onEscapeKeyDown}
onFocusOutside={onFocusOutside}
onDismiss={context.close}
{...context.dataset()}
{...others}
/></Popper.Positioner></Show>;
}
// src/select/select-hidden-select.tsx
function SelectHiddenSelect(props) {
const context = useSelectContext();
return <HiddenSelectBase
collection={context.listState().collection()}
selectionManager={context.listState().selectionManager()}
isOpen={context.isOpen()}
isMultiple={context.isMultiple()}
isVirtualized={context.isVirtualized()}
focusTrigger={() => context.triggerRef()?.focus()}
{...props}
/>;
}
// src/select/select-icon.tsx
import { mergeDefaultProps } from "@kobalte/utils";
function SelectIcon(props) {
const context = useSelectContext();
const mergedProps = mergeDefaultProps(
{ children: "\u25BC" },
props
);
return <Polymorphic
as="span"
aria-hidden="true"
{...context.dataset()}
{...mergedProps}
/>;
}
// src/select/select-label.tsx
import { callHandler } from "@kobalte/utils";
import {
splitProps as splitProps2
} from "solid-js";
function SelectLabel(props) {
const context = useSelectContext();
const [local, others] = splitProps2(props, ["onClick"]);
const onClick = (e) => {
callHandler(e, local.onClick);
if (!context.isDisabled()) {
context.triggerRef()?.focus();
}
};
return <FormControlLabel
as="span"
onClick={onClick}
{...others}
/>;
}
// src/select/select-listbox.tsx
import { callHandler as callHandler2, mergeDefaultProps as mergeDefaultProps2, mergeRefs as mergeRefs2 } from "@kobalte/utils";
import {
createEffect,
onCleanup,
splitProps as splitProps3
} from "solid-js";
function SelectListbox(props) {
const context = useSelectContext();
const mergedProps = mergeDefaultProps2(
{
id: context.generateId("listbox")
},
props
);
const [local, others] = splitProps3(mergedProps, ["ref", "id", "onKeyDown"]);
createEffect(() => onCleanup(context.registerListboxId(local.id)));
const onKeyDown = (e) => {
callHandler2(e, local.onKeyDown);
if (e.key === "Escape") {
e.preventDefault();
}
};
return <ListboxRoot
ref={mergeRefs2(context.setListboxRef, local.ref)}
id={local.id}
state={context.listState()}
virtualized={context.isVirtualized()}
autoFocus={context.autoFocus()}
shouldSelectOnPressUp
shouldFocusOnHover
shouldFocusWrap={context.shouldFocusWrap()}
disallowTypeAhead={context.disallowTypeAhead()}
aria-labelledby={context.listboxAriaLabelledBy()}
renderItem={context.renderItem}
renderSection={context.renderSection}
onKeyDown={onKeyDown}
{...others}
/>;
}
// src/select/select-portal.tsx
import { Show as Show2 } from "solid-js";
import { Portal } from "solid-js/web";
function SelectPortal(props) {
const context = useSelectContext();
return <Show2 when={context.contentPresent()}><Portal {...props} /></Show2>;
}
// src/select/select-root.tsx
import {
createMemo as createMemo2,
splitProps as splitProps5
} from "solid-js";
// src/select/select-base.tsx
import {
access,
createGenerateId,
focusWithoutScrolling as focusWithoutScrolling2,
isFunction,
mergeDefaultProps as mergeDefaultProps3
} from "@kobalte/utils";
import {
createEffect as createEffect2,
createMemo,
createSignal,
createUniqueId,
on,
splitProps as splitProps4
} from "solid-js";
import createPresence from "solid-presence";
function SelectBase(props) {
const defaultId = `select-${createUniqueId()}`;
const mergedProps = mergeDefaultProps3(
{
id: defaultId,
selectionMode: "single",
disallowEmptySelection: false,
closeOnSelection: props.selectionMode === "single",
allowDuplicateSelectionEvents: true,
gutter: 8,
sameWidth: true,
modal: false
},
props
);
const [local, popperProps, formControlProps, others] = splitProps4(
mergedProps,
[
"itemComponent",
"sectionComponent",
"open",
"defaultOpen",
"onOpenChange",
"value",
"defaultValue",
"onChange",
"placeholder",
"options",
"optionValue",
"optionTextValue",
"optionDisabled",
"optionGroupChildren",
"keyboardDelegate",
"allowDuplicateSelectionEvents",
"disallowEmptySelection",
"closeOnSelection",
"disallowTypeAhead",
"shouldFocusWrap",
"selectionBehavior",
"selectionMode",
"virtualized",
"modal",
"preventScroll",
"forceMount"
],
[
"getAnchorRect",
"placement",
"gutter",
"shift",
"flip",
"slide",
"overlap",
"sameWidth",
"fitViewport",
"hideWhenDetached",
"detachedPadding",
"arrowPadding",
"overflowPadding"
],
FORM_CONTROL_PROP_NAMES
);
const [triggerId, setTriggerId] = createSignal();
const [valueId, setValueId] = createSignal();
const [listboxId, setListboxId] = createSignal();
const [triggerRef, setTriggerRef] = createSignal();
const [contentRef, setContentRef] = createSignal();
const [listboxRef, setListboxRef] = createSignal();
const [listboxAriaLabelledBy, setListboxAriaLabelledBy] = createSignal();
const [focusStrategy, setFocusStrategy] = createSignal(true);
const getOptionValue = (option) => {
const optionValue = local.optionValue;
if (optionValue == null) {
return String(option);
}
return String(
isFunction(optionValue) ? optionValue(option) : option[optionValue]
);
};
const flattenOptions = createMemo(() => {
const optionGroupChildren = local.optionGroupChildren;
if (optionGroupChildren == null) {
return local.options;
}
return local.options.flatMap(
(item) => item[optionGroupChildren] ?? item
);
});
const flattenOptionKeys = createMemo(() => {
return flattenOptions().map((option) => getOptionValue(option));
});
const getOptionsFromValues = (values) => {
return [...values].map(
(value) => flattenOptions().find((option) => getOptionValue(option) === value)
).filter((option) => option != null);
};
const disclosureState = createDisclosureState({
open: () => local.open,
defaultOpen: () => local.defaultOpen,
onOpenChange: (isOpen) => local.onOpenChange?.(isOpen)
});
const listState = createListState({
selectedKeys: () => {
if (local.value != null) {
return local.value.map(getOptionValue);
}
return local.value;
},
defaultSelectedKeys: () => {
if (local.defaultValue != null) {
return local.defaultValue.map(getOptionValue);
}
return local.defaultValue;
},
onSelectionChange: (selectedKeys) => {
local.onChange?.(getOptionsFromValues(selectedKeys));
if (local.closeOnSelection) {
close();
}
},
allowDuplicateSelectionEvents: () => access(local.allowDuplicateSelectionEvents),
disallowEmptySelection: () => access(local.disallowEmptySelection),
selectionBehavior: () => access(local.selectionBehavior),
selectionMode: () => local.selectionMode,
dataSource: () => local.options ?? [],
getKey: () => local.optionValue,
getTextValue: () => local.optionTextValue,
getDisabled: () => local.optionDisabled,
getSectionChildren: () => local.optionGroupChildren
});
const selectedOptions = createMemo(() => {
return getOptionsFromValues(listState.selectionManager().selectedKeys());
});
const removeOptionFromSelection = (option) => {
listState.selectionManager().toggleSelection(getOptionValue(option));
};
const { present: contentPresent } = createPresence({
show: () => local.forceMount || disclosureState.isOpen(),
element: () => contentRef() ?? null
});
const focusListbox = () => {
const listboxEl = listboxRef();
if (listboxEl) {
focusWithoutScrolling2(listboxEl);
}
};
const open = (focusStrategy2) => {
if (local.options.length <= 0) {
return;
}
setFocusStrategy(focusStrategy2);
disclosureState.open();
let focusedKey = listState.selectionManager().firstSelectedKey();
if (focusedKey == null) {
if (focusStrategy2 === "first") {
focusedKey = listState.collection().getFirstKey();
} else if (focusStrategy2 === "last") {
focusedKey = listState.collection().getLastKey();
}
}
focusListbox();
listState.selectionManager().setFocused(true);
listState.selectionManager().setFocusedKey(focusedKey);
};
const close = () => {
disclosureState.close();
listState.selectionManager().setFocused(false);
listState.selectionManager().setFocusedKey(void 0);
};
const toggle = (focusStrategy2) => {
if (disclosureState.isOpen()) {
close();
} else {
open(focusStrategy2);
}
};
const { formControlContext } = createFormControl(formControlProps);
createFormResetListener(triggerRef, () => {
const defaultSelectedKeys = local.defaultValue ? [...local.defaultValue].map(getOptionValue) : new Selection();
listState.selectionManager().setSelectedKeys(defaultSelectedKeys);
});
const collator = createCollator({ usage: "search", sensitivity: "base" });
const delegate = createMemo(() => {
const keyboardDelegate = access(local.keyboardDelegate);
if (keyboardDelegate) {
return keyboardDelegate;
}
return new ListKeyboardDelegate(listState.collection, void 0, collator);
});
const renderItem = (item) => {
return local.itemComponent?.({ item });
};
const renderSection = (section) => {
return local.sectionComponent?.({ section });
};
createEffect2(
on(
[flattenOptionKeys],
([flattenOptionKeys2]) => {
const currentSelectedKeys = [
...listState.selectionManager().selectedKeys()
];
const keysToKeep = currentSelectedKeys.filter(
(key) => flattenOptionKeys2.includes(key)
);
listState.selectionManager().setSelectedKeys(keysToKeep);
},
{
defer: true
}
)
);
const dataset = createMemo(() => ({
"data-expanded": disclosureState.isOpen() ? "" : void 0,
"data-closed": !disclosureState.isOpen() ? "" : void 0
}));
const context = {
dataset,
isOpen: disclosureState.isOpen,
isDisabled: () => formControlContext.isDisabled() ?? false,
isMultiple: () => access(local.selectionMode) === "multiple",
isVirtualized: () => local.virtualized ?? false,
isModal: () => local.modal ?? false,
preventScroll: () => local.preventScroll ?? context.isModal(),
disallowTypeAhead: () => local.disallowTypeAhead ?? false,
shouldFocusWrap: () => local.shouldFocusWrap ?? false,
selectedOptions,
contentPresent,
autoFocus: focusStrategy,
triggerRef,
listState: () => listState,
keyboardDelegate: delegate,
triggerId,
valueId,
listboxId,
listboxAriaLabelledBy,
setListboxAriaLabelledBy,
setTriggerRef,
setContentRef,
setListboxRef,
open,
close,
toggle,
placeholder: () => local.placeholder,
renderItem,
renderSection,
removeOptionFromSelection,
generateId: createGenerateId(() => access(formControlProps.id)),
registerTriggerId: createRegisterId(setTriggerId),
registerValueId: createRegisterId(setValueId),
registerListboxId: createRegisterId(setListboxId)
};
return <FormControlContext.Provider value={formControlContext}><SelectContext.Provider value={context}><Popper anchorRef={triggerRef} contentRef={contentRef} {...popperProps}><Polymorphic
as="div"
role="group"
id={access(formControlProps.id)}
{...formControlContext.dataset()}
{...dataset()}
{...others}
/></Popper></SelectContext.Provider></FormControlContext.Provider>;
}
// src/select/select-root.tsx
function SelectRoot(props) {
const [local, others] = splitProps5(
props,
["value", "defaultValue", "onChange", "multiple"]
);
const value = createMemo2(() => {
if (local.value != null) {
return local.multiple ? local.value : [local.value];
}
return local.value;
});
const defaultValue = createMemo2(() => {
if (local.defaultValue != null) {
return local.multiple ? local.defaultValue : [local.defaultValue];
}
return local.defaultValue;
});
const onChange = (value2) => {
if (local.multiple) {
local.onChange?.(value2 ?? []);
} else {
local.onChange?.(value2[0] ?? null);
}
};
return <SelectBase
value={value()}
defaultValue={defaultValue()}
onChange={onChange}
selectionMode={local.multiple ? "multiple" : "single"}
{...others}
/>;
}
// src/select/select-trigger.tsx
import { callHandler as callHandler3, mergeDefaultProps as mergeDefaultProps4, mergeRefs as mergeRefs3 } from "@kobalte/utils";
import {
createEffect as createEffect3,
onCleanup as onCleanup2,
splitProps as splitProps6
} from "solid-js";
function SelectTrigger(props) {
const formControlContext = useFormControlContext();
const context = useSelectContext();
const mergedProps = mergeDefaultProps4(
{
id: context.generateId("trigger")
},
props
);
const [local, formControlFieldProps, others] = splitProps6(
mergedProps,
[
"ref",
"disabled",
"onPointerDown",
"onClick",
"onKeyDown",
"onFocus",
"onBlur"
],
FORM_CONTROL_FIELD_PROP_NAMES
);
const selectionManager = () => context.listState().selectionManager();
const keyboardDelegate = () => context.keyboardDelegate();
const isDisabled = () => local.disabled || context.isDisabled();
const { fieldProps } = createFormControlField(formControlFieldProps);
const { typeSelectHandlers } = createTypeSelect({
keyboardDelegate,
selectionManager,
onTypeSelect: (key) => selectionManager().select(key)
});
const ariaLabelledBy = () => {
return [context.listboxAriaLabelledBy(), context.valueId()].filter(Boolean).join(" ") || void 0;
};
const onPointerDown = (e) => {
callHandler3(e, local.onPointerDown);
e.currentTarget.dataset.pointerType = e.pointerType;
if (!isDisabled() && e.pointerType !== "touch" && e.button === 0) {
e.preventDefault();
context.toggle(true);
}
};
const onClick = (e) => {
callHandler3(e, local.onClick);
if (!isDisabled() && e.currentTarget.dataset.pointerType === "touch") {
context.toggle(true);
}
};
const onKeyDown = (e) => {
callHandler3(e, local.onKeyDown);
if (isDisabled()) {
return;
}
callHandler3(e, typeSelectHandlers.onKeyDown);
switch (e.key) {
case "Enter":
case " ":
case "ArrowDown":
e.stopPropagation();
e.preventDefault();
context.toggle("first");
break;
case "ArrowUp":
e.stopPropagation();
e.preventDefault();
context.toggle("last");
break;
case "ArrowLeft": {
e.preventDefault();
if (context.isMultiple()) {
return;
}
const firstSelectedKey = selectionManager().firstSelectedKey();
const key = firstSelectedKey != null ? keyboardDelegate().getKeyAbove?.(firstSelectedKey) : keyboardDelegate().getFirstKey?.();
if (key != null) {
selectionManager().select(key);
}
break;
}
case "ArrowRight": {
e.preventDefault();
if (context.isMultiple()) {
return;
}
const firstSelectedKey = selectionManager().firstSelectedKey();
const key = firstSelectedKey != null ? keyboardDelegate().getKeyBelow?.(firstSelectedKey) : keyboardDelegate().getFirstKey?.();
if (key != null) {
selectionManager().select(key);
}
break;
}
}
};
const onFocus = (e) => {
callHandler3(e, local.onFocus);
if (selectionManager().isFocused()) {
return;
}
selectionManager().setFocused(true);
};
const onBlur = (e) => {
callHandler3(e, local.onBlur);
if (context.isOpen()) {
return;
}
selectionManager().setFocused(false);
};
createEffect3(() => onCleanup2(context.registerTriggerId(fieldProps.id())));
createEffect3(() => {
context.setListboxAriaLabelledBy(
[
fieldProps.ariaLabelledBy(),
fieldProps.ariaLabel() && !fieldProps.ariaLabelledBy() ? fieldProps.id() : null
].filter(Boolean).join(" ") || void 0
);
});
return <ButtonRoot
ref={mergeRefs3(context.setTriggerRef, local.ref)}
id={fieldProps.id()}
disabled={isDisabled()}
aria-haspopup="listbox"
aria-expanded={context.isOpen()}
aria-controls={context.isOpen() ? context.listboxId() : void 0}
aria-label={fieldProps.ariaLabel()}
aria-labelledby={ariaLabelledBy()}
aria-describedby={fieldProps.ariaDescribedBy()}
onPointerDown={onPointerDown}
onClick={onClick}
onKeyDown={onKeyDown}
onFocus={onFocus}
onBlur={onBlur}
{...context.dataset()}
{...formControlContext.dataset()}
{...others}
/>;
}
// src/select/select-value.tsx
import {
isFunction as isFunction2,
mergeDefaultProps as mergeDefaultProps5
} from "@kobalte/utils";
import {
Show as Show3,
children,
createEffect as createEffect4,
onCleanup as onCleanup3,
splitProps as splitProps7
} from "solid-js";
function SelectValue(props) {
const formControlContext = useFormControlContext();
const context = useSelectContext();
const mergedProps = mergeDefaultProps5(
{
id: context.generateId("value")
},
props
);
const [local, others] = splitProps7(mergedProps, ["id", "children"]);
const selectionManager = () => context.listState().selectionManager();
const isSelectionEmpty = () => {
const selectedKeys = selectionManager().selectedKeys();
if (selectedKeys.size === 1 && selectedKeys.has("")) {
return true;
}
return selectionManager().isEmpty();
};
createEffect4(() => onCleanup3(context.registerValueId(local.id)));
return <Polymorphic
as="span"
id={local.id}
data-placeholder-shown={isSelectionEmpty() ? "" : void 0}
{...formControlContext.dataset()}
{...others}
><Show3 when={!isSelectionEmpty()} fallback={context.placeholder()}><SelectValueChild
state={{
selectedOption: () => context.selectedOptions()[0],
selectedOptions: () => context.selectedOptions(),
remove: (option) => context.removeOptionFromSelection(option),
clear: () => selectionManager().clearSelection()
}}
>{local.children}</SelectValueChild></Show3></Polymorphic>;
}
function SelectValueChild(props) {
const resolvedChildren = children(() => {
const body = props.children;
return isFunction2(body) ? body(props.state) : body;
});
return <>{resolvedChildren()}</>;
}
// src/select/index.tsx
var Select = Object.assign(SelectRoot, {
Arrow: PopperArrow,
Content: SelectContent,
Description: FormControlDescription,
ErrorMessage: FormControlErrorMessage,
HiddenSelect: SelectHiddenSelect,
Icon: SelectIcon,
Item: ListboxItem,
ItemDescription: ListboxItemDescription,
ItemIndicator: ListboxItemIndicator,
ItemLabel: ListboxItemLabel,
Label: SelectLabel,
Listbox: SelectListbox,
Portal: SelectPortal,
Section: ListboxSection,
Trigger: SelectTrigger,
Value: SelectValue
});
export {
useSelectContext,
SelectContent,
SelectHiddenSelect,
SelectIcon,
SelectLabel,
SelectListbox,
SelectPortal,
SelectRoot,
SelectTrigger,
SelectValue,
Select,
select_exports
};