@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.
Files changed (182) hide show
  1. package/dist/button/Button.svelte +65 -0
  2. package/dist/button/Button.svelte.d.ts +39 -0
  3. package/dist/button/index.d.ts +1 -0
  4. package/dist/button/index.js +1 -0
  5. package/dist/checkbox/Checkbox.svelte +60 -46
  6. package/dist/checkbox/Checkbox.svelte.d.ts +1 -1
  7. package/dist/close-button/CloseButton.svelte +10 -0
  8. package/dist/close-button/CloseButton.svelte.d.ts +25 -0
  9. package/dist/close-button/index.d.ts +1 -0
  10. package/dist/close-button/index.js +1 -0
  11. package/dist/combobox/Combobox.svelte +6 -0
  12. package/dist/combobox/Combobox.svelte.d.ts +50 -0
  13. package/dist/description/Description.svelte +50 -32
  14. package/dist/description/Description.svelte.d.ts +14 -5
  15. package/dist/field/Field.svelte +9 -9
  16. package/dist/fieldset/Fieldset.svelte +9 -9
  17. package/dist/hooks/document-overflow/adjust-scrollbar-padding.d.ts +2 -0
  18. package/dist/hooks/document-overflow/adjust-scrollbar-padding.js +18 -0
  19. package/dist/hooks/document-overflow/handle-ios-locking.d.ts +6 -0
  20. package/dist/hooks/document-overflow/handle-ios-locking.js +134 -0
  21. package/dist/hooks/document-overflow/overflow-store.d.ts +19 -0
  22. package/dist/hooks/document-overflow/overflow-store.js +76 -0
  23. package/dist/hooks/document-overflow/prevent-scroll.d.ts +2 -0
  24. package/dist/hooks/document-overflow/prevent-scroll.js +7 -0
  25. package/dist/hooks/document-overflow/use-document-overflow.svelte.d.ts +7 -0
  26. package/dist/hooks/document-overflow/use-document-overflow.svelte.js +27 -0
  27. package/dist/hooks/use-active-press.svelte.d.ts +14 -0
  28. package/dist/{actions/activePress.svelte.js → hooks/use-active-press.svelte.js} +33 -39
  29. package/dist/hooks/use-by-comparator.d.ts +2 -0
  30. package/dist/hooks/use-by-comparator.js +15 -0
  31. package/dist/hooks/use-controllable.svelte.d.ts +6 -0
  32. package/dist/hooks/use-controllable.svelte.js +34 -0
  33. package/dist/hooks/use-did-element-move.svelte.d.ts +6 -0
  34. package/dist/hooks/use-did-element-move.svelte.js +27 -0
  35. package/dist/hooks/use-disabled.d.ts +3 -0
  36. package/dist/hooks/use-disabled.js +9 -0
  37. package/dist/hooks/use-element-size.svelte.d.ts +7 -0
  38. package/dist/hooks/use-element-size.svelte.js +36 -0
  39. package/dist/hooks/use-flags.svelte.d.ts +8 -0
  40. package/dist/hooks/use-flags.svelte.js +18 -0
  41. package/dist/hooks/use-focus-ring.svelte.d.ts +10 -0
  42. package/dist/hooks/use-focus-ring.svelte.js +24 -0
  43. package/dist/hooks/use-hover.svelte.d.ts +26 -0
  44. package/dist/hooks/use-hover.svelte.js +124 -0
  45. package/dist/hooks/use-id.d.ts +1 -0
  46. package/dist/hooks/use-id.js +1 -0
  47. package/dist/hooks/use-inert-others.svelte.d.ts +32 -0
  48. package/dist/hooks/use-inert-others.svelte.js +114 -0
  49. package/dist/hooks/use-is-top-layer.svelte.d.ts +29 -0
  50. package/dist/hooks/use-is-top-layer.svelte.js +82 -0
  51. package/dist/hooks/use-on-disappear.svelte.d.ts +12 -0
  52. package/dist/hooks/use-on-disappear.svelte.js +38 -0
  53. package/dist/hooks/use-outside-click.svelte.d.ts +10 -0
  54. package/dist/hooks/use-outside-click.svelte.js +150 -0
  55. package/dist/hooks/use-reducer.d.ts +4 -0
  56. package/dist/hooks/use-reducer.js +11 -0
  57. package/dist/hooks/use-resolve-button-type.svelte.d.ts +10 -0
  58. package/dist/hooks/use-resolve-button-type.svelte.js +19 -0
  59. package/dist/hooks/use-scroll-lock.svelte.d.ts +5 -0
  60. package/dist/hooks/use-scroll-lock.svelte.js +24 -0
  61. package/dist/hooks/use-sync-refs.d.ts +7 -0
  62. package/dist/hooks/use-sync-refs.js +22 -0
  63. package/dist/hooks/use-text-value.svelte.d.ts +3 -0
  64. package/dist/hooks/use-text-value.svelte.js +20 -0
  65. package/dist/hooks/use-tracked-pointer.d.ts +4 -0
  66. package/dist/hooks/use-tracked-pointer.js +26 -0
  67. package/dist/hooks/use-transition.svelte.d.ts +20 -0
  68. package/dist/hooks/use-transition.svelte.js +252 -0
  69. package/dist/index.d.ts +4 -0
  70. package/dist/index.js +4 -0
  71. package/dist/internal/FocusSentinel.svelte +45 -0
  72. package/dist/internal/FocusSentinel.svelte.d.ts +17 -0
  73. package/dist/internal/FormFields.svelte +2 -4
  74. package/dist/internal/FormFields.svelte.d.ts +5 -6
  75. package/dist/internal/FormResolver.svelte +11 -16
  76. package/dist/internal/FormResolver.svelte.d.ts +2 -3
  77. package/dist/internal/Hidden.svelte +8 -8
  78. package/dist/internal/Hidden.svelte.d.ts +28 -19
  79. package/dist/internal/HoistFormFields.svelte.d.ts +1 -1
  80. package/dist/internal/Portal.svelte.d.ts +1 -1
  81. package/dist/internal/floating.svelte.d.ts +57 -0
  82. package/dist/internal/floating.svelte.js +477 -0
  83. package/dist/internal/frozen.svelte.d.ts +6 -0
  84. package/dist/internal/frozen.svelte.js +18 -0
  85. package/dist/internal/id.d.ts +8 -0
  86. package/dist/internal/id.js +11 -0
  87. package/dist/internal/open-closed.d.ts +14 -0
  88. package/dist/internal/open-closed.js +17 -0
  89. package/dist/internal/portal-force-root.svelte.d.ts +6 -0
  90. package/dist/internal/portal-force-root.svelte.js +11 -0
  91. package/dist/label/Label.svelte +53 -32
  92. package/dist/label/Label.svelte.d.ts +14 -5
  93. package/dist/legend/Legend.svelte.d.ts +1 -2
  94. package/dist/listbox/Listbox.svelte +451 -0
  95. package/dist/listbox/Listbox.svelte.d.ts +107 -0
  96. package/dist/listbox/ListboxButton.svelte +141 -0
  97. package/dist/listbox/ListboxButton.svelte.d.ts +41 -0
  98. package/dist/listbox/ListboxOption.svelte +138 -0
  99. package/dist/listbox/ListboxOption.svelte.d.ts +39 -0
  100. package/dist/listbox/ListboxOptions.svelte +267 -0
  101. package/dist/listbox/ListboxOptions.svelte.d.ts +39 -0
  102. package/dist/listbox/ListboxSelectedOption.svelte +25 -0
  103. package/dist/listbox/ListboxSelectedOption.svelte.d.ts +30 -0
  104. package/dist/listbox/index.d.ts +5 -0
  105. package/dist/listbox/index.js +5 -0
  106. package/dist/portal/InternalPortal.svelte +108 -0
  107. package/dist/portal/InternalPortal.svelte.d.ts +34 -0
  108. package/dist/portal/Portal.svelte +11 -0
  109. package/dist/portal/Portal.svelte.d.ts +23 -0
  110. package/dist/portal/PortalGroup.svelte +15 -0
  111. package/dist/portal/PortalGroup.svelte.d.ts +31 -0
  112. package/dist/switch/Switch.svelte +149 -0
  113. package/dist/switch/Switch.svelte.d.ts +44 -0
  114. package/dist/switch/SwitchGroup.svelte +38 -0
  115. package/dist/switch/SwitchGroup.svelte.d.ts +27 -0
  116. package/dist/switch/index.d.ts +2 -0
  117. package/dist/switch/index.js +2 -0
  118. package/dist/tabs/Button.svelte +65 -0
  119. package/dist/tabs/Button.svelte.d.ts +39 -0
  120. package/dist/tabs/Tab.svelte +161 -0
  121. package/dist/tabs/Tab.svelte.d.ts +36 -0
  122. package/dist/tabs/TabGroup.svelte +244 -0
  123. package/dist/tabs/TabGroup.svelte.d.ts +54 -0
  124. package/dist/tabs/TabList.svelte +18 -0
  125. package/dist/tabs/TabList.svelte.d.ts +28 -0
  126. package/dist/tabs/TabPanel.svelte +63 -0
  127. package/dist/tabs/TabPanel.svelte.d.ts +34 -0
  128. package/dist/tabs/TabPanels.svelte +13 -0
  129. package/dist/tabs/TabPanels.svelte.d.ts +27 -0
  130. package/dist/tabs/index.d.ts +5 -0
  131. package/dist/tabs/index.js +5 -0
  132. package/dist/test-utils/accessability-assertions.d.ts +271 -0
  133. package/dist/test-utils/accessability-assertions.js +1572 -0
  134. package/dist/test-utils/fake-pointer.d.ts +24 -0
  135. package/dist/test-utils/fake-pointer.js +48 -0
  136. package/dist/test-utils/interactions.d.ts +61 -0
  137. package/dist/test-utils/interactions.js +453 -0
  138. package/dist/test-utils/suppress-console-logs.d.ts +7 -0
  139. package/dist/test-utils/suppress-console-logs.js +17 -0
  140. package/dist/utils/StableCollection.svelte +43 -0
  141. package/dist/utils/StableCollection.svelte.d.ts +19 -0
  142. package/dist/utils/calculate-active-index.d.ts +25 -0
  143. package/dist/utils/calculate-active-index.js +74 -0
  144. package/dist/utils/close.d.ts +2 -0
  145. package/dist/utils/close.js +3 -0
  146. package/dist/utils/default-map.d.ts +5 -0
  147. package/dist/utils/default-map.js +15 -0
  148. package/dist/utils/disposables.d.ts +14 -12
  149. package/dist/utils/disposables.js +13 -10
  150. package/dist/utils/dom.d.ts +0 -2
  151. package/dist/utils/dom.js +2 -4
  152. package/dist/utils/env.d.ts +17 -0
  153. package/dist/utils/env.js +39 -0
  154. package/dist/utils/focus-management.d.ts +44 -0
  155. package/dist/utils/focus-management.js +242 -0
  156. package/dist/utils/focusVisible.svelte.d.ts +3 -3
  157. package/dist/utils/focusVisible.svelte.js +52 -41
  158. package/dist/utils/get-text-value.d.ts +1 -0
  159. package/dist/utils/get-text-value.js +71 -0
  160. package/dist/utils/id.d.ts +1 -1
  161. package/dist/utils/match.d.ts +1 -0
  162. package/dist/utils/match.js +13 -0
  163. package/dist/utils/once.d.ts +1 -0
  164. package/dist/utils/once.js +9 -0
  165. package/dist/utils/owner.d.ts +1 -0
  166. package/dist/utils/owner.js +8 -0
  167. package/dist/utils/platform.d.ts +2 -0
  168. package/dist/utils/platform.js +17 -0
  169. package/dist/utils/ref.svelte.d.ts +4 -0
  170. package/dist/utils/ref.svelte.js +4 -0
  171. package/dist/utils/render.d.ts +31 -0
  172. package/dist/utils/render.js +56 -0
  173. package/dist/utils/store.d.ts +11 -0
  174. package/dist/utils/store.js +20 -0
  175. package/dist/utils/types.d.ts +27 -0
  176. package/dist/utils/types.js +6 -0
  177. package/package.json +28 -21
  178. package/dist/actions/activePress.svelte.d.ts +0 -8
  179. package/dist/actions/focusRing.svelte.d.ts +0 -9
  180. package/dist/actions/focusRing.svelte.js +0 -34
  181. package/dist/utils/disabled.d.ts +0 -3
  182. 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,2 @@
1
+ import type { ScrollLockStep } from "./overflow-store.js";
2
+ export declare function preventScroll(): ScrollLockStep;
@@ -0,0 +1,7 @@
1
+ export function preventScroll() {
2
+ return {
3
+ before({ doc, d }) {
4
+ d.style(doc.documentElement, "overflow", "hidden");
5
+ },
6
+ };
7
+ }
@@ -0,0 +1,7 @@
1
+ export declare function useDocumentOverflowLockedEffect(options: {
2
+ shouldBeLocked: boolean;
3
+ doc: Document | null;
4
+ meta?: (meta: Record<string, any>) => Record<string, any>;
5
+ }): {
6
+ readonly locked: boolean;
7
+ };
@@ -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/dom.js";
2
+ import { getOwnerDocument } from "../utils/owner.js";
3
3
  function pointerRectFromPointerEvent(event) {
4
4
  // Center of the pointer geometry
5
- let offsetX = event.width / 2;
6
- let offsetY = event.height / 2;
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 createActivePress = ({ disabled }) => {
26
+ export const useActivePress = (options) => {
27
+ const { disabled } = $derived(options);
27
28
  let currentTarget = $state(null);
28
29
  let pressed = $state(false);
29
- const activePressAction = (node) => {
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
- let owner = getOwnerDocument(event.currentTarget);
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
- let pointerRect = pointerRectFromPointerEvent(event);
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,2 @@
1
+ export type ByComparator<T> = (NonNullable<T> extends never ? string : keyof NonNullable<T> & string) | ((a: T, z: T) => boolean);
2
+ export declare function useByComparator<T>(by?: ByComparator<T>): (a: T, z: T) => boolean;
@@ -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,6 @@
1
+ export declare function useControllable<T>(input: {
2
+ controlledValue: T | undefined;
3
+ }, onchange?: (value: T) => void, defaultValue?: T): {
4
+ readonly value: NonNullable<T>;
5
+ onchange: (value: T) => void | undefined;
6
+ };
@@ -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,6 @@
1
+ export declare function useDidElementMove(options: {
2
+ enabled: boolean;
3
+ element: HTMLElement | null;
4
+ }): {
5
+ readonly value: boolean;
6
+ };
@@ -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,3 @@
1
+ export declare const useDisabled: () => {
2
+ readonly value: boolean;
3
+ };
@@ -0,0 +1,9 @@
1
+ import { getContext } from "svelte";
2
+ export const useDisabled = () => {
3
+ const context = getContext("DisabledContext");
4
+ return {
5
+ get value() {
6
+ return context?.value ?? false;
7
+ },
8
+ };
9
+ };
@@ -0,0 +1,7 @@
1
+ export declare function useElementSize(options: {
2
+ element: HTMLElement | null;
3
+ unit: boolean;
4
+ }): {
5
+ readonly width: string | number;
6
+ readonly height: string | number;
7
+ };
@@ -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,10 @@
1
+ export declare const useFocusRing: (options?: {
2
+ autofocus?: boolean;
3
+ within?: boolean;
4
+ }) => {
5
+ readonly isFocusVisible: boolean;
6
+ focusProps: {
7
+ onfocus: () => void;
8
+ onblur: () => void;
9
+ };
10
+ };
@@ -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
+ };