Clean up Dialog internals

This commit is contained in:
Mats Bosson 2026-03-29 05:54:12 +07:00
parent 87af246d71
commit 8ab23a1722
6 changed files with 22 additions and 32 deletions

View File

@ -1,6 +1,6 @@
// packages/core/src/components/dialog/dialog-close.tsx // packages/core/src/components/dialog/dialog-close.tsx
import type { Component, JSX } from "solid-js"; import type { Component, JSX } from "solid-js";
import { splitProps } from "solid-js"; import { mergeProps, splitProps } from "solid-js";
import { Dynamic } from "solid-js/web"; import { Dynamic } from "solid-js/web";
import { useInternalDialogContext } from "./dialog-context"; import { useInternalDialogContext } from "./dialog-context";
@ -20,8 +20,9 @@ export function DialogClose(props: DialogCloseProps): JSX.Element {
ctx.setOpen(false); ctx.setOpen(false);
}; };
const closeProps = mergeProps(rest, { onClick: handleClick });
return ( return (
<Dynamic component={local.as ?? "button"} onClick={handleClick} {...rest}> <Dynamic component={local.as ?? "button"} {...closeProps}>
{local.children} {local.children}
</Dynamic> </Dynamic>
); );

View File

@ -3,7 +3,6 @@ import type { JSX } from "solid-js";
import { Show, createEffect, onCleanup, splitProps } from "solid-js"; import { Show, createEffect, onCleanup, splitProps } from "solid-js";
import { createDismiss } from "../../utilities/dismiss/create-dismiss"; import { createDismiss } from "../../utilities/dismiss/create-dismiss";
import { createFocusTrap } from "../../utilities/focus-trap/create-focus-trap"; import { createFocusTrap } from "../../utilities/focus-trap/create-focus-trap";
import { Portal } from "../../utilities/portal/portal";
import { createScrollLock } from "../../utilities/scroll-lock/create-scroll-lock"; import { createScrollLock } from "../../utilities/scroll-lock/create-scroll-lock";
import { useInternalDialogContext } from "./dialog-context"; import { useInternalDialogContext } from "./dialog-context";
@ -22,7 +21,8 @@ export interface DialogContentProps extends JSX.DialogHtmlAttributes<HTMLDialogE
} }
/** /**
* Dialog content panel. Portals to body, manages focus trap, scroll lock, and dismiss. * Dialog content panel. Manages focus trap, scroll lock, and dismiss.
* Renders inline; wrap with Dialog.Portal to control portaling.
* Only renders when open (unless forceMount is set). * Only renders when open (unless forceMount is set).
*/ */
export function DialogContent(props: DialogContentProps): JSX.Element { export function DialogContent(props: DialogContentProps): JSX.Element {
@ -61,19 +61,17 @@ export function DialogContent(props: DialogContentProps): JSX.Element {
return ( return (
<Show when={local.forceMount || ctx.isOpen()}> <Show when={local.forceMount || ctx.isOpen()}>
<Portal> <dialog
<dialog ref={contentRef}
ref={contentRef} id={ctx.contentId()}
id={ctx.contentId()} aria-modal={ctx.modal() || undefined}
aria-modal={ctx.modal() || undefined} aria-labelledby={ctx.titleId() || undefined}
aria-labelledby={ctx.titleId()} aria-describedby={ctx.descriptionId() || undefined}
aria-describedby={ctx.descriptionId()} data-state={ctx.isOpen() ? "open" : "closed"}
data-state={ctx.isOpen() ? "open" : "closed"} {...rest}
{...rest} >
> {local.children}
{local.children} </dialog>
</dialog>
</Portal>
</Show> </Show>
); );
} }

View File

@ -16,9 +16,6 @@ export interface InternalDialogContextValue {
/** Registered ID from Dialog.Description — used for aria-describedby */ /** Registered ID from Dialog.Description — used for aria-describedby */
descriptionId: Accessor<string | undefined>; descriptionId: Accessor<string | undefined>;
setDescriptionId: (id: string | undefined) => void; setDescriptionId: (id: string | undefined) => void;
/** Whether Dialog.Trigger has been explicitly rendered */
hasTrigger: Accessor<boolean>;
setHasTrigger: (has: boolean) => void;
} }
const InternalDialogContext = createContext<InternalDialogContextValue>(); const InternalDialogContext = createContext<InternalDialogContextValue>();

View File

@ -1,6 +1,5 @@
// packages/core/src/components/dialog/dialog-portal.tsx // packages/core/src/components/dialog/dialog-portal.tsx
import type { JSX } from "solid-js"; import type { JSX } from "solid-js";
import { splitProps } from "solid-js";
import { Portal } from "../../utilities/portal/portal"; import { Portal } from "../../utilities/portal/portal";
export interface DialogPortalProps { export interface DialogPortalProps {
@ -11,10 +10,9 @@ export interface DialogPortalProps {
/** Renders children into a portal (defaults to document.body). */ /** Renders children into a portal (defaults to document.body). */
export function DialogPortal(props: DialogPortalProps): JSX.Element { export function DialogPortal(props: DialogPortalProps): JSX.Element {
const [local, rest] = splitProps(props, ["target", "children"]); return props.target !== undefined ? (
return local.target !== undefined ? ( <Portal target={props.target}>{props.children}</Portal>
<Portal target={local.target}>{local.children}</Portal>
) : ( ) : (
<Portal>{local.children}</Portal> <Portal>{props.children}</Portal>
); );
} }

View File

@ -1,6 +1,6 @@
// packages/core/src/components/dialog/dialog-root.tsx // packages/core/src/components/dialog/dialog-root.tsx
import type { JSX } from "solid-js"; import type { JSX } from "solid-js";
import { createSignal } from "solid-js"; import { createUniqueId } from "solid-js";
import { import {
type CreateDisclosureStateOptions, type CreateDisclosureStateOptions,
createDisclosureState, createDisclosureState,
@ -44,11 +44,9 @@ export function DialogRoot(props: DialogRootProps): JSX.Element {
}, },
} as CreateDisclosureStateOptions); } as CreateDisclosureStateOptions);
const contentId = `pettyui-dialog-${Math.random().toString(36).slice(2, 9)}`; const contentId = createUniqueId();
const [titleId, setTitleId] = createRegisterId(); const [titleId, setTitleId] = createRegisterId();
const [descriptionId, setDescriptionId] = createRegisterId(); const [descriptionId, setDescriptionId] = createRegisterId();
const [hasTrigger, setHasTrigger] = createSignal(false);
const internalCtx: InternalDialogContextValue = { const internalCtx: InternalDialogContextValue = {
isOpen: disclosure.isOpen, isOpen: disclosure.isOpen,
setOpen: (open) => (open ? disclosure.open() : disclosure.close()), setOpen: (open) => (open ? disclosure.open() : disclosure.close()),
@ -58,8 +56,6 @@ export function DialogRoot(props: DialogRootProps): JSX.Element {
setTitleId, setTitleId,
descriptionId, descriptionId,
setDescriptionId, setDescriptionId,
hasTrigger,
setHasTrigger,
}; };
return ( return (

View File

@ -1,10 +1,10 @@
// packages/core/src/components/dialog/index.ts
import { DialogClose } from "./dialog-close"; import { DialogClose } from "./dialog-close";
import { DialogContent } from "./dialog-content"; import { DialogContent } from "./dialog-content";
import { useDialogContext } from "./dialog-context"; import { useDialogContext } from "./dialog-context";
import { DialogDescription } from "./dialog-description"; import { DialogDescription } from "./dialog-description";
import { DialogOverlay } from "./dialog-overlay"; import { DialogOverlay } from "./dialog-overlay";
import { DialogPortal } from "./dialog-portal"; import { DialogPortal } from "./dialog-portal";
// packages/core/src/components/dialog/index.ts
import { DialogRoot } from "./dialog-root"; import { DialogRoot } from "./dialog-root";
import { DialogTitle } from "./dialog-title"; import { DialogTitle } from "./dialog-title";
import { DialogTrigger } from "./dialog-trigger"; import { DialogTrigger } from "./dialog-trigger";