UNPKG

@kobalte/core

Version:

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

351 lines (344 loc) 10.8 kB
import { createFocusScope } from './ISKHZMHS.js'; import { createHideOutside } from './455ZADO2.js'; import { DismissableLayer } from './BASJUNIE.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 { mergeDefaultProps, mergeRefs, createGenerateId, callHandler, contains, focusWithoutScrolling } from '@kobalte/utils'; import { createContext, useContext, splitProps, createEffect, onCleanup, Show, createUniqueId, createSignal } from 'solid-js'; import createPreventScroll from 'solid-prevent-scroll'; import { combineStyle } from '@solid-primitives/props'; import createPresence from 'solid-presence'; // src/dialog/index.tsx var dialog_exports = {}; __export(dialog_exports, { CloseButton: () => DialogCloseButton, Content: () => DialogContent, Description: () => DialogDescription, Dialog: () => Dialog, Overlay: () => DialogOverlay, Portal: () => DialogPortal, Root: () => DialogRoot, Title: () => DialogTitle, Trigger: () => DialogTrigger, useDialogContext: () => useDialogContext }); var DialogContext = createContext(); function useDialogContext() { const context = useContext(DialogContext); if (context === void 0) { throw new Error("[kobalte]: `useDialogContext` must be used within a `Dialog` component"); } return context; } // src/dialog/dialog-close-button.tsx function DialogCloseButton(props) { const context = useDialogContext(); 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 }, others)); } function DialogContent(props) { let ref; const context = useDialogContext(); const mergedProps = mergeDefaultProps({ id: context.generateId("content") }, props); const [local, others] = splitProps(mergedProps, ["ref", "onOpenAutoFocus", "onCloseAutoFocus", "onPointerDownOutside", "onFocusOutside", "onInteractOutside"]); let hasInteractedOutside = false; let hasPointerDownOutside = false; const onPointerDownOutside = (e) => { local.onPointerDownOutside?.(e); if (context.modal() && e.detail.isContextMenu) { e.preventDefault(); } }; const onFocusOutside = (e) => { local.onFocusOutside?.(e); if (context.modal()) { e.preventDefault(); } }; const onInteractOutside = (e) => { local.onInteractOutside?.(e); if (context.modal()) { 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(); } }; const onCloseAutoFocus = (e) => { local.onCloseAutoFocus?.(e); if (context.modal()) { e.preventDefault(); focusWithoutScrolling(context.triggerRef()); } else { if (!e.defaultPrevented) { if (!hasInteractedOutside) { focusWithoutScrolling(context.triggerRef()); } e.preventDefault(); } hasInteractedOutside = false; hasPointerDownOutside = false; } }; createHideOutside({ isDisabled: () => !(context.isOpen() && context.modal()), targets: () => ref ? [ref] : [] }); createPreventScroll({ element: () => ref ?? null, enabled: () => context.contentPresent() && context.preventScroll() }); createFocusScope({ trapFocus: () => context.isOpen() && context.modal(), onMountAutoFocus: local.onOpenAutoFocus, onUnmountAutoFocus: onCloseAutoFocus }, () => ref); createEffect(() => onCleanup(context.registerContentId(others.id))); return createComponent(Show, { get when() { return context.contentPresent(); }, 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.modal())() && context.isOpen(); }, get excludedElements() { return [context.triggerRef]; }, get ["aria-labelledby"]() { return context.titleId(); }, get ["aria-describedby"]() { return context.descriptionId(); }, get ["data-expanded"]() { return context.isOpen() ? "" : void 0; }, get ["data-closed"]() { return !context.isOpen() ? "" : void 0; }, onPointerDownOutside, onFocusOutside, onInteractOutside, get onDismiss() { return context.close; } }, others)); } }); } function DialogDescription(props) { const context = useDialogContext(); 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; } }, others)); } function DialogOverlay(props) { const context = useDialogContext(); const [local, others] = splitProps(props, ["ref", "style", "onPointerDown"]); const onPointerDown = (e) => { callHandler(e, local.onPointerDown); if (e.target === e.currentTarget) { e.preventDefault(); } }; return createComponent(Show, { get when() { return context.overlayPresent(); }, get children() { return createComponent(Polymorphic, mergeProps({ as: "div", ref(r$) { const _ref$ = mergeRefs(context.setOverlayRef, local.ref); typeof _ref$ === "function" && _ref$(r$); }, get style() { return combineStyle({ "pointer-events": "auto" }, local.style); }, get ["data-expanded"]() { return context.isOpen() ? "" : void 0; }, get ["data-closed"]() { return !context.isOpen() ? "" : void 0; }, onPointerDown }, others)); } }); } function DialogPortal(props) { const context = useDialogContext(); return createComponent(Show, { get when() { return context.contentPresent() || context.overlayPresent(); }, get children() { return createComponent(Portal, props); } }); } // src/dialog/dialog.intl.ts var DIALOG_INTL_TRANSLATIONS = { // `aria-label` of Dialog.CloseButton. dismiss: "Dismiss" }; // src/dialog/dialog-root.tsx function DialogRoot(props) { const defaultId = `dialog-${createUniqueId()}`; const mergedProps = mergeDefaultProps({ id: defaultId, modal: true, translations: DIALOG_INTL_TRANSLATIONS }, props); const [contentId, setContentId] = createSignal(); const [titleId, setTitleId] = createSignal(); const [descriptionId, setDescriptionId] = createSignal(); const [overlayRef, setOverlayRef] = createSignal(); const [contentRef, setContentRef] = createSignal(); const [triggerRef, setTriggerRef] = createSignal(); const disclosureState = createDisclosureState({ open: () => mergedProps.open, defaultOpen: () => mergedProps.defaultOpen, onOpenChange: (isOpen) => mergedProps.onOpenChange?.(isOpen) }); const shouldMount = () => mergedProps.forceMount || disclosureState.isOpen(); const { present: overlayPresent } = createPresence({ show: shouldMount, element: () => overlayRef() ?? null }); const { present: contentPresent } = createPresence({ show: shouldMount, element: () => contentRef() ?? null }); const context = { translations: () => mergedProps.translations ?? DIALOG_INTL_TRANSLATIONS, isOpen: disclosureState.isOpen, modal: () => mergedProps.modal ?? true, preventScroll: () => mergedProps.preventScroll ?? context.modal(), contentId, titleId, descriptionId, triggerRef, overlayRef, setOverlayRef, contentRef, setContentRef, overlayPresent, contentPresent, close: disclosureState.close, toggle: disclosureState.toggle, setTriggerRef, generateId: createGenerateId(() => mergedProps.id), registerContentId: createRegisterId(setContentId), registerTitleId: createRegisterId(setTitleId), registerDescriptionId: createRegisterId(setDescriptionId) }; return createComponent(DialogContext.Provider, { value: context, get children() { return mergedProps.children; } }); } function DialogTitle(props) { const context = useDialogContext(); 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; } }, others)); } function DialogTrigger(props) { const context = useDialogContext(); const [local, others] = splitProps(props, ["ref", "onClick"]); 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; }, get ["data-expanded"]() { return context.isOpen() ? "" : void 0; }, get ["data-closed"]() { return !context.isOpen() ? "" : void 0; }, onClick }, others)); } // src/dialog/index.tsx var Dialog = Object.assign(DialogRoot, { CloseButton: DialogCloseButton, Content: DialogContent, Description: DialogDescription, Overlay: DialogOverlay, Portal: DialogPortal, Title: DialogTitle, Trigger: DialogTrigger }); export { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger, dialog_exports, useDialogContext };