@pzerelles/headlessui-svelte 2.1.2-next.6 → 2.1.2-next.8

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 (57) hide show
  1. package/dist/button/Button.svelte +54 -84
  2. package/dist/checkbox/Checkbox.svelte +120 -174
  3. package/dist/close-button/CloseButton.svelte +6 -12
  4. package/dist/combobox/Combobox.svelte +3 -50
  5. package/dist/data-interactive/DataInteractive.svelte +29 -57
  6. package/dist/description/Description.svelte +21 -32
  7. package/dist/dialog/Dialog.svelte +34 -69
  8. package/dist/dialog/DialogBackdrop.svelte +12 -29
  9. package/dist/dialog/DialogPanel.svelte +26 -49
  10. package/dist/dialog/DialogTitle.svelte +23 -38
  11. package/dist/dialog/InternalDialog.svelte +202 -263
  12. package/dist/field/Field.svelte +26 -49
  13. package/dist/fieldset/Fieldset.svelte +29 -50
  14. package/dist/focus-trap/FocusTrap.svelte +283 -419
  15. package/dist/input/Input.svelte +53 -85
  16. package/dist/input/Input.svelte.d.ts +4 -6
  17. package/dist/internal/FocusSentinel.svelte +8 -16
  18. package/dist/internal/ForcePortalRoot.svelte +3 -7
  19. package/dist/internal/FormFields.svelte +20 -31
  20. package/dist/internal/FormResolver.svelte +15 -20
  21. package/dist/internal/Hidden.svelte +23 -44
  22. package/dist/internal/HoistFormFields.svelte +4 -7
  23. package/dist/internal/MainTreeProvider.svelte +36 -89
  24. package/dist/internal/Portal.svelte +14 -18
  25. package/dist/label/Label.svelte +57 -91
  26. package/dist/legend/Legend.svelte +3 -18
  27. package/dist/listbox/Listbox.svelte +396 -588
  28. package/dist/listbox/ListboxButton.svelte +127 -176
  29. package/dist/listbox/ListboxOption.svelte +125 -166
  30. package/dist/listbox/ListboxOptions.svelte +244 -340
  31. package/dist/listbox/ListboxSelectedOption.svelte +15 -38
  32. package/dist/menu/Menu.svelte +218 -307
  33. package/dist/menu/MenuButton.svelte +115 -157
  34. package/dist/menu/MenuHeading.svelte +14 -34
  35. package/dist/menu/MenuItem.svelte +107 -145
  36. package/dist/menu/MenuItems.svelte +224 -298
  37. package/dist/menu/MenuSection.svelte +9 -26
  38. package/dist/menu/MenuSeparator.svelte +4 -20
  39. package/dist/portal/InternalPortal.svelte +85 -141
  40. package/dist/portal/Portal.svelte +2 -5
  41. package/dist/portal/PortalGroup.svelte +9 -30
  42. package/dist/switch/Switch.svelte +132 -179
  43. package/dist/switch/SwitchGroup.svelte +31 -44
  44. package/dist/tabs/Tab.svelte +143 -195
  45. package/dist/tabs/TabGroup.svelte +205 -292
  46. package/dist/tabs/TabList.svelte +11 -31
  47. package/dist/tabs/TabPanel.svelte +43 -68
  48. package/dist/tabs/TabPanels.svelte +7 -18
  49. package/dist/textarea/Textarea.svelte +53 -83
  50. package/dist/textarea/Textarea.svelte.d.ts +14 -11
  51. package/dist/transition/InternalTransitionChild.svelte +170 -259
  52. package/dist/transition/Transition.svelte +66 -96
  53. package/dist/transition/TransitionChild.svelte +11 -31
  54. package/dist/utils/ElementOrComponent.svelte +23 -44
  55. package/dist/utils/Generic.svelte +17 -29
  56. package/dist/utils/StableCollection.svelte +36 -54
  57. package/package.json +1 -1
@@ -1,443 +1,307 @@
1
- <script lang="ts" module>
2
- import { getOwnerDocument } from "../utils/owner.js"
3
- import type { ElementType, Props } from "../utils/types.js"
4
- import { history } from "../utils/active-element-history.js"
5
- import { useWatch } from "../hooks/use-watch.svelte.js"
6
- import { microTask } from "../utils/microTask.js"
7
- import { Focus, focusElement, focusIn, FocusResult } from "../utils/focus-management.js"
8
- import { useIsTopLayer } from "../hooks/use-is-top-layer.svelte.js"
9
- import { useIsMounted } from "../hooks/use-is-mounted.svelte.js"
10
- import { useEventListener } from "../hooks/use-event-listener.svelte.js"
11
- import { useTabDirection, Direction as TabDirection } from "../hooks/use-tab-direction.svelte.js"
12
- import { match } from "../utils/match.js"
13
- import { useDisposables } from "../utils/disposables.js"
14
- import Hidden, { HiddenFeatures } from "../internal/Hidden.svelte"
15
- import ElementOrComponent from "../utils/ElementOrComponent.svelte"
16
- import { FocusTrapFeatures } from "./FocusTrapFeatures.js"
17
-
18
- type Containers =
19
- // Lazy resolved containers
20
- | (() => Iterable<HTMLElement>)
21
-
22
- // List of containers
23
- | Iterable<HTMLElement>
24
-
25
- function resolveContainers(containers?: Containers): Set<HTMLElement> {
26
- if (!containers) return new Set<HTMLElement>()
27
- if (typeof containers === "function") return new Set(containers())
28
-
29
- let all = new Set<HTMLElement>()
30
- for (let container of containers) {
31
- if (container instanceof HTMLElement) {
32
- all.add(container)
33
- }
1
+ <script lang="ts" module>import { getOwnerDocument } from "../utils/owner.js";
2
+ import { history } from "../utils/active-element-history.js";
3
+ import { useWatch } from "../hooks/use-watch.svelte.js";
4
+ import { microTask } from "../utils/microTask.js";
5
+ import { Focus, focusElement, focusIn, FocusResult } from "../utils/focus-management.js";
6
+ import { useIsTopLayer } from "../hooks/use-is-top-layer.svelte.js";
7
+ import { useIsMounted } from "../hooks/use-is-mounted.svelte.js";
8
+ import { useEventListener } from "../hooks/use-event-listener.svelte.js";
9
+ import { useTabDirection, Direction as TabDirection } from "../hooks/use-tab-direction.svelte.js";
10
+ import { match } from "../utils/match.js";
11
+ import { useDisposables } from "../utils/disposables.js";
12
+ import Hidden, { HiddenFeatures } from "../internal/Hidden.svelte";
13
+ import ElementOrComponent from "../utils/ElementOrComponent.svelte";
14
+ import { FocusTrapFeatures } from "./FocusTrapFeatures.js";
15
+ function resolveContainers(containers) {
16
+ if (!containers) return /* @__PURE__ */ new Set();
17
+ if (typeof containers === "function") return new Set(containers());
18
+ let all = /* @__PURE__ */ new Set();
19
+ for (let container of containers) {
20
+ if (container instanceof HTMLElement) {
21
+ all.add(container);
34
22
  }
35
- return all
36
23
  }
37
-
38
- let DEFAULT_FOCUS_TRAP_TAG = "div" as const
39
-
40
- export * from "./FocusTrapFeatures.js"
41
-
42
- type FocusTrapRenderPropArg = {}
43
- type FocusTrapPropsWeControl = never
44
-
45
- export type FocusTrapProps<TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG> = Props<
46
- TTag,
47
- FocusTrapRenderPropArg,
48
- FocusTrapPropsWeControl,
49
- {
50
- initialFocus?: HTMLElement
51
- // A fallback element to focus, but this element will be skipped when tabbing around. This is
52
- // only done for focusing a fallback parent container (e.g.: A `Dialog`, but you want to tab
53
- // *inside* the dialog excluding the dialog itself).
54
- initialFocusFallback?: HTMLElement
55
- features?: FocusTrapFeatures
56
- containers?: Containers
24
+ return all;
25
+ }
26
+ let DEFAULT_FOCUS_TRAP_TAG = "div";
27
+ export * from "./FocusTrapFeatures.js";
28
+ function useRestoreElement(options) {
29
+ const { enabled } = $derived(options ?? { enabled: true });
30
+ let localHistory = $state(history.slice());
31
+ useWatch({
32
+ action: ([newEnabled], [oldEnabled]) => {
33
+ if (oldEnabled === true && newEnabled === false) {
34
+ microTask(() => {
35
+ localHistory.splice(0);
36
+ });
37
+ }
38
+ if (oldEnabled === false && newEnabled === true) {
39
+ localHistory = history.slice();
40
+ }
41
+ },
42
+ get dependencies() {
43
+ return [enabled, history, localHistory];
57
44
  }
58
- >
59
-
60
- function useRestoreElement(options?: { enabled: boolean }) {
61
- const { enabled } = $derived(options ?? { enabled: true })
62
- let localHistory = $state<HTMLElement[]>(history.slice())
63
-
64
- useWatch({
65
- action: ([newEnabled], [oldEnabled]) => {
66
- // We are disabling the restore element, so we need to clear it.
67
- if (oldEnabled === true && newEnabled === false) {
68
- // However, let's schedule it in a microTask, so that we can still read the value in the
69
- // places where we are restoring the focus.
70
- microTask(() => {
71
- localHistory.splice(0)
72
- })
73
- }
74
-
75
- // We are enabling the restore element, so we need to set it to the last "focused" element.
76
- if (oldEnabled === false && newEnabled === true) {
77
- localHistory = history.slice()
78
- }
79
- },
80
- get dependencies() {
81
- return [enabled, history, localHistory]
82
- },
83
- })
84
-
85
- // We want to return the last element that is still connected to the DOM, so we can restore the
86
- // focus to it.
87
- return {
88
- get lastElement() {
89
- return localHistory.find((x) => x != null && x.isConnected) ?? null
90
- },
45
+ });
46
+ return {
47
+ get lastElement() {
48
+ return localHistory.find((x) => x != null && x.isConnected) ?? null;
91
49
  }
92
- }
93
-
94
- function useRestoreFocus(options: { features: FocusTrapFeatures; ownerDocument: Document | null }) {
95
- const { features, ownerDocument } = $derived(options)
96
- const enabled = $derived(Boolean(features & FocusTrapFeatures.RestoreFocus))
97
-
98
- const restoreElement = useRestoreElement({
99
- get enabled() {
100
- return enabled
101
- },
102
- })
103
-
104
- // Restore the focus to the previous element when `enabled` becomes false again
105
- useWatch({
106
- action: () => {
107
- if (enabled) return
108
-
109
- if (ownerDocument?.activeElement === ownerDocument?.body) {
110
- focusElement(restoreElement.lastElement)
50
+ };
51
+ }
52
+ function useRestoreFocus(options) {
53
+ const { features, ownerDocument } = $derived(options);
54
+ const enabled = $derived(Boolean(features & FocusTrapFeatures.RestoreFocus));
55
+ const restoreElement = useRestoreElement({
56
+ get enabled() {
57
+ return enabled;
58
+ }
59
+ });
60
+ useWatch({
61
+ action: () => {
62
+ if (enabled) return;
63
+ if (ownerDocument?.activeElement === ownerDocument?.body) {
64
+ focusElement(restoreElement.lastElement);
65
+ }
66
+ },
67
+ get dependencies() {
68
+ return [enabled];
69
+ }
70
+ });
71
+ $effect(() => {
72
+ if (!enabled) return;
73
+ return () => focusElement(restoreElement.lastElement);
74
+ });
75
+ }
76
+ function useInitialFocus(options) {
77
+ const { features, ownerDocument, container, initialFocus, initialFocusFallback } = $derived(options);
78
+ let previousActiveElement = $state(null);
79
+ let enabled = useIsTopLayer({
80
+ get enabled() {
81
+ return Boolean(features & FocusTrapFeatures.InitialFocus);
82
+ },
83
+ scope: "focus-trap#initial-focus"
84
+ });
85
+ let mounted = useIsMounted();
86
+ useWatch({
87
+ action: () => {
88
+ if (features === FocusTrapFeatures.None) {
89
+ return;
90
+ }
91
+ if (!enabled) {
92
+ if (initialFocusFallback) {
93
+ focusElement(initialFocusFallback);
111
94
  }
112
- },
113
- get dependencies() {
114
- return [enabled]
115
- },
116
- })
117
-
118
- // Restore the focus to the previous element when the component is unmounted
119
- $effect(() => {
120
- if (!enabled) return
121
-
122
- return () => focusElement(restoreElement.lastElement)
123
- })
124
- }
125
-
126
- function useInitialFocus(options: {
127
- features: FocusTrapFeatures
128
- ownerDocument: Document | null
129
- container: HTMLElement | null
130
- initialFocus?: HTMLElement | null
131
- initialFocusFallback?: HTMLElement | null
132
- }) {
133
- const { features, ownerDocument, container, initialFocus, initialFocusFallback } = $derived(options)
134
- let previousActiveElement = $state<HTMLElement | null>(null)
135
- let enabled = useIsTopLayer({
136
- get enabled() {
137
- return Boolean(features & FocusTrapFeatures.InitialFocus)
138
- },
139
- scope: "focus-trap#initial-focus",
140
- })
141
-
142
- let mounted = useIsMounted()
143
-
144
- // Handle initial focus
145
- useWatch({
146
- action: () => {
147
- // No focus management needed
148
- if (features === FocusTrapFeatures.None) {
149
- return
95
+ return;
96
+ }
97
+ let containerElement = container;
98
+ if (!containerElement) return;
99
+ microTask(() => {
100
+ if (!mounted.current) {
101
+ return;
150
102
  }
151
-
152
- if (!enabled) {
153
- // If we are disabling the initialFocus, then we should focus the fallback element if one is
154
- // provided. This is needed to ensure _something_ is focused. Typically a wrapping element
155
- // (e.g.: `Dialog` component).
156
- //
157
- // Note: we _don't_ want to move focus to the `initialFocus` ref, because the `InitialFocus`
158
- // feature is disabled.
159
- if (initialFocusFallback) {
160
- focusElement(initialFocusFallback)
103
+ let activeElement = ownerDocument?.activeElement;
104
+ if (initialFocus) {
105
+ if (initialFocus === activeElement) {
106
+ previousActiveElement = activeElement;
107
+ return;
161
108
  }
162
-
163
- return
109
+ } else if (containerElement.contains(activeElement)) {
110
+ previousActiveElement = activeElement;
111
+ return;
164
112
  }
165
- let containerElement = container
166
- if (!containerElement) return
167
-
168
- // Delaying the focus to the next microtask ensures that a few conditions are true:
169
- // - The container is rendered
170
- // - Transitions could be started
171
- // If we don't do this, then focusing an element will immediately cancel any transitions. This
172
- // is not ideal because transitions will look broken.
173
- // There is an additional issue with doing this immediately. The FocusTrap is used inside a
174
- // Dialog, the Dialog is rendered inside of a Portal and the Portal is rendered at the end of
175
- // the `document.body`. This means that the moment we call focus, the browser immediately
176
- // tries to focus the element, which will still be at the bottom resulting in the page to
177
- // scroll down. Delaying this will prevent the page to scroll down entirely.
178
- microTask(() => {
179
- if (!mounted.current) {
180
- return
181
- }
182
-
183
- let activeElement = ownerDocument?.activeElement as HTMLElement
184
-
185
- if (initialFocus) {
186
- if (initialFocus === activeElement) {
187
- previousActiveElement = activeElement
188
- return // Initial focus ref is already the active element
113
+ if (initialFocus) {
114
+ focusElement(initialFocus);
115
+ } else {
116
+ if (features & FocusTrapFeatures.AutoFocus) {
117
+ if (focusIn(containerElement, Focus.First | Focus.AutoFocus) !== FocusResult.Error) {
118
+ return;
189
119
  }
190
- } else if (containerElement!.contains(activeElement)) {
191
- previousActiveElement = activeElement
192
- return // Already focused within Dialog
120
+ } else if (focusIn(containerElement, Focus.First) !== FocusResult.Error) {
121
+ return;
193
122
  }
194
-
195
- // Try to focus the initialFocus ref
196
- if (initialFocus) {
197
- focusElement(initialFocus)
198
- } else {
199
- if (features & FocusTrapFeatures.AutoFocus) {
200
- // Try to focus the first focusable element with `Focus.AutoFocus` feature enabled
201
- if (focusIn(containerElement!, Focus.First | Focus.AutoFocus) !== FocusResult.Error) {
202
- return // Worked, bail
203
- }
204
- }
205
-
206
- // Try to focus the first focusable element.
207
- else if (focusIn(containerElement!, Focus.First) !== FocusResult.Error) {
208
- return // Worked, bail
209
- }
210
-
211
- // Try the fallback
212
- if (initialFocusFallback) {
213
- focusElement(initialFocusFallback)
214
- if (ownerDocument?.activeElement === initialFocusFallback) {
215
- return // Worked, bail
216
- }
123
+ if (initialFocusFallback) {
124
+ focusElement(initialFocusFallback);
125
+ if (ownerDocument?.activeElement === initialFocusFallback) {
126
+ return;
217
127
  }
218
-
219
- // Nothing worked
220
- console.warn("There are no focusable elements inside the <FocusTrap />")
221
128
  }
222
-
223
- previousActiveElement = ownerDocument?.activeElement as HTMLElement
224
- })
225
- },
226
- get dependencies() {
227
- return [initialFocusFallback, enabled, features]
228
- },
229
- })
230
-
231
- return {
232
- get value() {
233
- return previousActiveElement
234
- },
235
- set value(element) {
236
- previousActiveElement = element
237
- },
238
- }
239
- }
240
-
241
- function useFocusLock(options: {
242
- features: FocusTrapFeatures
243
- ownerDocument: Document | null
244
- container: HTMLElement | null
245
- containers?: Containers
246
- previousActiveElement: HTMLElement | null
247
- }) {
248
- let { features, ownerDocument, container, containers, previousActiveElement } = $derived(options)
249
- const mounted = useIsMounted()
250
- const enabled = $derived(Boolean(features & FocusTrapFeatures.FocusLock))
251
-
252
- // Prevent programmatically escaping the container
253
- useEventListener({
254
- get element() {
255
- return ownerDocument?.defaultView
256
- },
257
- type: "focus",
258
- listener: (event) => {
259
- if (!enabled) return
260
- if (!mounted.current) return
261
-
262
- let allContainers = resolveContainers(containers)
263
- if (container instanceof HTMLElement) allContainers.add(container)
264
-
265
- let previous = previousActiveElement
266
- if (!previous) return
267
-
268
- let toElement = event.target as HTMLElement | null
269
-
270
- if (toElement && toElement instanceof HTMLElement) {
271
- if (!contains(allContainers, toElement)) {
272
- event.preventDefault()
273
- event.stopPropagation()
274
- focusElement(previous)
275
- } else {
276
- options.previousActiveElement = toElement
277
- focusElement(toElement)
278
- }
279
- } else {
280
- focusElement(previousActiveElement)
129
+ console.warn("There are no focusable elements inside the <FocusTrap />");
281
130
  }
282
- },
283
- options: true,
284
- })
285
- }
286
-
287
- function contains(containers: Set<HTMLElement>, element: HTMLElement) {
288
- for (let container of containers) {
289
- if (container.contains(element)) return true
290
- }
291
-
292
- return false
293
- }
294
- </script>
295
-
296
- <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG">
297
- let container = $state<HTMLElement | null>(null)
298
- let {
299
- ref = $bindable(),
300
- initialFocus,
301
- initialFocusFallback,
302
- containers,
303
- features = FocusTrapFeatures.InitialFocus |
304
- FocusTrapFeatures.TabLock |
305
- FocusTrapFeatures.FocusLock |
306
- FocusTrapFeatures.RestoreFocus,
307
- ...theirProps
308
- }: { as?: TTag } & FocusTrapProps<TTag> = $props()
309
-
310
- /*if (!useServerHandoffComplete()) {
311
- features = FocusTrapFeatures.None
312
- }*/
313
-
314
- const ownerDocument = $derived(getOwnerDocument(ref))
315
-
316
- useRestoreFocus({
317
- get features() {
318
- return features
319
- },
320
- get ownerDocument() {
321
- return ownerDocument
322
- },
323
- })
324
- let previousActiveElement = useInitialFocus({
325
- get features() {
326
- return features
327
- },
328
- get ownerDocument() {
329
- return ownerDocument
330
- },
331
- get container() {
332
- return container
333
- },
334
- get initialFocus() {
335
- return initialFocus
336
- },
337
- get initialFocusFallback() {
338
- return initialFocusFallback
339
- },
340
- })
341
-
342
- useFocusLock({
343
- get features() {
344
- return features
345
- },
346
- get ownerDocument() {
347
- return ownerDocument
348
- },
349
- get container() {
350
- return container
351
- },
352
- get containers() {
353
- return containers
131
+ previousActiveElement = ownerDocument?.activeElement;
132
+ });
354
133
  },
355
- get previousActiveElement() {
356
- return previousActiveElement.value
357
- },
358
- set previousActiveElement(element) {
359
- previousActiveElement.value = element
134
+ get dependencies() {
135
+ return [initialFocusFallback, enabled, features];
136
+ }
137
+ });
138
+ return {
139
+ get value() {
140
+ return previousActiveElement;
360
141
  },
361
- })
362
-
363
- const direction = useTabDirection()
364
- const handleFocus = (e: FocusEvent) => {
365
- let el = container as HTMLElement
366
- if (!el) return
367
-
368
- // TODO: Cleanup once we are using real browser tests
369
- let wrapper = process.env.NODE_ENV === "test" ? microTask : (cb: Function) => cb()
370
- wrapper(() => {
371
- match(direction.current, {
372
- [TabDirection.Forwards]: () => {
373
- focusIn(el, Focus.First, {
374
- skipElements: [e.relatedTarget, initialFocusFallback] as HTMLElement[],
375
- })
376
- },
377
- [TabDirection.Backwards]: () => {
378
- focusIn(el, Focus.Last, {
379
- skipElements: [e.relatedTarget, initialFocusFallback] as HTMLElement[],
380
- })
381
- },
382
- })
383
- })
384
- }
385
-
386
- let tabLockEnabled = useIsTopLayer({
387
- get enabled() {
388
- return Boolean(features & FocusTrapFeatures.TabLock)
142
+ set value(element) {
143
+ previousActiveElement = element;
144
+ }
145
+ };
146
+ }
147
+ function useFocusLock(options) {
148
+ let { features, ownerDocument, container, containers, previousActiveElement } = $derived(options);
149
+ const mounted = useIsMounted();
150
+ const enabled = $derived(Boolean(features & FocusTrapFeatures.FocusLock));
151
+ useEventListener({
152
+ get element() {
153
+ return ownerDocument?.defaultView;
389
154
  },
390
- scope: "focus-trap#tab-lock",
391
- })
392
-
393
- const d = useDisposables()
394
- let recentlyUsedTabKey = $state(false)
395
- const ourProps = $derived({
396
- onkeydown(e: KeyboardEvent) {
397
- if (e.key == "Tab") {
398
- recentlyUsedTabKey = true
399
- d.requestAnimationFrame(() => {
400
- recentlyUsedTabKey = false
401
- })
155
+ type: "focus",
156
+ listener: (event) => {
157
+ if (!enabled) return;
158
+ if (!mounted.current) return;
159
+ let allContainers = resolveContainers(containers);
160
+ if (container instanceof HTMLElement) allContainers.add(container);
161
+ let previous = previousActiveElement;
162
+ if (!previous) return;
163
+ let toElement = event.target;
164
+ if (toElement && toElement instanceof HTMLElement) {
165
+ if (!contains(allContainers, toElement)) {
166
+ event.preventDefault();
167
+ event.stopPropagation();
168
+ focusElement(previous);
169
+ } else {
170
+ options.previousActiveElement = toElement;
171
+ focusElement(toElement);
172
+ }
173
+ } else {
174
+ focusElement(previousActiveElement);
402
175
  }
403
176
  },
404
- onblur(e: FocusEvent) {
405
- if (!(features & FocusTrapFeatures.FocusLock)) return
406
-
407
- let allContainers = resolveContainers(containers)
408
- if (container instanceof HTMLElement) allContainers.add(container)
409
-
410
- let relatedTarget = e.relatedTarget
411
- if (!(relatedTarget instanceof HTMLElement)) return
177
+ options: true
178
+ });
179
+ }
180
+ function contains(containers, element) {
181
+ for (let container of containers) {
182
+ if (container.contains(element)) return true;
183
+ }
184
+ return false;
185
+ }
186
+ </script>
412
187
 
413
- // Known guards, leave them alone!
414
- if (relatedTarget.dataset.headlessuiFocusGuard === "true") {
415
- return
188
+ <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG">let container = $state(null);
189
+ let {
190
+ ref = $bindable(),
191
+ initialFocus,
192
+ initialFocusFallback,
193
+ containers,
194
+ features = FocusTrapFeatures.InitialFocus | FocusTrapFeatures.TabLock | FocusTrapFeatures.FocusLock | FocusTrapFeatures.RestoreFocus,
195
+ ...theirProps
196
+ } = $props();
197
+ const ownerDocument = $derived(getOwnerDocument(ref));
198
+ useRestoreFocus({
199
+ get features() {
200
+ return features;
201
+ },
202
+ get ownerDocument() {
203
+ return ownerDocument;
204
+ }
205
+ });
206
+ let previousActiveElement = useInitialFocus({
207
+ get features() {
208
+ return features;
209
+ },
210
+ get ownerDocument() {
211
+ return ownerDocument;
212
+ },
213
+ get container() {
214
+ return container;
215
+ },
216
+ get initialFocus() {
217
+ return initialFocus;
218
+ },
219
+ get initialFocusFallback() {
220
+ return initialFocusFallback;
221
+ }
222
+ });
223
+ useFocusLock({
224
+ get features() {
225
+ return features;
226
+ },
227
+ get ownerDocument() {
228
+ return ownerDocument;
229
+ },
230
+ get container() {
231
+ return container;
232
+ },
233
+ get containers() {
234
+ return containers;
235
+ },
236
+ get previousActiveElement() {
237
+ return previousActiveElement.value;
238
+ },
239
+ set previousActiveElement(element) {
240
+ previousActiveElement.value = element;
241
+ }
242
+ });
243
+ const direction = useTabDirection();
244
+ const handleFocus = (e) => {
245
+ let el = container;
246
+ if (!el) return;
247
+ let wrapper = process.env.NODE_ENV === "test" ? microTask : (cb) => cb();
248
+ wrapper(() => {
249
+ match(direction.current, {
250
+ [TabDirection.Forwards]: () => {
251
+ focusIn(el, Focus.First, {
252
+ skipElements: [e.relatedTarget, initialFocusFallback]
253
+ });
254
+ },
255
+ [TabDirection.Backwards]: () => {
256
+ focusIn(el, Focus.Last, {
257
+ skipElements: [e.relatedTarget, initialFocusFallback]
258
+ });
416
259
  }
417
-
418
- // Blur is triggered due to focus on relatedTarget, and the relatedTarget is not inside any
419
- // of the dialog containers. In other words, let's move focus back in!
420
- if (!contains(allContainers, relatedTarget)) {
421
- // Was the blur invoked via the keyboard? Redirect to the next in line.
422
- if (recentlyUsedTabKey) {
423
- focusIn(
424
- container as HTMLElement,
425
- match(direction.current, {
426
- [TabDirection.Forwards]: () => Focus.Next,
427
- [TabDirection.Backwards]: () => Focus.Previous,
428
- }) | Focus.WrapAround,
429
- { relativeTo: e.target as HTMLElement }
430
- )
431
- }
432
-
433
- // It was invoked via something else (e.g.: click, programmatically, ...). Redirect to the
434
- // previous active item in the FocusTrap
435
- else if (e.target instanceof HTMLElement) {
436
- focusElement(e.target)
437
- }
260
+ });
261
+ });
262
+ };
263
+ let tabLockEnabled = useIsTopLayer({
264
+ get enabled() {
265
+ return Boolean(features & FocusTrapFeatures.TabLock);
266
+ },
267
+ scope: "focus-trap#tab-lock"
268
+ });
269
+ const d = useDisposables();
270
+ let recentlyUsedTabKey = $state(false);
271
+ const ourProps = $derived({
272
+ onkeydown(e) {
273
+ if (e.key == "Tab") {
274
+ recentlyUsedTabKey = true;
275
+ d.requestAnimationFrame(() => {
276
+ recentlyUsedTabKey = false;
277
+ });
278
+ }
279
+ },
280
+ onblur(e) {
281
+ if (!(features & FocusTrapFeatures.FocusLock)) return;
282
+ let allContainers = resolveContainers(containers);
283
+ if (container instanceof HTMLElement) allContainers.add(container);
284
+ let relatedTarget = e.relatedTarget;
285
+ if (!(relatedTarget instanceof HTMLElement)) return;
286
+ if (relatedTarget.dataset.headlessuiFocusGuard === "true") {
287
+ return;
288
+ }
289
+ if (!contains(allContainers, relatedTarget)) {
290
+ if (recentlyUsedTabKey) {
291
+ focusIn(
292
+ container,
293
+ match(direction.current, {
294
+ [TabDirection.Forwards]: () => Focus.Next,
295
+ [TabDirection.Backwards]: () => Focus.Previous
296
+ }) | Focus.WrapAround,
297
+ { relativeTo: e.target }
298
+ );
299
+ } else if (e.target instanceof HTMLElement) {
300
+ focusElement(e.target);
438
301
  }
439
- },
440
- })
302
+ }
303
+ }
304
+ });
441
305
  </script>
442
306
 
443
307
  {#if tabLockEnabled}