@pzerelles/headlessui-svelte 2.0.0-next.1 → 2.1.2-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button/Button.svelte +61 -0
- package/dist/button/Button.svelte.d.ts +47 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +1 -0
- package/dist/checkbox/Checkbox.svelte +93 -61
- package/dist/checkbox/Checkbox.svelte.d.ts +45 -29
- package/dist/close-button/CloseButton.svelte +11 -0
- package/dist/close-button/CloseButton.svelte.d.ts +48 -0
- package/dist/close-button/index.d.ts +1 -0
- package/dist/close-button/index.js +1 -0
- package/dist/combobox/Combobox.svelte +6 -0
- package/dist/combobox/Combobox.svelte.d.ts +50 -0
- package/dist/data-interactive/DataInteractive.svelte +41 -0
- package/dist/data-interactive/DataInteractive.svelte.d.ts +39 -0
- package/dist/data-interactive/index.d.ts +1 -0
- package/dist/data-interactive/index.js +1 -0
- package/dist/description/Description.svelte +16 -41
- package/dist/description/Description.svelte.d.ts +15 -23
- package/dist/description/context.svelte.d.ts +17 -0
- package/dist/description/context.svelte.js +51 -0
- package/dist/dialog/Dialog.svelte +51 -0
- package/dist/dialog/Dialog.svelte.d.ts +60 -0
- package/dist/dialog/DialogBackdrop.svelte +39 -0
- package/dist/dialog/DialogBackdrop.svelte.d.ts +38 -0
- package/dist/dialog/DialogPanel.svelte +46 -0
- package/dist/dialog/DialogPanel.svelte.d.ts +40 -0
- package/dist/dialog/DialogTitle.svelte +29 -0
- package/dist/dialog/DialogTitle.svelte.d.ts +34 -0
- package/dist/dialog/InternalDialog.svelte +233 -0
- package/dist/dialog/InternalDialog.svelte.d.ts +42 -0
- package/dist/dialog/context.svelte.d.ts +15 -0
- package/dist/dialog/context.svelte.js +16 -0
- package/dist/dialog/index.d.ts +4 -0
- package/dist/dialog/index.js +4 -0
- package/dist/field/Field.svelte +14 -16
- package/dist/field/Field.svelte.d.ts +21 -17
- package/dist/fieldset/Fieldset.svelte +19 -17
- package/dist/fieldset/Fieldset.svelte.d.ts +21 -17
- package/dist/focus-trap/FocusTrap.svelte +332 -0
- package/dist/focus-trap/FocusTrap.svelte.d.ts +58 -0
- package/dist/hooks/document-overflow/adjust-scrollbar-padding.d.ts +2 -0
- package/dist/hooks/document-overflow/adjust-scrollbar-padding.js +18 -0
- package/dist/hooks/document-overflow/handle-ios-locking.d.ts +6 -0
- package/dist/hooks/document-overflow/handle-ios-locking.js +134 -0
- package/dist/hooks/document-overflow/overflow-store.d.ts +19 -0
- package/dist/hooks/document-overflow/overflow-store.js +76 -0
- package/dist/hooks/document-overflow/prevent-scroll.d.ts +2 -0
- package/dist/hooks/document-overflow/prevent-scroll.js +7 -0
- package/dist/hooks/document-overflow/use-document-overflow.svelte.d.ts +7 -0
- package/dist/hooks/document-overflow/use-document-overflow.svelte.js +27 -0
- package/dist/hooks/use-active-press.svelte.d.ts +14 -0
- package/dist/{actions/activePress.svelte.js → hooks/use-active-press.svelte.js} +33 -39
- package/dist/hooks/use-by-comparator.d.ts +2 -0
- package/dist/hooks/use-by-comparator.js +15 -0
- package/dist/hooks/use-controllable.svelte.d.ts +6 -0
- package/dist/hooks/use-controllable.svelte.js +34 -0
- package/dist/hooks/use-did-element-move.svelte.d.ts +6 -0
- package/dist/hooks/use-did-element-move.svelte.js +27 -0
- package/dist/hooks/use-disabled.d.ts +3 -0
- package/dist/hooks/use-disabled.js +9 -0
- package/dist/hooks/use-element-size.svelte.d.ts +7 -0
- package/dist/hooks/use-element-size.svelte.js +36 -0
- package/dist/hooks/use-escape.svelte.d.ts +5 -0
- package/dist/hooks/use-escape.svelte.js +26 -0
- package/dist/hooks/use-event-listener.svelte.d.ts +6 -0
- package/dist/hooks/use-event-listener.svelte.js +12 -0
- package/dist/hooks/use-flags.svelte.d.ts +8 -0
- package/dist/hooks/use-flags.svelte.js +18 -0
- package/dist/hooks/use-focus-ring.svelte.d.ts +10 -0
- package/dist/hooks/use-focus-ring.svelte.js +24 -0
- package/dist/hooks/use-hover.svelte.d.ts +26 -0
- package/dist/hooks/use-hover.svelte.js +124 -0
- package/dist/hooks/use-id.d.ts +1 -0
- package/dist/hooks/use-id.js +1 -0
- package/dist/hooks/use-inert-others.svelte.d.ts +32 -0
- package/dist/hooks/use-inert-others.svelte.js +114 -0
- package/dist/hooks/use-is-mounted.svelte.d.ts +3 -0
- package/dist/hooks/use-is-mounted.svelte.js +14 -0
- package/dist/hooks/use-is-top-layer.svelte.d.ts +29 -0
- package/dist/hooks/use-is-top-layer.svelte.js +82 -0
- package/dist/hooks/use-is-touch-device.svelte.d.ts +3 -0
- package/dist/hooks/use-is-touch-device.svelte.js +20 -0
- package/dist/hooks/use-on-disappear.svelte.d.ts +12 -0
- package/dist/hooks/use-on-disappear.svelte.js +38 -0
- package/dist/hooks/use-outside-click.svelte.d.ts +10 -0
- package/dist/hooks/use-outside-click.svelte.js +150 -0
- package/dist/hooks/use-reducer.d.ts +4 -0
- package/dist/hooks/use-reducer.js +11 -0
- package/dist/hooks/use-resolve-button-type.svelte.d.ts +10 -0
- package/dist/hooks/use-resolve-button-type.svelte.js +19 -0
- package/dist/hooks/use-root-containers.svelte.d.ts +9 -0
- package/dist/hooks/use-root-containers.svelte.js +50 -0
- package/dist/hooks/use-scroll-lock.svelte.d.ts +5 -0
- package/dist/hooks/use-scroll-lock.svelte.js +24 -0
- package/dist/hooks/use-sync-refs.d.ts +7 -0
- package/dist/hooks/use-sync-refs.js +22 -0
- package/dist/hooks/use-tab-direction.svelte.d.ts +7 -0
- package/dist/hooks/use-tab-direction.svelte.js +25 -0
- package/dist/hooks/use-text-value.svelte.d.ts +3 -0
- package/dist/hooks/use-text-value.svelte.js +20 -0
- package/dist/hooks/use-tracked-pointer.d.ts +4 -0
- package/dist/hooks/use-tracked-pointer.js +26 -0
- package/dist/hooks/use-transition.svelte.d.ts +20 -0
- package/dist/hooks/use-transition.svelte.js +253 -0
- package/dist/hooks/use-tree-walker.svelte.d.ts +8 -0
- package/dist/hooks/use-tree-walker.svelte.js +19 -0
- package/dist/hooks/use-watch.svelte.d.ts +4 -0
- package/dist/hooks/use-watch.svelte.js +16 -0
- package/dist/hooks/use-window-event.svelte.d.ts +6 -0
- package/dist/hooks/use-window-event.svelte.js +12 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/input/Input.svelte +59 -0
- package/dist/input/Input.svelte.d.ts +52 -0
- package/dist/input/index.d.ts +1 -0
- package/dist/input/index.js +1 -0
- package/dist/internal/FocusSentinel.svelte +45 -0
- package/dist/internal/FocusSentinel.svelte.d.ts +20 -0
- package/dist/internal/ForcePortalRoot.svelte +6 -0
- package/dist/internal/ForcePortalRoot.svelte.d.ts +22 -0
- package/dist/internal/FormFields.svelte +2 -4
- package/dist/internal/FormFields.svelte.d.ts +9 -7
- package/dist/internal/FormResolver.svelte +11 -16
- package/dist/internal/FormResolver.svelte.d.ts +6 -4
- package/dist/internal/Hidden.svelte +5 -9
- package/dist/internal/Hidden.svelte.d.ts +35 -19
- package/dist/internal/HoistFormFields.svelte.d.ts +5 -2
- package/dist/internal/MainTreeProvider.svelte +45 -0
- package/dist/internal/MainTreeProvider.svelte.d.ts +31 -0
- package/dist/internal/Portal.svelte.d.ts +5 -2
- package/dist/internal/close-provider.d.ts +7 -0
- package/dist/internal/close-provider.js +7 -0
- package/dist/internal/floating.svelte.d.ts +62 -0
- package/dist/internal/floating.svelte.js +488 -0
- package/dist/internal/frozen.svelte.d.ts +6 -0
- package/dist/internal/frozen.svelte.js +18 -0
- package/dist/internal/id.d.ts +8 -0
- package/dist/internal/id.js +11 -0
- package/dist/internal/open-closed.d.ts +14 -0
- package/dist/internal/open-closed.js +17 -0
- package/dist/internal/portal-force-root.svelte.d.ts +6 -0
- package/dist/internal/portal-force-root.svelte.js +11 -0
- package/dist/label/Label.svelte +17 -45
- package/dist/label/Label.svelte.d.ts +19 -23
- package/dist/label/context.svelte.d.ts +17 -0
- package/dist/label/context.svelte.js +56 -0
- package/dist/legend/Legend.svelte +4 -3
- package/dist/legend/Legend.svelte.d.ts +33 -16
- package/dist/listbox/Listbox.svelte +448 -0
- package/dist/listbox/Listbox.svelte.d.ts +126 -0
- package/dist/listbox/ListboxButton.svelte +139 -0
- package/dist/listbox/ListboxButton.svelte.d.ts +52 -0
- package/dist/listbox/ListboxOption.svelte +136 -0
- package/dist/listbox/ListboxOption.svelte.d.ts +50 -0
- package/dist/listbox/ListboxOptions.svelte +269 -0
- package/dist/listbox/ListboxOptions.svelte.d.ts +55 -0
- package/dist/listbox/ListboxSelectedOption.svelte +35 -0
- package/dist/listbox/ListboxSelectedOption.svelte.d.ts +40 -0
- package/dist/listbox/index.d.ts +5 -0
- package/dist/listbox/index.js +5 -0
- package/dist/menu/Menu.svelte +235 -0
- package/dist/menu/Menu.svelte.d.ts +42 -0
- package/dist/menu/MenuButton.svelte +127 -0
- package/dist/menu/MenuButton.svelte.d.ts +52 -0
- package/dist/menu/MenuHeading.svelte +19 -0
- package/dist/menu/MenuHeading.svelte.d.ts +39 -0
- package/dist/menu/MenuItem.svelte +114 -0
- package/dist/menu/MenuItem.svelte.d.ts +49 -0
- package/dist/menu/MenuItems.svelte +244 -0
- package/dist/menu/MenuItems.svelte.d.ts +55 -0
- package/dist/menu/MenuSection.svelte +14 -0
- package/dist/menu/MenuSection.svelte.d.ts +35 -0
- package/dist/menu/MenuSeparator.svelte +9 -0
- package/dist/menu/MenuSeparator.svelte.d.ts +35 -0
- package/dist/menu/context.svelte.d.ts +47 -0
- package/dist/menu/context.svelte.js +21 -0
- package/dist/menu/index.d.ts +7 -0
- package/dist/menu/index.js +7 -0
- package/dist/portal/InternalPortal.svelte +97 -0
- package/dist/portal/InternalPortal.svelte.d.ts +43 -0
- package/dist/portal/Portal.svelte +7 -0
- package/dist/portal/Portal.svelte.d.ts +23 -0
- package/dist/portal/PortalGroup.svelte +14 -0
- package/dist/portal/PortalGroup.svelte.d.ts +40 -0
- package/dist/switch/Switch.svelte +143 -0
- package/dist/switch/Switch.svelte.d.ts +61 -0
- package/dist/switch/SwitchGroup.svelte +37 -0
- package/dist/switch/SwitchGroup.svelte.d.ts +34 -0
- package/dist/switch/index.d.ts +2 -0
- package/dist/switch/index.js +2 -0
- package/dist/tabs/Tab.svelte +156 -0
- package/dist/tabs/Tab.svelte.d.ts +48 -0
- package/dist/tabs/TabGroup.svelte +241 -0
- package/dist/tabs/TabGroup.svelte.d.ts +67 -0
- package/dist/tabs/TabList.svelte +16 -0
- package/dist/tabs/TabList.svelte.d.ts +35 -0
- package/dist/tabs/TabPanel.svelte +61 -0
- package/dist/tabs/TabPanel.svelte.d.ts +47 -0
- package/dist/tabs/TabPanels.svelte +12 -0
- package/dist/tabs/TabPanels.svelte.d.ts +34 -0
- package/dist/tabs/index.d.ts +5 -0
- package/dist/tabs/index.js +5 -0
- package/dist/test-utils/accessability-assertions.d.ts +271 -0
- package/dist/test-utils/accessability-assertions.js +1572 -0
- package/dist/test-utils/fake-pointer.d.ts +24 -0
- package/dist/test-utils/fake-pointer.js +48 -0
- package/dist/test-utils/interactions.d.ts +61 -0
- package/dist/test-utils/interactions.js +453 -0
- package/dist/test-utils/suppress-console-logs.d.ts +7 -0
- package/dist/test-utils/suppress-console-logs.js +17 -0
- package/dist/transition/InternalTransitionChild.svelte +178 -0
- package/dist/transition/InternalTransitionChild.svelte.d.ts +55 -0
- package/dist/transition/Transition.svelte +89 -0
- package/dist/transition/Transition.svelte.d.ts +42 -0
- package/dist/transition/TransitionChild.svelte +16 -0
- package/dist/transition/TransitionChild.svelte.d.ts +44 -0
- package/dist/transition/context.svelte.d.ts +64 -0
- package/dist/transition/context.svelte.js +120 -0
- package/dist/transition/index.d.ts +2 -0
- package/dist/transition/index.js +2 -0
- package/dist/utils/ElementOrComponent.svelte +26 -0
- package/dist/utils/ElementOrComponent.svelte.d.ts +56 -0
- package/dist/utils/Generic.svelte +44 -0
- package/dist/utils/Generic.svelte.d.ts +35 -0
- package/dist/utils/StableCollection.svelte +43 -0
- package/dist/utils/StableCollection.svelte.d.ts +22 -0
- package/dist/utils/active-element-history.d.ts +1 -0
- package/dist/utils/active-element-history.js +35 -0
- package/dist/utils/alternative-types.d.ts +21 -0
- package/dist/utils/alternative-types.js +1 -0
- package/dist/utils/calculate-active-index.d.ts +25 -0
- package/dist/utils/calculate-active-index.js +74 -0
- package/dist/utils/class-names.d.ts +1 -0
- package/dist/utils/class-names.js +10 -0
- package/dist/utils/default-map.d.ts +5 -0
- package/dist/utils/default-map.js +15 -0
- package/dist/utils/disposables.d.ts +14 -12
- package/dist/utils/disposables.js +13 -10
- package/dist/utils/dom.d.ts +0 -2
- package/dist/utils/dom.js +2 -4
- package/dist/utils/env.d.ts +17 -0
- package/dist/utils/env.js +39 -0
- package/dist/utils/focus-management.d.ts +45 -0
- package/dist/utils/focus-management.js +242 -0
- package/dist/utils/focusVisible.svelte.d.ts +3 -3
- package/dist/utils/focusVisible.svelte.js +52 -41
- package/dist/utils/get-text-value.d.ts +1 -0
- package/dist/utils/get-text-value.js +71 -0
- package/dist/utils/id.d.ts +1 -1
- package/dist/utils/match.d.ts +1 -0
- package/dist/utils/match.js +13 -0
- package/dist/utils/on-document-ready.d.ts +1 -0
- package/dist/utils/on-document-ready.js +12 -0
- package/dist/utils/once.d.ts +1 -0
- package/dist/utils/once.js +9 -0
- package/dist/utils/owner.d.ts +1 -0
- package/dist/utils/owner.js +8 -0
- package/dist/utils/platform.d.ts +2 -0
- package/dist/utils/platform.js +17 -0
- package/dist/utils/ref.svelte.d.ts +4 -0
- package/dist/utils/ref.svelte.js +4 -0
- package/dist/utils/render.d.ts +34 -0
- package/dist/utils/render.js +119 -0
- package/dist/utils/state.d.ts +7 -1
- package/dist/utils/state.js +10 -6
- package/dist/utils/store.d.ts +11 -0
- package/dist/utils/store.js +20 -0
- package/dist/utils/types.d.ts +24 -0
- package/dist/utils/types.js +1 -0
- package/package.json +33 -25
- package/dist/actions/activePress.svelte.d.ts +0 -8
- package/dist/actions/focusRing.svelte.d.ts +0 -9
- package/dist/actions/focusRing.svelte.js +0 -34
- package/dist/utils/disabled.d.ts +0 -3
- package/dist/utils/disabled.js +0 -2
|
@@ -0,0 +1,332 @@
|
|
|
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
|
+
function resolveContainers(containers) {
|
|
15
|
+
if (!containers) return /* @__PURE__ */ new Set();
|
|
16
|
+
if (typeof containers === "function") return new Set(containers());
|
|
17
|
+
let all = /* @__PURE__ */ new Set();
|
|
18
|
+
for (let container of containers) {
|
|
19
|
+
if (container instanceof HTMLElement) {
|
|
20
|
+
all.add(container);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return all;
|
|
24
|
+
}
|
|
25
|
+
let DEFAULT_FOCUS_TRAP_TAG = "div";
|
|
26
|
+
export var FocusTrapFeatures = /* @__PURE__ */ ((FocusTrapFeatures2) => {
|
|
27
|
+
FocusTrapFeatures2[FocusTrapFeatures2["None"] = 0] = "None";
|
|
28
|
+
FocusTrapFeatures2[FocusTrapFeatures2["InitialFocus"] = 1] = "InitialFocus";
|
|
29
|
+
FocusTrapFeatures2[FocusTrapFeatures2["TabLock"] = 2] = "TabLock";
|
|
30
|
+
FocusTrapFeatures2[FocusTrapFeatures2["FocusLock"] = 4] = "FocusLock";
|
|
31
|
+
FocusTrapFeatures2[FocusTrapFeatures2["RestoreFocus"] = 8] = "RestoreFocus";
|
|
32
|
+
FocusTrapFeatures2[FocusTrapFeatures2["AutoFocus"] = 16] = "AutoFocus";
|
|
33
|
+
return FocusTrapFeatures2;
|
|
34
|
+
})(FocusTrapFeatures || {});
|
|
35
|
+
function useRestoreElement(options) {
|
|
36
|
+
const { enabled } = $derived(options ?? { enabled: true });
|
|
37
|
+
let localHistory = $state(history.slice());
|
|
38
|
+
useWatch({
|
|
39
|
+
action: ([newEnabled], [oldEnabled]) => {
|
|
40
|
+
if (oldEnabled === true && newEnabled === false) {
|
|
41
|
+
microTask(() => {
|
|
42
|
+
localHistory.splice(0);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (oldEnabled === false && newEnabled === true) {
|
|
46
|
+
localHistory = history.slice();
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
get dependencies() {
|
|
50
|
+
return [enabled, history, localHistory];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
get lastElement() {
|
|
55
|
+
return localHistory.find((x) => x != null && x.isConnected) ?? null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function useRestoreFocus(options) {
|
|
60
|
+
const { features, ownerDocument } = $derived(options);
|
|
61
|
+
const enabled = $derived(Boolean(features & 8 /* RestoreFocus */));
|
|
62
|
+
const restoreElement = useRestoreElement({
|
|
63
|
+
get enabled() {
|
|
64
|
+
return enabled;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
useWatch({
|
|
68
|
+
action: () => {
|
|
69
|
+
if (enabled) return;
|
|
70
|
+
if (ownerDocument?.activeElement === ownerDocument?.body) {
|
|
71
|
+
focusElement(restoreElement.lastElement);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
get dependencies() {
|
|
75
|
+
return [enabled];
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
$effect(() => {
|
|
79
|
+
if (!enabled) return;
|
|
80
|
+
return () => focusElement(restoreElement.lastElement);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function useInitialFocus(options) {
|
|
84
|
+
const { features, ownerDocument, container, initialFocus, initialFocusFallback } = $derived(options);
|
|
85
|
+
let previousActiveElement = $state(null);
|
|
86
|
+
let enabled = useIsTopLayer({
|
|
87
|
+
get enabled() {
|
|
88
|
+
return Boolean(features & 1 /* InitialFocus */);
|
|
89
|
+
},
|
|
90
|
+
scope: "focus-trap#initial-focus"
|
|
91
|
+
});
|
|
92
|
+
let mounted = useIsMounted();
|
|
93
|
+
useWatch({
|
|
94
|
+
action: () => {
|
|
95
|
+
if (features === 0 /* None */) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!enabled) {
|
|
99
|
+
if (initialFocusFallback) {
|
|
100
|
+
focusElement(initialFocusFallback);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let containerElement = container;
|
|
105
|
+
if (!containerElement) return;
|
|
106
|
+
microTask(() => {
|
|
107
|
+
if (!mounted.current) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let activeElement = ownerDocument?.activeElement;
|
|
111
|
+
if (initialFocus) {
|
|
112
|
+
if (initialFocus === activeElement) {
|
|
113
|
+
previousActiveElement = activeElement;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
} else if (containerElement.contains(activeElement)) {
|
|
117
|
+
previousActiveElement = activeElement;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (initialFocus) {
|
|
121
|
+
focusElement(initialFocus);
|
|
122
|
+
} else {
|
|
123
|
+
if (features & 16 /* AutoFocus */) {
|
|
124
|
+
if (focusIn(containerElement, Focus.First | Focus.AutoFocus) !== FocusResult.Error) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
} else if (focusIn(containerElement, Focus.First) !== FocusResult.Error) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (initialFocusFallback) {
|
|
131
|
+
focusElement(initialFocusFallback);
|
|
132
|
+
if (ownerDocument?.activeElement === initialFocusFallback) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
console.warn("There are no focusable elements inside the <FocusTrap />");
|
|
137
|
+
}
|
|
138
|
+
previousActiveElement = ownerDocument?.activeElement;
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
get dependencies() {
|
|
142
|
+
return [initialFocusFallback, enabled, features];
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
get value() {
|
|
147
|
+
return previousActiveElement;
|
|
148
|
+
},
|
|
149
|
+
set value(element) {
|
|
150
|
+
previousActiveElement = element;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function useFocusLock(options) {
|
|
155
|
+
let { features, ownerDocument, container, containers, previousActiveElement } = $derived(options);
|
|
156
|
+
const mounted = useIsMounted();
|
|
157
|
+
const enabled = $derived(Boolean(features & 4 /* FocusLock */));
|
|
158
|
+
useEventListener({
|
|
159
|
+
get element() {
|
|
160
|
+
return ownerDocument?.defaultView;
|
|
161
|
+
},
|
|
162
|
+
type: "focus",
|
|
163
|
+
listener: (event) => {
|
|
164
|
+
if (!enabled) return;
|
|
165
|
+
if (!mounted.current) return;
|
|
166
|
+
let allContainers = resolveContainers(containers);
|
|
167
|
+
if (container instanceof HTMLElement) allContainers.add(container);
|
|
168
|
+
let previous = previousActiveElement;
|
|
169
|
+
if (!previous) return;
|
|
170
|
+
let toElement = event.target;
|
|
171
|
+
if (toElement && toElement instanceof HTMLElement) {
|
|
172
|
+
if (!contains(allContainers, toElement)) {
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
focusElement(previous);
|
|
176
|
+
} else {
|
|
177
|
+
options.previousActiveElement = toElement;
|
|
178
|
+
focusElement(toElement);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
focusElement(previousActiveElement);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
options: true
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function contains(containers, element) {
|
|
188
|
+
for (let container of containers) {
|
|
189
|
+
if (container.contains(element)) return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG">let container = $state(null);
|
|
196
|
+
let {
|
|
197
|
+
ref = $bindable(),
|
|
198
|
+
initialFocus,
|
|
199
|
+
initialFocusFallback,
|
|
200
|
+
containers,
|
|
201
|
+
features = FocusTrapFeatures.InitialFocus | FocusTrapFeatures.TabLock | FocusTrapFeatures.FocusLock | FocusTrapFeatures.RestoreFocus,
|
|
202
|
+
...theirProps
|
|
203
|
+
} = $props();
|
|
204
|
+
const ownerDocument = $derived(getOwnerDocument(ref));
|
|
205
|
+
useRestoreFocus({
|
|
206
|
+
get features() {
|
|
207
|
+
return features;
|
|
208
|
+
},
|
|
209
|
+
get ownerDocument() {
|
|
210
|
+
return ownerDocument;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
let previousActiveElement = useInitialFocus({
|
|
214
|
+
get features() {
|
|
215
|
+
return features;
|
|
216
|
+
},
|
|
217
|
+
get ownerDocument() {
|
|
218
|
+
return ownerDocument;
|
|
219
|
+
},
|
|
220
|
+
get container() {
|
|
221
|
+
return container;
|
|
222
|
+
},
|
|
223
|
+
get initialFocus() {
|
|
224
|
+
return initialFocus;
|
|
225
|
+
},
|
|
226
|
+
get initialFocusFallback() {
|
|
227
|
+
return initialFocusFallback;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
useFocusLock({
|
|
231
|
+
get features() {
|
|
232
|
+
return features;
|
|
233
|
+
},
|
|
234
|
+
get ownerDocument() {
|
|
235
|
+
return ownerDocument;
|
|
236
|
+
},
|
|
237
|
+
get container() {
|
|
238
|
+
return container;
|
|
239
|
+
},
|
|
240
|
+
get containers() {
|
|
241
|
+
return containers;
|
|
242
|
+
},
|
|
243
|
+
get previousActiveElement() {
|
|
244
|
+
return previousActiveElement.value;
|
|
245
|
+
},
|
|
246
|
+
set previousActiveElement(element) {
|
|
247
|
+
previousActiveElement.value = element;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
const direction = useTabDirection();
|
|
251
|
+
const handleFocus = (e) => {
|
|
252
|
+
let el = container;
|
|
253
|
+
if (!el) return;
|
|
254
|
+
let wrapper = process.env.NODE_ENV === "test" ? microTask : (cb) => cb();
|
|
255
|
+
wrapper(() => {
|
|
256
|
+
match(direction.current, {
|
|
257
|
+
[TabDirection.Forwards]: () => {
|
|
258
|
+
focusIn(el, Focus.First, {
|
|
259
|
+
skipElements: [e.relatedTarget, initialFocusFallback]
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
[TabDirection.Backwards]: () => {
|
|
263
|
+
focusIn(el, Focus.Last, {
|
|
264
|
+
skipElements: [e.relatedTarget, initialFocusFallback]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
let tabLockEnabled = useIsTopLayer({
|
|
271
|
+
get enabled() {
|
|
272
|
+
return Boolean(features & FocusTrapFeatures.TabLock);
|
|
273
|
+
},
|
|
274
|
+
scope: "focus-trap#tab-lock"
|
|
275
|
+
});
|
|
276
|
+
const d = useDisposables();
|
|
277
|
+
let recentlyUsedTabKey = $state(false);
|
|
278
|
+
const ourProps = $derived({
|
|
279
|
+
onkeydown(e) {
|
|
280
|
+
if (e.key == "Tab") {
|
|
281
|
+
recentlyUsedTabKey = true;
|
|
282
|
+
d.requestAnimationFrame(() => {
|
|
283
|
+
recentlyUsedTabKey = false;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
onblur(e) {
|
|
288
|
+
if (!(features & FocusTrapFeatures.FocusLock)) return;
|
|
289
|
+
let allContainers = resolveContainers(containers);
|
|
290
|
+
if (container instanceof HTMLElement) allContainers.add(container);
|
|
291
|
+
let relatedTarget = e.relatedTarget;
|
|
292
|
+
if (!(relatedTarget instanceof HTMLElement)) return;
|
|
293
|
+
if (relatedTarget.dataset.headlessuiFocusGuard === "true") {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!contains(allContainers, relatedTarget)) {
|
|
297
|
+
if (recentlyUsedTabKey) {
|
|
298
|
+
focusIn(
|
|
299
|
+
container,
|
|
300
|
+
match(direction.current, {
|
|
301
|
+
[TabDirection.Forwards]: () => Focus.Next,
|
|
302
|
+
[TabDirection.Backwards]: () => Focus.Previous
|
|
303
|
+
}) | Focus.WrapAround,
|
|
304
|
+
{ relativeTo: e.target }
|
|
305
|
+
);
|
|
306
|
+
} else if (e.target instanceof HTMLElement) {
|
|
307
|
+
focusElement(e.target);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
</script>
|
|
313
|
+
|
|
314
|
+
{#if tabLockEnabled}
|
|
315
|
+
<Hidden
|
|
316
|
+
as="button"
|
|
317
|
+
type="button"
|
|
318
|
+
data-headlessui-focus-guard
|
|
319
|
+
onfocus={handleFocus}
|
|
320
|
+
features={HiddenFeatures.Focusable}
|
|
321
|
+
/>
|
|
322
|
+
{/if}
|
|
323
|
+
<ElementOrComponent {ourProps} {theirProps} defaultTag={DEFAULT_FOCUS_TRAP_TAG} name="FocusTrap" bind:ref />
|
|
324
|
+
{#if tabLockEnabled}
|
|
325
|
+
<Hidden
|
|
326
|
+
as="button"
|
|
327
|
+
type="button"
|
|
328
|
+
data-headlessui-focus-guard
|
|
329
|
+
onfocus={handleFocus}
|
|
330
|
+
features={HiddenFeatures.Focusable}
|
|
331
|
+
/>
|
|
332
|
+
{/if}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ElementType, Props } from "../utils/types.js";
|
|
2
|
+
type Containers = (() => Iterable<HTMLElement>) | Iterable<HTMLElement>;
|
|
3
|
+
declare let DEFAULT_FOCUS_TRAP_TAG: "div";
|
|
4
|
+
export declare enum FocusTrapFeatures {
|
|
5
|
+
/** No features enabled for the focus trap. */
|
|
6
|
+
None = 0,
|
|
7
|
+
/** Ensure that we move focus initially into the container. */
|
|
8
|
+
InitialFocus = 1,
|
|
9
|
+
/** Ensure that pressing `Tab` and `Shift+Tab` is trapped within the container. */
|
|
10
|
+
TabLock = 2,
|
|
11
|
+
/** Ensure that programmatically moving focus outside of the container is disallowed. */
|
|
12
|
+
FocusLock = 4,
|
|
13
|
+
/** Ensure that we restore the focus when unmounting the focus trap. */
|
|
14
|
+
RestoreFocus = 8,
|
|
15
|
+
/** Initial focus should look for the `data-autofocus` */
|
|
16
|
+
AutoFocus = 16
|
|
17
|
+
}
|
|
18
|
+
type FocusTrapRenderPropArg = {};
|
|
19
|
+
type FocusTrapPropsWeControl = never;
|
|
20
|
+
export type FocusTrapProps<TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG> = Props<TTag, FocusTrapRenderPropArg, FocusTrapPropsWeControl, {
|
|
21
|
+
initialFocus?: HTMLElement;
|
|
22
|
+
initialFocusFallback?: HTMLElement;
|
|
23
|
+
features?: FocusTrapFeatures;
|
|
24
|
+
containers?: Containers;
|
|
25
|
+
}>;
|
|
26
|
+
declare class __sveltets_Render<TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG> {
|
|
27
|
+
props(): {
|
|
28
|
+
as?: TTag | undefined;
|
|
29
|
+
} & (Exclude<keyof import("../utils/types.js").PropsOf<TTag>, ("as" | "children" | "refName" | "class") | "features" | "containers" | "initialFocus" | "initialFocusFallback"> extends infer T extends keyof import("../utils/types.js").PropsOf<TTag> ? { [P in T]: import("../utils/types.js").PropsOf<TTag>[P]; } : never) & {
|
|
30
|
+
children?: import("../utils/types.js").Children<FocusTrapRenderPropArg> | undefined;
|
|
31
|
+
ref?: HTMLElement;
|
|
32
|
+
} & (true extends (import("../utils/types.js").PropsOf<TTag> extends infer T_1 ? T_1 extends import("../utils/types.js").PropsOf<TTag> ? T_1 extends never ? never : "class" extends infer T_2 ? T_2 extends "class" ? T_2 extends keyof T_1 ? true : never : never : never : never : never) ? {
|
|
33
|
+
class?: import("../utils/types.js").PropsOf<TTag>["class"] | ((bag: FocusTrapRenderPropArg) => string) | undefined;
|
|
34
|
+
} : {}) & {
|
|
35
|
+
initialFocus?: HTMLElement;
|
|
36
|
+
initialFocusFallback?: HTMLElement;
|
|
37
|
+
features?: FocusTrapFeatures;
|
|
38
|
+
containers?: Containers;
|
|
39
|
+
};
|
|
40
|
+
events(): {} & {
|
|
41
|
+
[evt: string]: CustomEvent<any>;
|
|
42
|
+
};
|
|
43
|
+
slots(): {};
|
|
44
|
+
bindings(): "ref";
|
|
45
|
+
exports(): {};
|
|
46
|
+
}
|
|
47
|
+
interface $$IsomorphicComponent {
|
|
48
|
+
new <TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_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']>> & {
|
|
49
|
+
$$bindings?: ReturnType<__sveltets_Render<TTag>['bindings']>;
|
|
50
|
+
} & ReturnType<__sveltets_Render<TTag>['exports']>;
|
|
51
|
+
<TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG>(internal: unknown, props: ReturnType<__sveltets_Render<TTag>['props']> & {
|
|
52
|
+
$$events?: ReturnType<__sveltets_Render<TTag>['events']>;
|
|
53
|
+
}): ReturnType<__sveltets_Render<TTag>['exports']>;
|
|
54
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
55
|
+
}
|
|
56
|
+
declare const FocusTrap: $$IsomorphicComponent;
|
|
57
|
+
type FocusTrap<TTag extends ElementType = typeof DEFAULT_FOCUS_TRAP_TAG> = InstanceType<typeof FocusTrap<TTag>>;
|
|
58
|
+
export default FocusTrap;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function adjustScrollbarPadding() {
|
|
2
|
+
let scrollbarWidthBefore;
|
|
3
|
+
return {
|
|
4
|
+
before({ doc }) {
|
|
5
|
+
let documentElement = doc.documentElement;
|
|
6
|
+
let ownerWindow = doc.defaultView ?? window;
|
|
7
|
+
scrollbarWidthBefore = Math.max(0, ownerWindow.innerWidth - documentElement.clientWidth);
|
|
8
|
+
},
|
|
9
|
+
after({ doc, d }) {
|
|
10
|
+
let documentElement = doc.documentElement;
|
|
11
|
+
// Account for the change in scrollbar width
|
|
12
|
+
// NOTE: This is a bit of a hack, but it's the only way to do this
|
|
13
|
+
let scrollbarWidthAfter = Math.max(0, documentElement.clientWidth - documentElement.offsetWidth);
|
|
14
|
+
let scrollbarWidth = Math.max(0, scrollbarWidthBefore - scrollbarWidthAfter);
|
|
15
|
+
d.style(documentElement, "paddingRight", `${scrollbarWidth}px`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { disposables } from "../../utils/disposables.js";
|
|
2
|
+
import { isIOS } from "../../utils/platform.js";
|
|
3
|
+
export function handleIOSLocking() {
|
|
4
|
+
if (!isIOS()) {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
before({ doc, d, meta }) {
|
|
9
|
+
function inAllowedContainer(el) {
|
|
10
|
+
return meta.containers.flatMap((resolve) => resolve()).some((container) => container.contains(el));
|
|
11
|
+
}
|
|
12
|
+
d.microTask(() => {
|
|
13
|
+
// We need to be able to offset the body with the current scroll position. However, if you
|
|
14
|
+
// have `scroll-behavior: smooth` set, then changing the scrollTop in any way shape or form
|
|
15
|
+
// will trigger a "smooth" scroll and the new position would be incorrect.
|
|
16
|
+
//
|
|
17
|
+
// This is why we are forcing the `scroll-behavior: auto` here, and then restoring it later.
|
|
18
|
+
// We have to be a bit careful, because removing `scroll-behavior: auto` back to
|
|
19
|
+
// `scroll-behavior: smooth` can start triggering smooth scrolling. Delaying this by a
|
|
20
|
+
// microTask will guarantee that everything is done such that both enter/exit of the Dialog is
|
|
21
|
+
// not using smooth scrolling.
|
|
22
|
+
if (window.getComputedStyle(doc.documentElement).scrollBehavior !== "auto") {
|
|
23
|
+
let _d = disposables();
|
|
24
|
+
_d.style(doc.documentElement, "scrollBehavior", "auto");
|
|
25
|
+
d.add(() => d.microTask(() => _d.dispose()));
|
|
26
|
+
}
|
|
27
|
+
// Keep track of the current scroll position so that we can restore the scroll position if
|
|
28
|
+
// it has changed in the meantime.
|
|
29
|
+
let scrollPosition = window.scrollY ?? window.pageYOffset;
|
|
30
|
+
// Relatively hacky, but if you click a link like `<a href="#foo">` in the Dialog, and there
|
|
31
|
+
// exists an element on the page (outside of the Dialog) with that id, then the browser will
|
|
32
|
+
// scroll to that position. However, this is not the case if the element we want to scroll to
|
|
33
|
+
// is higher and the browser needs to scroll up, but it doesn't do that.
|
|
34
|
+
//
|
|
35
|
+
// Let's try and capture that element and store it, so that we can later scroll to it once the
|
|
36
|
+
// Dialog closes.
|
|
37
|
+
let scrollToElement = null;
|
|
38
|
+
d.addEventListener(doc, "click", (e) => {
|
|
39
|
+
if (!(e.target instanceof HTMLElement)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
let anchor = e.target.closest("a");
|
|
44
|
+
if (!anchor)
|
|
45
|
+
return;
|
|
46
|
+
let { hash } = new URL(anchor.href);
|
|
47
|
+
let el = doc.querySelector(hash);
|
|
48
|
+
if (el && !inAllowedContainer(el)) {
|
|
49
|
+
scrollToElement = el;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (err) { }
|
|
53
|
+
}, true);
|
|
54
|
+
// Rely on overscrollBehavior to prevent scrolling outside of the Dialog.
|
|
55
|
+
d.addEventListener(doc, "touchstart", (e) => {
|
|
56
|
+
if (e.target instanceof HTMLElement) {
|
|
57
|
+
if (inAllowedContainer(e.target)) {
|
|
58
|
+
// Find the root of the allowed containers
|
|
59
|
+
let rootContainer = e.target;
|
|
60
|
+
while (rootContainer.parentElement && inAllowedContainer(rootContainer.parentElement)) {
|
|
61
|
+
rootContainer = rootContainer.parentElement;
|
|
62
|
+
}
|
|
63
|
+
d.style(rootContainer, "overscrollBehavior", "contain");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
d.style(e.target, "touchAction", "none");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
d.addEventListener(doc, "touchmove", (e) => {
|
|
71
|
+
// Check if we are scrolling inside any of the allowed containers, if not let's cancel the event!
|
|
72
|
+
if (e.target instanceof HTMLElement) {
|
|
73
|
+
// Some inputs like `<input type=range>` use touch events to
|
|
74
|
+
// allow interaction. We should not prevent this event.
|
|
75
|
+
if (e.target.tagName === "INPUT") {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (inAllowedContainer(e.target)) {
|
|
79
|
+
// Even if we are in an allowed container, on iOS the main page can still scroll, we
|
|
80
|
+
// have to make sure that we `event.preventDefault()` this event to prevent that.
|
|
81
|
+
//
|
|
82
|
+
// However, if we happen to scroll on an element that is overflowing, or any of its
|
|
83
|
+
// parents are overflowing, then we should not call `event.preventDefault()` because
|
|
84
|
+
// otherwise we are preventing the user from scrolling inside that container which
|
|
85
|
+
// is not what we want.
|
|
86
|
+
let scrollableParent = e.target;
|
|
87
|
+
while (scrollableParent.parentElement &&
|
|
88
|
+
// Assumption: We are always used in a Headless UI Portal. Once we reach the
|
|
89
|
+
// portal itself, we can stop crawling up the tree.
|
|
90
|
+
scrollableParent.dataset.headlessuiPortal !== "") {
|
|
91
|
+
// Check if the scrollable container is overflowing or not.
|
|
92
|
+
//
|
|
93
|
+
// NOTE: we could check the `overflow`, `overflow-y` and `overflow-x` properties
|
|
94
|
+
// but when there is no overflow happening then the `overscrollBehavior` doesn't
|
|
95
|
+
// seem to work and the main page will still scroll. So instead we check if the
|
|
96
|
+
// scrollable container is overflowing or not and use that heuristic instead.
|
|
97
|
+
if (scrollableParent.scrollHeight > scrollableParent.clientHeight ||
|
|
98
|
+
scrollableParent.scrollWidth > scrollableParent.clientWidth) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
scrollableParent = scrollableParent.parentElement;
|
|
102
|
+
}
|
|
103
|
+
// We crawled up the tree until the beginning of the Portal, let's prevent the
|
|
104
|
+
// event if this is the case. If not, then we are in a container where we are
|
|
105
|
+
// allowed to scroll so we don't have to prevent the event.
|
|
106
|
+
if (scrollableParent.dataset.headlessuiPortal === "") {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// We are not in an allowed container, so let's prevent the event.
|
|
111
|
+
else {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}, { passive: false });
|
|
116
|
+
// Restore scroll position if a scrollToElement was captured.
|
|
117
|
+
d.add(() => {
|
|
118
|
+
let newScrollPosition = window.scrollY ?? window.pageYOffset;
|
|
119
|
+
// If the scroll position changed, then we can restore it to the previous value. This will
|
|
120
|
+
// happen if you focus an input field and the browser scrolls for you.
|
|
121
|
+
if (scrollPosition !== newScrollPosition) {
|
|
122
|
+
window.scrollTo(0, scrollPosition);
|
|
123
|
+
}
|
|
124
|
+
// If we captured an element that should be scrolled to, then we can try to do that if the
|
|
125
|
+
// element is still connected (aka, still in the DOM).
|
|
126
|
+
if (scrollToElement && scrollToElement.isConnected) {
|
|
127
|
+
scrollToElement.scrollIntoView({ block: "nearest" });
|
|
128
|
+
scrollToElement = null;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Disposables } from "../../utils/disposables.js";
|
|
2
|
+
interface DocEntry {
|
|
3
|
+
doc: Document;
|
|
4
|
+
count: number;
|
|
5
|
+
d: Disposables;
|
|
6
|
+
meta: Set<MetaFn>;
|
|
7
|
+
}
|
|
8
|
+
export type MetaFn = (meta: Record<string, any>) => Record<string, any>;
|
|
9
|
+
export interface Context<MetaType extends Record<string, any> = any> {
|
|
10
|
+
doc: Document;
|
|
11
|
+
d: Disposables;
|
|
12
|
+
meta: MetaType;
|
|
13
|
+
}
|
|
14
|
+
export interface ScrollLockStep<MetaType extends Record<string, any> = any> {
|
|
15
|
+
before?(ctx: Context<MetaType>): void;
|
|
16
|
+
after?(ctx: Context<MetaType>): void;
|
|
17
|
+
}
|
|
18
|
+
export declare let overflows: import("../../utils/store.js").Store<Map<Document, DocEntry>, "PUSH" | "POP" | "SCROLL_PREVENT" | "SCROLL_ALLOW" | "TEARDOWN">;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { disposables } from "../../utils/disposables.js";
|
|
2
|
+
import { createStore } from "../../utils/store.js";
|
|
3
|
+
import { adjustScrollbarPadding } from "./adjust-scrollbar-padding.js";
|
|
4
|
+
import { handleIOSLocking } from "./handle-ios-locking.js";
|
|
5
|
+
import { preventScroll } from "./prevent-scroll.js";
|
|
6
|
+
function buildMeta(fns) {
|
|
7
|
+
let tmp = {};
|
|
8
|
+
for (let fn of fns) {
|
|
9
|
+
Object.assign(tmp, fn(tmp));
|
|
10
|
+
}
|
|
11
|
+
return tmp;
|
|
12
|
+
}
|
|
13
|
+
export let overflows = createStore(() => new Map(), {
|
|
14
|
+
PUSH(doc, meta) {
|
|
15
|
+
let entry = this.get(doc) ?? {
|
|
16
|
+
doc,
|
|
17
|
+
count: 0,
|
|
18
|
+
d: disposables(),
|
|
19
|
+
meta: new Set(),
|
|
20
|
+
};
|
|
21
|
+
entry.count++;
|
|
22
|
+
entry.meta.add(meta);
|
|
23
|
+
this.set(doc, entry);
|
|
24
|
+
return this;
|
|
25
|
+
},
|
|
26
|
+
POP(doc, meta) {
|
|
27
|
+
let entry = this.get(doc);
|
|
28
|
+
if (entry) {
|
|
29
|
+
entry.count--;
|
|
30
|
+
entry.meta.delete(meta);
|
|
31
|
+
}
|
|
32
|
+
return this;
|
|
33
|
+
},
|
|
34
|
+
SCROLL_PREVENT({ doc, d, meta }) {
|
|
35
|
+
let ctx = {
|
|
36
|
+
doc,
|
|
37
|
+
d,
|
|
38
|
+
meta: buildMeta(meta),
|
|
39
|
+
};
|
|
40
|
+
let steps = [handleIOSLocking(), adjustScrollbarPadding(), preventScroll()];
|
|
41
|
+
// Run all `before` actions together
|
|
42
|
+
steps.forEach(({ before }) => before?.(ctx));
|
|
43
|
+
// Run all `after` actions together
|
|
44
|
+
steps.forEach(({ after }) => after?.(ctx));
|
|
45
|
+
},
|
|
46
|
+
SCROLL_ALLOW({ d }) {
|
|
47
|
+
d.dispose();
|
|
48
|
+
},
|
|
49
|
+
TEARDOWN({ doc }) {
|
|
50
|
+
this.delete(doc);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
// Update the document overflow state when the store changes
|
|
54
|
+
// This MUST happen outside of react for this to work properly.
|
|
55
|
+
overflows.subscribe(() => {
|
|
56
|
+
let docs = overflows.getSnapshot();
|
|
57
|
+
let styles = new Map();
|
|
58
|
+
// Read data from all the documents
|
|
59
|
+
for (let [doc] of docs) {
|
|
60
|
+
styles.set(doc, doc.documentElement.style.overflow);
|
|
61
|
+
}
|
|
62
|
+
// Write data to all the documents
|
|
63
|
+
for (let entry of docs.values()) {
|
|
64
|
+
let isHidden = styles.get(entry.doc) === "hidden";
|
|
65
|
+
let isLocked = entry.count !== 0;
|
|
66
|
+
let willChange = (isLocked && !isHidden) || (!isLocked && isHidden);
|
|
67
|
+
if (willChange) {
|
|
68
|
+
overflows.dispatch(entry.count > 0 ? "SCROLL_PREVENT" : "SCROLL_ALLOW", entry);
|
|
69
|
+
}
|
|
70
|
+
// We have to clean up after ourselves so we don't leak memory
|
|
71
|
+
// Using a WeakMap would be ideal, but it's not iterable
|
|
72
|
+
if (entry.count === 0) {
|
|
73
|
+
overflows.dispatch("TEARDOWN", entry);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|