@pzerelles/headlessui-svelte 2.1.2-next.31 → 2.1.2-next.32

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.
Files changed (113) hide show
  1. package/dist/button/Button.svelte +84 -54
  2. package/dist/button/Button.svelte.d.ts +2 -3
  3. package/dist/checkbox/Checkbox.svelte +173 -120
  4. package/dist/checkbox/Checkbox.svelte.d.ts +2 -3
  5. package/dist/close-button/CloseButton.svelte +12 -6
  6. package/dist/close-button/CloseButton.svelte.d.ts +7 -8
  7. package/dist/combobox/Combobox.svelte +50 -3
  8. package/dist/data-interactive/DataInteractive.svelte +55 -29
  9. package/dist/data-interactive/DataInteractive.svelte.d.ts +2 -3
  10. package/dist/description/Description.svelte +31 -21
  11. package/dist/description/Description.svelte.d.ts +1 -2
  12. package/dist/dialog/Dialog.svelte +320 -228
  13. package/dist/dialog/Dialog.svelte.d.ts +2 -3
  14. package/dist/dialog/DialogBackdrop.svelte +30 -13
  15. package/dist/dialog/DialogBackdrop.svelte.d.ts +2 -3
  16. package/dist/dialog/DialogPanel.svelte +49 -26
  17. package/dist/dialog/DialogPanel.svelte.d.ts +2 -3
  18. package/dist/dialog/DialogTitle.svelte +38 -23
  19. package/dist/dialog/DialogTitle.svelte.d.ts +2 -3
  20. package/dist/field/Field.svelte +47 -25
  21. package/dist/field/Field.svelte.d.ts +1 -2
  22. package/dist/fieldset/Fieldset.svelte +50 -29
  23. package/dist/fieldset/Fieldset.svelte.d.ts +1 -2
  24. package/dist/focus-trap/FocusTrap.svelte +419 -283
  25. package/dist/focus-trap/FocusTrap.svelte.d.ts +1 -2
  26. package/dist/input/Input.svelte +84 -53
  27. package/dist/input/Input.svelte.d.ts +2 -3
  28. package/dist/internal/FloatingProvider.svelte +14 -9
  29. package/dist/internal/FocusSentinel.svelte +16 -8
  30. package/dist/internal/ForcePortalRoot.svelte +7 -3
  31. package/dist/internal/FormFields.svelte +47 -34
  32. package/dist/internal/FormFieldsProvider.svelte +9 -5
  33. package/dist/internal/FormResolver.svelte +20 -15
  34. package/dist/internal/Hidden.svelte +50 -29
  35. package/dist/internal/Hidden.svelte.d.ts +1 -2
  36. package/dist/internal/MainTreeProvider.svelte +89 -36
  37. package/dist/internal/Portal.svelte +18 -14
  38. package/dist/label/Label.svelte +93 -58
  39. package/dist/label/Label.svelte.d.ts +1 -2
  40. package/dist/legend/Legend.svelte +12 -3
  41. package/dist/listbox/Listbox.svelte +525 -387
  42. package/dist/listbox/Listbox.svelte.d.ts +2 -3
  43. package/dist/listbox/ListboxButton.svelte +173 -127
  44. package/dist/listbox/ListboxButton.svelte.d.ts +2 -3
  45. package/dist/listbox/ListboxOption.svelte +170 -129
  46. package/dist/listbox/ListboxOption.svelte.d.ts +2 -3
  47. package/dist/listbox/ListboxOptions.svelte +400 -304
  48. package/dist/listbox/ListboxOptions.svelte.d.ts +2 -3
  49. package/dist/listbox/ListboxSelectedOption.svelte +38 -15
  50. package/dist/listbox/ListboxSelectedOption.svelte.d.ts +1 -2
  51. package/dist/menu/Menu.svelte +77 -51
  52. package/dist/menu/Menu.svelte.d.ts +2 -3
  53. package/dist/menu/MenuButton.svelte +157 -117
  54. package/dist/menu/MenuButton.svelte.d.ts +2 -3
  55. package/dist/menu/MenuHeading.svelte +32 -14
  56. package/dist/menu/MenuHeading.svelte.d.ts +1 -2
  57. package/dist/menu/MenuItem.svelte +142 -107
  58. package/dist/menu/MenuItem.svelte.d.ts +2 -3
  59. package/dist/menu/MenuItems.svelte +301 -229
  60. package/dist/menu/MenuItems.svelte.d.ts +2 -3
  61. package/dist/menu/MenuSection.svelte +24 -9
  62. package/dist/menu/MenuSection.svelte.d.ts +1 -2
  63. package/dist/menu/MenuSeparator.svelte +17 -4
  64. package/dist/menu/MenuSeparator.svelte.d.ts +1 -2
  65. package/dist/popover/Popover.svelte +216 -150
  66. package/dist/popover/Popover.svelte.d.ts +2 -3
  67. package/dist/popover/PopoverBackdrop.svelte +67 -41
  68. package/dist/popover/PopoverBackdrop.svelte.d.ts +2 -3
  69. package/dist/popover/PopoverButton.svelte +292 -212
  70. package/dist/popover/PopoverButton.svelte.d.ts +2 -3
  71. package/dist/popover/PopoverGroup.svelte +62 -35
  72. package/dist/popover/PopoverGroup.svelte.d.ts +1 -2
  73. package/dist/popover/PopoverPanel.svelte +311 -229
  74. package/dist/popover/PopoverPanel.svelte.d.ts +2 -3
  75. package/dist/portal/InternalPortal.svelte +141 -85
  76. package/dist/portal/InternalPortal.svelte.d.ts +1 -2
  77. package/dist/portal/Portal.svelte +5 -2
  78. package/dist/portal/PortalGroup.svelte +30 -9
  79. package/dist/portal/PortalGroup.svelte.d.ts +1 -2
  80. package/dist/select/Select.svelte +98 -68
  81. package/dist/select/Select.svelte.d.ts +2 -3
  82. package/dist/switch/Switch.svelte +179 -132
  83. package/dist/switch/Switch.svelte.d.ts +2 -3
  84. package/dist/switch/SwitchGroup.svelte +44 -31
  85. package/dist/switch/SwitchGroup.svelte.d.ts +1 -2
  86. package/dist/tabs/Tab.svelte +194 -142
  87. package/dist/tabs/Tab.svelte.d.ts +2 -3
  88. package/dist/tabs/TabGroup.svelte +86 -56
  89. package/dist/tabs/TabGroup.svelte.d.ts +2 -3
  90. package/dist/tabs/TabList.svelte +31 -11
  91. package/dist/tabs/TabList.svelte.d.ts +2 -3
  92. package/dist/tabs/TabPanel.svelte +67 -42
  93. package/dist/tabs/TabPanel.svelte.d.ts +2 -3
  94. package/dist/tabs/TabPanels.svelte +18 -7
  95. package/dist/tabs/TabPanels.svelte.d.ts +2 -3
  96. package/dist/textarea/Textarea.svelte +84 -53
  97. package/dist/textarea/Textarea.svelte.d.ts +2 -3
  98. package/dist/transition/InternalTransitionChild.svelte +259 -170
  99. package/dist/transition/InternalTransitionChild.svelte.d.ts +2 -3
  100. package/dist/transition/Transition.svelte +96 -66
  101. package/dist/transition/Transition.svelte.d.ts +2 -3
  102. package/dist/transition/TransitionChild.svelte +31 -11
  103. package/dist/transition/TransitionChild.svelte.d.ts +2 -3
  104. package/dist/utils/DisabledProvider.svelte +7 -3
  105. package/dist/utils/ElementOrComponent.svelte +46 -23
  106. package/dist/utils/ElementOrComponent.svelte.d.ts +8 -10
  107. package/dist/utils/Generic.svelte +30 -19
  108. package/dist/utils/Generic.svelte.d.ts +6 -7
  109. package/dist/utils/StableCollection.svelte +54 -36
  110. package/dist/utils/floating-ui/svelte/components/FloatingNode.svelte +27 -12
  111. package/dist/utils/floating-ui/svelte/components/FloatingTree.svelte +88 -44
  112. package/dist/utils/types.d.ts +4 -5
  113. package/package.json +1 -1
@@ -1,239 +1,331 @@
1
- <script lang="ts" module>import { RenderFeatures } from "../utils/render.js";
2
- export const DEFAULT_DIALOG_TAG = "div";
3
- export const DialogRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static;
4
- </script>
1
+ <script lang="ts" module>
2
+ import type { ElementType, Props } from "../utils/types.js"
3
+ import { RenderFeatures, type PropsForFeatures } from "../utils/render.js"
5
4
 
6
- <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_DIALOG_TAG">import { useId } from "../hooks/use-id.js";
7
- import { useMainTreeNode, useRootContainers } from "../hooks/use-root-containers.svelte.js";
8
- import { clearOpenClosedContext, State, useOpenClosed } from "../internal/open-closed.js";
9
- import { useNestedPortals } from "../portal/InternalPortal.svelte";
10
- import { getOwnerDocument } from "../utils/owner.js";
11
- import { useInertOthers } from "../hooks/use-inert-others.svelte.js";
12
- import { useOutsideClick } from "../hooks/use-outside-click.svelte.js";
13
- import { useEscape } from "../hooks/use-escape.svelte.js";
14
- import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js";
15
- import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js";
16
- import { setContext } from "svelte";
17
- import { useIsTouchDevice } from "../hooks/use-is-touch-device.svelte.js";
18
- import FocusTrap, { FocusTrapFeatures } from "../focus-trap/FocusTrap.svelte";
19
- import Portal from "../portal/Portal.svelte";
20
- import PortalGroup from "../portal/PortalGroup.svelte";
21
- import ForcePortalRoot from "../internal/ForcePortalRoot.svelte";
22
- import { createCloseContext } from "../internal/close-provider.js";
23
- import ElementOrComponent from "../utils/ElementOrComponent.svelte";
24
- import { DialogStates } from "./context.svelte.js";
25
- import { useDescriptions } from "../description/context.svelte.js";
26
- import MainTreeProvider from "../internal/MainTreeProvider.svelte";
27
- import Transition from "../transition/Transition.svelte";
28
- const internalId = useId();
29
- let {
30
- ref = $bindable(),
31
- id = `headlessui-dialog-${internalId}`,
32
- open: theirOpen,
33
- onclose,
34
- initialFocus,
35
- role: theirRole = "dialog",
36
- autofocus = true,
37
- __demoMode = false,
38
- unmount = false,
39
- transition = false,
40
- ...theirProps
41
- } = $props();
42
- const usesOpenClosedState = useOpenClosed();
43
- const hasOpen = $derived(theirOpen !== void 0 || usesOpenClosedState);
44
- const hasOnClose = $derived(theirProps.hasOwnProperty("onclose"));
45
- $effect(() => {
46
- if (!hasOpen && !hasOnClose) {
47
- throw new Error(`You have to provide an \`open\` and an \`onclose\` prop to the \`Dialog\` component.`);
48
- }
49
- if (!hasOpen) {
50
- throw new Error(`You provided an \`onclose\` prop to the \`Dialog\`, but forgot an \`open\` prop.`);
51
- }
52
- if (!hasOnClose) {
53
- throw new Error(`You provided an \`open\` prop to the \`Dialog\`, but forgot an \`onclose\` prop.`);
54
- }
55
- if (!usesOpenClosedState && typeof open !== "boolean") {
56
- throw new Error(
57
- `You provided an \`open\` prop to the \`Dialog\`, but the value is not a boolean. Received: ${open}`
58
- );
59
- }
60
- if (typeof theirProps.onclose !== "function") {
61
- throw new Error(
62
- `You provided an \`onclose\` prop to the \`Dialog\`, but the value is not a function. Received: ${theirProps.onclose}`
63
- );
64
- }
65
- });
66
- let didWarnOnRole = $state(false);
67
- const role = $derived.by(() => {
68
- if (theirRole === "dialog" || theirRole === "alertdialog") {
69
- return theirRole;
5
+ export const DEFAULT_DIALOG_TAG = "div" as const
6
+ export type DialogRenderPropArg = {
7
+ open: boolean
70
8
  }
71
- if (!didWarnOnRole) {
72
- didWarnOnRole = true;
73
- console.warn(
74
- `Invalid role [${theirRole}] passed to <Dialog />. Only \`dialog\` and and \`alertdialog\` are supported. Using \`dialog\` instead.`
75
- );
76
- }
77
- return "dialog";
78
- });
79
- const open = $derived(
80
- theirOpen === void 0 && usesOpenClosedState !== null ? (usesOpenClosedState.value & State.Open) === State.Open : theirOpen
81
- );
82
- const ownerDocument = $derived(getOwnerDocument(ref));
83
- const dialogState = $derived(open ? DialogStates.Open : DialogStates.Closed);
84
- let _state = $state({
85
- titleId: null,
86
- panelRef: null
87
- });
88
- const close = $derived(() => onclose(false));
89
- const setTitleId = (id2) => _state.titleId = id2;
90
- const enabled = $derived(dialogState === DialogStates.Open);
91
- const nestedPortals = useNestedPortals();
92
- const { portals } = $derived(nestedPortals);
93
- const defaultContainer = {
94
- get current() {
95
- return _state.panelRef ?? ref;
96
- }
97
- };
98
- const mainTreeNode = useMainTreeNode();
99
- let { resolvedContainers: resolvedRootContainers } = $derived(
100
- useRootContainers({
101
- get mainTreeNode() {
102
- return mainTreeNode.node;
103
- },
104
- get portals() {
105
- return portals;
106
- },
107
- get defaultContainers() {
108
- return defaultContainer.current ? [defaultContainer.current] : [];
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
109
27
  }
110
- })
111
- );
112
- const isClosing = $derived(
113
- usesOpenClosedState !== null ? (usesOpenClosedState.value & State.Closing) === State.Closing : false
114
- );
115
- const inertOthersEnabled = $derived(__demoMode ? false : isClosing ? false : enabled);
116
- useInertOthers({
117
- get enabled() {
118
- return inertOthersEnabled;
119
- },
120
- elements: {
121
- get allowed() {
122
- return [
123
- // Allow the headlessui-portal of the Dialog to be interactive. This
124
- // contains the current dialog and the necessary focus guard elements.
125
- ref?.closest("[data-headlessui-portal]") ?? null
126
- ];
127
- },
128
- get disallowed() {
129
- return [
130
- // Disallow the "main" tree root node
131
- mainTreeNode.node?.closest("body > *:not(#headlessui-portal-root)") ?? null
132
- ];
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.`)
133
78
  }
134
- }
135
- });
136
- useOutsideClick({
137
- get enabled() {
138
- return enabled;
139
- },
140
- get containers() {
141
- return resolvedRootContainers;
142
- },
143
- cb(event) {
144
- event.preventDefault();
145
- close();
146
- }
147
- });
148
- useEscape({
149
- get enabled() {
150
- return enabled;
151
- },
152
- get view() {
153
- return ownerDocument?.defaultView ?? null;
154
- },
155
- cb(event) {
156
- event.preventDefault();
157
- event.stopPropagation();
158
- if (document.activeElement && "blur" in document.activeElement && typeof document.activeElement.blur === "function") {
159
- document.activeElement.blur();
79
+
80
+ if (!hasOpen) {
81
+ throw new Error(`You provided an \`onclose\` prop to the \`Dialog\`, but forgot an \`open\` prop.`)
160
82
  }
161
- close();
162
- }
163
- });
164
- const scrollLockEnabled = $derived(__demoMode ? false : isClosing ? false : enabled);
165
- useScrollLock({
166
- get enabled() {
167
- return scrollLockEnabled;
168
- },
169
- get ownerDocument() {
170
- return ownerDocument;
171
- },
172
- resolveAllowedContainers() {
173
- return resolvedRootContainers;
174
- }
175
- });
176
- useOnDisappear({
177
- get enabled() {
178
- return enabled;
179
- },
180
- get ref() {
181
- return ref;
182
- },
183
- get ondisappear() {
184
- return close;
185
- }
186
- });
187
- const describedby = useDescriptions();
188
- setContext("DialogContext", {
189
- get titleId() {
190
- return _state.titleId;
191
- },
192
- get panelRef() {
193
- return _state.panelRef;
194
- },
195
- get dialogState() {
196
- return dialogState;
197
- },
198
- get close() {
199
- return close;
200
- },
201
- get unmount() {
202
- return unmount;
203
- },
204
- setTitleId
205
- });
206
- const slot = $derived({ open: dialogState === DialogStates.Open });
207
- const ourProps = $derived({
208
- id,
209
- role,
210
- tabIndex: -1,
211
- "aria-modal": __demoMode ? void 0 : dialogState === DialogStates.Open ? true : void 0,
212
- "aria-labelledby": _state.titleId,
213
- "aria-describedby": describedby.value,
214
- unmount
215
- });
216
- const shouldMoveFocusInside = !useIsTouchDevice().value;
217
- const focusTrapFeatures = $derived.by(() => {
218
- let focusTrapFeatures2 = FocusTrapFeatures.None;
219
- if (enabled && !__demoMode) {
220
- focusTrapFeatures2 |= FocusTrapFeatures.RestoreFocus;
221
- focusTrapFeatures2 |= FocusTrapFeatures.TabLock;
222
- if (autofocus) {
223
- focusTrapFeatures2 |= FocusTrapFeatures.AutoFocus;
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
+ )
224
92
  }
225
- if (shouldMoveFocusInside) {
226
- focusTrapFeatures2 |= FocusTrapFeatures.InitialFocus;
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
+ )
227
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
+ },
228
150
  }
229
- return focusTrapFeatures2;
230
- });
231
- clearOpenClosedContext();
232
- createCloseContext({
233
- get close() {
234
- return close;
235
- }
236
- });
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
+ })
237
329
  </script>
238
330
 
239
331
  {#snippet internal(transitionProps?: Record<string, any>)}
@@ -21,9 +21,8 @@ declare class __sveltets_Render<TTag extends ElementType = typeof DEFAULT_DIALOG
21
21
  props(): {
22
22
  as?: TTag | undefined;
23
23
  } & (Exclude<keyof import("../utils/types.js").PropsOf<TTag>, ("slot" | "as" | "children" | "class" | "ref") | "autofocus" | "unmount" | "static" | "id" | "role" | "onclose" | "initialFocus" | "transition" | DialogPropsWeControl | "open" | "__demoMode"> extends infer T extends keyof import("../utils/types.js").PropsOf<TTag> ? { [P in T]: import("../utils/types.js").PropsOf<TTag>[P]; } : never) & {
24
- children?: import("svelte").Snippet<[{
25
- slot: DialogRenderPropArg;
26
- props: Record<string, any>;
24
+ children?: import("svelte").Snippet<[DialogRenderPropArg & {
25
+ props?: Record<string, any>;
27
26
  }]> | undefined;
28
27
  class?: string | ((bag: DialogRenderPropArg) => string) | null | undefined;
29
28
  ref?: HTMLElement;
@@ -1,22 +1,39 @@
1
- <script lang="ts" module>let DEFAULT_BACKDROP_TAG = "div";
1
+ <script lang="ts" module>
2
+ import type { ElementType, Props } from "../utils/types.js"
3
+
4
+ let DEFAULT_BACKDROP_TAG = "div" as const
5
+ type BackdropRenderPropArg = {
6
+ open: boolean
7
+ }
8
+
9
+ export type DialogBackdropProps<TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG> = Props<
10
+ TTag,
11
+ BackdropRenderPropArg,
12
+ never,
13
+ { transition?: boolean }
14
+ >
2
15
  </script>
3
16
 
4
- <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG">import { DialogStates, useDialogContext } from "./context.svelte.js";
5
- import ElementOrComponent from "../utils/ElementOrComponent.svelte";
6
- import { mergeProps } from "../utils/render.js";
7
- import TransitionChild from "../transition/TransitionChild.svelte";
8
- let { ref = $bindable(), transition = false, ...theirProps } = $props();
9
- const _state = useDialogContext("Dialog.Panel");
10
- const { dialogState, unmount } = $derived(_state);
11
- const slot = $derived({ open: dialogState === DialogStates.Open });
12
- const ourProps = mergeProps({
13
- "aria-hidden": true
14
- });
17
+ <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG">
18
+ import { DialogStates, useDialogContext } from "./context.svelte.js"
19
+ import ElementOrComponent from "../utils/ElementOrComponent.svelte"
20
+ import { mergeProps } from "../utils/render.js"
21
+ import TransitionChild from "../transition/TransitionChild.svelte"
22
+
23
+ let { ref = $bindable(), transition = false, ...theirProps }: { as?: TTag } & DialogBackdropProps<TTag> = $props()
24
+ const _state = useDialogContext("Dialog.Panel")
25
+ const { dialogState, unmount } = $derived(_state)
26
+
27
+ const slot = $derived({ open: dialogState === DialogStates.Open } satisfies BackdropRenderPropArg)
28
+
29
+ const ourProps = mergeProps({
30
+ "aria-hidden": true,
31
+ })
15
32
  </script>
16
33
 
17
34
  {#if transition}
18
35
  <TransitionChild {unmount} {ref}>
19
- {#snippet children({ slot, props })}
36
+ {#snippet children({ props, ...slot })}
20
37
  <ElementOrComponent
21
38
  ourProps={{ ...ourProps, ...props }}
22
39
  {theirProps}
@@ -10,9 +10,8 @@ declare class __sveltets_Render<TTag extends ElementType = typeof DEFAULT_BACKDR
10
10
  props(): {
11
11
  as?: TTag | undefined;
12
12
  } & (Exclude<keyof import("../utils/types.js").PropsOf<TTag>, ("slot" | "as" | "children" | "class" | "ref") | "transition"> extends infer T extends keyof import("../utils/types.js").PropsOf<TTag> ? { [P in T]: import("../utils/types.js").PropsOf<TTag>[P]; } : never) & {
13
- children?: import("svelte").Snippet<[{
14
- slot: BackdropRenderPropArg;
15
- props: Record<string, any>;
13
+ children?: import("svelte").Snippet<[BackdropRenderPropArg & {
14
+ props?: Record<string, any>;
16
15
  }]> | undefined;
17
16
  class?: string | ((bag: BackdropRenderPropArg) => string) | null | undefined;
18
17
  ref?: HTMLElement;