@pzerelles/headlessui-svelte 2.1.2-next.22 → 2.1.2-next.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button/Button.svelte +84 -54
- package/dist/button/Button.svelte.d.ts +7 -4
- package/dist/checkbox/Checkbox.svelte +173 -120
- package/dist/checkbox/Checkbox.svelte.d.ts +7 -4
- package/dist/close-button/CloseButton.svelte +12 -6
- package/dist/close-button/CloseButton.svelte.d.ts +13 -10
- package/dist/combobox/Combobox.svelte +50 -3
- package/dist/data-interactive/DataInteractive.svelte +55 -29
- package/dist/data-interactive/DataInteractive.svelte.d.ts +7 -5
- package/dist/description/Description.svelte +39 -24
- package/dist/description/Description.svelte.d.ts +8 -5
- package/dist/description/context.svelte.js +13 -15
- package/dist/dialog/Dialog.svelte +358 -38
- package/dist/dialog/Dialog.svelte.d.ts +10 -7
- package/dist/dialog/DialogBackdrop.svelte +30 -13
- package/dist/dialog/DialogBackdrop.svelte.d.ts +7 -4
- package/dist/dialog/DialogPanel.svelte +49 -26
- package/dist/dialog/DialogPanel.svelte.d.ts +7 -4
- package/dist/dialog/DialogTitle.svelte +38 -23
- package/dist/dialog/DialogTitle.svelte.d.ts +7 -4
- package/dist/field/Field.svelte +50 -34
- package/dist/field/Field.svelte.d.ts +7 -4
- package/dist/fieldset/Fieldset.svelte +50 -29
- package/dist/fieldset/Fieldset.svelte.d.ts +7 -4
- package/dist/focus-trap/FocusTrap.svelte +419 -283
- package/dist/focus-trap/FocusTrap.svelte.d.ts +7 -4
- package/dist/hooks/use-disabled.d.ts +4 -1
- package/dist/hooks/use-disabled.js +10 -5
- package/dist/input/Input.svelte +84 -53
- package/dist/input/Input.svelte.d.ts +7 -4
- package/dist/internal/FloatingProvider.svelte +14 -9
- package/dist/internal/FocusSentinel.svelte +16 -8
- package/dist/internal/ForcePortalRoot.svelte +7 -3
- package/dist/internal/FormFields.svelte +47 -34
- package/dist/internal/FormFieldsProvider.svelte +9 -5
- package/dist/internal/FormResolver.svelte +20 -15
- package/dist/internal/Hidden.svelte +50 -29
- package/dist/internal/Hidden.svelte.d.ts +7 -4
- package/dist/internal/MainTreeProvider.svelte +89 -36
- package/dist/internal/Portal.svelte +18 -14
- package/dist/internal/floating-provider.svelte.js +1 -1
- package/dist/internal/floating.svelte.d.ts +5 -5
- package/dist/internal/floating.svelte.js +17 -17
- package/dist/label/Label.svelte +93 -58
- package/dist/label/Label.svelte.d.ts +7 -4
- package/dist/legend/Legend.svelte +12 -3
- package/dist/listbox/Listbox.svelte +525 -387
- package/dist/listbox/Listbox.svelte.d.ts +7 -5
- package/dist/listbox/ListboxButton.svelte +173 -127
- package/dist/listbox/ListboxButton.svelte.d.ts +7 -5
- package/dist/listbox/ListboxOption.svelte +170 -129
- package/dist/listbox/ListboxOption.svelte.d.ts +7 -5
- package/dist/listbox/ListboxOptions.svelte +400 -304
- package/dist/listbox/ListboxOptions.svelte.d.ts +7 -5
- package/dist/listbox/ListboxSelectedOption.svelte +38 -15
- package/dist/listbox/ListboxSelectedOption.svelte.d.ts +7 -4
- package/dist/listbox/index.d.ts +4 -4
- package/dist/listbox/index.js +1 -1
- package/dist/menu/Menu.svelte +78 -57
- package/dist/menu/Menu.svelte.d.ts +7 -5
- package/dist/menu/MenuButton.svelte +157 -117
- package/dist/menu/MenuButton.svelte.d.ts +7 -5
- package/dist/menu/MenuHeading.svelte +32 -14
- package/dist/menu/MenuHeading.svelte.d.ts +7 -5
- package/dist/menu/MenuItem.svelte +142 -107
- package/dist/menu/MenuItem.svelte.d.ts +8 -8
- package/dist/menu/MenuItems.svelte +301 -229
- package/dist/menu/MenuItems.svelte.d.ts +7 -5
- package/dist/menu/MenuSection.svelte +24 -9
- package/dist/menu/MenuSection.svelte.d.ts +7 -5
- package/dist/menu/MenuSeparator.svelte +17 -4
- package/dist/menu/MenuSeparator.svelte.d.ts +7 -6
- package/dist/menu/context.svelte.d.ts +1 -29
- package/dist/menu/context.svelte.js +29 -27
- package/dist/menu/index.d.ts +7 -7
- package/dist/popover/Popover.svelte +216 -150
- package/dist/popover/Popover.svelte.d.ts +7 -4
- package/dist/popover/PopoverBackdrop.svelte +67 -41
- package/dist/popover/PopoverBackdrop.svelte.d.ts +7 -4
- package/dist/popover/PopoverButton.svelte +292 -212
- package/dist/popover/PopoverButton.svelte.d.ts +7 -4
- package/dist/popover/PopoverGroup.svelte +62 -35
- package/dist/popover/PopoverGroup.svelte.d.ts +7 -4
- package/dist/popover/PopoverPanel.svelte +311 -229
- package/dist/popover/PopoverPanel.svelte.d.ts +7 -4
- package/dist/portal/InternalPortal.svelte +141 -85
- package/dist/portal/InternalPortal.svelte.d.ts +7 -4
- package/dist/portal/Portal.svelte +5 -2
- package/dist/portal/PortalGroup.svelte +30 -9
- package/dist/portal/PortalGroup.svelte.d.ts +7 -4
- package/dist/select/Select.svelte +98 -68
- package/dist/select/Select.svelte.d.ts +7 -4
- package/dist/switch/Switch.svelte +179 -132
- package/dist/switch/Switch.svelte.d.ts +7 -4
- package/dist/switch/SwitchGroup.svelte +44 -31
- package/dist/switch/SwitchGroup.svelte.d.ts +7 -4
- package/dist/tabs/Tab.svelte +194 -143
- package/dist/tabs/Tab.svelte.d.ts +7 -4
- package/dist/tabs/TabGroup.svelte +81 -214
- package/dist/tabs/TabGroup.svelte.d.ts +7 -24
- package/dist/tabs/TabList.svelte +31 -11
- package/dist/tabs/TabList.svelte.d.ts +7 -4
- package/dist/tabs/TabPanel.svelte +67 -43
- package/dist/tabs/TabPanel.svelte.d.ts +7 -4
- package/dist/tabs/TabPanels.svelte +18 -7
- package/dist/tabs/TabPanels.svelte.d.ts +7 -4
- package/dist/tabs/context.svelte.d.ts +31 -0
- package/dist/tabs/context.svelte.js +134 -0
- package/dist/textarea/Textarea.svelte +84 -53
- package/dist/textarea/Textarea.svelte.d.ts +7 -4
- package/dist/transition/InternalTransitionChild.svelte +259 -170
- package/dist/transition/InternalTransitionChild.svelte.d.ts +7 -4
- package/dist/transition/Transition.svelte +96 -66
- package/dist/transition/Transition.svelte.d.ts +7 -4
- package/dist/transition/TransitionChild.svelte +31 -11
- package/dist/transition/TransitionChild.svelte.d.ts +7 -4
- package/dist/utils/ElementOrComponent.svelte +43 -23
- package/dist/utils/ElementOrComponent.svelte.d.ts +10 -4
- package/dist/utils/Generic.svelte +36 -22
- package/dist/utils/Generic.svelte.d.ts +7 -4
- package/dist/utils/StableCollection.svelte +54 -36
- package/dist/utils/floating-ui/svelte/components/FloatingNode.svelte +27 -12
- package/dist/utils/floating-ui/svelte/components/FloatingTree.svelte +88 -44
- package/dist/utils/floating-ui/svelte/hooks/useFloating.svelte.js +7 -7
- package/dist/utils/floating-ui/svelte/hooks/useFloatingRootContext.svelte.js +1 -1
- package/dist/utils/floating-ui/svelte/types.d.ts +4 -4
- package/dist/utils/floating-ui/svelte-dom/types.d.ts +2 -2
- package/dist/utils/floating-ui/svelte-dom/useFloating.svelte.js +6 -6
- package/dist/utils/types.d.ts +11 -4
- package/package.json +2 -2
- package/dist/dialog/InternalDialog.svelte +0 -233
- package/dist/dialog/InternalDialog.svelte.d.ts +0 -42
|
@@ -1,34 +1,60 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Props, ElementType } from "../utils/types.js"
|
|
3
|
+
import { useFocusRing } from "../hooks/use-focus-ring.svelte.js"
|
|
4
|
+
import { useActivePress } from "../hooks/use-active-press.svelte.js"
|
|
5
|
+
import type { Snippet } from "svelte"
|
|
6
|
+
import { useHover } from "../hooks/use-hover.svelte.js"
|
|
7
|
+
import { mergeProps } from "../utils/render.js"
|
|
8
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte"
|
|
9
|
+
|
|
10
|
+
const DEFAULT_DATA_INTERACTIVE_TAG = "svelte:fragment" as const
|
|
11
|
+
|
|
12
|
+
type DataInteractiveRenderPropArg = {
|
|
13
|
+
hover: boolean
|
|
14
|
+
focus: boolean
|
|
15
|
+
active: boolean
|
|
16
|
+
}
|
|
17
|
+
type DataInteractivePropsWeControl = never
|
|
18
|
+
|
|
19
|
+
export type DataInteractiveProps<TTag extends ElementType = typeof DEFAULT_DATA_INTERACTIVE_TAG> = Props<
|
|
20
|
+
TTag,
|
|
21
|
+
DataInteractiveRenderPropArg,
|
|
22
|
+
DataInteractivePropsWeControl,
|
|
23
|
+
{}
|
|
24
|
+
>
|
|
7
25
|
</script>
|
|
8
26
|
|
|
9
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_DATA_INTERACTIVE_TAG">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
27
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_DATA_INTERACTIVE_TAG">
|
|
28
|
+
let { ref = $bindable(), ...theirProps }: { as?: TTag } & DataInteractiveProps<TTag> = $props()
|
|
29
|
+
|
|
30
|
+
// Ideally we can use a `disabled` prop, but that would depend on the props of the child element
|
|
31
|
+
// and we don't have access to that in this component.
|
|
32
|
+
|
|
33
|
+
const disabled = false
|
|
34
|
+
|
|
35
|
+
const { isHovered: hover, hoverProps } = $derived(
|
|
36
|
+
useHover({
|
|
37
|
+
get disabled() {
|
|
38
|
+
return disabled
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
const { pressed: active, pressProps } = $derived(
|
|
43
|
+
useActivePress({
|
|
44
|
+
get disabled() {
|
|
45
|
+
return disabled
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
const { isFocusVisible: focus, focusProps } = $derived(useFocusRing())
|
|
50
|
+
|
|
51
|
+
const slot = $derived({
|
|
52
|
+
hover,
|
|
53
|
+
focus,
|
|
54
|
+
active,
|
|
55
|
+
} satisfies DataInteractiveRenderPropArg)
|
|
56
|
+
|
|
57
|
+
const ourProps = $derived(mergeProps(focusProps, hoverProps, pressProps))
|
|
32
58
|
</script>
|
|
33
59
|
|
|
34
60
|
<ElementOrComponent
|
|
@@ -8,15 +8,17 @@ type DataInteractiveRenderPropArg = {
|
|
|
8
8
|
};
|
|
9
9
|
type DataInteractivePropsWeControl = never;
|
|
10
10
|
export type DataInteractiveProps<TTag extends ElementType = typeof DEFAULT_DATA_INTERACTIVE_TAG> = Props<TTag, DataInteractiveRenderPropArg, DataInteractivePropsWeControl, {}>;
|
|
11
|
-
export type DataInteractiveChildren = Snippet<[DataInteractiveRenderPropArg]>;
|
|
12
11
|
declare class __sveltets_Render<TTag extends ElementType = typeof DEFAULT_DATA_INTERACTIVE_TAG> {
|
|
13
12
|
props(): {
|
|
14
13
|
as?: TTag | undefined;
|
|
15
|
-
} & (Exclude<keyof import("../utils/types.js").PropsOf<TTag>, "as" | "children" | "
|
|
16
|
-
children?: Snippet<[
|
|
14
|
+
} & (Exclude<keyof import("../utils/types.js").PropsOf<TTag>, "as" | "children" | "class"> extends infer T extends keyof import("../utils/types.js").PropsOf<TTag> ? { [P in T]: import("../utils/types.js").PropsOf<TTag>[P]; } : never) & {
|
|
15
|
+
children?: Snippet<[{
|
|
16
|
+
slot: DataInteractiveRenderPropArg;
|
|
17
|
+
props: Record<string, any>;
|
|
18
|
+
}]> | undefined;
|
|
17
19
|
ref?: HTMLElement;
|
|
18
|
-
} & (true extends (import("
|
|
19
|
-
class?:
|
|
20
|
+
} & (true extends (import("svelte/elements").SvelteHTMLElements[TTag] extends infer T_1 ? T_1 extends import("svelte/elements").SvelteHTMLElements[TTag] ? T_1 extends never ? never : "class" extends infer T_2 ? T_2 extends "class" ? T_2 extends keyof T_1 ? true : never : never : never : never : never) ? {
|
|
21
|
+
class?: string | ((bag: DataInteractiveRenderPropArg) => string) | null | undefined;
|
|
20
22
|
} : {});
|
|
21
23
|
events(): {} & {
|
|
22
24
|
[evt: string]: CustomEvent<any>;
|
|
@@ -1,28 +1,43 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { ElementType, Props, PropsOf } from "../utils/types.js"
|
|
3
|
+
|
|
4
|
+
let DEFAULT_DESCRIPTION_TAG = "p" as const
|
|
5
|
+
|
|
6
|
+
export type DescriptionProps<TTag extends ElementType = typeof DEFAULT_DESCRIPTION_TAG> = Props<TTag>
|
|
2
7
|
</script>
|
|
3
8
|
|
|
4
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_DESCRIPTION_TAG">
|
|
5
|
-
import {
|
|
6
|
-
import { useDisabled } from "../hooks/use-disabled.js"
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_DESCRIPTION_TAG">
|
|
10
|
+
import { useId } from "../hooks/use-id.js"
|
|
11
|
+
import { useDisabled } from "../hooks/use-disabled.js"
|
|
12
|
+
import { useDescriptionContext } from "./context.svelte.js"
|
|
13
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte"
|
|
14
|
+
import { untrack } from "svelte"
|
|
15
|
+
|
|
16
|
+
const internalId = useId()
|
|
17
|
+
const providedDisabled = useDisabled()
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
ref = $bindable(),
|
|
21
|
+
id = `headlessui-description-${internalId}` as PropsOf<TTag>["id"],
|
|
22
|
+
...theirProps
|
|
23
|
+
}: { as?: TTag } & DescriptionProps<TTag> = $props()
|
|
24
|
+
|
|
25
|
+
const { register } = useDescriptionContext()
|
|
26
|
+
$effect(() => {
|
|
27
|
+
id
|
|
28
|
+
return untrack(() => register(id))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const disabled = $derived(providedDisabled.current || false)
|
|
32
|
+
const slot = $derived({ disabled })
|
|
33
|
+
const ourProps = $derived({ id })
|
|
24
34
|
</script>
|
|
25
35
|
|
|
26
|
-
<
|
|
27
|
-
{
|
|
28
|
-
|
|
36
|
+
<ElementOrComponent
|
|
37
|
+
{ourProps}
|
|
38
|
+
{theirProps}
|
|
39
|
+
slots={slot}
|
|
40
|
+
defaultTag={DEFAULT_DESCRIPTION_TAG}
|
|
41
|
+
name="Description"
|
|
42
|
+
bind:ref
|
|
43
|
+
/>
|
|
@@ -4,17 +4,20 @@ export type DescriptionProps<TTag extends ElementType = typeof DEFAULT_DESCRIPTI
|
|
|
4
4
|
declare class __sveltets_Render<TTag extends ElementType = typeof DEFAULT_DESCRIPTION_TAG> {
|
|
5
5
|
props(): {
|
|
6
6
|
as?: TTag | undefined;
|
|
7
|
-
} & (Exclude<keyof PropsOf<TTag>, "as" | "children" | "
|
|
8
|
-
children?: import("svelte").Snippet<[{
|
|
7
|
+
} & (Exclude<keyof PropsOf<TTag>, "as" | "children" | "class"> extends infer T extends keyof PropsOf<TTag> ? { [P in T]: PropsOf<TTag>[P]; } : never) & {
|
|
8
|
+
children?: import("svelte").Snippet<[{
|
|
9
|
+
slot: {};
|
|
10
|
+
props: Record<string, any>;
|
|
11
|
+
}]> | undefined;
|
|
9
12
|
ref?: HTMLElement;
|
|
10
|
-
} & (true extends (
|
|
11
|
-
class?:
|
|
13
|
+
} & (true extends (import("svelte/elements.js").SvelteHTMLElements[TTag] extends infer T_1 ? T_1 extends import("svelte/elements.js").SvelteHTMLElements[TTag] ? T_1 extends never ? never : "class" extends infer T_2 ? T_2 extends "class" ? T_2 extends keyof T_1 ? true : never : never : never : never : never) ? {
|
|
14
|
+
class?: string | ((bag: {}) => string) | null | undefined;
|
|
12
15
|
} : {});
|
|
13
16
|
events(): {} & {
|
|
14
17
|
[evt: string]: CustomEvent<any>;
|
|
15
18
|
};
|
|
16
19
|
slots(): {};
|
|
17
|
-
bindings(): "";
|
|
20
|
+
bindings(): "ref";
|
|
18
21
|
exports(): {};
|
|
19
22
|
}
|
|
20
23
|
interface $$IsomorphicComponent {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getContext, setContext } from "svelte";
|
|
2
2
|
export function useDescriptionContext() {
|
|
3
|
-
|
|
3
|
+
const context = getContext("DescriptionContext");
|
|
4
4
|
if (!context) {
|
|
5
|
-
|
|
5
|
+
const err = new Error("You used a <Description /> component, but it is not inside a relevant parent.");
|
|
6
6
|
if (Error.captureStackTrace)
|
|
7
7
|
Error.captureStackTrace(err, useDescriptionContext);
|
|
8
8
|
throw err;
|
|
@@ -18,10 +18,19 @@ export function useDescribedBy() {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
export const useDescriptions = (options = {}) => {
|
|
21
|
-
const { slot, name, props
|
|
22
|
-
|
|
21
|
+
const { slot, name, props } = $derived(options);
|
|
22
|
+
const descriptionIds = $state([]);
|
|
23
|
+
const register = (value) => {
|
|
24
|
+
descriptionIds.push(value);
|
|
25
|
+
return () => {
|
|
26
|
+
const idx = descriptionIds.indexOf(value);
|
|
27
|
+
if (idx !== -1)
|
|
28
|
+
descriptionIds.splice(idx, 1);
|
|
29
|
+
};
|
|
30
|
+
};
|
|
23
31
|
const value = $derived(descriptionIds.length > 0 ? descriptionIds.join(" ") : undefined);
|
|
24
32
|
const context = {
|
|
33
|
+
register,
|
|
25
34
|
get slot() {
|
|
26
35
|
return slot;
|
|
27
36
|
},
|
|
@@ -34,17 +43,6 @@ export const useDescriptions = (options = {}) => {
|
|
|
34
43
|
get value() {
|
|
35
44
|
return value;
|
|
36
45
|
},
|
|
37
|
-
register(value) {
|
|
38
|
-
descriptionIds.push(value);
|
|
39
|
-
return () => {
|
|
40
|
-
const clone = descriptionIds.slice();
|
|
41
|
-
const idx = clone.indexOf(value);
|
|
42
|
-
if (idx !== -1)
|
|
43
|
-
clone.splice(idx, 1);
|
|
44
|
-
descriptionIds = clone;
|
|
45
|
-
return descriptionIds;
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
46
|
};
|
|
49
47
|
setContext("DescriptionContext", context);
|
|
50
48
|
return context;
|
|
@@ -1,51 +1,371 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import {
|
|
3
|
-
import { RenderFeatures } from "../utils/render.js"
|
|
4
|
-
import { getContext } from "svelte";
|
|
5
|
-
import InternalDialog from "./InternalDialog.svelte";
|
|
6
|
-
import Transition from "../transition/Transition.svelte";
|
|
7
|
-
export const DEFAULT_DIALOG_TAG = "div";
|
|
8
|
-
export const DialogRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static;
|
|
9
|
-
</script>
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { ElementType, Props } from "../utils/types.js"
|
|
3
|
+
import { RenderFeatures, type PropsForFeatures } from "../utils/render.js"
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const hasOnClose = $derived(rest.hasOwnProperty("onclose"));
|
|
15
|
-
$effect(() => {
|
|
16
|
-
if (!hasOpen && !hasOnClose) {
|
|
17
|
-
throw new Error(`You have to provide an \`open\` and an \`onclose\` prop to the \`Dialog\` component.`);
|
|
18
|
-
}
|
|
19
|
-
if (!hasOpen) {
|
|
20
|
-
throw new Error(`You provided an \`onclose\` prop to the \`Dialog\`, but forgot an \`open\` prop.`);
|
|
21
|
-
}
|
|
22
|
-
if (!hasOnClose) {
|
|
23
|
-
throw new Error(`You provided an \`open\` prop to the \`Dialog\`, but forgot an \`onclose\` prop.`);
|
|
5
|
+
export const DEFAULT_DIALOG_TAG = "div" as const
|
|
6
|
+
export type DialogRenderPropArg = {
|
|
7
|
+
open: boolean
|
|
24
8
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
9
|
+
type DialogPropsWeControl = "aria-describedby" | "aria-labelledby" | "aria-modal"
|
|
10
|
+
|
|
11
|
+
export const DialogRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
|
|
12
|
+
|
|
13
|
+
export type DialogProps<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG> = Props<
|
|
14
|
+
TTag,
|
|
15
|
+
DialogRenderPropArg,
|
|
16
|
+
DialogPropsWeControl,
|
|
17
|
+
PropsForFeatures<typeof DialogRenderFeatures> & {
|
|
18
|
+
as?: TTag
|
|
19
|
+
id?: string
|
|
20
|
+
open?: boolean
|
|
21
|
+
onclose(value: boolean): void
|
|
22
|
+
initialFocus?: HTMLElement
|
|
23
|
+
role?: "dialog" | "alertdialog"
|
|
24
|
+
autofocus?: boolean
|
|
25
|
+
transition?: boolean
|
|
26
|
+
__demoMode?: boolean
|
|
27
|
+
}
|
|
28
|
+
>
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_DIALOG_TAG">
|
|
32
|
+
import { useId } from "../hooks/use-id.js"
|
|
33
|
+
import { useMainTreeNode, useRootContainers } from "../hooks/use-root-containers.svelte.js"
|
|
34
|
+
import { clearOpenClosedContext, State, useOpenClosed } from "../internal/open-closed.js"
|
|
35
|
+
import { useNestedPortals } from "../portal/InternalPortal.svelte"
|
|
36
|
+
import { getOwnerDocument } from "../utils/owner.js"
|
|
37
|
+
import { useInertOthers } from "../hooks/use-inert-others.svelte.js"
|
|
38
|
+
import { useOutsideClick } from "../hooks/use-outside-click.svelte.js"
|
|
39
|
+
import { useEscape } from "../hooks/use-escape.svelte.js"
|
|
40
|
+
import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js"
|
|
41
|
+
import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js"
|
|
42
|
+
import { setContext } from "svelte"
|
|
43
|
+
import { useIsTouchDevice } from "../hooks/use-is-touch-device.svelte.js"
|
|
44
|
+
import FocusTrap, { FocusTrapFeatures } from "../focus-trap/FocusTrap.svelte"
|
|
45
|
+
import Portal from "../portal/Portal.svelte"
|
|
46
|
+
import PortalGroup from "../portal/PortalGroup.svelte"
|
|
47
|
+
import ForcePortalRoot from "../internal/ForcePortalRoot.svelte"
|
|
48
|
+
import { createCloseContext } from "../internal/close-provider.js"
|
|
49
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte"
|
|
50
|
+
import { DialogStates, type DialogContext, type StateDefinition } from "./context.svelte.js"
|
|
51
|
+
import { useDescriptions } from "../description/context.svelte.js"
|
|
52
|
+
import MainTreeProvider from "../internal/MainTreeProvider.svelte"
|
|
53
|
+
import Transition from "../transition/Transition.svelte"
|
|
54
|
+
|
|
55
|
+
const internalId = useId()
|
|
56
|
+
let {
|
|
57
|
+
ref = $bindable(),
|
|
58
|
+
id = `headlessui-dialog-${internalId}`,
|
|
59
|
+
open: theirOpen,
|
|
60
|
+
onclose,
|
|
61
|
+
initialFocus,
|
|
62
|
+
role: theirRole = "dialog",
|
|
63
|
+
autofocus = true,
|
|
64
|
+
__demoMode = false,
|
|
65
|
+
unmount = false,
|
|
66
|
+
transition = false,
|
|
67
|
+
...theirProps
|
|
68
|
+
}: { as?: TTag } & DialogProps<TTag> = $props()
|
|
69
|
+
|
|
70
|
+
// Validations
|
|
71
|
+
const usesOpenClosedState = useOpenClosed()
|
|
72
|
+
const hasOpen = $derived(theirOpen !== undefined || usesOpenClosedState)
|
|
73
|
+
const hasOnClose = $derived(theirProps.hasOwnProperty("onclose"))
|
|
74
|
+
|
|
75
|
+
$effect(() => {
|
|
76
|
+
if (!hasOpen && !hasOnClose) {
|
|
77
|
+
throw new Error(`You have to provide an \`open\` and an \`onclose\` prop to the \`Dialog\` component.`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!hasOpen) {
|
|
81
|
+
throw new Error(`You provided an \`onclose\` prop to the \`Dialog\`, but forgot an \`open\` prop.`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!hasOnClose) {
|
|
85
|
+
throw new Error(`You provided an \`open\` prop to the \`Dialog\`, but forgot an \`onclose\` prop.`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!usesOpenClosedState && typeof open !== "boolean") {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`You provided an \`open\` prop to the \`Dialog\`, but the value is not a boolean. Received: ${open}`
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof theirProps.onclose !== "function") {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`You provided an \`onclose\` prop to the \`Dialog\`, but the value is not a function. Received: ${theirProps.onclose}`
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
let didWarnOnRole = $state(false)
|
|
102
|
+
|
|
103
|
+
const role = $derived.by(() => {
|
|
104
|
+
if (theirRole === "dialog" || theirRole === "alertdialog") {
|
|
105
|
+
return theirRole
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!didWarnOnRole) {
|
|
109
|
+
didWarnOnRole = true
|
|
110
|
+
console.warn(
|
|
111
|
+
`Invalid role [${theirRole}] passed to <Dialog />. Only \`dialog\` and and \`alertdialog\` are supported. Using \`dialog\` instead.`
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return "dialog"
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Update the `open` prop based on the open closed state
|
|
119
|
+
const open = $derived(
|
|
120
|
+
theirOpen === undefined && usesOpenClosedState !== null
|
|
121
|
+
? (usesOpenClosedState.value & State.Open) === State.Open
|
|
122
|
+
: theirOpen
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const ownerDocument = $derived(getOwnerDocument(ref))
|
|
126
|
+
|
|
127
|
+
const dialogState = $derived(open ? DialogStates.Open : DialogStates.Closed)
|
|
128
|
+
|
|
129
|
+
let _state = $state({
|
|
130
|
+
titleId: null,
|
|
131
|
+
panelRef: null,
|
|
132
|
+
} as StateDefinition)
|
|
133
|
+
|
|
134
|
+
const close = $derived(() => onclose(false))
|
|
135
|
+
|
|
136
|
+
const setTitleId = (id: string | null) => (_state.titleId = id)
|
|
137
|
+
|
|
138
|
+
const enabled = $derived(dialogState === DialogStates.Open)
|
|
139
|
+
const nestedPortals = useNestedPortals()
|
|
140
|
+
const { portals } = $derived(nestedPortals)
|
|
141
|
+
|
|
142
|
+
// We use this because reading these values during initial render(s)
|
|
143
|
+
// can result in `null` rather then the actual elements
|
|
144
|
+
// This doesn't happen when using certain components like a
|
|
145
|
+
// `<Dialog.Title>` because they cause the parent to re-render
|
|
146
|
+
const defaultContainer: { readonly current: HTMLElement | undefined } = {
|
|
147
|
+
get current() {
|
|
148
|
+
return _state.panelRef ?? ref
|
|
149
|
+
},
|
|
34
150
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
151
|
+
|
|
152
|
+
const mainTreeNode = useMainTreeNode()
|
|
153
|
+
let { resolvedContainers: resolvedRootContainers } = $derived(
|
|
154
|
+
useRootContainers({
|
|
155
|
+
get mainTreeNode() {
|
|
156
|
+
return mainTreeNode.node
|
|
157
|
+
},
|
|
158
|
+
get portals() {
|
|
159
|
+
return portals
|
|
160
|
+
},
|
|
161
|
+
get defaultContainers() {
|
|
162
|
+
return defaultContainer.current ? [defaultContainer.current] : []
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// When the `Dialog` is wrapped in a `Transition` (or another Headless UI component that exposes
|
|
168
|
+
// the OpenClosed state) then we get some information via context about its state. When the
|
|
169
|
+
// `Transition` is about to close, then the `State.Closing` state will be exposed. This allows us
|
|
170
|
+
// to enable/disable certain functionality in the `Dialog` upfront instead of waiting until the
|
|
171
|
+
// `Transition` is done transitioning.
|
|
172
|
+
const isClosing = $derived(
|
|
173
|
+
usesOpenClosedState !== null ? (usesOpenClosedState.value & State.Closing) === State.Closing : false
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
// Ensure other elements can't be interacted with
|
|
177
|
+
const inertOthersEnabled = $derived(__demoMode ? false : isClosing ? false : enabled)
|
|
178
|
+
useInertOthers({
|
|
179
|
+
get enabled() {
|
|
180
|
+
return inertOthersEnabled
|
|
181
|
+
},
|
|
182
|
+
elements: {
|
|
183
|
+
get allowed() {
|
|
184
|
+
return [
|
|
185
|
+
// Allow the headlessui-portal of the Dialog to be interactive. This
|
|
186
|
+
// contains the current dialog and the necessary focus guard elements.
|
|
187
|
+
ref?.closest<HTMLElement>("[data-headlessui-portal]") ?? null,
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
get disallowed() {
|
|
191
|
+
return [
|
|
192
|
+
// Disallow the "main" tree root node
|
|
193
|
+
mainTreeNode.node?.closest<HTMLElement>("body > *:not(#headlessui-portal-root)") ?? null,
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Close Dialog on outside click
|
|
200
|
+
useOutsideClick({
|
|
201
|
+
get enabled() {
|
|
202
|
+
return enabled
|
|
203
|
+
},
|
|
204
|
+
get containers() {
|
|
205
|
+
return resolvedRootContainers
|
|
206
|
+
},
|
|
207
|
+
cb(event) {
|
|
208
|
+
event.preventDefault()
|
|
209
|
+
close()
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Handle `Escape` to close
|
|
214
|
+
useEscape({
|
|
215
|
+
get enabled() {
|
|
216
|
+
return enabled
|
|
217
|
+
},
|
|
218
|
+
get view() {
|
|
219
|
+
return ownerDocument?.defaultView ?? null
|
|
220
|
+
},
|
|
221
|
+
cb(event) {
|
|
222
|
+
event.preventDefault()
|
|
223
|
+
event.stopPropagation()
|
|
224
|
+
|
|
225
|
+
// Ensure that we blur the current activeElement to prevent maintaining
|
|
226
|
+
// focus and potentially scrolling the page to the end (because the Dialog
|
|
227
|
+
// is rendered in a Portal at the end of the document.body and the browser
|
|
228
|
+
// tries to keep the focused element in view)
|
|
229
|
+
//
|
|
230
|
+
// Typically only happens in Safari.
|
|
231
|
+
if (
|
|
232
|
+
document.activeElement &&
|
|
233
|
+
"blur" in document.activeElement &&
|
|
234
|
+
typeof document.activeElement.blur === "function"
|
|
235
|
+
) {
|
|
236
|
+
document.activeElement.blur()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
close()
|
|
240
|
+
},
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// Scroll lock
|
|
244
|
+
const scrollLockEnabled = $derived(__demoMode ? false : isClosing ? false : enabled)
|
|
245
|
+
useScrollLock({
|
|
246
|
+
get enabled() {
|
|
247
|
+
return scrollLockEnabled
|
|
248
|
+
},
|
|
249
|
+
get ownerDocument() {
|
|
250
|
+
return ownerDocument
|
|
251
|
+
},
|
|
252
|
+
resolveAllowedContainers() {
|
|
253
|
+
return resolvedRootContainers
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// Ensure we close the dialog as soon as the dialog itself becomes hidden
|
|
258
|
+
useOnDisappear({
|
|
259
|
+
get enabled() {
|
|
260
|
+
return enabled
|
|
261
|
+
},
|
|
262
|
+
get ref() {
|
|
263
|
+
return ref
|
|
264
|
+
},
|
|
265
|
+
get ondisappear() {
|
|
266
|
+
return close
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const describedby = useDescriptions()
|
|
271
|
+
|
|
272
|
+
setContext<DialogContext>("DialogContext", {
|
|
273
|
+
get titleId() {
|
|
274
|
+
return _state.titleId
|
|
275
|
+
},
|
|
276
|
+
get panelRef() {
|
|
277
|
+
return _state.panelRef
|
|
278
|
+
},
|
|
279
|
+
get dialogState() {
|
|
280
|
+
return dialogState
|
|
281
|
+
},
|
|
282
|
+
get close() {
|
|
283
|
+
return close
|
|
284
|
+
},
|
|
285
|
+
get unmount() {
|
|
286
|
+
return unmount
|
|
287
|
+
},
|
|
288
|
+
setTitleId,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
const slot = $derived({ open: dialogState === DialogStates.Open } satisfies DialogRenderPropArg)
|
|
292
|
+
|
|
293
|
+
const ourProps = $derived({
|
|
294
|
+
id,
|
|
295
|
+
role,
|
|
296
|
+
tabIndex: -1,
|
|
297
|
+
"aria-modal": __demoMode ? undefined : dialogState === DialogStates.Open ? true : undefined,
|
|
298
|
+
"aria-labelledby": _state.titleId,
|
|
299
|
+
"aria-describedby": describedby.value,
|
|
300
|
+
unmount,
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
const shouldMoveFocusInside = !useIsTouchDevice().value
|
|
304
|
+
const focusTrapFeatures = $derived.by(() => {
|
|
305
|
+
let focusTrapFeatures = FocusTrapFeatures.None
|
|
306
|
+
|
|
307
|
+
if (enabled && !__demoMode) {
|
|
308
|
+
focusTrapFeatures |= FocusTrapFeatures.RestoreFocus
|
|
309
|
+
focusTrapFeatures |= FocusTrapFeatures.TabLock
|
|
310
|
+
|
|
311
|
+
if (autofocus) {
|
|
312
|
+
focusTrapFeatures |= FocusTrapFeatures.AutoFocus
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (shouldMoveFocusInside) {
|
|
316
|
+
focusTrapFeatures |= FocusTrapFeatures.InitialFocus
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return focusTrapFeatures
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
clearOpenClosedContext()
|
|
324
|
+
createCloseContext({
|
|
325
|
+
get close() {
|
|
326
|
+
return close
|
|
327
|
+
},
|
|
328
|
+
})
|
|
37
329
|
</script>
|
|
38
330
|
|
|
39
|
-
{#
|
|
331
|
+
{#snippet internal(transitionProps?: Record<string, any>)}
|
|
332
|
+
<ForcePortalRoot force={true}>
|
|
333
|
+
<Portal>
|
|
334
|
+
<PortalGroup target={ref ?? null}>
|
|
335
|
+
<ForcePortalRoot force={false}>
|
|
336
|
+
<FocusTrap
|
|
337
|
+
{initialFocus}
|
|
338
|
+
initialFocusFallback={ref}
|
|
339
|
+
containers={resolvedRootContainers}
|
|
340
|
+
features={focusTrapFeatures}
|
|
341
|
+
>
|
|
342
|
+
<ElementOrComponent
|
|
343
|
+
{ourProps}
|
|
344
|
+
theirProps={{ ...theirProps, ...transitionProps }}
|
|
345
|
+
slots={slot}
|
|
346
|
+
defaultTag={DEFAULT_DIALOG_TAG}
|
|
347
|
+
features={DialogRenderFeatures}
|
|
348
|
+
visible={dialogState === DialogStates.Open}
|
|
349
|
+
name="Dialog"
|
|
350
|
+
bind:ref
|
|
351
|
+
/>
|
|
352
|
+
</FocusTrap>
|
|
353
|
+
</ForcePortalRoot>
|
|
354
|
+
</PortalGroup>
|
|
355
|
+
</Portal>
|
|
356
|
+
</ForcePortalRoot>
|
|
357
|
+
{/snippet}
|
|
358
|
+
|
|
359
|
+
{#if (open !== undefined || transition) && !theirProps.static}
|
|
40
360
|
<MainTreeProvider>
|
|
41
|
-
<Transition show={open} {transition} unmount={
|
|
42
|
-
{#snippet children(
|
|
43
|
-
|
|
361
|
+
<Transition show={open} {transition} unmount={theirProps.unmount} {ref}>
|
|
362
|
+
{#snippet children({ props })}
|
|
363
|
+
{@render internal(props)}
|
|
44
364
|
{/snippet}
|
|
45
365
|
</Transition>
|
|
46
366
|
</MainTreeProvider>
|
|
47
367
|
{:else}
|
|
48
368
|
<MainTreeProvider>
|
|
49
|
-
|
|
369
|
+
{@render internal()}
|
|
50
370
|
</MainTreeProvider>
|
|
51
371
|
{/if}
|