UNPKG

@corvu/dialog

Version:

Unstyled, accessible and customizable UI primitives for SolidJS

605 lines (594 loc) 19.1 kB
// src/Close.tsx import { callEventHandler } from "@corvu/utils/dom"; import { createMemo, splitProps } from "solid-js"; import { DynamicButton } from "@corvu/utils/dynamic"; // src/context.ts import { createContext, useContext } from "solid-js"; import { createKeyedContext, useKeyedContext } from "@corvu/utils/create/keyedContext"; var DialogContext = createContext(); var createDialogContext = (contextId) => { if (contextId === void 0) return DialogContext; const context = createKeyedContext(`dialog-${contextId}`); return context; }; var useDialogContext = (contextId) => { if (contextId === void 0) { const context2 = useContext(DialogContext); if (!context2) { throw new Error( "[corvu]: Dialog context not found. Make sure to wrap Dialog components in <Dialog.Root>" ); } return context2; } const context = useKeyedContext(`dialog-${contextId}`); if (!context) { throw new Error( `[corvu]: Dialog context with id "${contextId}" not found. Make sure to wrap Dialog components in <Dialog.Root contextId="${contextId}">` ); } return context; }; var InternalDialogContext = createContext(); var createInternalDialogContext = (contextId) => { if (contextId === void 0) return InternalDialogContext; const context = createKeyedContext( `dialog-internal-${contextId}` ); return context; }; var useInternalDialogContext = (contextId) => { if (contextId === void 0) { const context2 = useContext(InternalDialogContext); if (!context2) { throw new Error( "[corvu]: Dialog context not found. Make sure to wrap Dialog components in <Dialog.Root>" ); } return context2; } const context = useKeyedContext( `dialog-internal-${contextId}` ); if (!context) { throw new Error( `[corvu]: Dialog context with id "${contextId}" not found. Make sure to wrap Dialog components in <Dialog.Root contextId="${contextId}">` ); } return context; }; // src/Close.tsx var DialogClose = (props) => { const [localProps, otherProps] = splitProps(props, [ "contextId", "onClick" ]); const context = createMemo( () => useInternalDialogContext(localProps.contextId) ); const onClick = (event) => { !callEventHandler(localProps.onClick, event) && context().setOpen(false); }; return <DynamicButton onClick={onClick} aria-label="close" data-corvu-dialog-close="" {...otherProps} />; }; var Close_default = DialogClose; // src/Content.tsx import { combineStyle } from "@corvu/utils/dom"; import { createMemo as createMemo2, Show, splitProps as splitProps2 } from "solid-js"; import { Dynamic } from "@corvu/utils/dynamic"; import { mergeRefs, some } from "@corvu/utils/reactivity"; import { dataIf } from "@corvu/utils"; import Dismissible from "solid-dismissible"; var DialogContent = (props) => { const [localProps, otherProps] = splitProps2(props, [ "forceMount", "contextId", "ref", "style" ]); const context = createMemo2( () => useInternalDialogContext(localProps.contextId) ); const show = () => some(context().open, () => localProps.forceMount, context().contentPresent); const enableDismissible = createMemo2( () => context().open() || context().contentPresent() ); return <Dismissible element={context().contentRef} enabled={enableDismissible()} dismissibleId={context().dialogId()} onDismiss={() => context().setOpen(false)} dismissOnEscapeKeyDown={context().closeOnEscapeKeyDown} dismissOnOutsideFocus={context().closeOnOutsideFocus} dismissOnOutsidePointer={context().closeOnOutsidePointer} outsidePointerStrategy={context().closeOnOutsidePointerStrategy} outsidePointerIgnore={() => [context().triggerRef()]} noOutsidePointerEvents={context().noOutsidePointerEvents} onEscapeKeyDown={context().onEscapeKeyDown} onOutsideFocus={context().onOutsideFocus} onOutsidePointer={context().onOutsidePointer} > {(props2) => <Show when={show()}> <Dynamic as="div" ref={mergeRefs(context().setContentRef, localProps.ref)} style={combineStyle( { "pointer-events": props2.isLastLayer ? "auto" : void 0 }, localProps.style )} id={context().dialogId()} role={context().role()} tabIndex="-1" aria-describedby={context().descriptionId()} aria-labelledby={context().labelId()} aria-modal={context().modal() ? "true" : "false"} data-closed={dataIf(!context().open())} data-open={dataIf(context().open())} data-corvu-dialog-content="" {...otherProps} /> </Show>} </Dismissible>; }; var Content_default = DialogContent; // src/Description.tsx import { createEffect, createMemo as createMemo3, onCleanup, splitProps as splitProps3 } from "solid-js"; import { Dynamic as Dynamic2 } from "@corvu/utils/dynamic"; var DialogDescription = (props) => { const [localProps, otherProps] = splitProps3(props, [ "contextId" ]); const context = createMemo3( () => useInternalDialogContext(localProps.contextId) ); createEffect(() => { const _context = context(); _context.registerDescriptionId(); onCleanup(() => _context.unregisterDescriptionId()); }); return <Dynamic2 as="p" id={context().descriptionId()} data-corvu-dialog-description="" {...otherProps} />; }; var Description_default = DialogDescription; // src/Label.tsx import { createEffect as createEffect2, createMemo as createMemo4, onCleanup as onCleanup2, splitProps as splitProps4 } from "solid-js"; import { Dynamic as Dynamic3 } from "@corvu/utils/dynamic"; var DialogLabel = (props) => { const [localProps, otherProps] = splitProps4(props, [ "contextId" ]); const context = createMemo4( () => useInternalDialogContext(localProps.contextId) ); createEffect2(() => { const _context = context(); _context.registerLabelId(); onCleanup2(() => _context.unregisterLabelId()); }); return <Dynamic3 as="h2" id={context().labelId()} data-corvu-dialog-label="" {...otherProps} />; }; var Label_default = DialogLabel; // src/Overlay.tsx import { combineStyle as combineStyle2 } from "@corvu/utils/dom"; import { createMemo as createMemo5, Show as Show2, splitProps as splitProps5 } from "solid-js"; import { Dynamic as Dynamic4 } from "@corvu/utils/dynamic"; import { mergeRefs as mergeRefs2, some as some2 } from "@corvu/utils/reactivity"; import { dataIf as dataIf2 } from "@corvu/utils"; var DialogOverlay = (props) => { const [localProps, otherProps] = splitProps5(props, [ "forceMount", "contextId", "ref", "style" ]); const context = createMemo5( () => useInternalDialogContext(localProps.contextId) ); const show = () => some2(context().open, () => localProps.forceMount, context().overlayPresent); return <Show2 when={show()}> <Dynamic4 as="div" ref={mergeRefs2(context().setOverlayRef, localProps.ref)} style={combineStyle2( { "pointer-events": "auto" }, localProps.style )} aria-hidden="true" data-closed={dataIf2(!context().open())} data-open={dataIf2(context().open())} data-corvu-dialog-overlay="" {...otherProps} /> </Show2>; }; var Overlay_default = DialogOverlay; // src/Portal.tsx import { createMemo as createMemo6, Show as Show3, splitProps as splitProps6 } from "solid-js"; import { Portal } from "solid-js/web"; import { some as some3 } from "@corvu/utils/reactivity"; var DialogPortal = (props) => { const [localProps, otherProps] = splitProps6(props, [ "forceMount", "contextId" ]); const context = createMemo6( () => useInternalDialogContext(localProps.contextId) ); const show = () => some3( context().open, () => localProps.forceMount, context().contentPresent, context().overlayPresent ); return <Show3 when={show()}> <Portal {...otherProps} /> </Show3>; }; var Portal_default = DialogPortal; // src/Root.tsx import { createMemo as createMemo7, createSignal, createUniqueId, mergeProps, untrack } from "solid-js"; import { access } from "@corvu/utils/reactivity"; import createControllableSignal from "@corvu/utils/create/controllableSignal"; import createFocusTrap from "solid-focus-trap"; import createOnce from "@corvu/utils/create/once"; import createPresence from "solid-presence"; import createPreventScroll from "solid-prevent-scroll"; import createRegister from "@corvu/utils/create/register"; import { isFunction } from "@corvu/utils"; var DEFAULT_MODAL = true; var DialogRoot = (props) => { const defaultedProps = mergeProps( { role: "dialog", initialOpen: false, modal: true, closeOnEscapeKeyDown: true, closeOnOutsideFocus: () => props.trapFocus ?? true, closeOnOutsidePointer: () => props.modal ?? DEFAULT_MODAL, closeOnOutsidePointerStrategy: "pointerup", noOutsidePointerEvents: () => props.modal ?? DEFAULT_MODAL, preventScroll: () => props.modal ?? DEFAULT_MODAL, hideScrollbar: true, preventScrollbarShift: true, preventScrollbarShiftMode: "padding", restoreScrollPosition: true, allowPinchZoom: true, trapFocus: true, restoreFocus: true, dialogId: createUniqueId() }, props ); const [open, setOpen] = createControllableSignal({ value: () => defaultedProps.open, initialValue: defaultedProps.initialOpen, onChange: defaultedProps.onOpenChange }); const [labelId, registerLabelId, unregisterLabelId] = createRegister({ value: () => defaultedProps.labelId ?? createUniqueId() }); const [descriptionId, registerDescriptionId, unregisterDescriptionId] = createRegister({ value: () => defaultedProps.descriptionId ?? createUniqueId() }); const [triggerRef, setTriggerRef] = createSignal(null); const [contentRef, setContentRef] = createSignal(null); const [overlayRef, setOverlayRef] = createSignal(null); const { present: contentPresent } = createPresence({ show: open, element: contentRef, onStateChange: (state) => { if (state === "present") { defaultedProps.onContentPresentChange?.(true); } else if (state === "hidden") { defaultedProps.onContentPresentChange?.(false); } } }); const { present: overlayPresent } = createPresence({ show: open, element: overlayRef, onStateChange: (state) => { if (state === "present") { defaultedProps.onOverlayPresentChange?.(true); } else if (state === "hidden") { defaultedProps.onOverlayPresentChange?.(false); } } }); createFocusTrap({ element: contentRef, enabled: () => open() && defaultedProps.trapFocus, initialFocusElement: () => defaultedProps.initialFocusEl ?? null, restoreFocus: () => defaultedProps.restoreFocus, finalFocusElement: () => defaultedProps.finalFocusEl ?? null, onFinalFocus: defaultedProps.onFinalFocus, onInitialFocus: defaultedProps.onInitialFocus }); createPreventScroll({ element: contentRef, enabled: () => contentPresent() && access(defaultedProps.preventScroll), hideScrollbar: () => defaultedProps.hideScrollbar, preventScrollbarShift: () => defaultedProps.preventScrollbarShift, preventScrollbarShiftMode: () => defaultedProps.preventScrollbarShiftMode, restoreScrollPosition: () => defaultedProps.restoreScrollPosition, allowPinchZoom: () => defaultedProps.allowPinchZoom }); const childrenProps = { get role() { return defaultedProps.role; }, get open() { return open(); }, setOpen, get modal() { return defaultedProps.modal; }, get closeOnEscapeKeyDown() { return defaultedProps.closeOnEscapeKeyDown; }, get closeOnOutsideFocus() { return access(defaultedProps.closeOnOutsideFocus); }, get closeOnOutsidePointer() { return access(defaultedProps.closeOnOutsidePointer); }, get closeOnOutsidePointerStrategy() { return defaultedProps.closeOnOutsidePointerStrategy; }, get noOutsidePointerEvents() { return access(defaultedProps.noOutsidePointerEvents); }, get preventScroll() { return access(defaultedProps.preventScroll); }, get hideScrollbar() { return defaultedProps.hideScrollbar; }, get preventScrollbarShift() { return access(defaultedProps.preventScrollbarShift); }, get preventScrollbarShiftMode() { return defaultedProps.preventScrollbarShiftMode; }, get restoreScrollPosition() { return defaultedProps.restoreScrollPosition; }, get allowPinchZoom() { return defaultedProps.allowPinchZoom; }, get trapFocus() { return defaultedProps.trapFocus; }, get restoreFocus() { return defaultedProps.restoreFocus; }, get initialFocusEl() { return defaultedProps.initialFocusEl; }, get finalFocusEl() { return defaultedProps.finalFocusEl; }, get contentPresent() { return contentPresent(); }, get contentRef() { return contentRef(); }, get overlayPresent() { return overlayPresent(); }, get overlayRef() { return overlayRef(); }, get dialogId() { return defaultedProps.dialogId; }, get labelId() { return labelId(); }, get descriptionId() { return descriptionId(); } }; const memoizedChildren = createOnce(() => defaultedProps.children); const resolveChildren = () => { const children = memoizedChildren()(); if (isFunction(children)) { return children(childrenProps); } return children; }; const memoizedDialogRoot = createMemo7(() => { const DialogContext2 = createDialogContext(defaultedProps.contextId); const InternalDialogContext2 = createInternalDialogContext( defaultedProps.contextId ); return <DialogContext2.Provider value={{ role: () => defaultedProps.role, open, setOpen, modal: () => defaultedProps.modal, closeOnEscapeKeyDown: () => defaultedProps.closeOnEscapeKeyDown, closeOnOutsideFocus: () => access(defaultedProps.closeOnOutsideFocus), closeOnOutsidePointer: () => access(defaultedProps.closeOnOutsidePointer), closeOnOutsidePointerStrategy: () => defaultedProps.closeOnOutsidePointerStrategy, noOutsidePointerEvents: () => access(defaultedProps.noOutsidePointerEvents), preventScroll: () => access(defaultedProps.preventScroll), hideScrollbar: () => defaultedProps.hideScrollbar, preventScrollbarShift: () => access(defaultedProps.preventScrollbarShift), preventScrollbarShiftMode: () => defaultedProps.preventScrollbarShiftMode, restoreScrollPosition: () => defaultedProps.restoreScrollPosition, allowPinchZoom: () => defaultedProps.allowPinchZoom, trapFocus: () => defaultedProps.trapFocus, restoreFocus: () => defaultedProps.restoreFocus, initialFocusEl: () => defaultedProps.initialFocusEl, finalFocusEl: () => defaultedProps.finalFocusEl, contentPresent, contentRef, overlayPresent, overlayRef, dialogId: () => defaultedProps.dialogId, labelId, descriptionId }} > <InternalDialogContext2.Provider value={{ role: () => defaultedProps.role, open, setOpen, modal: () => defaultedProps.modal, closeOnEscapeKeyDown: () => defaultedProps.closeOnEscapeKeyDown, onEscapeKeyDown: defaultedProps.onEscapeKeyDown, closeOnOutsideFocus: () => access(defaultedProps.closeOnOutsideFocus), closeOnOutsidePointer: () => access(defaultedProps.closeOnOutsidePointer), closeOnOutsidePointerStrategy: () => defaultedProps.closeOnOutsidePointerStrategy, onOutsideFocus: defaultedProps.onOutsideFocus, onOutsidePointer: defaultedProps.onOutsidePointer, noOutsidePointerEvents: () => access(defaultedProps.noOutsidePointerEvents), preventScroll: () => access(defaultedProps.preventScroll), hideScrollbar: () => defaultedProps.hideScrollbar, preventScrollbarShift: () => access(defaultedProps.preventScrollbarShift), preventScrollbarShiftMode: () => defaultedProps.preventScrollbarShiftMode, restoreScrollPosition: () => defaultedProps.restoreScrollPosition, allowPinchZoom: () => defaultedProps.allowPinchZoom, trapFocus: () => defaultedProps.trapFocus, restoreFocus: () => defaultedProps.restoreFocus, initialFocusEl: () => defaultedProps.initialFocusEl, finalFocusEl: () => defaultedProps.finalFocusEl, contentPresent, contentRef, overlayPresent, overlayRef, dialogId: () => defaultedProps.dialogId, labelId, registerLabelId, unregisterLabelId, descriptionId, registerDescriptionId, unregisterDescriptionId, setContentRef, setOverlayRef, triggerRef, setTriggerRef }} > {untrack(() => resolveChildren())} </InternalDialogContext2.Provider> </DialogContext2.Provider>; }); return memoizedDialogRoot; }; var Root_default = DialogRoot; // src/Trigger.tsx import { callEventHandler as callEventHandler2 } from "@corvu/utils/dom"; import { createMemo as createMemo8, splitProps as splitProps7 } from "solid-js"; import { DynamicButton as DynamicButton2 } from "@corvu/utils/dynamic"; import { dataIf as dataIf3 } from "@corvu/utils"; import { mergeRefs as mergeRefs3 } from "@corvu/utils/reactivity"; var DialogTrigger = (props) => { const [localProps, otherProps] = splitProps7(props, [ "contextId", "ref", "onClick" ]); const context = createMemo8( () => useInternalDialogContext(localProps.contextId) ); const onClick = (e) => { !callEventHandler2(localProps.onClick, e) && context().setOpen((open) => !open); }; return <DynamicButton2 ref={mergeRefs3(context().setTriggerRef, localProps.ref)} onClick={onClick} aria-controls={context().dialogId()} aria-expanded={context().open() ? "true" : "false"} aria-haspopup="dialog" data-closed={dataIf3(!context().open())} data-open={dataIf3(context().open())} data-corvu-dialog-trigger="" {...otherProps} />; }; var Trigger_default = DialogTrigger; // src/index.ts var Dialog = Object.assign(Root_default, { Trigger: Trigger_default, Portal: Portal_default, Overlay: Overlay_default, Content: Content_default, Label: Label_default, Description: Description_default, Close: Close_default, useContext: useDialogContext }); var index_default = Dialog; export { Close_default as Close, Content_default as Content, Description_default as Description, Label_default as Label, Overlay_default as Overlay, Portal_default as Portal, Root_default as Root, Trigger_default as Trigger, index_default as default, useDialogContext as useContext };