@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.
Files changed (275) hide show
  1. package/dist/button/Button.svelte +61 -0
  2. package/dist/button/Button.svelte.d.ts +47 -0
  3. package/dist/button/index.d.ts +1 -0
  4. package/dist/button/index.js +1 -0
  5. package/dist/checkbox/Checkbox.svelte +93 -61
  6. package/dist/checkbox/Checkbox.svelte.d.ts +45 -29
  7. package/dist/close-button/CloseButton.svelte +11 -0
  8. package/dist/close-button/CloseButton.svelte.d.ts +48 -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/data-interactive/DataInteractive.svelte +41 -0
  14. package/dist/data-interactive/DataInteractive.svelte.d.ts +39 -0
  15. package/dist/data-interactive/index.d.ts +1 -0
  16. package/dist/data-interactive/index.js +1 -0
  17. package/dist/description/Description.svelte +16 -41
  18. package/dist/description/Description.svelte.d.ts +15 -23
  19. package/dist/description/context.svelte.d.ts +17 -0
  20. package/dist/description/context.svelte.js +51 -0
  21. package/dist/dialog/Dialog.svelte +51 -0
  22. package/dist/dialog/Dialog.svelte.d.ts +60 -0
  23. package/dist/dialog/DialogBackdrop.svelte +39 -0
  24. package/dist/dialog/DialogBackdrop.svelte.d.ts +38 -0
  25. package/dist/dialog/DialogPanel.svelte +46 -0
  26. package/dist/dialog/DialogPanel.svelte.d.ts +40 -0
  27. package/dist/dialog/DialogTitle.svelte +29 -0
  28. package/dist/dialog/DialogTitle.svelte.d.ts +34 -0
  29. package/dist/dialog/InternalDialog.svelte +233 -0
  30. package/dist/dialog/InternalDialog.svelte.d.ts +42 -0
  31. package/dist/dialog/context.svelte.d.ts +15 -0
  32. package/dist/dialog/context.svelte.js +16 -0
  33. package/dist/dialog/index.d.ts +4 -0
  34. package/dist/dialog/index.js +4 -0
  35. package/dist/field/Field.svelte +14 -16
  36. package/dist/field/Field.svelte.d.ts +21 -17
  37. package/dist/fieldset/Fieldset.svelte +19 -17
  38. package/dist/fieldset/Fieldset.svelte.d.ts +21 -17
  39. package/dist/focus-trap/FocusTrap.svelte +332 -0
  40. package/dist/focus-trap/FocusTrap.svelte.d.ts +58 -0
  41. package/dist/hooks/document-overflow/adjust-scrollbar-padding.d.ts +2 -0
  42. package/dist/hooks/document-overflow/adjust-scrollbar-padding.js +18 -0
  43. package/dist/hooks/document-overflow/handle-ios-locking.d.ts +6 -0
  44. package/dist/hooks/document-overflow/handle-ios-locking.js +134 -0
  45. package/dist/hooks/document-overflow/overflow-store.d.ts +19 -0
  46. package/dist/hooks/document-overflow/overflow-store.js +76 -0
  47. package/dist/hooks/document-overflow/prevent-scroll.d.ts +2 -0
  48. package/dist/hooks/document-overflow/prevent-scroll.js +7 -0
  49. package/dist/hooks/document-overflow/use-document-overflow.svelte.d.ts +7 -0
  50. package/dist/hooks/document-overflow/use-document-overflow.svelte.js +27 -0
  51. package/dist/hooks/use-active-press.svelte.d.ts +14 -0
  52. package/dist/{actions/activePress.svelte.js → hooks/use-active-press.svelte.js} +33 -39
  53. package/dist/hooks/use-by-comparator.d.ts +2 -0
  54. package/dist/hooks/use-by-comparator.js +15 -0
  55. package/dist/hooks/use-controllable.svelte.d.ts +6 -0
  56. package/dist/hooks/use-controllable.svelte.js +34 -0
  57. package/dist/hooks/use-did-element-move.svelte.d.ts +6 -0
  58. package/dist/hooks/use-did-element-move.svelte.js +27 -0
  59. package/dist/hooks/use-disabled.d.ts +3 -0
  60. package/dist/hooks/use-disabled.js +9 -0
  61. package/dist/hooks/use-element-size.svelte.d.ts +7 -0
  62. package/dist/hooks/use-element-size.svelte.js +36 -0
  63. package/dist/hooks/use-escape.svelte.d.ts +5 -0
  64. package/dist/hooks/use-escape.svelte.js +26 -0
  65. package/dist/hooks/use-event-listener.svelte.d.ts +6 -0
  66. package/dist/hooks/use-event-listener.svelte.js +12 -0
  67. package/dist/hooks/use-flags.svelte.d.ts +8 -0
  68. package/dist/hooks/use-flags.svelte.js +18 -0
  69. package/dist/hooks/use-focus-ring.svelte.d.ts +10 -0
  70. package/dist/hooks/use-focus-ring.svelte.js +24 -0
  71. package/dist/hooks/use-hover.svelte.d.ts +26 -0
  72. package/dist/hooks/use-hover.svelte.js +124 -0
  73. package/dist/hooks/use-id.d.ts +1 -0
  74. package/dist/hooks/use-id.js +1 -0
  75. package/dist/hooks/use-inert-others.svelte.d.ts +32 -0
  76. package/dist/hooks/use-inert-others.svelte.js +114 -0
  77. package/dist/hooks/use-is-mounted.svelte.d.ts +3 -0
  78. package/dist/hooks/use-is-mounted.svelte.js +14 -0
  79. package/dist/hooks/use-is-top-layer.svelte.d.ts +29 -0
  80. package/dist/hooks/use-is-top-layer.svelte.js +82 -0
  81. package/dist/hooks/use-is-touch-device.svelte.d.ts +3 -0
  82. package/dist/hooks/use-is-touch-device.svelte.js +20 -0
  83. package/dist/hooks/use-on-disappear.svelte.d.ts +12 -0
  84. package/dist/hooks/use-on-disappear.svelte.js +38 -0
  85. package/dist/hooks/use-outside-click.svelte.d.ts +10 -0
  86. package/dist/hooks/use-outside-click.svelte.js +150 -0
  87. package/dist/hooks/use-reducer.d.ts +4 -0
  88. package/dist/hooks/use-reducer.js +11 -0
  89. package/dist/hooks/use-resolve-button-type.svelte.d.ts +10 -0
  90. package/dist/hooks/use-resolve-button-type.svelte.js +19 -0
  91. package/dist/hooks/use-root-containers.svelte.d.ts +9 -0
  92. package/dist/hooks/use-root-containers.svelte.js +50 -0
  93. package/dist/hooks/use-scroll-lock.svelte.d.ts +5 -0
  94. package/dist/hooks/use-scroll-lock.svelte.js +24 -0
  95. package/dist/hooks/use-sync-refs.d.ts +7 -0
  96. package/dist/hooks/use-sync-refs.js +22 -0
  97. package/dist/hooks/use-tab-direction.svelte.d.ts +7 -0
  98. package/dist/hooks/use-tab-direction.svelte.js +25 -0
  99. package/dist/hooks/use-text-value.svelte.d.ts +3 -0
  100. package/dist/hooks/use-text-value.svelte.js +20 -0
  101. package/dist/hooks/use-tracked-pointer.d.ts +4 -0
  102. package/dist/hooks/use-tracked-pointer.js +26 -0
  103. package/dist/hooks/use-transition.svelte.d.ts +20 -0
  104. package/dist/hooks/use-transition.svelte.js +253 -0
  105. package/dist/hooks/use-tree-walker.svelte.d.ts +8 -0
  106. package/dist/hooks/use-tree-walker.svelte.js +19 -0
  107. package/dist/hooks/use-watch.svelte.d.ts +4 -0
  108. package/dist/hooks/use-watch.svelte.js +16 -0
  109. package/dist/hooks/use-window-event.svelte.d.ts +6 -0
  110. package/dist/hooks/use-window-event.svelte.js +12 -0
  111. package/dist/index.d.ts +13 -0
  112. package/dist/index.js +13 -0
  113. package/dist/input/Input.svelte +59 -0
  114. package/dist/input/Input.svelte.d.ts +52 -0
  115. package/dist/input/index.d.ts +1 -0
  116. package/dist/input/index.js +1 -0
  117. package/dist/internal/FocusSentinel.svelte +45 -0
  118. package/dist/internal/FocusSentinel.svelte.d.ts +20 -0
  119. package/dist/internal/ForcePortalRoot.svelte +6 -0
  120. package/dist/internal/ForcePortalRoot.svelte.d.ts +22 -0
  121. package/dist/internal/FormFields.svelte +2 -4
  122. package/dist/internal/FormFields.svelte.d.ts +9 -7
  123. package/dist/internal/FormResolver.svelte +11 -16
  124. package/dist/internal/FormResolver.svelte.d.ts +6 -4
  125. package/dist/internal/Hidden.svelte +5 -9
  126. package/dist/internal/Hidden.svelte.d.ts +35 -19
  127. package/dist/internal/HoistFormFields.svelte.d.ts +5 -2
  128. package/dist/internal/MainTreeProvider.svelte +45 -0
  129. package/dist/internal/MainTreeProvider.svelte.d.ts +31 -0
  130. package/dist/internal/Portal.svelte.d.ts +5 -2
  131. package/dist/internal/close-provider.d.ts +7 -0
  132. package/dist/internal/close-provider.js +7 -0
  133. package/dist/internal/floating.svelte.d.ts +62 -0
  134. package/dist/internal/floating.svelte.js +488 -0
  135. package/dist/internal/frozen.svelte.d.ts +6 -0
  136. package/dist/internal/frozen.svelte.js +18 -0
  137. package/dist/internal/id.d.ts +8 -0
  138. package/dist/internal/id.js +11 -0
  139. package/dist/internal/open-closed.d.ts +14 -0
  140. package/dist/internal/open-closed.js +17 -0
  141. package/dist/internal/portal-force-root.svelte.d.ts +6 -0
  142. package/dist/internal/portal-force-root.svelte.js +11 -0
  143. package/dist/label/Label.svelte +17 -45
  144. package/dist/label/Label.svelte.d.ts +19 -23
  145. package/dist/label/context.svelte.d.ts +17 -0
  146. package/dist/label/context.svelte.js +56 -0
  147. package/dist/legend/Legend.svelte +4 -3
  148. package/dist/legend/Legend.svelte.d.ts +33 -16
  149. package/dist/listbox/Listbox.svelte +448 -0
  150. package/dist/listbox/Listbox.svelte.d.ts +126 -0
  151. package/dist/listbox/ListboxButton.svelte +139 -0
  152. package/dist/listbox/ListboxButton.svelte.d.ts +52 -0
  153. package/dist/listbox/ListboxOption.svelte +136 -0
  154. package/dist/listbox/ListboxOption.svelte.d.ts +50 -0
  155. package/dist/listbox/ListboxOptions.svelte +269 -0
  156. package/dist/listbox/ListboxOptions.svelte.d.ts +55 -0
  157. package/dist/listbox/ListboxSelectedOption.svelte +35 -0
  158. package/dist/listbox/ListboxSelectedOption.svelte.d.ts +40 -0
  159. package/dist/listbox/index.d.ts +5 -0
  160. package/dist/listbox/index.js +5 -0
  161. package/dist/menu/Menu.svelte +235 -0
  162. package/dist/menu/Menu.svelte.d.ts +42 -0
  163. package/dist/menu/MenuButton.svelte +127 -0
  164. package/dist/menu/MenuButton.svelte.d.ts +52 -0
  165. package/dist/menu/MenuHeading.svelte +19 -0
  166. package/dist/menu/MenuHeading.svelte.d.ts +39 -0
  167. package/dist/menu/MenuItem.svelte +114 -0
  168. package/dist/menu/MenuItem.svelte.d.ts +49 -0
  169. package/dist/menu/MenuItems.svelte +244 -0
  170. package/dist/menu/MenuItems.svelte.d.ts +55 -0
  171. package/dist/menu/MenuSection.svelte +14 -0
  172. package/dist/menu/MenuSection.svelte.d.ts +35 -0
  173. package/dist/menu/MenuSeparator.svelte +9 -0
  174. package/dist/menu/MenuSeparator.svelte.d.ts +35 -0
  175. package/dist/menu/context.svelte.d.ts +47 -0
  176. package/dist/menu/context.svelte.js +21 -0
  177. package/dist/menu/index.d.ts +7 -0
  178. package/dist/menu/index.js +7 -0
  179. package/dist/portal/InternalPortal.svelte +97 -0
  180. package/dist/portal/InternalPortal.svelte.d.ts +43 -0
  181. package/dist/portal/Portal.svelte +7 -0
  182. package/dist/portal/Portal.svelte.d.ts +23 -0
  183. package/dist/portal/PortalGroup.svelte +14 -0
  184. package/dist/portal/PortalGroup.svelte.d.ts +40 -0
  185. package/dist/switch/Switch.svelte +143 -0
  186. package/dist/switch/Switch.svelte.d.ts +61 -0
  187. package/dist/switch/SwitchGroup.svelte +37 -0
  188. package/dist/switch/SwitchGroup.svelte.d.ts +34 -0
  189. package/dist/switch/index.d.ts +2 -0
  190. package/dist/switch/index.js +2 -0
  191. package/dist/tabs/Tab.svelte +156 -0
  192. package/dist/tabs/Tab.svelte.d.ts +48 -0
  193. package/dist/tabs/TabGroup.svelte +241 -0
  194. package/dist/tabs/TabGroup.svelte.d.ts +67 -0
  195. package/dist/tabs/TabList.svelte +16 -0
  196. package/dist/tabs/TabList.svelte.d.ts +35 -0
  197. package/dist/tabs/TabPanel.svelte +61 -0
  198. package/dist/tabs/TabPanel.svelte.d.ts +47 -0
  199. package/dist/tabs/TabPanels.svelte +12 -0
  200. package/dist/tabs/TabPanels.svelte.d.ts +34 -0
  201. package/dist/tabs/index.d.ts +5 -0
  202. package/dist/tabs/index.js +5 -0
  203. package/dist/test-utils/accessability-assertions.d.ts +271 -0
  204. package/dist/test-utils/accessability-assertions.js +1572 -0
  205. package/dist/test-utils/fake-pointer.d.ts +24 -0
  206. package/dist/test-utils/fake-pointer.js +48 -0
  207. package/dist/test-utils/interactions.d.ts +61 -0
  208. package/dist/test-utils/interactions.js +453 -0
  209. package/dist/test-utils/suppress-console-logs.d.ts +7 -0
  210. package/dist/test-utils/suppress-console-logs.js +17 -0
  211. package/dist/transition/InternalTransitionChild.svelte +178 -0
  212. package/dist/transition/InternalTransitionChild.svelte.d.ts +55 -0
  213. package/dist/transition/Transition.svelte +89 -0
  214. package/dist/transition/Transition.svelte.d.ts +42 -0
  215. package/dist/transition/TransitionChild.svelte +16 -0
  216. package/dist/transition/TransitionChild.svelte.d.ts +44 -0
  217. package/dist/transition/context.svelte.d.ts +64 -0
  218. package/dist/transition/context.svelte.js +120 -0
  219. package/dist/transition/index.d.ts +2 -0
  220. package/dist/transition/index.js +2 -0
  221. package/dist/utils/ElementOrComponent.svelte +26 -0
  222. package/dist/utils/ElementOrComponent.svelte.d.ts +56 -0
  223. package/dist/utils/Generic.svelte +44 -0
  224. package/dist/utils/Generic.svelte.d.ts +35 -0
  225. package/dist/utils/StableCollection.svelte +43 -0
  226. package/dist/utils/StableCollection.svelte.d.ts +22 -0
  227. package/dist/utils/active-element-history.d.ts +1 -0
  228. package/dist/utils/active-element-history.js +35 -0
  229. package/dist/utils/alternative-types.d.ts +21 -0
  230. package/dist/utils/alternative-types.js +1 -0
  231. package/dist/utils/calculate-active-index.d.ts +25 -0
  232. package/dist/utils/calculate-active-index.js +74 -0
  233. package/dist/utils/class-names.d.ts +1 -0
  234. package/dist/utils/class-names.js +10 -0
  235. package/dist/utils/default-map.d.ts +5 -0
  236. package/dist/utils/default-map.js +15 -0
  237. package/dist/utils/disposables.d.ts +14 -12
  238. package/dist/utils/disposables.js +13 -10
  239. package/dist/utils/dom.d.ts +0 -2
  240. package/dist/utils/dom.js +2 -4
  241. package/dist/utils/env.d.ts +17 -0
  242. package/dist/utils/env.js +39 -0
  243. package/dist/utils/focus-management.d.ts +45 -0
  244. package/dist/utils/focus-management.js +242 -0
  245. package/dist/utils/focusVisible.svelte.d.ts +3 -3
  246. package/dist/utils/focusVisible.svelte.js +52 -41
  247. package/dist/utils/get-text-value.d.ts +1 -0
  248. package/dist/utils/get-text-value.js +71 -0
  249. package/dist/utils/id.d.ts +1 -1
  250. package/dist/utils/match.d.ts +1 -0
  251. package/dist/utils/match.js +13 -0
  252. package/dist/utils/on-document-ready.d.ts +1 -0
  253. package/dist/utils/on-document-ready.js +12 -0
  254. package/dist/utils/once.d.ts +1 -0
  255. package/dist/utils/once.js +9 -0
  256. package/dist/utils/owner.d.ts +1 -0
  257. package/dist/utils/owner.js +8 -0
  258. package/dist/utils/platform.d.ts +2 -0
  259. package/dist/utils/platform.js +17 -0
  260. package/dist/utils/ref.svelte.d.ts +4 -0
  261. package/dist/utils/ref.svelte.js +4 -0
  262. package/dist/utils/render.d.ts +34 -0
  263. package/dist/utils/render.js +119 -0
  264. package/dist/utils/state.d.ts +7 -1
  265. package/dist/utils/state.js +10 -6
  266. package/dist/utils/store.d.ts +11 -0
  267. package/dist/utils/store.js +20 -0
  268. package/dist/utils/types.d.ts +24 -0
  269. package/dist/utils/types.js +1 -0
  270. package/package.json +33 -25
  271. package/dist/actions/activePress.svelte.d.ts +0 -8
  272. package/dist/actions/focusRing.svelte.d.ts +0 -9
  273. package/dist/actions/focusRing.svelte.js +0 -34
  274. package/dist/utils/disabled.d.ts +0 -3
  275. package/dist/utils/disabled.js +0 -2
@@ -0,0 +1,114 @@
1
+ import { disposables } from "../utils/disposables.js";
2
+ import { getOwnerDocument } from "../utils/owner.js";
3
+ import { useIsTopLayer } from "./use-is-top-layer.svelte.js";
4
+ const originals = new Map();
5
+ const counts = new Map();
6
+ function markInert(element) {
7
+ // Increase count
8
+ let count = counts.get(element) ?? 0;
9
+ counts.set(element, count + 1);
10
+ // Already marked as inert, no need to do it again
11
+ if (count !== 0)
12
+ return () => markNotInert(element);
13
+ // Keep track of previous values, so that we can restore them when we are done
14
+ originals.set(element, {
15
+ "aria-hidden": element.getAttribute("aria-hidden"),
16
+ inert: element.inert,
17
+ });
18
+ // Mark as inert
19
+ element.setAttribute("aria-hidden", "true");
20
+ element.inert = true;
21
+ return () => markNotInert(element);
22
+ }
23
+ function markNotInert(element) {
24
+ // Decrease counts
25
+ let count = counts.get(element) ?? 1; // Should always exist
26
+ if (count === 1)
27
+ counts.delete(element); // We are the last one, so we can delete the count
28
+ else
29
+ counts.set(element, count - 1); // We are not the last one
30
+ // Not the last one, so we don't restore the original values (yet)
31
+ if (count !== 1)
32
+ return;
33
+ let original = originals.get(element);
34
+ if (!original)
35
+ return; // Should never happen
36
+ // Restore original values
37
+ if (original["aria-hidden"] === null)
38
+ element.removeAttribute("aria-hidden");
39
+ else
40
+ element.setAttribute("aria-hidden", original["aria-hidden"]);
41
+ element.inert = original.inert;
42
+ // Remove tracking of original values
43
+ originals.delete(element);
44
+ }
45
+ /**
46
+ * Mark all elements on the page as inert, except for the ones that are allowed.
47
+ *
48
+ * We move up the tree from the allowed elements, and mark all their siblings as
49
+ * inert. If any of the children happens to be a parent of one of the elements,
50
+ * then that child will not be marked as inert.
51
+ *
52
+ * E.g.:
53
+ *
54
+ * ```html
55
+ * <body> <!-- Stop at body -->
56
+ * <header></header> <!-- Inert, sibling of parent -->
57
+ * <main> <!-- Not inert, parent of allowed element -->
58
+ * <div>Sidebar</div> <!-- Inert, sibling of parent -->
59
+ * <div> <!-- Not inert, parent of allowed element -->
60
+ * <listbox> <!-- Not inert, parent of allowed element -->
61
+ * <button></button> <!-- Not inert, allowed element -->
62
+ * <options></options> <!-- Not inert, allowed element -->
63
+ * </listbox>
64
+ * </div>
65
+ * </main>
66
+ * <footer></footer> <!-- Inert, sibling of parent -->
67
+ * </body>
68
+ * ```
69
+ */
70
+ export function useInertOthers(options) {
71
+ const { enabled, elements } = $derived(options);
72
+ const { allowed, disallowed } = $derived(elements ?? {});
73
+ let isTopLayer = useIsTopLayer({
74
+ get enabled() {
75
+ return enabled;
76
+ },
77
+ scope: "inert-others",
78
+ });
79
+ $effect(() => {
80
+ if (!isTopLayer.value)
81
+ return;
82
+ let d = disposables();
83
+ // Mark all disallowed elements as inert
84
+ for (let element of disallowed ?? []) {
85
+ if (!element)
86
+ continue;
87
+ d.add(markInert(element));
88
+ }
89
+ // Mark all siblings of allowed elements (and parents) as inert
90
+ let allowedElements = allowed ?? [];
91
+ for (let element of allowedElements) {
92
+ if (!element)
93
+ continue;
94
+ let ownerDocument = getOwnerDocument(element);
95
+ if (!ownerDocument)
96
+ continue;
97
+ let parent = element.parentElement;
98
+ while (parent && parent !== ownerDocument.body) {
99
+ // Mark all siblings as inert
100
+ for (let node of parent.children) {
101
+ // If the node contains any of the elements we should not mark it as inert
102
+ // because it would make the elements unreachable.
103
+ if (node.tagName.toLowerCase() === "svelte:fragment" || allowedElements.some((el) => node.contains(el)))
104
+ continue;
105
+ // Mark the node as inert
106
+ d.add(markInert(node));
107
+ }
108
+ // Move up the tree
109
+ parent = parent.parentElement;
110
+ }
111
+ }
112
+ return d.dispose;
113
+ });
114
+ }
@@ -0,0 +1,3 @@
1
+ export declare function useIsMounted(): {
2
+ readonly current: boolean;
3
+ };
@@ -0,0 +1,14 @@
1
+ export function useIsMounted() {
2
+ let mounted = $state(false);
3
+ $effect(() => {
4
+ mounted = true;
5
+ return () => {
6
+ mounted = false;
7
+ };
8
+ });
9
+ return {
10
+ get current() {
11
+ return mounted;
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A hook that returns whether the current node is on the top of the hierarchy,
3
+ * aka "top layer". Note: this does not use the native DOM "top-layer" but
4
+ * conceptually it's the same thing.
5
+ *
6
+ * The hierarchy is also shared across multiple components that use the same
7
+ * scope.
8
+ *
9
+ * This is useful to use in components and hooks that mutate the DOM or share
10
+ * some global state.
11
+ *
12
+ * A use case for this is to use this inside of a `useOutsideClick` hook where
13
+ * only the last rendered component should handle the outside click event.
14
+ *
15
+ * ```ts
16
+ * <Dialog>
17
+ * <Menu>
18
+ * <MenuButton></MenuButton> // Pressing escape on an open `Menu` should close the `Menu` and not the `Dialog`.
19
+ * // …
20
+ * </Menu>
21
+ * </Dialog>
22
+ * ```
23
+ */
24
+ export declare function useIsTopLayer(options: {
25
+ readonly enabled: boolean;
26
+ readonly scope: string;
27
+ }): {
28
+ value: boolean;
29
+ };
@@ -0,0 +1,82 @@
1
+ import { DefaultMap } from "../utils/default-map.js";
2
+ import { createStore } from "../utils/store.js";
3
+ import { useId } from "./use-id.js";
4
+ /**
5
+ * Map of stable hierarchy stores based on a given scope.
6
+ */
7
+ let hierarchyStores = new DefaultMap(() => createStore(() => [], {
8
+ ADD(id) {
9
+ if (this.includes(id))
10
+ return this;
11
+ return [...this, id];
12
+ },
13
+ REMOVE(id) {
14
+ let idx = this.indexOf(id);
15
+ if (idx === -1)
16
+ return this;
17
+ let copy = this.slice();
18
+ copy.splice(idx, 1);
19
+ return copy;
20
+ },
21
+ }));
22
+ /**
23
+ * A hook that returns whether the current node is on the top of the hierarchy,
24
+ * aka "top layer". Note: this does not use the native DOM "top-layer" but
25
+ * conceptually it's the same thing.
26
+ *
27
+ * The hierarchy is also shared across multiple components that use the same
28
+ * scope.
29
+ *
30
+ * This is useful to use in components and hooks that mutate the DOM or share
31
+ * some global state.
32
+ *
33
+ * A use case for this is to use this inside of a `useOutsideClick` hook where
34
+ * only the last rendered component should handle the outside click event.
35
+ *
36
+ * ```ts
37
+ * <Dialog>
38
+ * <Menu>
39
+ * <MenuButton></MenuButton> // Pressing escape on an open `Menu` should close the `Menu` and not the `Dialog`.
40
+ * // …
41
+ * </Menu>
42
+ * </Dialog>
43
+ * ```
44
+ */
45
+ export function useIsTopLayer(options) {
46
+ const { enabled, scope } = $derived(options);
47
+ const hierarchyStore = hierarchyStores.get(scope);
48
+ const id = useId();
49
+ let hierarchy = $state(hierarchyStore.getSnapshot());
50
+ $effect(() => {
51
+ const unsubscribe = hierarchyStore.subscribe(() => {
52
+ hierarchy = hierarchyStore.getSnapshot();
53
+ });
54
+ return unsubscribe;
55
+ });
56
+ $effect(() => {
57
+ if (!enabled)
58
+ return;
59
+ hierarchyStore.dispatch("ADD", id);
60
+ return () => hierarchyStore.dispatch("REMOVE", id);
61
+ });
62
+ const value = $derived.by(() => {
63
+ if (!enabled)
64
+ return false;
65
+ let idx = hierarchy.indexOf(id);
66
+ let hierarchyLength = hierarchy.length;
67
+ // Not in the hierarchy yet
68
+ if (idx === -1) {
69
+ // Assume that it will be inserted at the end, then it means that the `idx`
70
+ // will be the length of the current hierarchy.
71
+ idx = hierarchyLength;
72
+ // Increase the hierarchy length as-if the node is already in the hierarchy.
73
+ hierarchyLength += 1;
74
+ }
75
+ return idx === hierarchyLength - 1;
76
+ });
77
+ return {
78
+ get value() {
79
+ return value;
80
+ },
81
+ };
82
+ }
@@ -0,0 +1,3 @@
1
+ export declare function useIsTouchDevice(): {
2
+ readonly value: boolean;
3
+ };
@@ -0,0 +1,20 @@
1
+ export function useIsTouchDevice() {
2
+ const mq = typeof window !== "undefined" && typeof window.matchMedia === "function"
3
+ ? window.matchMedia("(pointer: coarse)")
4
+ : null;
5
+ let isTouchDevice = $state(mq?.matches ?? false);
6
+ $effect(() => {
7
+ if (!mq)
8
+ return;
9
+ function handle(event) {
10
+ isTouchDevice = event.matches;
11
+ }
12
+ mq.addEventListener("change", handle);
13
+ return () => mq.removeEventListener("change", handle);
14
+ });
15
+ return {
16
+ get value() {
17
+ return isTouchDevice;
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A hook to ensure that a callback is called when the element has disappeared
3
+ * from the screen.
4
+ *
5
+ * This can happen if you use Tailwind classes like: `hidden md:block`, once the
6
+ * viewport is smaller than `md` the element will disappear.
7
+ */
8
+ export declare function useOnDisappear(options: {
9
+ enabled: boolean;
10
+ ref: HTMLElement | null | undefined;
11
+ ondisappear: () => void;
12
+ }): void;
@@ -0,0 +1,38 @@
1
+ import { disposables } from "../utils/disposables.js";
2
+ /**
3
+ * A hook to ensure that a callback is called when the element has disappeared
4
+ * from the screen.
5
+ *
6
+ * This can happen if you use Tailwind classes like: `hidden md:block`, once the
7
+ * viewport is smaller than `md` the element will disappear.
8
+ */
9
+ export function useOnDisappear(options) {
10
+ const { enabled, ref, ondisappear } = $derived(options);
11
+ let listenerRef = (element) => {
12
+ let rect = element.getBoundingClientRect();
13
+ if (rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0) {
14
+ ondisappear();
15
+ }
16
+ };
17
+ $effect(() => {
18
+ if (!enabled)
19
+ return;
20
+ const element = ref;
21
+ if (!element)
22
+ return;
23
+ let d = disposables();
24
+ // Try using ResizeObserver
25
+ if (typeof ResizeObserver !== "undefined") {
26
+ let observer = new ResizeObserver(() => listenerRef(element));
27
+ observer.observe(element);
28
+ d.add(() => observer.disconnect());
29
+ }
30
+ // Try using IntersectionObserver
31
+ if (typeof IntersectionObserver !== "undefined") {
32
+ let observer = new IntersectionObserver(() => listenerRef(element));
33
+ observer.observe(element);
34
+ d.add(() => observer.disconnect());
35
+ }
36
+ return () => d.dispose();
37
+ });
38
+ }
@@ -0,0 +1,10 @@
1
+ import type { MutableRefObject } from "../utils/ref.svelte.js";
2
+ type Container = MutableRefObject<HTMLElement | null> | HTMLElement | null;
3
+ type ContainerCollection = Container[] | Set<Container>;
4
+ type ContainerInput = Container | ContainerCollection;
5
+ export declare function useOutsideClick(options: {
6
+ enabled: boolean;
7
+ containers: ContainerInput | (() => ContainerInput);
8
+ cb: (event: MouseEvent | PointerEvent | FocusEvent | TouchEvent, target: HTMLElement) => void;
9
+ }): void;
10
+ export {};
@@ -0,0 +1,150 @@
1
+ import { disposables } from "../utils/disposables.js";
2
+ import { FocusableMode, isFocusableElement } from "../utils/focus-management.js";
3
+ import { isMobile } from "../utils/platform.js";
4
+ import { useIsTopLayer } from "./use-is-top-layer.svelte.js";
5
+ // If the user moves their finger by ${MOVE_THRESHOLD_PX} pixels or more, we'll
6
+ // assume that they are scrolling and not clicking. This will prevent the click
7
+ // from being triggered when the user is scrolling.
8
+ //
9
+ // This also allows you to "cancel" the click by moving your finger more than
10
+ // the threshold in pixels in any direction.
11
+ const MOVE_THRESHOLD_PX = 30;
12
+ export function useOutsideClick(options) {
13
+ const { enabled, containers, cb } = $derived(options);
14
+ const isTopLayer = useIsTopLayer({
15
+ get enabled() {
16
+ return enabled;
17
+ },
18
+ scope: "outside-click",
19
+ });
20
+ const handleOutsideClick = (event, resolveTarget) => {
21
+ // Check whether the event got prevented already. This can happen if you
22
+ // use the useOutsideClick hook in both a Dialog and a Menu and the inner
23
+ // Menu "cancels" the default behavior so that only the Menu closes and
24
+ // not the Dialog (yet)
25
+ if (event.defaultPrevented)
26
+ return;
27
+ let target = resolveTarget(event);
28
+ if (target === null) {
29
+ return;
30
+ }
31
+ // Ignore if the target doesn't exist in the DOM anymore
32
+ if (!target.getRootNode().contains(target))
33
+ return;
34
+ // Ignore if the target was removed from the DOM by the time the handler
35
+ // was called
36
+ if (!target.isConnected)
37
+ return;
38
+ let _containers = (function resolve(containers) {
39
+ if (typeof containers === "function") {
40
+ return resolve(containers());
41
+ }
42
+ if (Array.isArray(containers)) {
43
+ return containers;
44
+ }
45
+ if (containers instanceof Set) {
46
+ return containers;
47
+ }
48
+ return [containers];
49
+ })(containers);
50
+ // Ignore if the target exists in one of the containers
51
+ for (let container of _containers) {
52
+ if (container === null)
53
+ continue;
54
+ let domNode = container instanceof HTMLElement ? container : container.current;
55
+ if (domNode?.contains(target)) {
56
+ return;
57
+ }
58
+ // If the click crossed a shadow boundary, we need to check if the
59
+ // container is inside the tree by using `composedPath` to "pierce" the
60
+ // shadow boundary
61
+ if (event.composed && event.composedPath().includes(domNode)) {
62
+ return;
63
+ }
64
+ }
65
+ // This allows us to check whether the event was defaultPrevented when you
66
+ // are nesting this inside a `<Dialog />` for example.
67
+ if (
68
+ // This check allows us to know whether or not we clicked on a
69
+ // "focusable" element like a button or an input. This is a backwards
70
+ // compatibility check so that you can open a <Menu /> and click on
71
+ // another <Menu /> which should close Menu A and open Menu B. We might
72
+ // revisit that so that you will require 2 clicks instead.
73
+ !isFocusableElement(target, FocusableMode.Loose) &&
74
+ // This could be improved, but the `Combobox.Button` adds tabIndex={-1}
75
+ // to make it unfocusable via the keyboard so that tabbing to the next
76
+ // item from the input doesn't first go to the button.
77
+ target.tabIndex !== -1) {
78
+ event.preventDefault();
79
+ }
80
+ return cb(event, target);
81
+ };
82
+ let initialClickTarget = $state(null);
83
+ const addEventListeners = (enabled) => {
84
+ if (!enabled || typeof document === "undefined" || typeof window === "undefined")
85
+ return;
86
+ const d = disposables();
87
+ d.addEventListener(document, "pointerdown", (event) => {
88
+ initialClickTarget = event.composedPath?.()?.[0] || event.target;
89
+ }, true);
90
+ d.addEventListener(document, "mousedown", (event) => {
91
+ initialClickTarget = event.composedPath?.()?.[0] || event.target;
92
+ }, true);
93
+ d.addEventListener(document, "click", (event) => {
94
+ if (isMobile())
95
+ return;
96
+ if (!initialClickTarget)
97
+ return;
98
+ handleOutsideClick(event, () => {
99
+ return initialClickTarget;
100
+ });
101
+ initialClickTarget = null;
102
+ },
103
+ // We will use the `capture` phase so that layers in between with `event.stopPropagation()`
104
+ // don't "cancel" this outside click check. E.g.: A `Menu` inside a `DialogPanel` if the `Menu`
105
+ // is open, and you click outside of it in the `DialogPanel` the `Menu` should close. However,
106
+ // the `DialogPanel` has a `onClick(e) { e.stopPropagation() }` which would cancel this.
107
+ true);
108
+ let startPosition = $state({ x: 0, y: 0 });
109
+ d.addEventListener(document, "touchstart", (event) => {
110
+ startPosition.x = event.touches[0].clientX;
111
+ startPosition.y = event.touches[0].clientY;
112
+ }, true);
113
+ d.addEventListener(document, "touchend", (event) => {
114
+ // If the user moves their finger by ${MOVE_THRESHOLD_PX} pixels or more,
115
+ // we'll assume that they are scrolling and not clicking.
116
+ let endPosition = { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
117
+ if (Math.abs(endPosition.x - startPosition.x) >= MOVE_THRESHOLD_PX ||
118
+ Math.abs(endPosition.y - startPosition.y) >= MOVE_THRESHOLD_PX) {
119
+ return;
120
+ }
121
+ return handleOutsideClick(event, () => {
122
+ if (event.target instanceof HTMLElement) {
123
+ return event.target;
124
+ }
125
+ return null;
126
+ });
127
+ },
128
+ // We will use the `capture` phase so that layers in between with `event.stopPropagation()`
129
+ // don't "cancel" this outside click check. E.g.: A `Menu` inside a `DialogPanel` if the `Menu`
130
+ // is open, and you click outside of it in the `DialogPanel` the `Menu` should close. However,
131
+ // the `DialogPanel` has a `onClick(e) { e.stopPropagation() }` which would cancel this.
132
+ true);
133
+ // When content inside an iframe is clicked `window` will receive a blur event
134
+ // This can happen when an iframe _inside_ a window is clicked
135
+ // Or, if headless UI is *in* the iframe, when a content in a window containing that iframe is clicked
136
+ // In this case we care only about the first case so we check to see if the active element is the iframe
137
+ // If so this was because of a click, focus, or other interaction with the child iframe
138
+ // and we can consider it an "outside click"
139
+ d.addEventListener(window, "blur", (event) => {
140
+ return handleOutsideClick(event, () => {
141
+ return window.document.activeElement instanceof HTMLIFrameElement ? window.document.activeElement : null;
142
+ });
143
+ }, true);
144
+ return d.dispose;
145
+ };
146
+ $effect(() => {
147
+ initialClickTarget;
148
+ return addEventListeners(isTopLayer.value);
149
+ });
150
+ }
@@ -0,0 +1,4 @@
1
+ export declare const useReducer: <State, Action>(reducer: (state: State, action: Action) => State, initialState: State) => {
2
+ readonly state: State;
3
+ dispatch(action: Action): void;
4
+ };
@@ -0,0 +1,11 @@
1
+ export const useReducer = (reducer, initialState) => {
2
+ let state = $state(initialState);
3
+ return {
4
+ get state() {
5
+ return state;
6
+ },
7
+ dispatch(action) {
8
+ state = reducer(state, action);
9
+ },
10
+ };
11
+ };
@@ -0,0 +1,10 @@
1
+ import type { MutableRefObject } from "../utils/ref.svelte.js";
2
+ export declare function useResolveButtonType<TTag>(options: {
3
+ props: {
4
+ type?: string;
5
+ as?: TTag;
6
+ };
7
+ ref: MutableRefObject<HTMLElement | null | undefined>;
8
+ }): {
9
+ readonly type: string | undefined;
10
+ };
@@ -0,0 +1,19 @@
1
+ import { untrack } from "svelte";
2
+ function resolveType(props) {
3
+ if (props.type)
4
+ return props.type;
5
+ const tag = props.as ?? "button";
6
+ if (typeof tag === "string" && tag.toLowerCase() === "button")
7
+ return "button";
8
+ return undefined;
9
+ }
10
+ export function useResolveButtonType(options) {
11
+ const { props, ref } = $derived(options);
12
+ return {
13
+ get type() {
14
+ return ref.current && ref.current instanceof HTMLButtonElement && !ref.current.hasAttribute("type")
15
+ ? "button"
16
+ : resolveType(props);
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,9 @@
1
+ export { default as MainTreeProvider, useMainTreeNode } from "../internal/MainTreeProvider.svelte";
2
+ export declare function useRootContainers(options?: {
3
+ defaultContainers?: (HTMLElement | null)[];
4
+ portals?: HTMLElement[];
5
+ mainTreeNode?: HTMLElement | null;
6
+ }): {
7
+ readonly resolvedContainers: HTMLElement[];
8
+ contains: (element: HTMLElement) => boolean;
9
+ };
@@ -0,0 +1,50 @@
1
+ import { getOwnerDocument } from "../utils/owner.js";
2
+ export { default as MainTreeProvider, useMainTreeNode } from "../internal/MainTreeProvider.svelte";
3
+ export function useRootContainers(options = {}) {
4
+ const { defaultContainers = [], portals,
5
+ // Reference to a node in the "main" tree, not in the portalled Dialog tree.
6
+ mainTreeNode, } = $derived(options);
7
+ const ownerDocument = $derived(getOwnerDocument(mainTreeNode));
8
+ const resolvedContainers = $derived.by(() => {
9
+ let containers = [];
10
+ // Resolve default containers
11
+ for (let container of defaultContainers) {
12
+ if (container === null)
13
+ continue;
14
+ containers.push(container);
15
+ }
16
+ // Resolve portal containers
17
+ if (portals) {
18
+ for (let portal of portals) {
19
+ containers.push(portal);
20
+ }
21
+ }
22
+ // Resolve third party (root) containers
23
+ for (let container of ownerDocument?.querySelectorAll("html > *, body > *") ?? []) {
24
+ if (container === document.body)
25
+ continue; // Skip `<body>`
26
+ if (container === document.head)
27
+ continue; // Skip `<head>`
28
+ if (!(container instanceof HTMLElement))
29
+ continue; // Skip non-HTMLElements
30
+ if (container.id === "headlessui-portal-root")
31
+ continue; // Skip the Headless UI portal root
32
+ if (mainTreeNode) {
33
+ if (container.contains(mainTreeNode))
34
+ continue; // Skip if it is the main app
35
+ if (container.contains(mainTreeNode?.getRootNode()?.host))
36
+ continue; // Skip if it is the main app (and the component is inside a shadow root)
37
+ }
38
+ if (containers.some((defaultContainer) => container.contains(defaultContainer)))
39
+ continue; // Skip if the current container is part of a container we've already seen (e.g.: default container / portal)
40
+ containers.push(container);
41
+ }
42
+ return containers;
43
+ });
44
+ return {
45
+ get resolvedContainers() {
46
+ return resolvedContainers;
47
+ },
48
+ contains: (element) => resolvedContainers.some((container) => container.contains(element)),
49
+ };
50
+ }
@@ -0,0 +1,5 @@
1
+ export declare function useScrollLock(options: {
2
+ enabled: boolean;
3
+ ownerDocument: Document | null;
4
+ resolveAllowedContainers?: () => HTMLElement[];
5
+ }): void;
@@ -0,0 +1,24 @@
1
+ import { useDocumentOverflowLockedEffect } from "./document-overflow/use-document-overflow.svelte.js";
2
+ import { useIsTopLayer } from "./use-is-top-layer.svelte.js";
3
+ export function useScrollLock(options) {
4
+ const { enabled, ownerDocument, resolveAllowedContainers = () => [document.body] } = $derived(options);
5
+ const isTopLayer = useIsTopLayer({
6
+ get enabled() {
7
+ return enabled;
8
+ },
9
+ scope: "scroll-lock",
10
+ });
11
+ useDocumentOverflowLockedEffect({
12
+ get shouldBeLocked() {
13
+ return isTopLayer.value;
14
+ },
15
+ get doc() {
16
+ return ownerDocument;
17
+ },
18
+ get meta() {
19
+ return (meta) => ({
20
+ containers: [...(meta.containers ?? []), resolveAllowedContainers()],
21
+ });
22
+ },
23
+ });
24
+ }
@@ -0,0 +1,7 @@
1
+ import { type MutableRefObject } from "../utils/ref.svelte.js";
2
+ declare const Optional: unique symbol;
3
+ export declare function optionalRef<T>(cb: (ref: T) => void, isOptional?: boolean): ((ref: T) => void) & {
4
+ [Optional]: boolean;
5
+ };
6
+ export declare function useSyncRefs<TType>(...refs: (MutableRefObject<TType | null> | ((instance: TType) => void) | null)[]): ((value: TType) => void) | undefined;
7
+ export {};
@@ -0,0 +1,22 @@
1
+ import { useRef } from "../utils/ref.svelte.js";
2
+ const Optional = Symbol();
3
+ export function optionalRef(cb, isOptional = true) {
4
+ return Object.assign(cb, { [Optional]: isOptional });
5
+ }
6
+ export function useSyncRefs(...refs) {
7
+ let syncRefs = (value) => {
8
+ for (let ref of refs) {
9
+ if (ref == null)
10
+ continue;
11
+ if (typeof ref === "function")
12
+ ref(value);
13
+ else
14
+ ref.current = value;
15
+ }
16
+ };
17
+ return refs.every((ref) => ref == null ||
18
+ // @ts-expect-error
19
+ ref?.[Optional])
20
+ ? undefined
21
+ : syncRefs;
22
+ }