@kobalte/core
Version:
Unstyled components and primitives for building accessible web apps and design systems with SolidJS.
1,622 lines (1,586 loc) • 47.1 kB
JSX
import {
Popper
} from "./2CTBMVJ4.jsx";
import {
createSelectableList
} from "./N3GAC5SS.jsx";
import {
createListState,
createSelectableItem
} from "./QZDH5R5B.jsx";
import {
createDomCollection,
createDomCollectionItem,
useOptionalDomCollectionContext
} from "./SOM3K36D.jsx";
import {
useLocale
} 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 {
createToggleState
} from "./VI7QYH27.jsx";
import {
createRegisterId
} from "./JNCCF6MP.jsx";
import {
createControllableSignal
} from "./FN6EICGO.jsx";
import {
createTagName
} from "./OYES4GOP.jsx";
import {
Polymorphic
} from "./FLVHQV4A.jsx";
// src/menubar/menubar-context.tsx
import {
createContext,
useContext
} from "solid-js";
var MenubarContext = createContext();
function useOptionalMenubarContext() {
return useContext(MenubarContext);
}
function useMenubarContext() {
const context = useOptionalMenubarContext();
if (context === void 0) {
throw new Error(
"[kobalte]: `useMenubarContext` must be used within a `Menubar` component"
);
}
return context;
}
// src/navigation-menu/navigation-menu-context.tsx
import {
createContext as createContext2,
useContext as useContext2
} from "solid-js";
var NavigationMenuContext = createContext2();
function useOptionalNavigationMenuContext() {
return useContext2(NavigationMenuContext);
}
function useNavigationMenuContext() {
const context = useOptionalNavigationMenuContext();
if (context === void 0) {
throw new Error(
"[kobalte]: `useNavigationMenuContext` must be used within a `NavigationMenu` component"
);
}
return context;
}
// src/menu/menu-checkbox-item.tsx
import { mergeDefaultProps as mergeDefaultProps2 } from "@kobalte/utils";
import { splitProps as splitProps2 } from "solid-js";
// src/menu/menu-item-base.tsx
import {
callHandler,
composeEventHandlers,
createGenerateId,
focusWithoutScrolling,
mergeDefaultProps,
mergeRefs
} from "@kobalte/utils";
import {
createMemo,
createSignal,
createUniqueId,
splitProps
} from "solid-js";
// src/menu/menu-context.tsx
import { createContext as createContext3, useContext as useContext3 } from "solid-js";
var MenuContext = createContext3();
function useOptionalMenuContext() {
return useContext3(MenuContext);
}
function useMenuContext() {
const context = useOptionalMenuContext();
if (context === void 0) {
throw new Error(
"[kobalte]: `useMenuContext` must be used within a `Menu` component"
);
}
return context;
}
// src/menu/menu-item.context.tsx
import { createContext as createContext4, useContext as useContext4 } from "solid-js";
var MenuItemContext = createContext4();
function useMenuItemContext() {
const context = useContext4(MenuItemContext);
if (context === void 0) {
throw new Error(
"[kobalte]: `useMenuItemContext` must be used within a `Menu.Item` component"
);
}
return context;
}
// src/menu/menu-root-context.tsx
import { createContext as createContext5, useContext as useContext5 } from "solid-js";
var MenuRootContext = createContext5();
function useMenuRootContext() {
const context = useContext5(MenuRootContext);
if (context === void 0) {
throw new Error(
"[kobalte]: `useMenuRootContext` must be used within a `MenuRoot` component"
);
}
return context;
}
// src/menu/menu-item-base.tsx
function MenuItemBase(props) {
let ref;
const rootContext = useMenuRootContext();
const menuContext = useMenuContext();
const mergedProps = mergeDefaultProps(
{
id: rootContext.generateId(`item-${createUniqueId()}`)
},
props
);
const [local, others] = splitProps(mergedProps, [
"ref",
"textValue",
"disabled",
"closeOnSelect",
"checked",
"indeterminate",
"onSelect",
"onPointerMove",
"onPointerLeave",
"onPointerDown",
"onPointerUp",
"onClick",
"onKeyDown",
"onMouseDown",
"onFocus"
]);
const [labelId, setLabelId] = createSignal();
const [descriptionId, setDescriptionId] = createSignal();
const [labelRef, setLabelRef] = createSignal();
const selectionManager = () => menuContext.listState().selectionManager();
const key = () => others.id;
const isHighlighted = () => selectionManager().focusedKey() === key();
const onSelect = () => {
local.onSelect?.();
if (local.closeOnSelect) {
setTimeout(() => {
menuContext.close(true);
});
}
};
createDomCollectionItem({
getItem: () => ({
ref: () => ref,
type: "item",
key: key(),
textValue: local.textValue ?? labelRef()?.textContent ?? ref?.textContent ?? "",
disabled: local.disabled ?? false
})
});
const selectableItem = createSelectableItem(
{
key,
selectionManager,
shouldSelectOnPressUp: true,
allowsDifferentPressOrigin: true,
disabled: () => local.disabled
},
() => ref
);
const onPointerMove = (e) => {
callHandler(e, local.onPointerMove);
if (e.pointerType !== "mouse") {
return;
}
if (local.disabled) {
menuContext.onItemLeave(e);
} else {
menuContext.onItemEnter(e);
if (!e.defaultPrevented) {
focusWithoutScrolling(e.currentTarget);
menuContext.listState().selectionManager().setFocused(true);
menuContext.listState().selectionManager().setFocusedKey(key());
}
}
};
const onPointerLeave = (e) => {
callHandler(e, local.onPointerLeave);
if (e.pointerType !== "mouse") {
return;
}
menuContext.onItemLeave(e);
};
const onPointerUp = (e) => {
callHandler(e, local.onPointerUp);
if (!local.disabled && e.button === 0) {
onSelect();
}
};
const onKeyDown = (e) => {
callHandler(e, local.onKeyDown);
if (e.repeat) {
return;
}
if (local.disabled) {
return;
}
switch (e.key) {
case "Enter":
case " ":
onSelect();
break;
}
};
const ariaChecked = createMemo(() => {
if (local.indeterminate) {
return "mixed";
}
if (local.checked == null) {
return void 0;
}
return local.checked;
});
const dataset = createMemo(() => ({
"data-indeterminate": local.indeterminate ? "" : void 0,
"data-checked": local.checked && !local.indeterminate ? "" : void 0,
"data-disabled": local.disabled ? "" : void 0,
"data-highlighted": isHighlighted() ? "" : void 0
}));
const context = {
isChecked: () => local.checked,
dataset,
setLabelRef,
generateId: createGenerateId(() => others.id),
registerLabel: createRegisterId(setLabelId),
registerDescription: createRegisterId(setDescriptionId)
};
return <MenuItemContext.Provider value={context}><Polymorphic
as="div"
ref={mergeRefs((el) => ref = el, local.ref)}
tabIndex={selectableItem.tabIndex()}
aria-checked={ariaChecked()}
aria-disabled={local.disabled}
aria-labelledby={labelId()}
aria-describedby={descriptionId()}
data-key={selectableItem.dataKey()}
onPointerDown={composeEventHandlers([
local.onPointerDown,
selectableItem.onPointerDown
])}
onPointerUp={composeEventHandlers([
onPointerUp,
selectableItem.onPointerUp
])}
onClick={composeEventHandlers([local.onClick, selectableItem.onClick])}
onKeyDown={composeEventHandlers([onKeyDown, selectableItem.onKeyDown])}
onMouseDown={composeEventHandlers([
local.onMouseDown,
selectableItem.onMouseDown
])}
onFocus={composeEventHandlers([local.onFocus, selectableItem.onFocus])}
onPointerMove={onPointerMove}
onPointerLeave={onPointerLeave}
{...dataset()}
{...others}
/></MenuItemContext.Provider>;
}
// src/menu/menu-checkbox-item.tsx
function MenuCheckboxItem(props) {
const mergedProps = mergeDefaultProps2(
{
closeOnSelect: false
},
props
);
const [local, others] = splitProps2(mergedProps, [
"checked",
"defaultChecked",
"onChange",
"onSelect"
]);
const state = createToggleState({
isSelected: () => local.checked,
defaultIsSelected: () => local.defaultChecked,
onSelectedChange: (checked) => local.onChange?.(checked),
isDisabled: () => others.disabled
});
const onSelect = () => {
local.onSelect?.();
state.toggle();
};
return <MenuItemBase
role="menuitemcheckbox"
checked={state.isSelected()}
onSelect={onSelect}
{...others}
/>;
}
// src/menu/menu-trigger.tsx
import {
callHandler as callHandler2,
mergeDefaultProps as mergeDefaultProps3,
mergeRefs as mergeRefs2,
scrollIntoViewport
} from "@kobalte/utils";
import {
createEffect,
createMemo as createMemo2,
on,
onCleanup,
splitProps as splitProps3
} from "solid-js";
var MENUBAR_KEYS = {
next: (dir, orientation) => dir === "ltr" ? orientation === "horizontal" ? "ArrowRight" : "ArrowDown" : orientation === "horizontal" ? "ArrowLeft" : "ArrowUp",
previous: (dir, orientation) => MENUBAR_KEYS.next(dir === "ltr" ? "rtl" : "ltr", orientation)
};
var MENU_KEYS = {
first: (orientation) => orientation === "horizontal" ? "ArrowDown" : "ArrowRight",
last: (orientation) => orientation === "horizontal" ? "ArrowUp" : "ArrowLeft"
};
function MenuTrigger(props) {
const rootContext = useMenuRootContext();
const context = useMenuContext();
const optionalMenubarContext = useOptionalMenubarContext();
const { direction } = useLocale();
const mergedProps = mergeDefaultProps3(
{
id: rootContext.generateId("trigger")
},
props
);
const [local, others] = splitProps3(mergedProps, [
"ref",
"id",
"disabled",
"onPointerDown",
"onClick",
"onKeyDown",
"onMouseOver",
"onFocus"
]);
let key = () => rootContext.value();
if (optionalMenubarContext !== void 0) {
key = () => rootContext.value() ?? local.id;
if (optionalMenubarContext.lastValue() === void 0)
optionalMenubarContext.setLastValue(key);
}
const tagName = createTagName(
() => context.triggerRef(),
() => "button"
);
const isNativeLink = createMemo2(() => {
return tagName() === "a" && context.triggerRef()?.getAttribute("href") != null;
});
createEffect(
on(
() => optionalMenubarContext?.value(),
(value) => {
if (!isNativeLink())
return;
if (value === key())
context.triggerRef()?.focus();
}
)
);
const handleClick = () => {
if (optionalMenubarContext !== void 0) {
if (!context.isOpen()) {
if (!optionalMenubarContext.autoFocusMenu()) {
optionalMenubarContext.setAutoFocusMenu(true);
}
context.open(false);
} else {
if (optionalMenubarContext.value() === key())
optionalMenubarContext.closeMenu();
}
} else
context.toggle(true);
};
const onPointerDown = (e) => {
callHandler2(e, local.onPointerDown);
e.currentTarget.dataset.pointerType = e.pointerType;
if (!local.disabled && e.pointerType !== "touch" && e.button === 0) {
handleClick();
}
};
const onClick = (e) => {
callHandler2(e, local.onClick);
if (!local.disabled) {
if (e.currentTarget.dataset.pointerType === "touch")
handleClick();
}
};
const onKeyDown = (e) => {
callHandler2(e, local.onKeyDown);
if (local.disabled) {
return;
}
if (isNativeLink()) {
switch (e.key) {
case "Enter":
case " ":
return;
}
}
switch (e.key) {
case "Enter":
case " ":
case MENU_KEYS.first(rootContext.orientation()):
e.stopPropagation();
e.preventDefault();
scrollIntoViewport(e.currentTarget);
context.open("first");
optionalMenubarContext?.setAutoFocusMenu(true);
optionalMenubarContext?.setValue(key);
break;
case MENU_KEYS.last(rootContext.orientation()):
e.stopPropagation();
e.preventDefault();
context.open("last");
break;
case MENUBAR_KEYS.next(direction(), rootContext.orientation()):
if (optionalMenubarContext === void 0)
break;
e.stopPropagation();
e.preventDefault();
optionalMenubarContext.nextMenu();
break;
case MENUBAR_KEYS.previous(direction(), rootContext.orientation()):
if (optionalMenubarContext === void 0)
break;
e.stopPropagation();
e.preventDefault();
optionalMenubarContext.previousMenu();
break;
}
};
const onMouseOver = (e) => {
callHandler2(e, local.onMouseOver);
if (context.triggerRef()?.dataset.pointerType === "touch")
return;
if (!local.disabled && optionalMenubarContext !== void 0 && optionalMenubarContext.value() !== void 0) {
optionalMenubarContext.setValue(key);
}
};
const onFocus = (e) => {
callHandler2(e, local.onFocus);
if (optionalMenubarContext !== void 0 && e.currentTarget.dataset.pointerType !== "touch")
optionalMenubarContext.setValue(key);
};
createEffect(() => onCleanup(context.registerTriggerId(local.id)));
return <ButtonRoot
ref={mergeRefs2(context.setTriggerRef, local.ref)}
data-kb-menu-value-trigger={rootContext.value()}
id={local.id}
disabled={local.disabled}
aria-haspopup="true"
aria-expanded={context.isOpen()}
aria-controls={context.isOpen() ? context.contentId() : void 0}
data-highlighted={key() !== void 0 && optionalMenubarContext?.value() === key() ? true : void 0}
tabIndex={optionalMenubarContext !== void 0 ? optionalMenubarContext.value() === key() || optionalMenubarContext.lastValue() === key() ? 0 : -1 : void 0}
onPointerDown={onPointerDown}
onMouseOver={onMouseOver}
onClick={onClick}
onKeyDown={onKeyDown}
onFocus={onFocus}
role={optionalMenubarContext !== void 0 ? "menuitem" : void 0}
{...context.dataset()}
{...others}
/>;
}
// src/menu/menu-content.tsx
import { mergeRefs as mergeRefs4 } from "@kobalte/utils";
import { splitProps as splitProps5 } from "solid-js";
import createPreventScroll from "solid-prevent-scroll";
// src/menu/menu-content-base.tsx
import {
callHandler as callHandler3,
composeEventHandlers as composeEventHandlers2,
contains,
mergeDefaultProps as mergeDefaultProps4,
mergeRefs as mergeRefs3
} from "@kobalte/utils";
import {
Show,
createEffect as createEffect2,
createUniqueId as createUniqueId2,
onCleanup as onCleanup2,
splitProps as splitProps4
} from "solid-js";
import { combineStyle } from "@solid-primitives/props";
function MenuContentBase(props) {
let ref;
const rootContext = useMenuRootContext();
const context = useMenuContext();
const optionalMenubarContext = useOptionalMenubarContext();
const optionalNavigationMenuContext = useOptionalNavigationMenuContext();
const { direction } = useLocale();
const mergedProps = mergeDefaultProps4(
{
id: rootContext.generateId(`content-${createUniqueId2()}`)
},
props
);
const [local, others] = splitProps4(mergedProps, [
"ref",
"id",
"style",
"onOpenAutoFocus",
"onCloseAutoFocus",
"onEscapeKeyDown",
"onFocusOutside",
"onPointerEnter",
"onPointerMove",
"onKeyDown",
"onMouseDown",
"onFocusIn",
"onFocusOut"
]);
let lastPointerX = 0;
const isRootModalContent = () => {
return context.parentMenuContext() == null && optionalMenubarContext === void 0 && rootContext.isModal();
};
const selectableList = createSelectableList(
{
selectionManager: context.listState().selectionManager,
collection: context.listState().collection,
autoFocus: context.autoFocus,
deferAutoFocus: true,
// ensure all menu items are mounted and collection is not empty before trying to autofocus.
shouldFocusWrap: true,
disallowTypeAhead: () => !context.listState().selectionManager().isFocused(),
orientation: () => rootContext.orientation() === "horizontal" ? "vertical" : "horizontal"
},
() => ref
);
createFocusScope(
{
trapFocus: () => isRootModalContent() && context.isOpen(),
onMountAutoFocus: (event) => {
if (optionalMenubarContext === void 0)
local.onOpenAutoFocus?.(event);
},
onUnmountAutoFocus: local.onCloseAutoFocus
},
() => ref
);
const onKeyDown = (e) => {
if (!contains(e.currentTarget, e.target)) {
return;
}
if (e.key === "Tab" && context.isOpen()) {
e.preventDefault();
}
if (optionalMenubarContext !== void 0) {
if (e.currentTarget.getAttribute("aria-haspopup") !== "true")
switch (e.key) {
case MENUBAR_KEYS.next(direction(), rootContext.orientation()):
e.stopPropagation();
e.preventDefault();
context.close(true);
optionalMenubarContext.setAutoFocusMenu(true);
optionalMenubarContext.nextMenu();
break;
case MENUBAR_KEYS.previous(direction(), rootContext.orientation()):
if (e.currentTarget.hasAttribute("data-closed"))
break;
e.stopPropagation();
e.preventDefault();
context.close(true);
optionalMenubarContext.setAutoFocusMenu(true);
optionalMenubarContext.previousMenu();
break;
}
}
};
const onEscapeKeyDown = (e) => {
local.onEscapeKeyDown?.(e);
optionalMenubarContext?.setAutoFocusMenu(false);
context.close(true);
};
const onFocusOutside = (e) => {
local.onFocusOutside?.(e);
if (rootContext.isModal()) {
e.preventDefault();
}
};
const onPointerEnter = (e) => {
callHandler3(e, local.onPointerEnter);
if (!context.isOpen()) {
return;
}
context.parentMenuContext()?.listState().selectionManager().setFocused(false);
context.parentMenuContext()?.listState().selectionManager().setFocusedKey(void 0);
};
const onPointerMove = (e) => {
callHandler3(e, local.onPointerMove);
if (e.pointerType !== "mouse") {
return;
}
const target = e.target;
const pointerXHasChanged = lastPointerX !== e.clientX;
if (contains(e.currentTarget, target) && pointerXHasChanged) {
context.setPointerDir(e.clientX > lastPointerX ? "right" : "left");
lastPointerX = e.clientX;
}
};
createEffect2(() => onCleanup2(context.registerContentId(local.id)));
onCleanup2(() => context.setContentRef(void 0));
const commonAttributes = {
ref: mergeRefs3((el) => {
context.setContentRef(el);
ref = el;
}, local.ref),
role: "menu",
get id() {
return local.id;
},
get tabIndex() {
return selectableList.tabIndex();
},
get "aria-labelledby"() {
return context.triggerId();
},
onKeyDown: composeEventHandlers2([
local.onKeyDown,
selectableList.onKeyDown,
onKeyDown
]),
onMouseDown: composeEventHandlers2([
local.onMouseDown,
selectableList.onMouseDown
]),
onFocusIn: composeEventHandlers2([
local.onFocusIn,
selectableList.onFocusIn
]),
onFocusOut: composeEventHandlers2([
local.onFocusOut,
selectableList.onFocusOut
]),
onPointerEnter,
onPointerMove,
get "data-orientation"() {
return rootContext.orientation();
}
};
return <Show when={context.contentPresent()}><Show
when={optionalNavigationMenuContext === void 0 || context.parentMenuContext() != null}
fallback={<Polymorphic
as="div"
{...context.dataset()}
{...commonAttributes}
{...others}
/>}
><Popper.Positioner><DismissableLayer
disableOutsidePointerEvents={isRootModalContent() && context.isOpen()}
excludedElements={[context.triggerRef]}
bypassTopMostLayerCheck
style={combineStyle(
{
"--kb-menu-content-transform-origin": "var(--kb-popper-content-transform-origin)",
position: "relative"
},
local.style
)}
onEscapeKeyDown={onEscapeKeyDown}
onFocusOutside={onFocusOutside}
onDismiss={context.close}
{...context.dataset()}
{...commonAttributes}
{...others}
/></Popper.Positioner></Show></Show>;
}
// src/menu/menu-content.tsx
function MenuContent(props) {
let ref;
const rootContext = useMenuRootContext();
const context = useMenuContext();
const [local, others] = splitProps5(props, ["ref"]);
createPreventScroll({
element: () => ref ?? null,
enabled: () => context.contentPresent() && rootContext.preventScroll()
});
return <MenuContentBase
ref={mergeRefs4((el) => {
ref = el;
}, local.ref)}
{...others}
/>;
}
// src/menu/menu-group.tsx
import { createGenerateId as createGenerateId2, mergeDefaultProps as mergeDefaultProps5 } from "@kobalte/utils";
import { createSignal as createSignal2, createUniqueId as createUniqueId3 } from "solid-js";
// src/menu/menu-group-context.tsx
import { createContext as createContext6, useContext as useContext6 } from "solid-js";
var MenuGroupContext = createContext6();
function useMenuGroupContext() {
const context = useContext6(MenuGroupContext);
if (context === void 0) {
throw new Error(
"[kobalte]: `useMenuGroupContext` must be used within a `Menu.Group` component"
);
}
return context;
}
// src/menu/menu-group.tsx
function MenuGroup(props) {
const rootContext = useMenuRootContext();
const mergedProps = mergeDefaultProps5(
{
id: rootContext.generateId(`group-${createUniqueId3()}`)
},
props
);
const [labelId, setLabelId] = createSignal2();
const context = {
generateId: createGenerateId2(() => mergedProps.id),
registerLabelId: createRegisterId(setLabelId)
};
return <MenuGroupContext.Provider value={context}><Polymorphic
as="div"
role="group"
aria-labelledby={labelId()}
{...mergedProps}
/></MenuGroupContext.Provider>;
}
// src/menu/menu-group-label.tsx
import { mergeDefaultProps as mergeDefaultProps6 } from "@kobalte/utils";
import {
createEffect as createEffect3,
onCleanup as onCleanup3,
splitProps as splitProps6
} from "solid-js";
function MenuGroupLabel(props) {
const context = useMenuGroupContext();
const mergedProps = mergeDefaultProps6(
{
id: context.generateId("label")
},
props
);
const [local, others] = splitProps6(mergedProps, ["id"]);
createEffect3(() => onCleanup3(context.registerLabelId(local.id)));
return <Polymorphic
as="span"
id={local.id}
aria-hidden="true"
{...others}
/>;
}
// src/menu/menu-icon.tsx
import { mergeDefaultProps as mergeDefaultProps7 } from "@kobalte/utils";
function MenuIcon(props) {
const context = useMenuContext();
const mergedProps = mergeDefaultProps7(
{ children: "\u25BC" },
props
);
return <Polymorphic
as="span"
aria-hidden="true"
{...context.dataset()}
{...mergedProps}
/>;
}
// src/menu/menu-item.tsx
function MenuItem(props) {
return <MenuItemBase
role="menuitem"
closeOnSelect
{...props}
/>;
}
// src/menu/menu-item-description.tsx
import { mergeDefaultProps as mergeDefaultProps8 } from "@kobalte/utils";
import {
createEffect as createEffect4,
onCleanup as onCleanup4,
splitProps as splitProps7
} from "solid-js";
function MenuItemDescription(props) {
const context = useMenuItemContext();
const mergedProps = mergeDefaultProps8(
{
id: context.generateId("description")
},
props
);
const [local, others] = splitProps7(mergedProps, ["id"]);
createEffect4(() => onCleanup4(context.registerDescription(local.id)));
return <Polymorphic
as="div"
id={local.id}
{...context.dataset()}
{...others}
/>;
}
// src/menu/menu-item-indicator.tsx
import { mergeDefaultProps as mergeDefaultProps9 } from "@kobalte/utils";
import { Show as Show2, splitProps as splitProps8 } from "solid-js";
function MenuItemIndicator(props) {
const context = useMenuItemContext();
const mergedProps = mergeDefaultProps9(
{
id: context.generateId("indicator")
},
props
);
const [local, others] = splitProps8(mergedProps, ["forceMount"]);
return <Show2 when={local.forceMount || context.isChecked()}><Polymorphic
as="div"
{...context.dataset()}
{...others}
/></Show2>;
}
// src/menu/menu-item-label.tsx
import { mergeDefaultProps as mergeDefaultProps10, mergeRefs as mergeRefs5 } from "@kobalte/utils";
import {
createEffect as createEffect5,
onCleanup as onCleanup5,
splitProps as splitProps9
} from "solid-js";
function MenuItemLabel(props) {
const context = useMenuItemContext();
const mergedProps = mergeDefaultProps10(
{
id: context.generateId("label")
},
props
);
const [local, others] = splitProps9(mergedProps, ["ref", "id"]);
createEffect5(() => onCleanup5(context.registerLabel(local.id)));
return <Polymorphic
as="div"
ref={mergeRefs5(context.setLabelRef, local.ref)}
id={local.id}
{...context.dataset()}
{...others}
/>;
}
// src/menu/menu-portal.tsx
import { Show as Show3 } from "solid-js";
import { Portal } from "solid-js/web";
function MenuPortal(props) {
const context = useMenuContext();
return <Show3 when={context.contentPresent()}><Portal {...props} /></Show3>;
}
// src/menu/menu-radio-group.tsx
import { mergeDefaultProps as mergeDefaultProps11 } from "@kobalte/utils";
import {
createUniqueId as createUniqueId4,
splitProps as splitProps10
} from "solid-js";
// src/menu/menu-radio-group-context.tsx
import { createContext as createContext7, useContext as useContext7 } from "solid-js";
var MenuRadioGroupContext = createContext7();
function useMenuRadioGroupContext() {
const context = useContext7(MenuRadioGroupContext);
if (context === void 0) {
throw new Error(
"[kobalte]: `useMenuRadioGroupContext` must be used within a `Menu.RadioGroup` component"
);
}
return context;
}
// src/menu/menu-radio-group.tsx
function MenuRadioGroup(props) {
const rootContext = useMenuRootContext();
const defaultId = rootContext.generateId(`radiogroup-${createUniqueId4()}`);
const mergedProps = mergeDefaultProps11(
{
id: defaultId
},
props
);
const [local, others] = splitProps10(mergedProps, [
"value",
"defaultValue",
"onChange",
"disabled"
]);
const [selected, setSelected] = createControllableSignal({
value: () => local.value,
defaultValue: () => local.defaultValue,
onChange: (value) => local.onChange?.(value)
});
const context = {
isDisabled: () => local.disabled,
isSelectedValue: (value) => value === selected(),
setSelectedValue: (value) => setSelected(value)
};
return <MenuRadioGroupContext.Provider value={context}><MenuGroup
{...others}
/></MenuRadioGroupContext.Provider>;
}
// src/menu/menu-radio-item.tsx
import { mergeDefaultProps as mergeDefaultProps12 } from "@kobalte/utils";
import { splitProps as splitProps11 } from "solid-js";
function MenuRadioItem(props) {
const context = useMenuRadioGroupContext();
const mergedProps = mergeDefaultProps12(
{ closeOnSelect: false },
props
);
const [local, others] = splitProps11(mergedProps, ["value", "onSelect"]);
const onSelect = () => {
local.onSelect?.();
context.setSelectedValue(local.value);
};
return <MenuItemBase
role="menuitemradio"
checked={context.isSelectedValue(local.value)}
onSelect={onSelect}
{...others}
/>;
}
// src/menu/menu.tsx
import {
focusWithoutScrolling as focusWithoutScrolling2,
mergeDefaultProps as mergeDefaultProps13,
removeItemFromArray
} from "@kobalte/utils";
import {
Show as Show4,
createEffect as createEffect6,
createMemo as createMemo3,
createSignal as createSignal3,
onCleanup as onCleanup6,
splitProps as splitProps12
} from "solid-js";
import createPresence from "solid-presence";
// src/menu/utils.ts
import { isPointInPolygon } from "@kobalte/utils";
function getPointerGraceArea(placement, event, contentEl) {
const basePlacement = placement.split("-")[0];
const contentRect = contentEl.getBoundingClientRect();
const polygon = [];
const pointerX = event.clientX;
const pointerY = event.clientY;
switch (basePlacement) {
case "top":
polygon.push([pointerX, pointerY + 5]);
polygon.push([contentRect.left, contentRect.bottom]);
polygon.push([contentRect.left, contentRect.top]);
polygon.push([contentRect.right, contentRect.top]);
polygon.push([contentRect.right, contentRect.bottom]);
break;
case "right":
polygon.push([pointerX - 5, pointerY]);
polygon.push([contentRect.left, contentRect.top]);
polygon.push([contentRect.right, contentRect.top]);
polygon.push([contentRect.right, contentRect.bottom]);
polygon.push([contentRect.left, contentRect.bottom]);
break;
case "bottom":
polygon.push([pointerX, pointerY - 5]);
polygon.push([contentRect.right, contentRect.top]);
polygon.push([contentRect.right, contentRect.bottom]);
polygon.push([contentRect.left, contentRect.bottom]);
polygon.push([contentRect.left, contentRect.top]);
break;
case "left":
polygon.push([pointerX + 5, pointerY]);
polygon.push([contentRect.right, contentRect.bottom]);
polygon.push([contentRect.left, contentRect.bottom]);
polygon.push([contentRect.left, contentRect.top]);
polygon.push([contentRect.right, contentRect.top]);
break;
}
return polygon;
}
function isPointerInGraceArea(event, area) {
if (!area) {
return false;
}
return isPointInPolygon([event.clientX, event.clientY], area);
}
// src/menu/menu.tsx
function Menu(props) {
const rootContext = useMenuRootContext();
const parentDomCollectionContext = useOptionalDomCollectionContext();
const parentMenuContext = useOptionalMenuContext();
const optionalMenubarContext = useOptionalMenubarContext();
const optionalNavigationMenuContext = useOptionalNavigationMenuContext();
const mergedProps = mergeDefaultProps13(
{
placement: rootContext.orientation() === "horizontal" ? "bottom-start" : "right-start"
},
props
);
const [local, others] = splitProps12(mergedProps, [
"open",
"defaultOpen",
"onOpenChange"
]);
let pointerGraceTimeoutId = 0;
let pointerGraceIntent = null;
let pointerDir = "right";
const [triggerId, setTriggerId] = createSignal3();
const [contentId, setContentId] = createSignal3();
const [triggerRef, setTriggerRef] = createSignal3();
const [contentRef, setContentRef] = createSignal3();
const [focusStrategy, setFocusStrategy] = createSignal3(true);
const [currentPlacement, setCurrentPlacement] = createSignal3(
others.placement
);
const [nestedMenus, setNestedMenus] = createSignal3([]);
const [items, setItems] = createSignal3([]);
const { DomCollectionProvider } = createDomCollection({
items,
onItemsChange: setItems
});
const disclosureState = createDisclosureState({
open: () => local.open,
defaultOpen: () => local.defaultOpen,
onOpenChange: (isOpen) => local.onOpenChange?.(isOpen)
});
const { present: contentPresent } = createPresence({
show: () => rootContext.forceMount() || disclosureState.isOpen(),
element: () => contentRef() ?? null
});
const listState = createListState({
selectionMode: "none",
dataSource: items
});
const open = (focusStrategy2) => {
setFocusStrategy(focusStrategy2);
disclosureState.open();
};
const close = (recursively = false) => {
disclosureState.close();
if (recursively && parentMenuContext) {
parentMenuContext.close(true);
}
};
const toggle = (focusStrategy2) => {
setFocusStrategy(focusStrategy2);
disclosureState.toggle();
};
const _focusContent = () => {
const content = contentRef();
if (content) {
focusWithoutScrolling2(content);
listState.selectionManager().setFocused(true);
listState.selectionManager().setFocusedKey(void 0);
}
};
const focusContent = () => {
if (optionalNavigationMenuContext != null)
setTimeout(() => _focusContent());
else
_focusContent();
};
const registerNestedMenu = (element) => {
setNestedMenus((prev) => [...prev, element]);
const parentUnregister = parentMenuContext?.registerNestedMenu(element);
return () => {
setNestedMenus((prev) => removeItemFromArray(prev, element));
parentUnregister?.();
};
};
const isPointerMovingToSubmenu = (e) => {
const isMovingTowards = pointerDir === pointerGraceIntent?.side;
return isMovingTowards && isPointerInGraceArea(e, pointerGraceIntent?.area);
};
const onItemEnter = (e) => {
if (isPointerMovingToSubmenu(e)) {
e.preventDefault();
}
};
const onItemLeave = (e) => {
if (isPointerMovingToSubmenu(e)) {
return;
}
focusContent();
};
const onTriggerLeave = (e) => {
if (isPointerMovingToSubmenu(e)) {
e.preventDefault();
}
};
createHideOutside({
isDisabled: () => {
return !(parentMenuContext == null && disclosureState.isOpen() && rootContext.isModal());
},
targets: () => [contentRef(), ...nestedMenus()].filter(Boolean)
});
createEffect6(() => {
const contentEl = contentRef();
if (!contentEl || !parentMenuContext) {
return;
}
const parentUnregister = parentMenuContext.registerNestedMenu(contentEl);
onCleanup6(() => {
parentUnregister();
});
});
createEffect6(() => {
if (parentMenuContext !== void 0)
return;
optionalMenubarContext?.registerMenu(rootContext.value(), [
contentRef(),
...nestedMenus()
]);
});
createEffect6(() => {
if (parentMenuContext !== void 0 || optionalMenubarContext === void 0)
return;
if (optionalMenubarContext.value() === rootContext.value()) {
triggerRef()?.focus();
if (optionalMenubarContext.autoFocusMenu())
open(true);
} else
close();
});
createEffect6(() => {
if (parentMenuContext !== void 0 || optionalMenubarContext === void 0)
return;
if (disclosureState.isOpen())
optionalMenubarContext.setValue(rootContext.value());
});
onCleanup6(() => {
if (parentMenuContext !== void 0)
return;
optionalMenubarContext?.unregisterMenu(rootContext.value());
});
const dataset = createMemo3(() => ({
"data-expanded": disclosureState.isOpen() ? "" : void 0,
"data-closed": !disclosureState.isOpen() ? "" : void 0
}));
const context = {
dataset,
isOpen: disclosureState.isOpen,
contentPresent,
nestedMenus,
currentPlacement,
pointerGraceTimeoutId: () => pointerGraceTimeoutId,
autoFocus: focusStrategy,
listState: () => listState,
parentMenuContext: () => parentMenuContext,
triggerRef,
contentRef,
triggerId,
contentId,
setTriggerRef,
setContentRef,
open,
close,
toggle,
focusContent,
onItemEnter,
onItemLeave,
onTriggerLeave,
setPointerDir: (dir) => pointerDir = dir,
setPointerGraceTimeoutId: (id) => pointerGraceTimeoutId = id,
setPointerGraceIntent: (intent) => pointerGraceIntent = intent,
registerNestedMenu,
registerItemToParentDomCollection: parentDomCollectionContext?.registerItem,
registerTriggerId: createRegisterId(setTriggerId),
registerContentId: createRegisterId(setContentId)
};
return <DomCollectionProvider><MenuContext.Provider value={context}><Show4
when={optionalNavigationMenuContext === void 0}
fallback={others.children}
><Popper
anchorRef={triggerRef}
contentRef={contentRef}
onCurrentPlacementChange={setCurrentPlacement}
{...others}
/></Show4></MenuContext.Provider></DomCollectionProvider>;
}
// src/menu/menu-sub.tsx
function MenuSub(props) {
const { direction } = useLocale();
return <Menu
placement={direction() === "rtl" ? "left-start" : "right-start"}
flip
{...props}
/>;
}
// src/menu/menu-sub-content.tsx
import {
callHandler as callHandler4,
contains as contains2,
focusWithoutScrolling as focusWithoutScrolling3
} from "@kobalte/utils";
import {
splitProps as splitProps13
} from "solid-js";
var SUB_CLOSE_KEYS = {
close: (dir, orientation) => {
if (dir === "ltr") {
return [orientation === "horizontal" ? "ArrowLeft" : "ArrowUp"];
}
return [orientation === "horizontal" ? "ArrowRight" : "ArrowDown"];
}
};
function MenuSubContent(props) {
const context = useMenuContext();
const rootContext = useMenuRootContext();
const [local, others] = splitProps13(props, [
"onFocusOutside",
"onKeyDown"
]);
const { direction } = useLocale();
const onOpenAutoFocus = (e) => {
e.preventDefault();
};
const onCloseAutoFocus = (e) => {
e.preventDefault();
};
const onFocusOutside = (e) => {
local.onFocusOutside?.(e);
const target = e.target;
if (!contains2(context.triggerRef(), target)) {
context.close();
}
};
const onKeyDown = (e) => {
callHandler4(e, local.onKeyDown);
const isKeyDownInside = contains2(e.currentTarget, e.target);
const isCloseKey = SUB_CLOSE_KEYS.close(
direction(),
rootContext.orientation()
).includes(e.key);
const isSubMenu = context.parentMenuContext() != null;
if (isKeyDownInside && isCloseKey && isSubMenu) {
context.close();
focusWithoutScrolling3(context.triggerRef());
}
};
return <MenuContentBase
onOpenAutoFocus={onOpenAutoFocus}
onCloseAutoFocus={onCloseAutoFocus}
onFocusOutside={onFocusOutside}
onKeyDown={onKeyDown}
{...others}
/>;
}
// src/menu/menu-sub-trigger.tsx
import {
callHandler as callHandler5,
composeEventHandlers as composeEventHandlers3,
focusWithoutScrolling as focusWithoutScrolling4,
mergeDefaultProps as mergeDefaultProps14,
mergeRefs as mergeRefs6
} from "@kobalte/utils";
import {
createEffect as createEffect7,
createUniqueId as createUniqueId5,
on as on2,
onCleanup as onCleanup7,
splitProps as splitProps14
} from "solid-js";
import { isServer } from "solid-js/web";
var SELECTION_KEYS = ["Enter", " "];
var SUB_OPEN_KEYS = {
open: (dir, orientation) => {
if (dir === "ltr") {
return [
...SELECTION_KEYS,
orientation === "horizontal" ? "ArrowRight" : "ArrowDown"
];
}
return [
...SELECTION_KEYS,
orientation === "horizontal" ? "ArrowLeft" : "ArrowUp"
];
}
};
function MenuSubTrigger(props) {
let ref;
const rootContext = useMenuRootContext();
const context = useMenuContext();
const mergedProps = mergeDefaultProps14(
{
id: rootContext.generateId(`sub-trigger-${createUniqueId5()}`)
},
props
);
const [local, others] = splitProps14(mergedProps, [
"ref",
"id",
"textValue",
"disabled",
"onPointerMove",
"onPointerLeave",
"onPointerDown",
"onPointerUp",
"onClick",
"onKeyDown",
"onMouseDown",
"onFocus"
]);
let openTimeoutId = null;
const clearOpenTimeout = () => {
if (isServer) {
return;
}
if (openTimeoutId) {
window.clearTimeout(openTimeoutId);
}
openTimeoutId = null;
};
const { direction } = useLocale();
const key = () => local.id;
const parentSelectionManager = () => {
const parentMenuContext = context.parentMenuContext();
if (parentMenuContext == null) {
throw new Error(
"[kobalte]: `Menu.SubTrigger` must be used within a `Menu.Sub` component"
);
}
return parentMenuContext.listState().selectionManager();
};
const collection = () => context.listState().collection();
const isHighlighted = () => parentSelectionManager().focusedKey() === key();
const selectableItem = createSelectableItem(
{
key,
selectionManager: parentSelectionManager,
shouldSelectOnPressUp: true,
allowsDifferentPressOrigin: true,
disabled: () => local.disabled
},
() => ref
);
const onClick = (e) => {
callHandler5(e, local.onClick);
if (!context.isOpen() && !local.disabled) {
context.open(true);
}
};
const onPointerMove = (e) => {
callHandler5(e, local.onPointerMove);
if (e.pointerType !== "mouse") {
return;
}
const parentMenuContext = context.parentMenuContext();
parentMenuContext?.onItemEnter(e);
if (e.defaultPrevented) {
return;
}
if (local.disabled) {
parentMenuContext?.onItemLeave(e);
return;
}
if (!context.isOpen() && !openTimeoutId) {
context.parentMenuContext()?.setPointerGraceIntent(null);
openTimeoutId = window.setTimeout(() => {
context.open(false);
clearOpenTimeout();
}, 100);
}
parentMenuContext?.onItemEnter(e);
if (!e.defaultPrevented) {
if (context.listState().selectionManager().isFocused()) {
context.listState().selectionManager().setFocused(false);
context.listState().selectionManager().setFocusedKey(void 0);
}
focusWithoutScrolling4(e.currentTarget);
parentMenuContext?.listState().selectionManager().setFocused(true);
parentMenuContext?.listState().selectionManager().setFocusedKey(key());
}
};
const onPointerLeave = (e) => {
callHandler5(e, local.onPointerLeave);
if (e.pointerType !== "mouse") {
return;
}
clearOpenTimeout();
const parentMenuContext = context.parentMenuContext();
const contentEl = context.contentRef();
if (contentEl) {
parentMenuContext?.setPointerGraceIntent({
area: getPointerGraceArea(context.currentPlacement(), e, contentEl),
// Safe because sub menu always open "left" or "right".
side: context.currentPlacement().split("-")[0]
});
window.clearTimeout(parentMenuContext?.pointerGraceTimeoutId());
const pointerGraceTimeoutId = window.setTimeout(() => {
parentMenuContext?.setPointerGraceIntent(null);
}, 300);
parentMenuContext?.setPointerGraceTimeoutId(pointerGraceTimeoutId);
} else {
parentMenuContext?.onTriggerLeave(e);
if (e.defaultPrevented) {
return;
}
parentMenuContext?.setPointerGraceIntent(null);
}
parentMenuContext?.onItemLeave(e);
};
const onKeyDown = (e) => {
callHandler5(e, local.onKeyDown);
if (e.repeat) {
return;
}
if (local.disabled) {
return;
}
if (SUB_OPEN_KEYS.open(direction(), rootContext.orientation()).includes(e.key)) {
e.stopPropagation();
e.preventDefault();
parentSelectionManager().setFocused(false);
parentSelectionManager().setFocusedKey(void 0);
if (!context.isOpen()) {
context.open("first");
}
context.focusContent();
context.listState().selectionManager().setFocused(true);
context.listState().selectionManager().setFocusedKey(collection().getFirstKey());
}
};
createEffect7(() => {
if (context.registerItemToParentDomCollection == null) {
throw new Error(
"[kobalte]: `Menu.SubTrigger` must be used within a `Menu.Sub` component"
);
}
const unregister = context.registerItemToParentDomCollection({
ref: () => ref,
type: "item",
key: key(),
textValue: local.textValue ?? ref?.textContent ?? "",
disabled: local.disabled ?? false
});
onCleanup7(unregister);
});
createEffect7(
on2(
() => context.parentMenuContext()?.pointerGraceTimeoutId(),
(pointerGraceTimer) => {
onCleanup7(() => {
window.clearTimeout(pointerGraceTimer);
context.parentMenuContext()?.setPointerGraceIntent(null);
});
}
)
);
createEffect7(() => onCleanup7(context.registerTriggerId(local.id)));
onCleanup7(() => {
clearOpenTimeout();
});
return <Polymorphic
as="div"
ref={mergeRefs6((el) => {
context.setTriggerRef(el);
ref = el;
}, local.ref)}
id={local.id}
role="menuitem"
tabIndex={selectableItem.tabIndex()}
aria-haspopup="true"
aria-expanded={context.isOpen()}
aria-controls={context.isOpen() ? context.contentId() : void 0}
aria-disabled={local.disabled}
data-key={selectableItem.dataKey()}
data-highlighted={isHighlighted() ? "" : void 0}
data-disabled={local.disabled ? "" : void 0}
onPointerDown={composeEventHandlers3([
local.onPointerDown,
selectableItem.onPointerDown
])}
onPointerUp={composeEventHandlers3([
local.onPointerUp,
selectableItem.onPointerUp
])}
onClick={composeEventHandlers3([onClick, selectableItem.onClick])}
onKeyDown={composeEventHandlers3([onKeyDown, selectableItem.onKeyDown])}
onMouseDown={composeEventHandlers3([
local.onMouseDown,
selectableItem.onMouseDown
])}
onFocus={composeEventHandlers3([local.onFocus, selectableItem.onFocus])}
onPointerMove={onPointerMove}
onPointerLeave={onPointerLeave}
{...context.dataset()}
{...others}
/>;
}
// src/menu/menu-root.tsx
import {
createGenerateId as createGenerateId3,
mergeDefaultProps as mergeDefaultProps15
} from "@kobalte/utils";
import { createUniqueId as createUniqueId6, splitProps as splitProps15 } from "solid-js";
function MenuRoot(props) {
const optionalMenubarContext = useOptionalMenubarContext();
const defaultId = `menu-${createUniqueId6()}`;
const mergedProps = mergeDefaultProps15(
{
id: defaultId,
modal: true
},
props
);
const [local, others] = splitProps15(mergedProps, [
"id",
"modal",
"preventScroll",
"forceMount",
"open",
"defaultOpen",
"onOpenChange",
"value",
"orientation"
]);
const disclosureState = createDisclosureState({
open: () => local.open,
defaultOpen: () => local.defaultOpen,
onOpenChange: (isOpen) => local.onOpenChange?.(isOpen)
});
const context = {
isModal: () => local.modal ?? true,
preventScroll: () => local.preventScroll ?? context.isModal(),
forceMount: () => local.forceMount ?? false,
generateId: createGenerateId3(() => local.id),
value: () => local.value,
orientation: () => local.orientation ?? optionalMenubarContext?.orientation() ?? "horizontal"
};
return <MenuRootContext.Provider value={context}><Menu
open={disclosureState.isOpen()}
onOpenChange={disclosureState.setIsOpen}
{...others}
/></MenuRootContext.Provider>;
}
export {
MenubarContext,
useMenubarContext,
NavigationMenuContext,
useNavigationMenuContext,
useOptionalMenuContext,
useMenuContext,
useMenuRootContext,
MenuCheckboxItem,
MenuTrigger,
MenuContent,
MenuGroup,
MenuGroupLabel,
MenuIcon,
MenuItem,
MenuItemDescription,
MenuItemIndicator,
MenuItemLabel,
MenuPortal,
MenuRadioGroup,
MenuRadioItem,
MenuRoot,
MenuSub,
MenuSubContent,
MenuSubTrigger
};