This commit is contained in:
eewing
2026-02-17 14:10:16 -06:00
parent 2bca5834c5
commit cf73cd3b4c
11246 changed files with 1690552 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import { DialogCloseState } from "../dialog.svelte.js";
import type { DialogCloseProps } from "../types.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
id = createId(uid),
ref = $bindable(null),
disabled = false,
...restProps
}: DialogCloseProps = $props();
const closeState = DialogCloseState.create({
variant: boxWith(() => "close"),
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
disabled: boxWith(() => Boolean(disabled)),
});
const mergedProps = $derived(mergeProps(restProps, closeState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<button {...mergedProps}>
{@render children?.()}
</button>
{/if}
@@ -0,0 +1,3 @@
declare const DialogClose: import("svelte").Component<import("../types.js").DialogTriggerProps, {}, "ref">;
type DialogClose = ReturnType<typeof DialogClose>;
export default DialogClose;
+95
View File
@@ -0,0 +1,95 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import { DialogContentState } from "../dialog.svelte.js";
import type { DialogContentProps } from "../types.js";
import DismissibleLayer from "../../utilities/dismissible-layer/dismissible-layer.svelte";
import EscapeLayer from "../../utilities/escape-layer/escape-layer.svelte";
import FocusScope from "../../utilities/focus-scope/focus-scope.svelte";
import TextSelectionLayer from "../../utilities/text-selection-layer/text-selection-layer.svelte";
import { createId } from "../../../internal/create-id.js";
import { noop } from "../../../internal/noop.js";
import ScrollLock from "../../utilities/scroll-lock/scroll-lock.svelte";
const uid = $props.id();
let {
id = createId(uid),
children,
child,
ref = $bindable(null),
forceMount = false,
onCloseAutoFocus = noop,
onOpenAutoFocus = noop,
onEscapeKeydown = noop,
onInteractOutside = noop,
trapFocus = true,
preventScroll = true,
restoreScrollDelay = null,
...restProps
}: DialogContentProps = $props();
const contentState = DialogContentState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, contentState.props));
</script>
{#if contentState.shouldRender || forceMount}
<FocusScope
ref={contentState.opts.ref}
loop
{trapFocus}
enabled={contentState.root.opts.open.current}
{onOpenAutoFocus}
{onCloseAutoFocus}
>
{#snippet focusScope({ props: focusScopeProps })}
<EscapeLayer
{...mergedProps}
enabled={contentState.root.opts.open.current}
ref={contentState.opts.ref}
onEscapeKeydown={(e) => {
onEscapeKeydown(e);
if (e.defaultPrevented) return;
contentState.root.handleClose();
}}
>
<DismissibleLayer
{...mergedProps}
ref={contentState.opts.ref}
enabled={contentState.root.opts.open.current}
onInteractOutside={(e) => {
onInteractOutside(e);
if (e.defaultPrevented) return;
contentState.root.handleClose();
}}
>
<TextSelectionLayer
{...mergedProps}
ref={contentState.opts.ref}
enabled={contentState.root.opts.open.current}
>
{#if child}
{#if contentState.root.opts.open.current}
<ScrollLock {preventScroll} {restoreScrollDelay} />
{/if}
{@render child({
props: mergeProps(mergedProps, focusScopeProps),
...contentState.snippetProps,
})}
{:else}
<ScrollLock {preventScroll} />
<div {...mergeProps(mergedProps, focusScopeProps)}>
{@render children?.()}
</div>
{/if}
</TextSelectionLayer>
</DismissibleLayer>
</EscapeLayer>
{/snippet}
</FocusScope>
{/if}
@@ -0,0 +1,4 @@
import type { DialogContentProps } from "../types.js";
declare const DialogContent: import("svelte").Component<DialogContentProps, {}, "ref">;
type DialogContent = ReturnType<typeof DialogContent>;
export default DialogContent;
@@ -0,0 +1,34 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import { DialogDescriptionState } from "../dialog.svelte.js";
import type { DialogDescriptionProps } from "../types.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
id = createId(uid),
children,
child,
ref = $bindable(null),
...restProps
}: DialogDescriptionProps = $props();
const descriptionState = DialogDescriptionState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, descriptionState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { DialogDescriptionProps } from "../types.js";
declare const DialogDescription: import("svelte").Component<DialogDescriptionProps, {}, "ref">;
type DialogDescription = ReturnType<typeof DialogDescription>;
export default DialogDescription;
+37
View File
@@ -0,0 +1,37 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import { DialogOverlayState } from "../dialog.svelte.js";
import type { DialogOverlayProps } from "../types.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
id = createId(uid),
forceMount = false,
child,
children,
ref = $bindable(null),
...restProps
}: DialogOverlayProps = $props();
const overlayState = DialogOverlayState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, overlayState.props));
</script>
{#if overlayState.shouldRender || forceMount}
{#if child}
{@render child({ props: mergeProps(mergedProps), ...overlayState.snippetProps })}
{:else}
<div {...mergeProps(mergedProps)}>
{@render children?.(overlayState.snippetProps)}
</div>
{/if}
{/if}
@@ -0,0 +1,4 @@
import type { DialogOverlayProps } from "../types.js";
declare const DialogOverlay: import("svelte").Component<DialogOverlayProps, {}, "ref">;
type DialogOverlay = ReturnType<typeof DialogOverlay>;
export default DialogOverlay;
+36
View File
@@ -0,0 +1,36 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import { DialogTitleState } from "../dialog.svelte.js";
import type { DialogTitleProps } from "../types.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
id = createId(uid),
ref = $bindable(null),
child,
children,
level = 2,
...restProps
}: DialogTitleProps = $props();
const titleState = DialogTitleState.create({
id: boxWith(() => id),
level: boxWith(() => level),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, titleState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
@@ -0,0 +1,4 @@
import type { DialogTitleProps } from "../types.js";
declare const DialogTitle: import("svelte").Component<DialogTitleProps, {}, "ref">;
type DialogTitle = ReturnType<typeof DialogTitle>;
export default DialogTitle;
+36
View File
@@ -0,0 +1,36 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import { DialogTriggerState } from "../dialog.svelte.js";
import type { DialogTriggerProps } from "../types.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
id = createId(uid),
ref = $bindable(null),
children,
child,
disabled = false,
...restProps
}: DialogTriggerProps = $props();
const triggerState = DialogTriggerState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
disabled: boxWith(() => Boolean(disabled)),
});
const mergedProps = $derived(mergeProps(restProps, triggerState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<button {...mergedProps}>
{@render children?.()}
</button>
{/if}
@@ -0,0 +1,4 @@
import type { DialogTriggerProps } from "../types.js";
declare const DialogTrigger: import("svelte").Component<DialogTriggerProps, {}, "ref">;
type DialogTrigger = ReturnType<typeof DialogTrigger>;
export default DialogTrigger;
+27
View File
@@ -0,0 +1,27 @@
<script lang="ts">
import { boxWith } from "svelte-toolbelt";
import { DialogRootState } from "../dialog.svelte.js";
import type { DialogRootProps } from "../types.js";
import { noop } from "../../../internal/noop.js";
let {
open = $bindable(false),
onOpenChange = noop,
onOpenChangeComplete = noop,
children,
}: DialogRootProps = $props();
DialogRootState.create({
variant: boxWith(() => "dialog"),
open: boxWith(
() => open,
(v) => {
open = v;
onOpenChange(v);
}
),
onOpenChangeComplete: boxWith(() => onOpenChangeComplete),
});
</script>
{@render children?.()}
+3
View File
@@ -0,0 +1,3 @@
declare const Dialog: import("svelte").Component<import("../types.js").DialogRootPropsWithoutHTML, {}, "open">;
type Dialog = ReturnType<typeof Dialog>;
export default Dialog;
+203
View File
@@ -0,0 +1,203 @@
import { type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
import type { BitsKeyboardEvent, BitsMouseEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
import { PresenceManager } from "../../internal/presence-manager.svelte.js";
type DialogVariant = "alert-dialog" | "dialog";
declare const dialogAttrs: import("../../internal/attrs.js").CreateBitsAttrsReturn<readonly ["content", "trigger", "overlay", "title", "description", "close", "cancel", "action"]>;
interface DialogRootStateOpts extends WritableBoxedValues<{
open: boolean;
}>, ReadableBoxedValues<{
variant: DialogVariant;
onOpenChangeComplete: OnChangeFn<boolean>;
}> {
}
export declare class DialogRootState {
static create(opts: DialogRootStateOpts): DialogRootState;
readonly opts: DialogRootStateOpts;
triggerNode: HTMLElement | null;
contentNode: HTMLElement | null;
overlayNode: HTMLElement | null;
descriptionNode: HTMLElement | null;
contentId: string | undefined;
titleId: string | undefined;
triggerId: string | undefined;
descriptionId: string | undefined;
cancelNode: HTMLElement | null;
nestedOpenCount: number;
readonly depth: number;
readonly parent: DialogRootState | null;
contentPresence: PresenceManager;
overlayPresence: PresenceManager;
constructor(opts: DialogRootStateOpts, parent: DialogRootState | null);
handleOpen(): void;
handleClose(): void;
getBitsAttr: typeof dialogAttrs.getAttr;
incrementNested(): void;
decrementNested(): void;
readonly sharedProps: {
readonly "data-state": "open" | "closed";
};
}
interface DialogTriggerStateOpts extends WithRefOpts, ReadableBoxedValues<{
disabled: boolean;
}> {
}
export declare class DialogTriggerState {
static create(opts: DialogTriggerStateOpts): DialogTriggerState;
readonly opts: DialogTriggerStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: DialogTriggerStateOpts, root: DialogRootState);
onclick(e: BitsMouseEvent): void;
onkeydown(e: BitsKeyboardEvent): void;
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
readonly "aria-haspopup": "dialog";
readonly "aria-expanded": "true" | "false";
readonly "aria-controls": string | undefined;
readonly onkeydown: (e: BitsKeyboardEvent) => void;
readonly onclick: (e: BitsMouseEvent) => void;
readonly disabled: true | undefined;
};
}
interface DialogCloseStateOpts extends WithRefOpts, ReadableBoxedValues<{
variant: "action" | "cancel" | "close";
disabled: boolean;
}> {
}
export declare class DialogCloseState {
static create(opts: DialogCloseStateOpts): DialogCloseState;
readonly opts: DialogCloseStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: DialogCloseStateOpts, root: DialogRootState);
onclick(e: BitsMouseEvent): void;
onkeydown(e: BitsKeyboardEvent): void;
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
readonly onclick: (e: BitsMouseEvent) => void;
readonly onkeydown: (e: BitsKeyboardEvent) => void;
readonly disabled: true | undefined;
readonly tabindex: 0;
};
}
interface DialogActionStateOpts extends WithRefOpts {
}
export declare class DialogActionState {
static create(opts: DialogActionStateOpts): DialogActionState;
readonly opts: DialogActionStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: DialogActionStateOpts, root: DialogRootState);
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
};
}
interface DialogTitleStateOpts extends WithRefOpts, ReadableBoxedValues<{
level: 1 | 2 | 3 | 4 | 5 | 6;
}> {
}
export declare class DialogTitleState {
static create(opts: DialogTitleStateOpts): DialogTitleState;
readonly opts: DialogTitleStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: DialogTitleStateOpts, root: DialogRootState);
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
readonly role: "heading";
readonly "aria-level": 1 | 2 | 3 | 4 | 5 | 6;
};
}
interface DialogDescriptionStateOpts extends WithRefOpts {
}
export declare class DialogDescriptionState {
static create(opts: DialogDescriptionStateOpts): DialogDescriptionState;
readonly opts: DialogDescriptionStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: DialogDescriptionStateOpts, root: DialogRootState);
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
};
}
interface DialogContentStateOpts extends WithRefOpts {
}
export declare class DialogContentState {
static create(opts: DialogContentStateOpts): DialogContentState;
readonly opts: DialogContentStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: DialogContentStateOpts, root: DialogRootState);
readonly snippetProps: {
open: boolean;
};
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
readonly role: "dialog" | "alertdialog";
readonly "aria-modal": "true";
readonly "aria-describedby": string | undefined;
readonly "aria-labelledby": string | undefined;
readonly style: {
readonly pointerEvents: "auto";
readonly outline: "none" | undefined;
readonly "--bits-dialog-depth": number;
readonly "--bits-dialog-nested-count": number;
readonly contain: "layout style";
};
readonly tabindex: -1 | undefined;
readonly "data-nested-open": "" | undefined;
readonly "data-nested": "" | undefined;
};
get shouldRender(): boolean;
}
interface DialogOverlayStateOpts extends WithRefOpts {
}
export declare class DialogOverlayState {
static create(opts: DialogOverlayStateOpts): DialogOverlayState;
readonly opts: DialogOverlayStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: DialogOverlayStateOpts, root: DialogRootState);
readonly snippetProps: {
open: boolean;
};
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
readonly style: {
readonly pointerEvents: "auto";
readonly "--bits-dialog-depth": number;
readonly "--bits-dialog-nested-count": number;
};
readonly "data-nested-open": "" | undefined;
readonly "data-nested": "" | undefined;
};
get shouldRender(): boolean;
}
interface AlertDialogCancelStateOpts extends WithRefOpts, ReadableBoxedValues<{
disabled: boolean;
}> {
}
export declare class AlertDialogCancelState {
static create(opts: AlertDialogCancelStateOpts): AlertDialogCancelState;
readonly opts: AlertDialogCancelStateOpts;
readonly root: DialogRootState;
readonly attachment: RefAttachment;
constructor(opts: AlertDialogCancelStateOpts, root: DialogRootState);
onclick(e: BitsMouseEvent): void;
onkeydown(e: BitsKeyboardEvent): void;
readonly props: {
readonly "data-state": "open" | "closed";
readonly id: string;
readonly onclick: (e: BitsMouseEvent) => void;
readonly onkeydown: (e: BitsKeyboardEvent) => void;
readonly tabindex: 0;
};
}
export {};
+358
View File
@@ -0,0 +1,358 @@
import { attachRef, boxWith, onDestroyEffect, } from "svelte-toolbelt";
import { Context, watch } from "runed";
import { createBitsAttrs, boolToStr, getDataOpenClosed, boolToEmptyStrOrUndef, } from "../../internal/attrs.js";
import { kbd } from "../../internal/kbd.js";
import { PresenceManager } from "../../internal/presence-manager.svelte.js";
const dialogAttrs = createBitsAttrs({
component: "dialog",
parts: ["content", "trigger", "overlay", "title", "description", "close", "cancel", "action"],
});
const DialogRootContext = new Context("Dialog.Root | AlertDialog.Root");
export class DialogRootState {
static create(opts) {
const parent = DialogRootContext.getOr(null);
return DialogRootContext.set(new DialogRootState(opts, parent));
}
opts;
triggerNode = $state(null);
contentNode = $state(null);
overlayNode = $state(null);
descriptionNode = $state(null);
contentId = $state(undefined);
titleId = $state(undefined);
triggerId = $state(undefined);
descriptionId = $state(undefined);
cancelNode = $state(null);
nestedOpenCount = $state(0);
depth;
parent;
contentPresence;
overlayPresence;
constructor(opts, parent) {
this.opts = opts;
this.parent = parent;
this.depth = parent ? parent.depth + 1 : 0;
this.handleOpen = this.handleOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.contentPresence = new PresenceManager({
ref: boxWith(() => this.contentNode),
open: this.opts.open,
enabled: true,
onComplete: () => {
this.opts.onOpenChangeComplete.current(this.opts.open.current);
},
});
this.overlayPresence = new PresenceManager({
ref: boxWith(() => this.overlayNode),
open: this.opts.open,
enabled: true,
});
watch(() => this.opts.open.current, (isOpen) => {
if (!this.parent)
return;
if (isOpen) {
this.parent.incrementNested();
}
else {
this.parent.decrementNested();
}
}, { lazy: true });
onDestroyEffect(() => {
if (this.opts.open.current) {
this.parent?.decrementNested();
}
});
}
handleOpen() {
if (this.opts.open.current)
return;
this.opts.open.current = true;
}
handleClose() {
if (!this.opts.open.current)
return;
this.opts.open.current = false;
}
getBitsAttr = (part) => {
return dialogAttrs.getAttr(part, this.opts.variant.current);
};
incrementNested() {
this.nestedOpenCount++;
this.parent?.incrementNested();
}
decrementNested() {
if (this.nestedOpenCount === 0)
return;
this.nestedOpenCount--;
this.parent?.decrementNested();
}
sharedProps = $derived.by(() => ({
"data-state": getDataOpenClosed(this.opts.open.current),
}));
}
export class DialogTriggerState {
static create(opts) {
return new DialogTriggerState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(this.opts.ref, (v) => {
this.root.triggerNode = v;
this.root.triggerId = v?.id;
});
this.onclick = this.onclick.bind(this);
this.onkeydown = this.onkeydown.bind(this);
}
onclick(e) {
if (this.opts.disabled.current)
return;
if (e.button > 0)
return;
this.root.handleOpen();
}
onkeydown(e) {
if (this.opts.disabled.current)
return;
if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
e.preventDefault();
this.root.handleOpen();
}
}
props = $derived.by(() => ({
id: this.opts.id.current,
"aria-haspopup": "dialog",
"aria-expanded": boolToStr(this.root.opts.open.current),
"aria-controls": this.root.contentId,
[this.root.getBitsAttr("trigger")]: "",
onkeydown: this.onkeydown,
onclick: this.onclick,
disabled: this.opts.disabled.current ? true : undefined,
...this.root.sharedProps,
...this.attachment,
}));
}
export class DialogCloseState {
static create(opts) {
return new DialogCloseState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(this.opts.ref);
this.onclick = this.onclick.bind(this);
this.onkeydown = this.onkeydown.bind(this);
}
onclick(e) {
if (this.opts.disabled.current)
return;
if (e.button > 0)
return;
this.root.handleClose();
}
onkeydown(e) {
if (this.opts.disabled.current)
return;
if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
e.preventDefault();
this.root.handleClose();
}
}
props = $derived.by(() => ({
id: this.opts.id.current,
[this.root.getBitsAttr(this.opts.variant.current)]: "",
onclick: this.onclick,
onkeydown: this.onkeydown,
disabled: this.opts.disabled.current ? true : undefined,
tabindex: 0,
...this.root.sharedProps,
...this.attachment,
}));
}
export class DialogActionState {
static create(opts) {
return new DialogActionState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(this.opts.ref);
}
props = $derived.by(() => ({
id: this.opts.id.current,
[this.root.getBitsAttr("action")]: "",
...this.root.sharedProps,
...this.attachment,
}));
}
export class DialogTitleState {
static create(opts) {
return new DialogTitleState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.root.titleId = this.opts.id.current;
this.attachment = attachRef(this.opts.ref);
watch.pre(() => this.opts.id.current, (id) => {
this.root.titleId = id;
});
}
props = $derived.by(() => ({
id: this.opts.id.current,
role: "heading",
"aria-level": this.opts.level.current,
[this.root.getBitsAttr("title")]: "",
...this.root.sharedProps,
...this.attachment,
}));
}
export class DialogDescriptionState {
static create(opts) {
return new DialogDescriptionState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.root.descriptionId = this.opts.id.current;
this.attachment = attachRef(this.opts.ref, (v) => {
this.root.descriptionNode = v;
});
watch.pre(() => this.opts.id.current, (id) => {
this.root.descriptionId = id;
});
}
props = $derived.by(() => ({
id: this.opts.id.current,
[this.root.getBitsAttr("description")]: "",
...this.root.sharedProps,
...this.attachment,
}));
}
export class DialogContentState {
static create(opts) {
return new DialogContentState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(this.opts.ref, (v) => {
this.root.contentNode = v;
this.root.contentId = v?.id;
});
}
snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
props = $derived.by(() => ({
id: this.opts.id.current,
role: this.root.opts.variant.current === "alert-dialog" ? "alertdialog" : "dialog",
"aria-modal": "true",
"aria-describedby": this.root.descriptionId,
"aria-labelledby": this.root.titleId,
[this.root.getBitsAttr("content")]: "",
style: {
pointerEvents: "auto",
outline: this.root.opts.variant.current === "alert-dialog" ? "none" : undefined,
"--bits-dialog-depth": this.root.depth,
"--bits-dialog-nested-count": this.root.nestedOpenCount,
// CSS containment isolates style/layout calculations from the rest of the page,
// improving performance when there's a large DOM behind the dialog.
// Paint is omitted so tooltips/selects can render outside dialog bounds.
contain: "layout style",
},
tabindex: this.root.opts.variant.current === "alert-dialog" ? -1 : undefined,
"data-nested-open": boolToEmptyStrOrUndef(this.root.nestedOpenCount > 0),
"data-nested": boolToEmptyStrOrUndef(this.root.parent !== null),
...this.root.sharedProps,
...this.attachment,
}));
get shouldRender() {
return this.root.contentPresence.shouldRender;
}
}
export class DialogOverlayState {
static create(opts) {
return new DialogOverlayState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(this.opts.ref, (v) => (this.root.overlayNode = v));
}
snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
props = $derived.by(() => ({
id: this.opts.id.current,
[this.root.getBitsAttr("overlay")]: "",
style: {
pointerEvents: "auto",
"--bits-dialog-depth": this.root.depth,
"--bits-dialog-nested-count": this.root.nestedOpenCount,
},
"data-nested-open": boolToEmptyStrOrUndef(this.root.nestedOpenCount > 0),
"data-nested": boolToEmptyStrOrUndef(this.root.parent !== null),
...this.root.sharedProps,
...this.attachment,
}));
get shouldRender() {
return this.root.overlayPresence.shouldRender;
}
}
export class AlertDialogCancelState {
static create(opts) {
return new AlertDialogCancelState(opts, DialogRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(this.opts.ref, (v) => (this.root.cancelNode = v));
this.onclick = this.onclick.bind(this);
this.onkeydown = this.onkeydown.bind(this);
}
onclick(e) {
if (this.opts.disabled.current)
return;
if (e.button > 0)
return;
this.root.handleClose();
}
onkeydown(e) {
if (this.opts.disabled.current)
return;
if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
e.preventDefault();
this.root.handleClose();
}
}
props = $derived.by(() => ({
id: this.opts.id.current,
[this.root.getBitsAttr("cancel")]: "",
onclick: this.onclick,
onkeydown: this.onkeydown,
tabindex: 0,
...this.root.sharedProps,
...this.attachment,
}));
}
+9
View File
@@ -0,0 +1,9 @@
export { default as Root } from "./components/dialog.svelte";
export { default as Title } from "./components/dialog-title.svelte";
export { default as Close } from "./components/dialog-close.svelte";
export { default as Portal } from "../utilities/portal/portal.svelte";
export { default as Content } from "./components/dialog-content.svelte";
export { default as Overlay } from "./components/dialog-overlay.svelte";
export { default as Trigger } from "./components/dialog-trigger.svelte";
export { default as Description } from "./components/dialog-description.svelte";
export type { DialogRootProps as RootProps, DialogTitleProps as TitleProps, DialogCloseProps as CloseProps, DialogPortalProps as PortalProps, DialogContentProps as ContentProps, DialogOverlayProps as OverlayProps, DialogTriggerProps as TriggerProps, DialogDescriptionProps as DescriptionProps, } from "./types.js";
+8
View File
@@ -0,0 +1,8 @@
export { default as Root } from "./components/dialog.svelte";
export { default as Title } from "./components/dialog-title.svelte";
export { default as Close } from "./components/dialog-close.svelte";
export { default as Portal } from "../utilities/portal/portal.svelte";
export { default as Content } from "./components/dialog-content.svelte";
export { default as Overlay } from "./components/dialog-overlay.svelte";
export { default as Trigger } from "./components/dialog-trigger.svelte";
export { default as Description } from "./components/dialog-description.svelte";
+1
View File
@@ -0,0 +1 @@
export * as Dialog from "./exports.js";
+1
View File
@@ -0,0 +1 @@
export * as Dialog from "./exports.js";
+49
View File
@@ -0,0 +1,49 @@
import type { EscapeLayerProps } from "../utilities/escape-layer/types.js";
import type { DismissibleLayerProps } from "../utilities/dismissible-layer/types.js";
import type { PresenceLayerProps } from "../utilities/presence-layer/types.js";
import type { FocusScopeProps } from "../utilities/focus-scope/types.js";
import type { TextSelectionLayerProps } from "../utilities/text-selection-layer/types.js";
import type { ScrollLockProps } from "../utilities/scroll-lock/index.js";
import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildren, Without } from "../../internal/types.js";
import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
import type { PortalProps } from "../utilities/portal/index.js";
export type DialogRootPropsWithoutHTML = WithChildren<{
/**
* The open state of the dialog.
*/
open?: boolean;
/**
* A callback that is called when the popover's open state changes.
*/
onOpenChange?: OnChangeFn<boolean>;
/**
* A callback called when the dialog finishes opening/closing animations.
*/
onOpenChangeComplete?: OnChangeFn<boolean>;
}>;
export type DialogRootProps = DialogRootPropsWithoutHTML;
export type DialogContentSnippetProps = {
open: boolean;
};
export type DialogContentPropsWithoutHTML = WithChildNoChildrenSnippetProps<Omit<EscapeLayerProps & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & PresenceLayerProps & FocusScopeProps & TextSelectionLayerProps & ScrollLockProps, "loop">, DialogContentSnippetProps>;
export type DialogContentProps = DialogContentPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, DialogContentPropsWithoutHTML>;
export type DialogOverlaySnippetProps = {
open: boolean;
};
export type DialogOverlayPropsWithoutHTML = WithChild<PresenceLayerProps, DialogOverlaySnippetProps>;
export type DialogOverlayProps = DialogOverlayPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, DialogOverlayPropsWithoutHTML>;
export type DialogPortalPropsWithoutHTML = PortalProps;
export type DialogPortalProps = DialogPortalPropsWithoutHTML;
export type DialogTriggerPropsWithoutHTML = WithChild;
export type DialogTriggerProps = DialogTriggerPropsWithoutHTML & Without<BitsPrimitiveButtonAttributes, DialogTriggerPropsWithoutHTML>;
export type DialogTitlePropsWithoutHTML = WithChild<{
/**
* The heading level of the dialog title.
*/
level?: 1 | 2 | 3 | 4 | 5 | 6;
}>;
export type DialogTitleProps = DialogTitlePropsWithoutHTML & Without<BitsPrimitiveDivAttributes, DialogTitlePropsWithoutHTML>;
export type DialogClosePropsWithoutHTML = DialogTriggerPropsWithoutHTML;
export type DialogCloseProps = DialogTriggerProps;
export type DialogDescriptionPropsWithoutHTML = WithChild;
export type DialogDescriptionProps = DialogDescriptionPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, DialogDescriptionPropsWithoutHTML>;
+1
View File
@@ -0,0 +1 @@
export {};