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

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 (123) hide show
  1. package/dist/button/Button.svelte +84 -55
  2. package/dist/button/Button.svelte.d.ts +4 -32
  3. package/dist/checkbox/Checkbox.svelte +177 -121
  4. package/dist/checkbox/Checkbox.svelte.d.ts +14 -32
  5. package/dist/close-button/CloseButton.svelte +10 -7
  6. package/dist/close-button/CloseButton.svelte.d.ts +2 -44
  7. package/dist/data-interactive/DataInteractive.svelte +49 -37
  8. package/dist/data-interactive/DataInteractive.svelte.d.ts +7 -30
  9. package/dist/description/Description.svelte +35 -22
  10. package/dist/description/Description.svelte.d.ts +7 -28
  11. package/dist/dialog/Dialog.svelte +326 -232
  12. package/dist/dialog/Dialog.svelte.d.ts +4 -42
  13. package/dist/dialog/DialogBackdrop.svelte +33 -16
  14. package/dist/dialog/DialogBackdrop.svelte.d.ts +4 -29
  15. package/dist/dialog/DialogPanel.svelte +60 -29
  16. package/dist/dialog/DialogPanel.svelte.d.ts +4 -30
  17. package/dist/dialog/DialogTitle.svelte +51 -24
  18. package/dist/dialog/DialogTitle.svelte.d.ts +6 -27
  19. package/dist/field/Field.svelte +44 -28
  20. package/dist/field/Field.svelte.d.ts +4 -30
  21. package/dist/fieldset/Fieldset.svelte +48 -30
  22. package/dist/fieldset/Fieldset.svelte.d.ts +5 -31
  23. package/dist/focus-trap/FocusTrap.svelte +430 -298
  24. package/dist/focus-trap/FocusTrap.svelte.d.ts +5 -34
  25. package/dist/hooks/use-inert-others.svelte.js +10 -10
  26. package/dist/hooks/use-resolve-button-type.svelte.js +0 -1
  27. package/dist/input/Input.svelte +95 -54
  28. package/dist/input/Input.svelte.d.ts +13 -27
  29. package/dist/internal/FloatingProvider.svelte +14 -9
  30. package/dist/internal/FocusSentinel.svelte +49 -40
  31. package/dist/internal/ForcePortalRoot.svelte +7 -3
  32. package/dist/internal/FormFields.svelte +47 -34
  33. package/dist/internal/FormFieldsProvider.svelte +9 -5
  34. package/dist/internal/FormResolver.svelte +25 -16
  35. package/dist/internal/Hidden.svelte +45 -38
  36. package/dist/internal/Hidden.svelte.d.ts +4 -30
  37. package/dist/internal/MainTreeProvider.svelte +90 -37
  38. package/dist/internal/Portal.svelte +18 -14
  39. package/dist/label/Label.svelte +100 -59
  40. package/dist/label/Label.svelte.d.ts +7 -32
  41. package/dist/legend/Legend.svelte +27 -4
  42. package/dist/legend/Legend.svelte.d.ts +4 -3
  43. package/dist/listbox/Listbox.svelte +518 -391
  44. package/dist/listbox/Listbox.svelte.d.ts +11 -35
  45. package/dist/listbox/ListboxButton.svelte +175 -128
  46. package/dist/listbox/ListboxButton.svelte.d.ts +5 -32
  47. package/dist/listbox/ListboxOption.svelte +171 -130
  48. package/dist/listbox/ListboxOption.svelte.d.ts +12 -26
  49. package/dist/listbox/ListboxOptions.svelte +403 -305
  50. package/dist/listbox/ListboxOptions.svelte.d.ts +4 -38
  51. package/dist/listbox/ListboxSelectedOption.svelte +40 -19
  52. package/dist/listbox/ListboxSelectedOption.svelte.d.ts +8 -33
  53. package/dist/menu/Menu.svelte +76 -52
  54. package/dist/menu/Menu.svelte.d.ts +3 -31
  55. package/dist/menu/MenuButton.svelte +158 -118
  56. package/dist/menu/MenuButton.svelte.d.ts +4 -34
  57. package/dist/menu/MenuHeading.svelte +34 -15
  58. package/dist/menu/MenuHeading.svelte.d.ts +4 -31
  59. package/dist/menu/MenuItem.svelte +143 -108
  60. package/dist/menu/MenuItem.svelte.d.ts +5 -32
  61. package/dist/menu/MenuItems.svelte +301 -230
  62. package/dist/menu/MenuItems.svelte.d.ts +4 -38
  63. package/dist/menu/MenuSection.svelte +26 -10
  64. package/dist/menu/MenuSection.svelte.d.ts +5 -29
  65. package/dist/menu/MenuSeparator.svelte +20 -5
  66. package/dist/menu/MenuSeparator.svelte.d.ts +5 -28
  67. package/dist/popover/Popover.svelte +217 -151
  68. package/dist/popover/Popover.svelte.d.ts +4 -30
  69. package/dist/popover/PopoverBackdrop.svelte +71 -42
  70. package/dist/popover/PopoverBackdrop.svelte.d.ts +6 -34
  71. package/dist/popover/PopoverButton.svelte +302 -222
  72. package/dist/popover/PopoverButton.svelte.d.ts +6 -29
  73. package/dist/popover/PopoverGroup.svelte +64 -36
  74. package/dist/popover/PopoverGroup.svelte.d.ts +5 -28
  75. package/dist/popover/PopoverPanel.svelte +335 -248
  76. package/dist/popover/PopoverPanel.svelte.d.ts +5 -36
  77. package/dist/popover/index.d.ts +1 -1
  78. package/dist/portal/InternalPortal.svelte +143 -86
  79. package/dist/portal/InternalPortal.svelte.d.ts +4 -30
  80. package/dist/portal/Portal.svelte +8 -4
  81. package/dist/portal/Portal.svelte.d.ts +2 -18
  82. package/dist/portal/PortalGroup.svelte +23 -10
  83. package/dist/portal/PortalGroup.svelte.d.ts +3 -31
  84. package/dist/select/Select.svelte +100 -69
  85. package/dist/select/Select.svelte.d.ts +5 -32
  86. package/dist/switch/Switch.svelte +181 -133
  87. package/dist/switch/Switch.svelte.d.ts +5 -38
  88. package/dist/switch/SwitchGroup.svelte +45 -32
  89. package/dist/switch/SwitchGroup.svelte.d.ts +7 -28
  90. package/dist/tabs/Tab.svelte +195 -143
  91. package/dist/tabs/Tab.svelte.d.ts +4 -32
  92. package/dist/tabs/TabGroup.svelte +87 -57
  93. package/dist/tabs/TabGroup.svelte.d.ts +4 -34
  94. package/dist/tabs/TabList.svelte +31 -12
  95. package/dist/tabs/TabList.svelte.d.ts +5 -28
  96. package/dist/tabs/TabPanel.svelte +69 -44
  97. package/dist/tabs/TabPanel.svelte.d.ts +4 -34
  98. package/dist/tabs/TabPanels.svelte +19 -8
  99. package/dist/tabs/TabPanels.svelte.d.ts +5 -27
  100. package/dist/textarea/Textarea.svelte +87 -54
  101. package/dist/textarea/Textarea.svelte.d.ts +13 -27
  102. package/dist/transition/InternalTransitionChild.svelte +267 -171
  103. package/dist/transition/InternalTransitionChild.svelte.d.ts +3 -33
  104. package/dist/transition/Transition.svelte +88 -67
  105. package/dist/transition/Transition.svelte.d.ts +3 -36
  106. package/dist/transition/TransitionChild.svelte +31 -12
  107. package/dist/transition/TransitionChild.svelte.d.ts +8 -35
  108. package/dist/transition/context.svelte.js +7 -7
  109. package/dist/utils/DisabledProvider.svelte +7 -3
  110. package/dist/utils/ElementOrComponent.svelte +88 -24
  111. package/dist/utils/ElementOrComponent.svelte.d.ts +32 -27
  112. package/dist/utils/StableCollection.svelte +54 -36
  113. package/dist/utils/floating-ui/svelte/components/FloatingNode.svelte +27 -12
  114. package/dist/utils/floating-ui/svelte/components/FloatingTree.svelte +88 -44
  115. package/dist/utils/state.js +4 -4
  116. package/dist/utils/types.d.ts +14 -12
  117. package/package.json +12 -12
  118. package/dist/combobox/Combobox.svelte +0 -6
  119. package/dist/combobox/Combobox.svelte.d.ts +0 -50
  120. package/dist/utils/Generic.svelte +0 -46
  121. package/dist/utils/Generic.svelte.d.ts +0 -32
  122. package/dist/utils/alternative-types.d.ts +0 -20
  123. package/dist/utils/alternative-types.js +0 -1
@@ -1,254 +1,339 @@
1
- <script lang="ts" module>import { RenderFeatures } from "../utils/render.js";
2
- const DEFAULT_PANEL_TAG = "div";
3
- const PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static;
4
- </script>
1
+ <script lang="ts" module>
2
+ import type { Props } from "../utils/types.js"
3
+ import { RenderFeatures } from "../utils/render.js"
5
4
 
6
- <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_PANEL_TAG">import { useId } from "../hooks/use-id.js";
7
- import ElementOrComponent from "../utils/ElementOrComponent.svelte";
8
- import { mergeProps } from "../utils/render.js";
9
- import {
10
- useFloatingPanel,
11
- useFloatingPanelProps,
12
- useResolvedAnchor
13
- } from "../internal/floating.svelte.js";
14
- import {
15
- PopoverStates,
16
- usePopoverAPIContext,
17
- usePopoverContext
18
- } from "./context.svelte.js";
19
- import { getOwnerDocument } from "../utils/owner.js";
20
- import { clearOpenClosedContext, State, useOpenClosed } from "../internal/open-closed.js";
21
- import { transitionDataAttributes, useTransition } from "../hooks/use-transition.svelte.js";
22
- import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js";
23
- import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js";
24
- import { Focus, focusIn, FocusResult, getFocusableElements } from "../utils/focus-management.js";
25
- import { useElementSize } from "../hooks/use-element-size.svelte.js";
26
- import { useTabDirection, Direction as TabDirection } from "../hooks/use-tab-direction.svelte.js";
27
- import { match } from "../utils/match.js";
28
- import { microTask } from "../utils/microTask.js";
29
- import { setContext, untrack } from "svelte";
30
- import Portal from "../portal/Portal.svelte";
31
- import Hidden, { HiddenFeatures } from "../internal/Hidden.svelte";
32
- let internalId = useId();
33
- let {
34
- ref = $bindable(),
35
- id = `headlessui-popover-panel-${internalId}`,
36
- focus = false,
37
- anchor: rawAnchor,
38
- portal: theirPortal = false,
39
- modal = false,
40
- transition = false,
41
- ...theirProps
42
- } = $props();
43
- const context = usePopoverContext("PopoverPanel");
44
- const api = usePopoverAPIContext("PopoverPanel");
45
- const { close, isPortalled } = $derived(api);
46
- const beforePanelSentinelId = `headlessui-focus-sentinel-before-${internalId}`;
47
- const afterPanelSentinelId = `headlessui-focus-sentinel-after-${internalId}`;
48
- const resolvedAnchor = useResolvedAnchor({
49
- get anchor() {
50
- return rawAnchor;
51
- }
52
- });
53
- const { anchor } = $derived(resolvedAnchor);
54
- const floatingPanel = useFloatingPanel({
55
- get placement() {
56
- return anchor;
57
- }
58
- });
59
- const { setFloating, styles } = $derived(floatingPanel);
60
- const getFloatingPanelProps = useFloatingPanelProps();
61
- const portal = $derived(!!anchor || theirPortal);
62
- $effect(() => {
63
- if (anchor) setFloating(ref ?? null);
64
- untrack(() => context.setPanel(ref));
65
- });
66
- const ownerDocument = $derived(getOwnerDocument(ref));
67
- $effect(() => {
68
- id;
69
- return untrack(() => {
70
- context.setPanelId(id);
71
- return () => {
72
- context.setPanelId(void 0);
73
- };
74
- });
75
- });
76
- const usesOpenClosedState = useOpenClosed();
77
- const _transition = useTransition({
78
- get enabled() {
79
- return transition;
80
- },
81
- get element() {
82
- return ref;
83
- },
84
- get show() {
85
- return usesOpenClosedState !== null ? (usesOpenClosedState.value & State.Open) === State.Open : context.popoverState === PopoverStates.Open;
86
- }
87
- });
88
- const { visible, data: transitionData } = $derived(_transition);
89
- useOnDisappear({
90
- get enabled() {
91
- return visible;
92
- },
93
- get ref() {
94
- return context.button;
95
- },
96
- ondisappear: () => {
97
- context.closePopover();
98
- }
99
- });
100
- const scrollLockEnabled = $derived(context.__demoMode ? false : modal && visible);
101
- useScrollLock({
102
- get enabled() {
103
- return scrollLockEnabled;
104
- },
105
- get ownerDocument() {
106
- return ownerDocument;
107
- }
108
- });
109
- const handleKeyDown = (event) => {
110
- switch (event.key) {
111
- case "Escape":
112
- if (context.popoverState !== PopoverStates.Open) return;
113
- if (!ref) return;
114
- if (ownerDocument?.activeElement && !ref.contains(ownerDocument.activeElement)) {
115
- return;
116
- }
117
- event.preventDefault();
118
- event.stopPropagation();
119
- context.closePopover();
120
- context.button?.focus();
121
- break;
122
- }
123
- };
124
- $effect(() => {
125
- if (theirProps.static) return;
126
- if (context.popoverState === PopoverStates.Closed && (theirProps.unmount ?? true)) {
127
- context.setPanel(void 0);
5
+ const DEFAULT_PANEL_TAG = "div" as const
6
+ type PanelRenderPropArg = {
7
+ open: boolean
8
+ close: (focusableElement?: HTMLElement) => void
128
9
  }
129
- });
130
- $effect(() => {
131
- if (context.__demoMode) return;
132
- if (!focus) return;
133
- if (context.popoverState !== PopoverStates.Open) return;
134
- if (!ref) return;
135
- const activeElement = ownerDocument?.activeElement;
136
- if (ref.contains(activeElement)) return;
137
- focusIn(ref, Focus.First);
138
- });
139
- const slot = $derived({
140
- open: context.popoverState === PopoverStates.Open,
141
- close
142
- });
143
- const buttonSize = useElementSize({
144
- get element() {
145
- return context.button ?? null;
146
- },
147
- unit: true
148
- });
149
- const ourProps = $derived(
150
- mergeProps(anchor ? getFloatingPanelProps() : {}, {
151
- id,
152
- onkeydown: handleKeyDown,
153
- onblur: focus && context.popoverState === PopoverStates.Open ? (event) => {
154
- let el = event.relatedTarget;
155
- if (!el) return;
156
- if (!ref) return;
157
- if (ref.contains(el)) return;
158
- context.closePopover();
159
- if (context.beforePanelSentinel?.contains?.(el) || context.afterPanelSentinel?.contains?.(el)) {
160
- el.focus({ preventScroll: true });
10
+
11
+ const PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
12
+
13
+ type PanelPropsWeControl = "tabIndex"
14
+
15
+ export type PopoverPanelProps = Props<
16
+ typeof DEFAULT_PANEL_TAG,
17
+ PanelRenderPropArg,
18
+ {
19
+ element?: HTMLElement
20
+ id?: string
21
+ focus?: boolean
22
+ anchor?: AnchorProps
23
+ portal?: boolean
24
+ modal?: boolean
25
+ transition?: boolean
26
+
27
+ // ItemsRenderFeatures
28
+ static?: boolean
29
+ unmount?: boolean
30
+ }
31
+ >
32
+ </script>
33
+
34
+ <script lang="ts">
35
+ import { useId } from "../hooks/use-id.js"
36
+ import ElementOrComponent from "../utils/ElementOrComponent.svelte"
37
+ import { mergeProps } from "../utils/render.js"
38
+ import {
39
+ useFloatingPanel,
40
+ useFloatingPanelProps,
41
+ useResolvedAnchor,
42
+ type AnchorProps,
43
+ } from "../internal/floating.svelte.js"
44
+ import {
45
+ type PopoverAPIContext,
46
+ type PopoverPanelContext,
47
+ PopoverStates,
48
+ usePopoverAPIContext,
49
+ usePopoverContext,
50
+ } from "./context.svelte.js"
51
+ import { getOwnerDocument } from "../utils/owner.js"
52
+ import { clearOpenClosedContext, State, useOpenClosed } from "../internal/open-closed.js"
53
+ import { transitionDataAttributes, useTransition } from "../hooks/use-transition.svelte.js"
54
+ import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js"
55
+ import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js"
56
+ import { Focus, focusIn, FocusResult, getFocusableElements } from "../utils/focus-management.js"
57
+ import { useElementSize } from "../hooks/use-element-size.svelte.js"
58
+ import { useTabDirection, Direction as TabDirection } from "../hooks/use-tab-direction.svelte.js"
59
+ import { match } from "../utils/match.js"
60
+ import { microTask } from "../utils/microTask.js"
61
+ import { setContext, untrack } from "svelte"
62
+ import Portal from "../portal/Portal.svelte"
63
+ import Hidden, { HiddenFeatures } from "../internal/Hidden.svelte"
64
+
65
+ let internalId = useId()
66
+ let {
67
+ element = $bindable(),
68
+ id = `headlessui-popover-panel-${internalId}`,
69
+ focus = false,
70
+ anchor: rawAnchor,
71
+ portal: theirPortal = false,
72
+ modal = false,
73
+ transition = false,
74
+ ...theirProps
75
+ }: PopoverPanelProps = $props()
76
+
77
+ const context = usePopoverContext("PopoverPanel")
78
+ const api = usePopoverAPIContext("PopoverPanel")
79
+ const { close, isPortalled } = $derived(api)
80
+
81
+ const beforePanelSentinelId = `headlessui-focus-sentinel-before-${internalId}`
82
+ const afterPanelSentinelId = `headlessui-focus-sentinel-after-${internalId}`
83
+
84
+ const resolvedAnchor = useResolvedAnchor({
85
+ get anchor() {
86
+ return rawAnchor
87
+ },
88
+ })
89
+ const { anchor } = $derived(resolvedAnchor)
90
+ const floatingPanel = useFloatingPanel({
91
+ get placement() {
92
+ return anchor
93
+ },
94
+ })
95
+ const { setFloating, styles } = $derived(floatingPanel)
96
+ const getFloatingPanelProps = useFloatingPanelProps()
97
+
98
+ // Always enable `portal` functionality, when `anchor` is enabled
99
+ const portal = $derived(!!anchor || theirPortal)
100
+
101
+ $effect(() => {
102
+ if (anchor) setFloating(element ?? null)
103
+ untrack(() => context.setPanel(element))
104
+ })
105
+ const ownerDocument = $derived(getOwnerDocument(element))
106
+
107
+ $effect(() => {
108
+ id
109
+ return untrack(() => {
110
+ context.setPanelId(id)
111
+ return () => {
112
+ context.setPanelId(undefined)
161
113
  }
162
- } : void 0,
163
- tabIndex: -1,
164
- style: [theirProps.style, styles, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
165
- ...transitionDataAttributes(transitionData)
114
+ })
166
115
  })
167
- );
168
- const direction = useTabDirection();
169
- const handleBeforeFocus = () => {
170
- let el = ref;
171
- if (!el) return;
172
- function run() {
173
- match(direction.current, {
174
- [TabDirection.Forwards]: () => {
175
- let result = focusIn(el, Focus.First);
176
- if (result === FocusResult.Error) {
177
- context.afterPanelSentinel?.focus();
116
+
117
+ const usesOpenClosedState = useOpenClosed()
118
+ const _transition = useTransition({
119
+ get enabled() {
120
+ return transition
121
+ },
122
+ get element() {
123
+ return element
124
+ },
125
+ get show() {
126
+ return usesOpenClosedState !== null
127
+ ? (usesOpenClosedState.value & State.Open) === State.Open
128
+ : context.popoverState === PopoverStates.Open
129
+ },
130
+ })
131
+ const { visible, data: transitionData } = $derived(_transition)
132
+
133
+ // Ensure we close the popover as soon as the button becomes hidden
134
+ useOnDisappear({
135
+ get enabled() {
136
+ return visible
137
+ },
138
+ get ref() {
139
+ return context.button
140
+ },
141
+ ondisappear: () => {
142
+ context.closePopover()
143
+ },
144
+ })
145
+
146
+ // Enable scroll locking when the popover is visible, and `modal` is enabled
147
+ const scrollLockEnabled = $derived(context.__demoMode ? false : modal && visible)
148
+ useScrollLock({
149
+ get enabled() {
150
+ return scrollLockEnabled
151
+ },
152
+ get ownerDocument() {
153
+ return ownerDocument
154
+ },
155
+ })
156
+
157
+ const handleKeyDown = (event: KeyboardEvent) => {
158
+ switch (event.key) {
159
+ case "Escape":
160
+ if (context.popoverState !== PopoverStates.Open) return
161
+ if (!element) return
162
+ if (ownerDocument?.activeElement && !element.contains(ownerDocument.activeElement)) {
163
+ return
178
164
  }
179
- },
180
- [TabDirection.Backwards]: () => {
181
- context.button?.focus({ preventScroll: true });
182
- }
183
- });
165
+ event.preventDefault()
166
+ event.stopPropagation()
167
+ context.closePopover()
168
+ context.button?.focus()
169
+ break
170
+ }
184
171
  }
185
- if (process.env.NODE_ENV === "test") {
186
- microTask(run);
187
- } else {
188
- run();
189
- }
190
- };
191
- const handleAfterFocus = () => {
192
- let el = ref;
193
- if (!el) return;
194
- function run() {
195
- match(direction.current, {
196
- [TabDirection.Forwards]: () => {
197
- if (!context.button) return;
198
- const elements = getFocusableElements();
199
- const idx = elements.indexOf(context.button);
200
- const before = elements.slice(0, idx + 1);
201
- const after = elements.slice(idx + 1);
202
- const combined = [...after, ...before];
203
- for (const element of combined.slice()) {
204
- if (element.dataset.headlessuiFocusGuard === "true" || ref?.contains(element)) {
205
- let idx2 = combined.indexOf(element);
206
- if (idx2 !== -1) combined.splice(idx2, 1);
172
+
173
+ // Unlink on "unmount" children
174
+ $effect(() => {
175
+ if (theirProps.static) return
176
+
177
+ if (context.popoverState === PopoverStates.Closed && (theirProps.unmount ?? true)) {
178
+ context.setPanel(undefined)
179
+ }
180
+ }) //, [state.popoverState, props.unmount, props.static, dispatch])
181
+
182
+ // Move focus within panel
183
+ $effect(() => {
184
+ if (context.__demoMode) return
185
+ if (!focus) return
186
+ if (context.popoverState !== PopoverStates.Open) return
187
+ if (!element) return
188
+
189
+ const activeElement = ownerDocument?.activeElement as HTMLElement
190
+ if (element.contains(activeElement)) return // Already focused within Dialog
191
+
192
+ focusIn(element, Focus.First)
193
+ }) //, [state.__demoMode, focus, internalPanelRef.current, state.popoverState])
194
+
195
+ const slot = $derived({
196
+ open: context.popoverState === PopoverStates.Open,
197
+ close,
198
+ } satisfies PanelRenderPropArg)
199
+
200
+ const buttonSize = useElementSize({
201
+ get element() {
202
+ return context.button ?? null
203
+ },
204
+ unit: true,
205
+ })
206
+ const ourProps: Record<string, any> = $derived(
207
+ mergeProps(anchor ? getFloatingPanelProps() : {}, {
208
+ id,
209
+ onkeydown: handleKeyDown,
210
+ onblur:
211
+ focus && context.popoverState === PopoverStates.Open
212
+ ? (event: FocusEvent) => {
213
+ let el = event.relatedTarget as HTMLElement
214
+ if (!el) return
215
+ if (!element) return
216
+ if (element.contains(el)) return
217
+
218
+ context.closePopover()
219
+
220
+ if (context.beforePanelSentinel?.contains?.(el) || context.afterPanelSentinel?.contains?.(el)) {
221
+ el.focus({ preventScroll: true })
222
+ }
223
+ }
224
+ : undefined,
225
+ tabIndex: -1,
226
+ style: [theirProps.style, styles, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
227
+ ...transitionDataAttributes(transitionData),
228
+ })
229
+ )
230
+
231
+ const direction = useTabDirection()
232
+ const handleBeforeFocus = () => {
233
+ let el = element as HTMLElement
234
+ if (!el) return
235
+
236
+ function run() {
237
+ match(direction.current, {
238
+ [TabDirection.Forwards]: () => {
239
+ // Try to focus the first thing in the panel. But if that fails (e.g.: there are no
240
+ // focusable elements, then we can move outside of the panel)
241
+ let result = focusIn(el, Focus.First)
242
+ if (result === FocusResult.Error) {
243
+ context.afterPanelSentinel?.focus()
207
244
  }
208
- }
209
- focusIn(combined, Focus.First, { sorted: false });
210
- },
211
- [TabDirection.Backwards]: () => {
212
- let result = focusIn(el, Focus.Previous);
213
- if (result === FocusResult.Error) {
214
- context.button?.focus();
215
- }
216
- }
217
- });
218
- }
219
- if (process.env.NODE_ENV === "test") {
220
- microTask(run);
221
- } else {
222
- run();
223
- }
224
- };
225
- clearOpenClosedContext();
226
- setContext("PopoverPanelContext", {
227
- get value() {
228
- return id;
245
+ },
246
+ [TabDirection.Backwards]: () => {
247
+ // Coming from the PopoverPanel (which is portalled to somewhere else). Let's redirect
248
+ // the focus to the PopoverButton again.
249
+ context.button?.focus({ preventScroll: true })
250
+ },
251
+ })
252
+ }
253
+
254
+ // TODO: Cleanup once we are using real browser tests
255
+ if (process.env.NODE_ENV === "test") {
256
+ microTask(run)
257
+ } else {
258
+ run()
259
+ }
229
260
  }
230
- });
231
- setContext("PopoverAPIContext", {
232
- get close() {
233
- return close;
234
- },
235
- get isPortalled() {
236
- return isPortalled;
261
+
262
+ const handleAfterFocus = () => {
263
+ let el = element as HTMLElement
264
+ if (!el) return
265
+
266
+ function run() {
267
+ match(direction.current, {
268
+ [TabDirection.Forwards]: () => {
269
+ if (!context.button) return
270
+
271
+ const elements = getFocusableElements()
272
+
273
+ const idx = elements.indexOf(context.button)
274
+ const before = elements.slice(0, idx + 1)
275
+ const after = elements.slice(idx + 1)
276
+
277
+ const combined = [...after, ...before]
278
+
279
+ // Ignore sentinel buttons and items inside the panel
280
+ for (const element of combined.slice()) {
281
+ if (element.dataset.headlessuiFocusGuard === "true" || element?.contains(element)) {
282
+ let idx = combined.indexOf(element)
283
+ if (idx !== -1) combined.splice(idx, 1)
284
+ }
285
+ }
286
+
287
+ focusIn(combined, Focus.First, { sorted: false })
288
+ },
289
+ [TabDirection.Backwards]: () => {
290
+ // Try to focus the first thing in the panel. But if that fails (e.g.: there are no
291
+ // focusable elements, then we can move outside of the panel)
292
+ let result = focusIn(el, Focus.Previous)
293
+ if (result === FocusResult.Error) {
294
+ context.button?.focus()
295
+ }
296
+ },
297
+ })
298
+ }
299
+
300
+ // TODO: Cleanup once we are using real browser tests
301
+ if (process.env.NODE_ENV === "test") {
302
+ microTask(run)
303
+ } else {
304
+ run()
305
+ }
237
306
  }
238
- });
307
+
308
+ clearOpenClosedContext()
309
+ setContext<PopoverPanelContext>("PopoverPanelContext", {
310
+ get value() {
311
+ return id
312
+ },
313
+ })
314
+ setContext<PopoverAPIContext>("PopoverAPIContext", {
315
+ get close() {
316
+ return close
317
+ },
318
+ get isPortalled() {
319
+ return isPortalled
320
+ },
321
+ })
239
322
  </script>
240
323
 
241
324
  <Portal enabled={portal ? theirProps.static || visible : false}>
242
325
  {#if visible && isPortalled}
243
- <Hidden
244
- id={beforePanelSentinelId}
245
- bind:ref={context.beforePanelSentinel as HTMLElement}
246
- features={HiddenFeatures.Focusable}
247
- data-headlessui-focus-guard
248
- as="button"
249
- type="button"
250
- onfocus={handleBeforeFocus}
251
- />
326
+ <Hidden asChild id={beforePanelSentinelId} features={HiddenFeatures.Focusable}>
327
+ {#snippet children({ props })}
328
+ <button
329
+ {...props}
330
+ type="button"
331
+ data-headlessui-focus-guard
332
+ onfocus={handleBeforeFocus}
333
+ bind:this={context.beforePanelSentinel}>&zwnj;</button
334
+ >
335
+ {/snippet}
336
+ </Hidden>
252
337
  {/if}
253
338
  <ElementOrComponent
254
339
  {ourProps}
@@ -258,17 +343,19 @@ setContext("PopoverAPIContext", {
258
343
  features={PanelRenderFeatures}
259
344
  {visible}
260
345
  name="PopoverPanel"
261
- bind:ref
346
+ bind:element
262
347
  />
263
348
  {#if visible && isPortalled}
264
- <Hidden
265
- id={afterPanelSentinelId}
266
- bind:ref={context.afterPanelSentinel as HTMLElement}
267
- features={HiddenFeatures.Focusable}
268
- data-headlessui-focus-guard
269
- as="button"
270
- type="button"
271
- onfocus={handleAfterFocus}
272
- />
349
+ <Hidden asChild id={afterPanelSentinelId} features={HiddenFeatures.Focusable}>
350
+ {#snippet children({ props })}
351
+ <button
352
+ {...props}
353
+ type="button"
354
+ data-headlessui-focus-guard
355
+ onfocus={handleAfterFocus}
356
+ bind:this={context.afterPanelSentinel}>&zwnj;</button
357
+ >
358
+ {/snippet}
359
+ </Hidden>
273
360
  {/if}
274
361
  </Portal>
@@ -1,11 +1,12 @@
1
- import type { ElementType, Props, PropsOf } from "../utils/types.js";
1
+ import type { Props } from "../utils/types.js";
2
2
  declare const DEFAULT_PANEL_TAG: "div";
3
3
  type PanelRenderPropArg = {
4
4
  open: boolean;
5
5
  close: (focusableElement?: HTMLElement) => void;
6
6
  };
7
- type PanelPropsWeControl = "tabIndex";
8
- export type PopoverPanelProps<TTag extends ElementType = typeof DEFAULT_PANEL_TAG> = Props<TTag, PanelRenderPropArg, PanelPropsWeControl, {
7
+ export type PopoverPanelProps = Props<typeof DEFAULT_PANEL_TAG, PanelRenderPropArg, {
8
+ element?: HTMLElement;
9
+ id?: string;
9
10
  focus?: boolean;
10
11
  anchor?: AnchorProps;
11
12
  portal?: boolean;
@@ -15,37 +16,5 @@ export type PopoverPanelProps<TTag extends ElementType = typeof DEFAULT_PANEL_TA
15
16
  unmount?: boolean;
16
17
  }>;
17
18
  import { type AnchorProps } from "../internal/floating.svelte.js";
18
- declare class __sveltets_Render<TTag extends ElementType = typeof DEFAULT_PANEL_TAG> {
19
- props(): {
20
- as?: TTag | undefined;
21
- } & (Exclude<keyof PropsOf<TTag>, ("slot" | "as" | "children" | "class" | "ref") | "focus" | "anchor" | "unmount" | "static" | "tabIndex" | "transition" | "portal" | "modal"> extends infer T extends keyof PropsOf<TTag> ? { [P in T]: PropsOf<TTag>[P]; } : never) & {
22
- children?: import("svelte").Snippet<[{
23
- slot: PanelRenderPropArg;
24
- props: Record<string, any>;
25
- }]> | undefined;
26
- class?: string | ((bag: PanelRenderPropArg) => string) | null | undefined;
27
- ref?: HTMLElement;
28
- } & {
29
- focus?: boolean;
30
- anchor?: AnchorProps;
31
- portal?: boolean;
32
- modal?: boolean;
33
- transition?: boolean;
34
- static?: boolean;
35
- unmount?: boolean;
36
- };
37
- events(): {};
38
- slots(): {};
39
- bindings(): "ref";
40
- exports(): {};
41
- }
42
- interface $$IsomorphicComponent {
43
- new <TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TTag>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TTag>['props']>, ReturnType<__sveltets_Render<TTag>['events']>, ReturnType<__sveltets_Render<TTag>['slots']>> & {
44
- $$bindings?: ReturnType<__sveltets_Render<TTag>['bindings']>;
45
- } & ReturnType<__sveltets_Render<TTag>['exports']>;
46
- <TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(internal: unknown, props: ReturnType<__sveltets_Render<TTag>['props']> & {}): ReturnType<__sveltets_Render<TTag>['exports']>;
47
- z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
48
- }
49
- declare const PopoverPanel: $$IsomorphicComponent;
50
- type PopoverPanel<TTag extends ElementType = typeof DEFAULT_PANEL_TAG> = InstanceType<typeof PopoverPanel<TTag>>;
19
+ declare const PopoverPanel: import("svelte").Component<PopoverPanelProps, {}, "element">;
51
20
  export default PopoverPanel;
@@ -1,5 +1,5 @@
1
1
  export { default as Popover, type PopoverProps } from "./Popover.svelte";
2
2
  export { default as PopoverBackdrop, type PopoverBackdropProps } from "./PopoverBackdrop.svelte";
3
- export { default as PopoverButton, type PopoverButtonProps, type PopoverButtonComponentProps, type PopoverButtonSlot, type PopoverButtonPropsWeControl, } from "./PopoverButton.svelte";
3
+ export { default as PopoverButton, type PopoverButtonProps, type PopoverButtonSlot, type PopoverButtonPropsWeControl, } from "./PopoverButton.svelte";
4
4
  export { default as PopoverGroup, type PopoverGroupProps } from "./PopoverGroup.svelte";
5
5
  export { default as PopoverPanel, type PopoverPanelProps } from "./PopoverPanel.svelte";