@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,241 +1,323 @@
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 { ElementType, Props, PropsOf } 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<TTag extends ElementType = typeof DEFAULT_PANEL_TAG> = Props<
16
+ TTag,
17
+ PanelRenderPropArg,
18
+ PanelPropsWeControl,
19
+ {
20
+ focus?: boolean
21
+ anchor?: AnchorProps
22
+ portal?: boolean
23
+ modal?: boolean
24
+ transition?: boolean
25
+
26
+ // ItemsRenderFeatures
27
+ static?: boolean
28
+ unmount?: boolean
29
+ }
30
+ >
31
+ </script>
32
+
33
+ <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_PANEL_TAG">
34
+ import { useId } from "../hooks/use-id.js"
35
+ import ElementOrComponent from "../utils/ElementOrComponent.svelte"
36
+ import { mergeProps } from "../utils/render.js"
37
+ import {
38
+ useFloatingPanel,
39
+ useFloatingPanelProps,
40
+ useResolvedAnchor,
41
+ type AnchorProps,
42
+ } from "../internal/floating.svelte.js"
43
+ import {
44
+ type PopoverAPIContext,
45
+ type PopoverPanelContext,
46
+ PopoverStates,
47
+ usePopoverAPIContext,
48
+ usePopoverContext,
49
+ } from "./context.svelte.js"
50
+ import { getOwnerDocument } from "../utils/owner.js"
51
+ import { clearOpenClosedContext, State, useOpenClosed } from "../internal/open-closed.js"
52
+ import { transitionDataAttributes, useTransition } from "../hooks/use-transition.svelte.js"
53
+ import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js"
54
+ import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js"
55
+ import { Focus, focusIn, FocusResult, getFocusableElements } from "../utils/focus-management.js"
56
+ import { useElementSize } from "../hooks/use-element-size.svelte.js"
57
+ import { useTabDirection, Direction as TabDirection } from "../hooks/use-tab-direction.svelte.js"
58
+ import { match } from "../utils/match.js"
59
+ import { microTask } from "../utils/microTask.js"
60
+ import { setContext, untrack } from "svelte"
61
+ import Portal from "../portal/Portal.svelte"
62
+ import Hidden, { HiddenFeatures } from "../internal/Hidden.svelte"
63
+
64
+ let internalId = useId()
65
+ let {
66
+ ref = $bindable(),
67
+ id = `headlessui-popover-panel-${internalId}` as PropsOf<TTag>["id"],
68
+ focus = false,
69
+ anchor: rawAnchor,
70
+ portal: theirPortal = false,
71
+ modal = false,
72
+ transition = false,
73
+ ...theirProps
74
+ }: { as?: TTag } & PopoverPanelProps<TTag> = $props()
75
+
76
+ const context = usePopoverContext("PopoverPanel")
77
+ const api = usePopoverAPIContext("PopoverPanel")
78
+ const { close, isPortalled } = $derived(api)
79
+
80
+ const beforePanelSentinelId = `headlessui-focus-sentinel-before-${internalId}`
81
+ const afterPanelSentinelId = `headlessui-focus-sentinel-after-${internalId}`
82
+
83
+ const resolvedAnchor = useResolvedAnchor({
84
+ get anchor() {
85
+ return rawAnchor
86
+ },
87
+ })
88
+ const { anchor } = $derived(resolvedAnchor)
89
+ const floatingPanel = useFloatingPanel({
90
+ get placement() {
91
+ return anchor
92
+ },
93
+ })
94
+ const { setFloating, styles } = $derived(floatingPanel)
95
+ const getFloatingPanelProps = useFloatingPanelProps()
96
+
97
+ // Always enable `portal` functionality, when `anchor` is enabled
98
+ const portal = $derived(!!anchor || theirPortal)
99
+
100
+ $effect(() => {
101
+ if (anchor) setFloating(ref ?? null)
102
+ untrack(() => context.setPanel(ref))
103
+ })
104
+ const ownerDocument = $derived(getOwnerDocument(ref))
105
+
106
+ $effect(() => {
107
+ id
108
+ return untrack(() => {
109
+ context.setPanelId(id)
110
+ return () => {
111
+ context.setPanelId(undefined)
161
112
  }
162
- } : void 0,
163
- tabIndex: -1,
164
- style: [theirProps.style, styles, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
165
- ...transitionDataAttributes(transitionData)
113
+ })
166
114
  })
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();
115
+
116
+ const usesOpenClosedState = useOpenClosed()
117
+ const _transition = useTransition({
118
+ get enabled() {
119
+ return transition
120
+ },
121
+ get element() {
122
+ return ref
123
+ },
124
+ get show() {
125
+ return usesOpenClosedState !== null
126
+ ? (usesOpenClosedState.value & State.Open) === State.Open
127
+ : context.popoverState === PopoverStates.Open
128
+ },
129
+ })
130
+ const { visible, data: transitionData } = $derived(_transition)
131
+
132
+ // Ensure we close the popover as soon as the button becomes hidden
133
+ useOnDisappear({
134
+ get enabled() {
135
+ return visible
136
+ },
137
+ get ref() {
138
+ return context.button
139
+ },
140
+ ondisappear: () => {
141
+ context.closePopover()
142
+ },
143
+ })
144
+
145
+ // Enable scroll locking when the popover is visible, and `modal` is enabled
146
+ const scrollLockEnabled = $derived(context.__demoMode ? false : modal && visible)
147
+ useScrollLock({
148
+ get enabled() {
149
+ return scrollLockEnabled
150
+ },
151
+ get ownerDocument() {
152
+ return ownerDocument
153
+ },
154
+ })
155
+
156
+ const handleKeyDown = (event: KeyboardEvent) => {
157
+ switch (event.key) {
158
+ case "Escape":
159
+ if (context.popoverState !== PopoverStates.Open) return
160
+ if (!ref) return
161
+ if (ownerDocument?.activeElement && !ref.contains(ownerDocument.activeElement)) {
162
+ return
178
163
  }
179
- },
180
- [TabDirection.Backwards]: () => {
181
- context.button?.focus({ preventScroll: true });
182
- }
183
- });
164
+ event.preventDefault()
165
+ event.stopPropagation()
166
+ context.closePopover()
167
+ context.button?.focus()
168
+ break
169
+ }
184
170
  }
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);
171
+
172
+ // Unlink on "unmount" children
173
+ $effect(() => {
174
+ if (theirProps.static) return
175
+
176
+ if (context.popoverState === PopoverStates.Closed && (theirProps.unmount ?? true)) {
177
+ context.setPanel(undefined)
178
+ }
179
+ }) //, [state.popoverState, props.unmount, props.static, dispatch])
180
+
181
+ // Move focus within panel
182
+ $effect(() => {
183
+ if (context.__demoMode) return
184
+ if (!focus) return
185
+ if (context.popoverState !== PopoverStates.Open) return
186
+ if (!ref) return
187
+
188
+ const activeElement = ownerDocument?.activeElement as HTMLElement
189
+ if (ref.contains(activeElement)) return // Already focused within Dialog
190
+
191
+ focusIn(ref, Focus.First)
192
+ }) //, [state.__demoMode, focus, internalPanelRef.current, state.popoverState])
193
+
194
+ const slot = $derived({
195
+ open: context.popoverState === PopoverStates.Open,
196
+ close,
197
+ } satisfies PanelRenderPropArg)
198
+
199
+ const buttonSize = useElementSize({
200
+ get element() {
201
+ return context.button ?? null
202
+ },
203
+ unit: true,
204
+ })
205
+ const ourProps: Record<string, any> = $derived(
206
+ mergeProps(anchor ? getFloatingPanelProps() : {}, {
207
+ id,
208
+ onkeydown: handleKeyDown,
209
+ onblur:
210
+ focus && context.popoverState === PopoverStates.Open
211
+ ? (event: FocusEvent) => {
212
+ let el = event.relatedTarget as HTMLElement
213
+ if (!el) return
214
+ if (!ref) return
215
+ if (ref.contains(el)) return
216
+
217
+ context.closePopover()
218
+
219
+ if (context.beforePanelSentinel?.contains?.(el) || context.afterPanelSentinel?.contains?.(el)) {
220
+ el.focus({ preventScroll: true })
221
+ }
222
+ }
223
+ : undefined,
224
+ tabIndex: -1,
225
+ style: [theirProps.style, styles, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
226
+ ...transitionDataAttributes(transitionData),
227
+ })
228
+ )
229
+
230
+ const direction = useTabDirection()
231
+ const handleBeforeFocus = () => {
232
+ let el = ref as HTMLElement
233
+ if (!el) return
234
+
235
+ function run() {
236
+ match(direction.current, {
237
+ [TabDirection.Forwards]: () => {
238
+ // Try to focus the first thing in the panel. But if that fails (e.g.: there are no
239
+ // focusable elements, then we can move outside of the panel)
240
+ let result = focusIn(el, Focus.First)
241
+ if (result === FocusResult.Error) {
242
+ context.afterPanelSentinel?.focus()
207
243
  }
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;
244
+ },
245
+ [TabDirection.Backwards]: () => {
246
+ // Coming from the PopoverPanel (which is portalled to somewhere else). Let's redirect
247
+ // the focus to the PopoverButton again.
248
+ context.button?.focus({ preventScroll: true })
249
+ },
250
+ })
251
+ }
252
+
253
+ // TODO: Cleanup once we are using real browser tests
254
+ if (process.env.NODE_ENV === "test") {
255
+ microTask(run)
256
+ } else {
257
+ run()
258
+ }
229
259
  }
230
- });
231
- setContext("PopoverAPIContext", {
232
- get close() {
233
- return close;
234
- },
235
- get isPortalled() {
236
- return isPortalled;
260
+
261
+ const handleAfterFocus = () => {
262
+ let el = ref as HTMLElement
263
+ if (!el) return
264
+
265
+ function run() {
266
+ match(direction.current, {
267
+ [TabDirection.Forwards]: () => {
268
+ if (!context.button) return
269
+
270
+ const elements = getFocusableElements()
271
+
272
+ const idx = elements.indexOf(context.button)
273
+ const before = elements.slice(0, idx + 1)
274
+ const after = elements.slice(idx + 1)
275
+
276
+ const combined = [...after, ...before]
277
+
278
+ // Ignore sentinel buttons and items inside the panel
279
+ for (const element of combined.slice()) {
280
+ if (element.dataset.headlessuiFocusGuard === "true" || ref?.contains(element)) {
281
+ let idx = combined.indexOf(element)
282
+ if (idx !== -1) combined.splice(idx, 1)
283
+ }
284
+ }
285
+
286
+ focusIn(combined, Focus.First, { sorted: false })
287
+ },
288
+ [TabDirection.Backwards]: () => {
289
+ // Try to focus the first thing in the panel. But if that fails (e.g.: there are no
290
+ // focusable elements, then we can move outside of the panel)
291
+ let result = focusIn(el, Focus.Previous)
292
+ if (result === FocusResult.Error) {
293
+ context.button?.focus()
294
+ }
295
+ },
296
+ })
297
+ }
298
+
299
+ // TODO: Cleanup once we are using real browser tests
300
+ if (process.env.NODE_ENV === "test") {
301
+ microTask(run)
302
+ } else {
303
+ run()
304
+ }
237
305
  }
238
- });
306
+
307
+ clearOpenClosedContext()
308
+ setContext<PopoverPanelContext>("PopoverPanelContext", {
309
+ get value() {
310
+ return id
311
+ },
312
+ })
313
+ setContext<PopoverAPIContext>("PopoverAPIContext", {
314
+ get close() {
315
+ return close
316
+ },
317
+ get isPortalled() {
318
+ return isPortalled
319
+ },
320
+ })
239
321
  </script>
240
322
 
241
323
  <Portal enabled={portal ? theirProps.static || visible : false}>
@@ -19,9 +19,8 @@ declare class __sveltets_Render<TTag extends ElementType = typeof DEFAULT_PANEL_
19
19
  props(): {
20
20
  as?: TTag | undefined;
21
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>;
22
+ children?: import("svelte").Snippet<[PanelRenderPropArg & {
23
+ props?: Record<string, any>;
25
24
  }]> | undefined;
26
25
  class?: string | ((bag: PanelRenderPropArg) => string) | null | undefined;
27
26
  ref?: HTMLElement;