@pzerelles/headlessui-svelte 2.0.0-next.1 → 2.1.1-next.1
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 +65 -0
- package/dist/button/Button.svelte.d.ts +39 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +1 -0
- package/dist/checkbox/Checkbox.svelte +60 -46
- package/dist/checkbox/Checkbox.svelte.d.ts +1 -1
- package/dist/close-button/CloseButton.svelte +10 -0
- package/dist/close-button/CloseButton.svelte.d.ts +25 -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/description/Description.svelte +50 -32
- package/dist/description/Description.svelte.d.ts +14 -5
- package/dist/field/Field.svelte +9 -9
- package/dist/fieldset/Fieldset.svelte +9 -9
- 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-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-top-layer.svelte.d.ts +29 -0
- package/dist/hooks/use-is-top-layer.svelte.js +82 -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-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-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 +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/internal/FocusSentinel.svelte +45 -0
- package/dist/internal/FocusSentinel.svelte.d.ts +17 -0
- package/dist/internal/FormFields.svelte +2 -4
- package/dist/internal/FormFields.svelte.d.ts +5 -6
- package/dist/internal/FormResolver.svelte +11 -16
- package/dist/internal/FormResolver.svelte.d.ts +2 -3
- package/dist/internal/Hidden.svelte +8 -8
- package/dist/internal/Hidden.svelte.d.ts +28 -19
- package/dist/internal/HoistFormFields.svelte.d.ts +1 -1
- package/dist/internal/Portal.svelte.d.ts +1 -1
- package/dist/internal/floating.svelte.d.ts +57 -0
- package/dist/internal/floating.svelte.js +477 -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 +53 -32
- package/dist/label/Label.svelte.d.ts +14 -5
- package/dist/legend/Legend.svelte.d.ts +1 -2
- package/dist/listbox/Listbox.svelte +451 -0
- package/dist/listbox/Listbox.svelte.d.ts +107 -0
- package/dist/listbox/ListboxButton.svelte +141 -0
- package/dist/listbox/ListboxButton.svelte.d.ts +41 -0
- package/dist/listbox/ListboxOption.svelte +138 -0
- package/dist/listbox/ListboxOption.svelte.d.ts +39 -0
- package/dist/listbox/ListboxOptions.svelte +267 -0
- package/dist/listbox/ListboxOptions.svelte.d.ts +39 -0
- package/dist/listbox/ListboxSelectedOption.svelte +25 -0
- package/dist/listbox/ListboxSelectedOption.svelte.d.ts +30 -0
- package/dist/listbox/index.d.ts +5 -0
- package/dist/listbox/index.js +5 -0
- package/dist/portal/InternalPortal.svelte +108 -0
- package/dist/portal/InternalPortal.svelte.d.ts +34 -0
- package/dist/portal/Portal.svelte +11 -0
- package/dist/portal/Portal.svelte.d.ts +23 -0
- package/dist/portal/PortalGroup.svelte +15 -0
- package/dist/portal/PortalGroup.svelte.d.ts +31 -0
- package/dist/switch/Switch.svelte +149 -0
- package/dist/switch/Switch.svelte.d.ts +44 -0
- package/dist/switch/SwitchGroup.svelte +38 -0
- package/dist/switch/SwitchGroup.svelte.d.ts +27 -0
- package/dist/switch/index.d.ts +2 -0
- package/dist/switch/index.js +2 -0
- package/dist/tabs/Button.svelte +65 -0
- package/dist/tabs/Button.svelte.d.ts +39 -0
- package/dist/tabs/Tab.svelte +161 -0
- package/dist/tabs/Tab.svelte.d.ts +36 -0
- package/dist/tabs/TabGroup.svelte +244 -0
- package/dist/tabs/TabGroup.svelte.d.ts +54 -0
- package/dist/tabs/TabList.svelte +18 -0
- package/dist/tabs/TabList.svelte.d.ts +28 -0
- package/dist/tabs/TabPanel.svelte +63 -0
- package/dist/tabs/TabPanel.svelte.d.ts +34 -0
- package/dist/tabs/TabPanels.svelte +13 -0
- package/dist/tabs/TabPanels.svelte.d.ts +27 -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/utils/StableCollection.svelte +43 -0
- package/dist/utils/StableCollection.svelte.d.ts +19 -0
- package/dist/utils/calculate-active-index.d.ts +25 -0
- package/dist/utils/calculate-active-index.js +74 -0
- package/dist/utils/close.d.ts +2 -0
- package/dist/utils/close.js +3 -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 +44 -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/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 +31 -0
- package/dist/utils/render.js +56 -0
- package/dist/utils/store.d.ts +11 -0
- package/dist/utils/store.js +20 -0
- package/dist/utils/types.d.ts +27 -0
- package/dist/utils/types.js +6 -0
- package/package.json +28 -21
- 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,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
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { overflows } from "./overflow-store.js";
|
|
2
|
+
export function useDocumentOverflowLockedEffect(options) {
|
|
3
|
+
const { shouldBeLocked, doc, meta = () => ({ containers: [] }) } = $derived(options);
|
|
4
|
+
let store = $state(overflows.getSnapshot());
|
|
5
|
+
$effect(() => {
|
|
6
|
+
const unsubscribe = overflows.subscribe(() => {
|
|
7
|
+
store = overflows.getSnapshot();
|
|
8
|
+
});
|
|
9
|
+
return unsubscribe;
|
|
10
|
+
});
|
|
11
|
+
const entry = $derived(doc ? store.get(doc) : undefined);
|
|
12
|
+
const locked = $derived(entry ? entry.count > 0 : false);
|
|
13
|
+
$effect(() => {
|
|
14
|
+
if (!doc || !shouldBeLocked) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Prevent the document from scrolling
|
|
18
|
+
overflows.dispatch("PUSH", doc, meta);
|
|
19
|
+
// Allow document to scroll
|
|
20
|
+
return () => overflows.dispatch("POP", doc, meta);
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
get locked() {
|
|
24
|
+
return locked;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const useActivePress: (options: {
|
|
2
|
+
disabled?: boolean;
|
|
3
|
+
}) => {
|
|
4
|
+
readonly pressed: boolean;
|
|
5
|
+
readonly pressProps: {
|
|
6
|
+
onpointerdown?: undefined;
|
|
7
|
+
onpointerup?: undefined;
|
|
8
|
+
onclick?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
onpointerdown: (event: PointerEvent) => void;
|
|
11
|
+
onpointerup: () => void;
|
|
12
|
+
onclick: () => void;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { disposables } from "../utils/disposables.js";
|
|
2
|
-
import { getOwnerDocument } from "../utils/
|
|
2
|
+
import { getOwnerDocument } from "../utils/owner.js";
|
|
3
3
|
function pointerRectFromPointerEvent(event) {
|
|
4
4
|
// Center of the pointer geometry
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const offsetX = event.width / 2;
|
|
6
|
+
const offsetY = event.height / 2;
|
|
7
7
|
return {
|
|
8
8
|
top: event.clientY - offsetY,
|
|
9
9
|
right: event.clientX + offsetX,
|
|
@@ -23,27 +23,28 @@ function areRectsOverlapping(a, b) {
|
|
|
23
23
|
}
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
|
-
export const
|
|
26
|
+
export const useActivePress = (options) => {
|
|
27
|
+
const { disabled } = $derived(options);
|
|
27
28
|
let currentTarget = $state(null);
|
|
28
29
|
let pressed = $state(false);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
30
|
+
const d = disposables();
|
|
31
|
+
const reset = () => {
|
|
32
|
+
currentTarget = null;
|
|
33
|
+
pressed = false;
|
|
34
|
+
d.dispose();
|
|
35
|
+
};
|
|
36
|
+
const handlePointerDown = (event) => {
|
|
37
|
+
d.dispose(); // Cancel any scheduled tasks
|
|
38
|
+
if (currentTarget !== null)
|
|
39
|
+
return;
|
|
40
|
+
// Keep track of the current element
|
|
41
|
+
currentTarget = event.currentTarget;
|
|
42
|
+
// We are definitely pressing the element now
|
|
43
|
+
pressed = true;
|
|
44
|
+
// Setup global handlers to catch events on elements that are not the current element
|
|
45
|
+
{
|
|
46
|
+
const owner = getOwnerDocument(event.currentTarget);
|
|
47
|
+
if (owner) {
|
|
47
48
|
// `pointerup` on any element means that we are no longer pressing the current element
|
|
48
49
|
d.addEventListener(owner, "pointerup", reset, false);
|
|
49
50
|
// `pointerleave` isn't called consistently (if at all) on iOS Safari, so we use `pointermove` instead
|
|
@@ -51,34 +52,27 @@ export const createActivePress = ({ disabled }) => {
|
|
|
51
52
|
// so that we can tell if the pointer is still over the element or not.
|
|
52
53
|
d.addEventListener(owner, "pointermove", (event) => {
|
|
53
54
|
if (currentTarget) {
|
|
54
|
-
|
|
55
|
+
const pointerRect = pointerRectFromPointerEvent(event);
|
|
55
56
|
pressed = areRectsOverlapping(pointerRect, currentTarget.getBoundingClientRect());
|
|
56
57
|
}
|
|
57
58
|
}, false);
|
|
58
59
|
// Whenever the browser decides to fire a `pointercancel` event, we should abort
|
|
59
60
|
d.addEventListener(owner, "pointercancel", reset, false);
|
|
60
61
|
}
|
|
61
|
-
};
|
|
62
|
-
if (!disabled) {
|
|
63
|
-
node.addEventListener("pointerdown", handlePointerDown);
|
|
64
|
-
node.addEventListener("pointerup", reset);
|
|
65
|
-
node.addEventListener("click", reset);
|
|
66
62
|
}
|
|
67
|
-
return {
|
|
68
|
-
destroy: () => {
|
|
69
|
-
if (!disabled) {
|
|
70
|
-
node.removeEventListener("pointerdown", handlePointerDown);
|
|
71
|
-
node.removeEventListener("pointerup", reset);
|
|
72
|
-
node.removeEventListener("click", reset);
|
|
73
|
-
}
|
|
74
|
-
d.dispose();
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
63
|
};
|
|
78
64
|
return {
|
|
79
|
-
activePressAction,
|
|
80
65
|
get pressed() {
|
|
81
66
|
return pressed;
|
|
82
67
|
},
|
|
68
|
+
get pressProps() {
|
|
69
|
+
return disabled
|
|
70
|
+
? {}
|
|
71
|
+
: {
|
|
72
|
+
onpointerdown: handlePointerDown,
|
|
73
|
+
onpointerup: reset,
|
|
74
|
+
onclick: reset,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
83
77
|
};
|
|
84
78
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function defaultBy(a, z) {
|
|
2
|
+
if (a !== null && z !== null && typeof a === "object" && typeof z === "object" && "id" in a && "id" in z) {
|
|
3
|
+
return a.id === z.id;
|
|
4
|
+
}
|
|
5
|
+
return a === z;
|
|
6
|
+
}
|
|
7
|
+
export function useByComparator(by = defaultBy) {
|
|
8
|
+
return (a, z) => {
|
|
9
|
+
if (typeof by === "string") {
|
|
10
|
+
const property = by;
|
|
11
|
+
return a?.[property] === z?.[property];
|
|
12
|
+
}
|
|
13
|
+
return by(a, z);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function useControllable(input, onchange, defaultValue) {
|
|
2
|
+
let internalValue = $state(defaultValue);
|
|
3
|
+
const isControlled = $derived(input.controlledValue !== undefined);
|
|
4
|
+
let wasControlled = isControlled;
|
|
5
|
+
let didWarnOnUncontrolledToControlled = false;
|
|
6
|
+
let didWarnOnControlledToUncontrolled = false;
|
|
7
|
+
$effect(() => {
|
|
8
|
+
if (isControlled && !wasControlled && !didWarnOnUncontrolledToControlled) {
|
|
9
|
+
didWarnOnUncontrolledToControlled = true;
|
|
10
|
+
wasControlled = isControlled;
|
|
11
|
+
console.error("A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.");
|
|
12
|
+
}
|
|
13
|
+
else if (!isControlled && wasControlled && !didWarnOnControlledToUncontrolled) {
|
|
14
|
+
didWarnOnControlledToUncontrolled = true;
|
|
15
|
+
wasControlled = isControlled;
|
|
16
|
+
console.error("A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.");
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const value = $derived(isControlled ? input.controlledValue : internalValue);
|
|
20
|
+
return {
|
|
21
|
+
get value() {
|
|
22
|
+
return value;
|
|
23
|
+
},
|
|
24
|
+
onchange: (value) => {
|
|
25
|
+
if (isControlled) {
|
|
26
|
+
return onchange?.(value);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
internalValue = value;
|
|
30
|
+
return onchange?.(value);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function useDidElementMove(options) {
|
|
2
|
+
const { enabled, element } = $derived(options);
|
|
3
|
+
let elementPosition = $state({ left: 0, top: 0 });
|
|
4
|
+
$effect(() => {
|
|
5
|
+
if (!element)
|
|
6
|
+
return;
|
|
7
|
+
let DOMRect = element.getBoundingClientRect();
|
|
8
|
+
if (DOMRect)
|
|
9
|
+
elementPosition = DOMRect;
|
|
10
|
+
});
|
|
11
|
+
const value = $derived.by(() => {
|
|
12
|
+
if (element == null)
|
|
13
|
+
return false;
|
|
14
|
+
if (!enabled)
|
|
15
|
+
return false;
|
|
16
|
+
if (element === document.activeElement)
|
|
17
|
+
return false;
|
|
18
|
+
let buttonRect = element.getBoundingClientRect();
|
|
19
|
+
let didElementMove = buttonRect.top !== elementPosition.top || buttonRect.left !== elementPosition.left;
|
|
20
|
+
return didElementMove;
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
get value() {
|
|
24
|
+
return value;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function computeSize(element) {
|
|
2
|
+
if (element === null)
|
|
3
|
+
return { width: 0, height: 0 };
|
|
4
|
+
const { width, height } = element.getBoundingClientRect();
|
|
5
|
+
return { width, height };
|
|
6
|
+
}
|
|
7
|
+
export function useElementSize(options) {
|
|
8
|
+
const { element, unit = false } = $derived(options);
|
|
9
|
+
// When the element changes during a re-render, we want to make sure we
|
|
10
|
+
// compute the correct size as soon as possible. However, once the element is
|
|
11
|
+
// stable, we also want to watch for changes to the element. The `identity`
|
|
12
|
+
// state can be used to recompute the size.
|
|
13
|
+
let size = $state(computeSize(element));
|
|
14
|
+
const observeSize = (element) => {
|
|
15
|
+
if (!element)
|
|
16
|
+
return;
|
|
17
|
+
const observer = new ResizeObserver(() => {
|
|
18
|
+
const { width, height } = computeSize(element);
|
|
19
|
+
if (width !== size.width || height !== size.height)
|
|
20
|
+
size = { width, height };
|
|
21
|
+
});
|
|
22
|
+
observer.observe(element);
|
|
23
|
+
return () => {
|
|
24
|
+
observer.disconnect();
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
$effect(() => observeSize(element));
|
|
28
|
+
return {
|
|
29
|
+
get width() {
|
|
30
|
+
return unit ? `${size.width}px` : size.width;
|
|
31
|
+
},
|
|
32
|
+
get height() {
|
|
33
|
+
return unit ? `${size.height}px` : size.height;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function useFlags(initialFlags?: number): {
|
|
2
|
+
readonly flags: number;
|
|
3
|
+
setFlag: (flag: number) => number;
|
|
4
|
+
addFlag: (flag: number) => number;
|
|
5
|
+
hasFlag: (flag: number) => boolean;
|
|
6
|
+
removeFlag: (flag: number) => number;
|
|
7
|
+
toggleFlag: (flag: number) => number;
|
|
8
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function useFlags(initialFlags = 0) {
|
|
2
|
+
let flags = $state(initialFlags);
|
|
3
|
+
let setFlag = (flag) => (flags = flag);
|
|
4
|
+
let addFlag = (flag) => (flags = flags | flag);
|
|
5
|
+
let hasFlag = (flag) => (flags & flag) === flag;
|
|
6
|
+
let removeFlag = (flag) => (flags = flags & ~flag);
|
|
7
|
+
let toggleFlag = (flag) => (flags = flags ^ flag);
|
|
8
|
+
return {
|
|
9
|
+
get flags() {
|
|
10
|
+
return flags;
|
|
11
|
+
},
|
|
12
|
+
setFlag,
|
|
13
|
+
addFlag,
|
|
14
|
+
hasFlag,
|
|
15
|
+
removeFlag,
|
|
16
|
+
toggleFlag,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isFocusVisible, useFocusVisibleListener } from "../utils/focusVisible.svelte.js";
|
|
2
|
+
export const useFocusRing = (options = {}) => {
|
|
3
|
+
const { autofocus, within } = $derived(options);
|
|
4
|
+
let focused = $state(false);
|
|
5
|
+
let _isFocusVisible = $state(autofocus || isFocusVisible());
|
|
6
|
+
useFocusVisibleListener((isFocusVisible) => {
|
|
7
|
+
_isFocusVisible = isFocusVisible;
|
|
8
|
+
});
|
|
9
|
+
return {
|
|
10
|
+
get isFocusVisible() {
|
|
11
|
+
return _isFocusVisible && focused;
|
|
12
|
+
},
|
|
13
|
+
focusProps: {
|
|
14
|
+
onfocus: () => {
|
|
15
|
+
if (!within)
|
|
16
|
+
focused = true;
|
|
17
|
+
},
|
|
18
|
+
onblur: () => {
|
|
19
|
+
if (!within)
|
|
20
|
+
focused = false;
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface HoverEvent {
|
|
2
|
+
/** The type of hover event being fired. */
|
|
3
|
+
type: "hoverstart" | "hoverend";
|
|
4
|
+
/** The pointer type that triggered the hover event. */
|
|
5
|
+
pointerType: "mouse" | "pen";
|
|
6
|
+
/** The target element of the hover event. */
|
|
7
|
+
target: Element;
|
|
8
|
+
}
|
|
9
|
+
export declare const useHover: (options?: {
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}) => {
|
|
12
|
+
readonly isHovered: boolean;
|
|
13
|
+
hoverProps: {
|
|
14
|
+
onpointerenter: (e: PointerEvent) => void;
|
|
15
|
+
onpointerleave: (e: PointerEvent) => void;
|
|
16
|
+
onmouseenter?: undefined;
|
|
17
|
+
onmouseleave?: undefined;
|
|
18
|
+
ontouchstart?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
onmouseenter: (e: MouseEvent) => void;
|
|
21
|
+
onmouseleave: (e: MouseEvent) => void;
|
|
22
|
+
ontouchstart: () => void;
|
|
23
|
+
onpointerenter?: undefined;
|
|
24
|
+
onpointerleave?: undefined;
|
|
25
|
+
};
|
|
26
|
+
};
|