UNPKG

@kobalte/core

Version:

Unstyled components and primitives for building accessible web apps and design systems with SolidJS.

336 lines (329 loc) 11 kB
import { PopperArrow, Popper } from './4X2EKUJ3.js'; import { createHideOutside } from './455ZADO2.js'; import { createFocusScope } from './ISKHZMHS.js'; import { DismissableLayer } from './7KU4OSOB.js'; import { createDisclosureState } from './7LCANGHD.js'; import { ButtonRoot } from './7OVKXYPU.js'; import { createRegisterId } from './E4R2EMM4.js'; import { Polymorphic } from './6Y7B2NEO.js'; import { __export } from './5ZKAE4VZ.js'; import { createComponent, mergeProps, memo, Portal } from 'solid-js/web'; import { mergeRefs, mergeDefaultProps, createGenerateId, callHandler, focusWithoutScrolling, contains } from '@kobalte/utils'; import { createContext, splitProps, createEffect, onCleanup, Show, createUniqueId, createSignal, createMemo, useContext } from 'solid-js'; import { combineStyle } from '@solid-primitives/props'; import createPreventScroll from 'solid-prevent-scroll'; import createPresence from 'solid-presence'; // src/popover/index.tsx var popover_exports = {}; __export(popover_exports, { Anchor: () => PopoverAnchor, Arrow: () => PopperArrow, CloseButton: () => PopoverCloseButton, Content: () => PopoverContent, Description: () => PopoverDescription, Popover: () => Popover, Portal: () => PopoverPortal, Root: () => PopoverRoot, Title: () => PopoverTitle, Trigger: () => PopoverTrigger }); var PopoverContext = createContext(); function usePopoverContext() { const context = useContext(PopoverContext); if (context === void 0) { throw new Error("[kobalte]: `usePopoverContext` must be used within a `Popover` component"); } return context; } // src/popover/popover-anchor.tsx function PopoverAnchor(props) { const context = usePopoverContext(); const [local, others] = splitProps(props, ["ref"]); return createComponent(Polymorphic, mergeProps({ as: "div", ref(r$) { const _ref$ = mergeRefs(context.setDefaultAnchorRef, local.ref); typeof _ref$ === "function" && _ref$(r$); } }, () => context.dataset(), others)); } function PopoverCloseButton(props) { const context = usePopoverContext(); const [local, others] = splitProps(props, ["aria-label", "onClick"]); const onClick = (e) => { callHandler(e, local.onClick); context.close(); }; return createComponent(ButtonRoot, mergeProps({ get ["aria-label"]() { return local["aria-label"] || context.translations().dismiss; }, onClick }, () => context.dataset(), others)); } function PopoverContent(props) { let ref; const context = usePopoverContext(); const mergedProps = mergeDefaultProps({ id: context.generateId("content") }, props); const [local, others] = splitProps(mergedProps, ["ref", "style", "onOpenAutoFocus", "onCloseAutoFocus", "onPointerDownOutside", "onFocusOutside", "onInteractOutside"]); let isRightClickOutside = false; let hasInteractedOutside = false; let hasPointerDownOutside = false; const onCloseAutoFocus = (e) => { local.onCloseAutoFocus?.(e); if (context.isModal()) { e.preventDefault(); if (!isRightClickOutside) { focusWithoutScrolling(context.triggerRef()); } } else { if (!e.defaultPrevented) { if (!hasInteractedOutside) { focusWithoutScrolling(context.triggerRef()); } e.preventDefault(); } hasInteractedOutside = false; hasPointerDownOutside = false; } }; const onPointerDownOutside = (e) => { local.onPointerDownOutside?.(e); if (context.isModal()) { isRightClickOutside = e.detail.isContextMenu; } }; const onFocusOutside = (e) => { local.onFocusOutside?.(e); if (context.isOpen() && context.isModal()) { e.preventDefault(); } }; const onInteractOutside = (e) => { local.onInteractOutside?.(e); if (context.isModal()) { return; } if (!e.defaultPrevented) { hasInteractedOutside = true; if (e.detail.originalEvent.type === "pointerdown") { hasPointerDownOutside = true; } } if (contains(context.triggerRef(), e.target)) { e.preventDefault(); } if (e.detail.originalEvent.type === "focusin" && hasPointerDownOutside) { 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: local.onOpenAutoFocus, onUnmountAutoFocus: onCloseAutoFocus }, () => ref); createEffect(() => onCleanup(context.registerContentId(others.id))); return createComponent(Show, { get when() { return context.contentPresent(); }, get children() { return createComponent(Popper.Positioner, { get children() { return createComponent(DismissableLayer, mergeProps({ ref(r$) { const _ref$ = mergeRefs((el) => { context.setContentRef(el); ref = el; }, local.ref); typeof _ref$ === "function" && _ref$(r$); }, role: "dialog", tabIndex: -1, get disableOutsidePointerEvents() { return memo(() => !!context.isOpen())() && context.isModal(); }, get excludedElements() { return [context.triggerRef]; }, get style() { return combineStyle({ "--kb-popover-content-transform-origin": "var(--kb-popper-content-transform-origin)", position: "relative" }, local.style); }, get ["aria-labelledby"]() { return context.titleId(); }, get ["aria-describedby"]() { return context.descriptionId(); }, onPointerDownOutside, onFocusOutside, onInteractOutside, get onDismiss() { return context.close; } }, () => context.dataset(), others)); } }); } }); } function PopoverDescription(props) { const context = usePopoverContext(); const mergedProps = mergeDefaultProps({ id: context.generateId("description") }, props); const [local, others] = splitProps(mergedProps, ["id"]); createEffect(() => onCleanup(context.registerDescriptionId(local.id))); return createComponent(Polymorphic, mergeProps({ as: "p", get id() { return local.id; } }, () => context.dataset(), others)); } function PopoverPortal(props) { const context = usePopoverContext(); return createComponent(Show, { get when() { return context.contentPresent(); }, get children() { return createComponent(Portal, props); } }); } // src/popover/popover.intl.ts var POPOVER_INTL_TRANSLATIONS = { // `aria-label` of Popover.CloseButton. dismiss: "Dismiss" }; // src/popover/popover-root.tsx function PopoverRoot(props) { const defaultId = `popover-${createUniqueId()}`; const mergedProps = mergeDefaultProps({ id: defaultId, modal: false, translations: POPOVER_INTL_TRANSLATIONS }, props); const [local, others] = splitProps(mergedProps, ["translations", "id", "open", "defaultOpen", "onOpenChange", "modal", "preventScroll", "forceMount", "anchorRef"]); const [defaultAnchorRef, setDefaultAnchorRef] = createSignal(); const [triggerRef, setTriggerRef] = createSignal(); const [contentRef, setContentRef] = createSignal(); const [contentId, setContentId] = createSignal(); const [titleId, setTitleId] = createSignal(); const [descriptionId, setDescriptionId] = createSignal(); const disclosureState = createDisclosureState({ open: () => local.open, defaultOpen: () => local.defaultOpen, onOpenChange: (isOpen) => local.onOpenChange?.(isOpen) }); const anchorRef = () => { return local.anchorRef?.() ?? defaultAnchorRef() ?? triggerRef(); }; const { present: contentPresent } = createPresence({ show: () => local.forceMount || disclosureState.isOpen(), element: () => contentRef() ?? null }); const dataset = createMemo(() => ({ "data-expanded": disclosureState.isOpen() ? "" : void 0, "data-closed": !disclosureState.isOpen() ? "" : void 0 })); const context = { translations: () => local.translations ?? POPOVER_INTL_TRANSLATIONS, dataset, isOpen: disclosureState.isOpen, isModal: () => local.modal ?? false, preventScroll: () => local.preventScroll ?? context.isModal(), contentPresent, triggerRef, contentId, titleId, descriptionId, setDefaultAnchorRef, setTriggerRef, setContentRef, close: disclosureState.close, toggle: disclosureState.toggle, generateId: createGenerateId(() => local.id), registerContentId: createRegisterId(setContentId), registerTitleId: createRegisterId(setTitleId), registerDescriptionId: createRegisterId(setDescriptionId) }; return createComponent(PopoverContext.Provider, { value: context, get children() { return createComponent(Popper, mergeProps({ anchorRef, contentRef }, others)); } }); } function PopoverTitle(props) { const context = usePopoverContext(); const mergedProps = mergeDefaultProps({ id: context.generateId("title") }, props); const [local, others] = splitProps(mergedProps, ["id"]); createEffect(() => onCleanup(context.registerTitleId(local.id))); return createComponent(Polymorphic, mergeProps({ as: "h2", get id() { return local.id; } }, () => context.dataset(), others)); } function PopoverTrigger(props) { const context = usePopoverContext(); const [local, others] = splitProps(props, ["ref", "onClick", "onPointerDown"]); const onPointerDown = (e) => { callHandler(e, local.onPointerDown); e.preventDefault(); }; const onClick = (e) => { callHandler(e, local.onClick); context.toggle(); }; return createComponent(ButtonRoot, mergeProps({ ref(r$) { const _ref$ = mergeRefs(context.setTriggerRef, local.ref); typeof _ref$ === "function" && _ref$(r$); }, "aria-haspopup": "dialog", get ["aria-expanded"]() { return context.isOpen(); }, get ["aria-controls"]() { return memo(() => !!context.isOpen())() ? context.contentId() : void 0; }, onPointerDown, onClick }, () => context.dataset(), others)); } // src/popover/index.tsx var Popover = Object.assign(PopoverRoot, { Anchor: PopoverAnchor, Arrow: PopperArrow, CloseButton: PopoverCloseButton, Content: PopoverContent, Description: PopoverDescription, Portal: PopoverPortal, Title: PopoverTitle, Trigger: PopoverTrigger }); export { Popover, PopoverAnchor, PopoverCloseButton, PopoverContent, PopoverDescription, PopoverPortal, PopoverRoot, PopoverTitle, PopoverTrigger, popover_exports };