@funstack/ui 0.0.0-stage → 0.1.0-alpha.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 (299) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -2
  3. package/dist/accordion/accordion-context.d.mts +5 -0
  4. package/dist/accordion/accordion-context.d.mts.map +1 -0
  5. package/dist/accordion/accordion-context.mjs +9 -0
  6. package/dist/accordion/accordion-context.mjs.map +1 -0
  7. package/dist/accordion/accordion-item.d.mts +53 -0
  8. package/dist/accordion/accordion-item.d.mts.map +1 -0
  9. package/dist/accordion/accordion-item.mjs +85 -0
  10. package/dist/accordion/accordion-item.mjs.map +1 -0
  11. package/dist/accordion/accordion-panel.d.mts +40 -0
  12. package/dist/accordion/accordion-panel.d.mts.map +1 -0
  13. package/dist/accordion/accordion-panel.mjs +40 -0
  14. package/dist/accordion/accordion-panel.mjs.map +1 -0
  15. package/dist/accordion/accordion-root.d.mts +90 -0
  16. package/dist/accordion/accordion-root.d.mts.map +1 -0
  17. package/dist/accordion/accordion-root.mjs +229 -0
  18. package/dist/accordion/accordion-root.mjs.map +1 -0
  19. package/dist/accordion/accordion-trigger.d.mts +37 -0
  20. package/dist/accordion/accordion-trigger.d.mts.map +1 -0
  21. package/dist/accordion/accordion-trigger.mjs +81 -0
  22. package/dist/accordion/accordion-trigger.mjs.map +1 -0
  23. package/dist/accordion/index.d.mts +20 -0
  24. package/dist/accordion/index.d.mts.map +1 -0
  25. package/dist/accordion/index.mjs +20 -0
  26. package/dist/accordion/index.mjs.map +1 -0
  27. package/dist/checkbox/checkbox.d.mts +92 -0
  28. package/dist/checkbox/checkbox.d.mts.map +1 -0
  29. package/dist/checkbox/checkbox.mjs +82 -0
  30. package/dist/checkbox/checkbox.mjs.map +1 -0
  31. package/dist/checkbox/index.d.mts +3 -0
  32. package/dist/checkbox/index.mjs +1 -0
  33. package/dist/checkbox-group/checkbox-group-context.mjs +24 -0
  34. package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
  35. package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
  36. package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
  37. package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
  38. package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
  39. package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
  40. package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
  41. package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
  42. package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
  43. package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
  44. package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
  45. package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
  46. package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
  47. package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
  48. package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
  49. package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
  50. package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
  51. package/dist/checkbox-group/index.d.mts +23 -0
  52. package/dist/checkbox-group/index.d.mts.map +1 -0
  53. package/dist/checkbox-group/index.mjs +23 -0
  54. package/dist/checkbox-group/index.mjs.map +1 -0
  55. package/dist/dialog/dialog-close.d.mts +44 -0
  56. package/dist/dialog/dialog-close.d.mts.map +1 -0
  57. package/dist/dialog/dialog-close.mjs +76 -0
  58. package/dist/dialog/dialog-close.mjs.map +1 -0
  59. package/dist/dialog/dialog-context.mjs +13 -0
  60. package/dist/dialog/dialog-context.mjs.map +1 -0
  61. package/dist/dialog/dialog-description.d.mts +22 -0
  62. package/dist/dialog/dialog-description.d.mts.map +1 -0
  63. package/dist/dialog/dialog-description.mjs +31 -0
  64. package/dist/dialog/dialog-description.mjs.map +1 -0
  65. package/dist/dialog/dialog-form.d.mts +58 -0
  66. package/dist/dialog/dialog-form.d.mts.map +1 -0
  67. package/dist/dialog/dialog-form.mjs +108 -0
  68. package/dist/dialog/dialog-form.mjs.map +1 -0
  69. package/dist/dialog/dialog-popup.d.mts +81 -0
  70. package/dist/dialog/dialog-popup.d.mts.map +1 -0
  71. package/dist/dialog/dialog-popup.mjs +367 -0
  72. package/dist/dialog/dialog-popup.mjs.map +1 -0
  73. package/dist/dialog/dialog-root.d.mts +70 -0
  74. package/dist/dialog/dialog-root.d.mts.map +1 -0
  75. package/dist/dialog/dialog-root.mjs +113 -0
  76. package/dist/dialog/dialog-root.mjs.map +1 -0
  77. package/dist/dialog/dialog-title.d.mts +23 -0
  78. package/dist/dialog/dialog-title.d.mts.map +1 -0
  79. package/dist/dialog/dialog-title.mjs +31 -0
  80. package/dist/dialog/dialog-title.mjs.map +1 -0
  81. package/dist/dialog/dialog-trigger.d.mts +39 -0
  82. package/dist/dialog/dialog-trigger.d.mts.map +1 -0
  83. package/dist/dialog/dialog-trigger.mjs +80 -0
  84. package/dist/dialog/dialog-trigger.mjs.map +1 -0
  85. package/dist/dialog/index.d.mts +28 -0
  86. package/dist/dialog/index.d.mts.map +1 -0
  87. package/dist/dialog/index.mjs +28 -0
  88. package/dist/dialog/index.mjs.map +1 -0
  89. package/dist/index.d.mts +60 -0
  90. package/dist/index.mjs +12 -0
  91. package/dist/internal/action-details.mjs +47 -0
  92. package/dist/internal/action-details.mjs.map +1 -0
  93. package/dist/internal/anchor-position.d.mts +7 -0
  94. package/dist/internal/anchor-position.d.mts.map +1 -0
  95. package/dist/internal/anchor-position.mjs +16 -0
  96. package/dist/internal/anchor-position.mjs.map +1 -0
  97. package/dist/internal/data-attr.mjs +13 -0
  98. package/dist/internal/data-attr.mjs.map +1 -0
  99. package/dist/internal/merge-props.mjs +119 -0
  100. package/dist/internal/merge-props.mjs.map +1 -0
  101. package/dist/internal/platform-attributes.d.mts +55 -0
  102. package/dist/internal/platform-attributes.d.mts.map +1 -0
  103. package/dist/internal/sync-presence.mjs +19 -0
  104. package/dist/internal/sync-presence.mjs.map +1 -0
  105. package/dist/internal/transition-boundary.mjs +52 -0
  106. package/dist/internal/transition-boundary.mjs.map +1 -0
  107. package/dist/internal/transition-types.mjs +15 -0
  108. package/dist/internal/transition-types.mjs.map +1 -0
  109. package/dist/internal/types.d.mts +38 -0
  110. package/dist/internal/types.d.mts.map +1 -0
  111. package/dist/internal/use-action-trigger.mjs +58 -0
  112. package/dist/internal/use-action-trigger.mjs.map +1 -0
  113. package/dist/internal/use-controllable-action.mjs +115 -0
  114. package/dist/internal/use-controllable-action.mjs.map +1 -0
  115. package/dist/internal/use-delayed-pair.mjs +40 -0
  116. package/dist/internal/use-delayed-pair.mjs.map +1 -0
  117. package/dist/internal/use-dev-warning.mjs +21 -0
  118. package/dist/internal/use-dev-warning.mjs.map +1 -0
  119. package/dist/internal/use-exit-animation.mjs +55 -0
  120. package/dist/internal/use-exit-animation.mjs.map +1 -0
  121. package/dist/internal/use-form-reset.mjs +50 -0
  122. package/dist/internal/use-form-reset.mjs.map +1 -0
  123. package/dist/internal/use-native-checked.mjs +113 -0
  124. package/dist/internal/use-native-checked.mjs.map +1 -0
  125. package/dist/internal/use-native-group-reconciliation.mjs +64 -0
  126. package/dist/internal/use-native-group-reconciliation.mjs.map +1 -0
  127. package/dist/internal/use-popover-popup.mjs +274 -0
  128. package/dist/internal/use-popover-popup.mjs.map +1 -0
  129. package/dist/internal/use-popover-root.d.mts +2 -0
  130. package/dist/internal/use-popover-root.mjs +112 -0
  131. package/dist/internal/use-popover-root.mjs.map +1 -0
  132. package/dist/internal/use-popover-trigger.mjs +86 -0
  133. package/dist/internal/use-popover-trigger.mjs.map +1 -0
  134. package/dist/internal/use-render.mjs +51 -0
  135. package/dist/internal/use-render.mjs.map +1 -0
  136. package/dist/internal/use-until-found.mjs +40 -0
  137. package/dist/internal/use-until-found.mjs.map +1 -0
  138. package/dist/menu/index.d.mts +34 -0
  139. package/dist/menu/index.d.mts.map +1 -0
  140. package/dist/menu/index.mjs +34 -0
  141. package/dist/menu/index.mjs.map +1 -0
  142. package/dist/menu/menu-checkbox-item.d.mts +42 -0
  143. package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
  144. package/dist/menu/menu-checkbox-item.mjs +71 -0
  145. package/dist/menu/menu-checkbox-item.mjs.map +1 -0
  146. package/dist/menu/menu-context.d.mts +1 -0
  147. package/dist/menu/menu-context.mjs +28 -0
  148. package/dist/menu/menu-context.mjs.map +1 -0
  149. package/dist/menu/menu-dom.mjs +37 -0
  150. package/dist/menu/menu-dom.mjs.map +1 -0
  151. package/dist/menu/menu-group.d.mts +48 -0
  152. package/dist/menu/menu-group.d.mts.map +1 -0
  153. package/dist/menu/menu-group.mjs +69 -0
  154. package/dist/menu/menu-group.mjs.map +1 -0
  155. package/dist/menu/menu-hover.mjs +29 -0
  156. package/dist/menu/menu-hover.mjs.map +1 -0
  157. package/dist/menu/menu-item.d.mts +45 -0
  158. package/dist/menu/menu-item.d.mts.map +1 -0
  159. package/dist/menu/menu-item.mjs +54 -0
  160. package/dist/menu/menu-item.mjs.map +1 -0
  161. package/dist/menu/menu-popup.d.mts +69 -0
  162. package/dist/menu/menu-popup.d.mts.map +1 -0
  163. package/dist/menu/menu-popup.mjs +207 -0
  164. package/dist/menu/menu-popup.mjs.map +1 -0
  165. package/dist/menu/menu-radio-group.d.mts +37 -0
  166. package/dist/menu/menu-radio-group.d.mts.map +1 -0
  167. package/dist/menu/menu-radio-group.mjs +72 -0
  168. package/dist/menu/menu-radio-group.mjs.map +1 -0
  169. package/dist/menu/menu-radio-item.d.mts +33 -0
  170. package/dist/menu/menu-radio-item.d.mts.map +1 -0
  171. package/dist/menu/menu-radio-item.mjs +62 -0
  172. package/dist/menu/menu-radio-item.mjs.map +1 -0
  173. package/dist/menu/menu-root.d.mts +54 -0
  174. package/dist/menu/menu-root.d.mts.map +1 -0
  175. package/dist/menu/menu-root.mjs +102 -0
  176. package/dist/menu/menu-root.mjs.map +1 -0
  177. package/dist/menu/menu-submenu-trigger.d.mts +31 -0
  178. package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
  179. package/dist/menu/menu-submenu-trigger.mjs +92 -0
  180. package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
  181. package/dist/menu/menu-trigger.d.mts +36 -0
  182. package/dist/menu/menu-trigger.d.mts.map +1 -0
  183. package/dist/menu/menu-trigger.mjs +81 -0
  184. package/dist/menu/menu-trigger.mjs.map +1 -0
  185. package/dist/menu/use-menu-item.mjs +54 -0
  186. package/dist/menu/use-menu-item.mjs.map +1 -0
  187. package/dist/popover/index.d.mts +28 -0
  188. package/dist/popover/index.d.mts.map +1 -0
  189. package/dist/popover/index.mjs +28 -0
  190. package/dist/popover/index.mjs.map +1 -0
  191. package/dist/popover/popover-close.d.mts +41 -0
  192. package/dist/popover/popover-close.d.mts.map +1 -0
  193. package/dist/popover/popover-close.mjs +90 -0
  194. package/dist/popover/popover-close.mjs.map +1 -0
  195. package/dist/popover/popover-context.mjs +26 -0
  196. package/dist/popover/popover-context.mjs.map +1 -0
  197. package/dist/popover/popover-description.d.mts +22 -0
  198. package/dist/popover/popover-description.d.mts.map +1 -0
  199. package/dist/popover/popover-description.mjs +28 -0
  200. package/dist/popover/popover-description.mjs.map +1 -0
  201. package/dist/popover/popover-popup.d.mts +82 -0
  202. package/dist/popover/popover-popup.d.mts.map +1 -0
  203. package/dist/popover/popover-popup.mjs +93 -0
  204. package/dist/popover/popover-popup.mjs.map +1 -0
  205. package/dist/popover/popover-root.d.mts +64 -0
  206. package/dist/popover/popover-root.d.mts.map +1 -0
  207. package/dist/popover/popover-root.mjs +39 -0
  208. package/dist/popover/popover-root.mjs.map +1 -0
  209. package/dist/popover/popover-title.d.mts +23 -0
  210. package/dist/popover/popover-title.d.mts.map +1 -0
  211. package/dist/popover/popover-title.mjs +28 -0
  212. package/dist/popover/popover-title.mjs.map +1 -0
  213. package/dist/popover/popover-trigger.d.mts +37 -0
  214. package/dist/popover/popover-trigger.d.mts.map +1 -0
  215. package/dist/popover/popover-trigger.mjs +40 -0
  216. package/dist/popover/popover-trigger.mjs.map +1 -0
  217. package/dist/radio-group/index.d.mts +20 -0
  218. package/dist/radio-group/index.d.mts.map +1 -0
  219. package/dist/radio-group/index.mjs +20 -0
  220. package/dist/radio-group/index.mjs.map +1 -0
  221. package/dist/radio-group/radio-group-context.mjs +13 -0
  222. package/dist/radio-group/radio-group-context.mjs.map +1 -0
  223. package/dist/radio-group/radio-group-item.d.mts +54 -0
  224. package/dist/radio-group/radio-group-item.d.mts.map +1 -0
  225. package/dist/radio-group/radio-group-item.mjs +83 -0
  226. package/dist/radio-group/radio-group-item.mjs.map +1 -0
  227. package/dist/radio-group/radio-group-label.d.mts +21 -0
  228. package/dist/radio-group/radio-group-label.d.mts.map +1 -0
  229. package/dist/radio-group/radio-group-label.mjs +30 -0
  230. package/dist/radio-group/radio-group-label.mjs.map +1 -0
  231. package/dist/radio-group/radio-group-root.d.mts +92 -0
  232. package/dist/radio-group/radio-group-root.d.mts.map +1 -0
  233. package/dist/radio-group/radio-group-root.mjs +137 -0
  234. package/dist/radio-group/radio-group-root.mjs.map +1 -0
  235. package/dist/switch/index.d.mts +3 -0
  236. package/dist/switch/index.mjs +1 -0
  237. package/dist/switch/switch.d.mts +78 -0
  238. package/dist/switch/switch.d.mts.map +1 -0
  239. package/dist/switch/switch.mjs +77 -0
  240. package/dist/switch/switch.mjs.map +1 -0
  241. package/dist/tabs/index.d.mts +24 -0
  242. package/dist/tabs/index.d.mts.map +1 -0
  243. package/dist/tabs/index.mjs +23 -0
  244. package/dist/tabs/index.mjs.map +1 -0
  245. package/dist/tabs/tabs-context.d.mts +7 -0
  246. package/dist/tabs/tabs-context.d.mts.map +1 -0
  247. package/dist/tabs/tabs-context.mjs +14 -0
  248. package/dist/tabs/tabs-context.mjs.map +1 -0
  249. package/dist/tabs/tabs-ids.mjs +39 -0
  250. package/dist/tabs/tabs-ids.mjs.map +1 -0
  251. package/dist/tabs/tabs-indicator.d.mts +32 -0
  252. package/dist/tabs/tabs-indicator.d.mts.map +1 -0
  253. package/dist/tabs/tabs-indicator.mjs +49 -0
  254. package/dist/tabs/tabs-indicator.mjs.map +1 -0
  255. package/dist/tabs/tabs-list.d.mts +34 -0
  256. package/dist/tabs/tabs-list.d.mts.map +1 -0
  257. package/dist/tabs/tabs-list.mjs +130 -0
  258. package/dist/tabs/tabs-list.mjs.map +1 -0
  259. package/dist/tabs/tabs-panel.d.mts +50 -0
  260. package/dist/tabs/tabs-panel.d.mts.map +1 -0
  261. package/dist/tabs/tabs-panel.mjs +81 -0
  262. package/dist/tabs/tabs-panel.mjs.map +1 -0
  263. package/dist/tabs/tabs-root.d.mts +64 -0
  264. package/dist/tabs/tabs-root.d.mts.map +1 -0
  265. package/dist/tabs/tabs-root.mjs +90 -0
  266. package/dist/tabs/tabs-root.mjs.map +1 -0
  267. package/dist/tabs/tabs-tab.d.mts +49 -0
  268. package/dist/tabs/tabs-tab.d.mts.map +1 -0
  269. package/dist/tabs/tabs-tab.mjs +89 -0
  270. package/dist/tabs/tabs-tab.mjs.map +1 -0
  271. package/dist/toggle/index.d.mts +3 -0
  272. package/dist/toggle/index.mjs +1 -0
  273. package/dist/toggle/toggle.d.mts +57 -0
  274. package/dist/toggle/toggle.d.mts.map +1 -0
  275. package/dist/toggle/toggle.mjs +59 -0
  276. package/dist/toggle/toggle.mjs.map +1 -0
  277. package/dist/tooltip/index.d.mts +20 -0
  278. package/dist/tooltip/index.d.mts.map +1 -0
  279. package/dist/tooltip/index.mjs +19 -0
  280. package/dist/tooltip/index.mjs.map +1 -0
  281. package/dist/tooltip/tooltip-context.d.mts +5 -0
  282. package/dist/tooltip/tooltip-context.d.mts.map +1 -0
  283. package/dist/tooltip/tooltip-context.mjs +13 -0
  284. package/dist/tooltip/tooltip-context.mjs.map +1 -0
  285. package/dist/tooltip/tooltip-popup.d.mts +49 -0
  286. package/dist/tooltip/tooltip-popup.d.mts.map +1 -0
  287. package/dist/tooltip/tooltip-popup.mjs +52 -0
  288. package/dist/tooltip/tooltip-popup.mjs.map +1 -0
  289. package/dist/tooltip/tooltip-root.d.mts +41 -0
  290. package/dist/tooltip/tooltip-root.d.mts.map +1 -0
  291. package/dist/tooltip/tooltip-root.mjs +86 -0
  292. package/dist/tooltip/tooltip-root.mjs.map +1 -0
  293. package/dist/tooltip/tooltip-trigger.d.mts +38 -0
  294. package/dist/tooltip/tooltip-trigger.d.mts.map +1 -0
  295. package/dist/tooltip/tooltip-trigger.mjs +55 -0
  296. package/dist/tooltip/tooltip-trigger.mjs.map +1 -0
  297. package/dist/tooltip/use-tooltip-interaction.mjs +119 -0
  298. package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -0
  299. package/package.json +73 -3
@@ -0,0 +1,41 @@
1
+ import { StateValue } from "../internal/types.mjs";
2
+ import { TooltipProvides } from "./tooltip-context.mjs";
3
+ import { ReactNode } from "react";
4
+ //#region src/tooltip/tooltip-root.d.ts
5
+ export interface TooltipRootState {
6
+ readonly disabled: boolean;
7
+ }
8
+ export interface TooltipRootProps {
9
+ /**
10
+ * Milliseconds from hover or keyboard focus to open. Default 250 (Chromium's
11
+ * `interest-delay-start`). The same delay applies to hover and focus.
12
+ */
13
+ delay?: number;
14
+ /** Milliseconds from de-hover or blur to close. Default 150 (Chromium's `interest-delay-end`). */
15
+ closeDelay?: number;
16
+ /** When true the tooltip never opens. The ARIA relation stays in place. */
17
+ disabled?: boolean;
18
+ /**
19
+ * Which accessibility relation the tooltip provides for the trigger.
20
+ * `"description"` (default): `aria-describedby`, for triggers that have their own name.
21
+ * `"name"`: `aria-labelledby`, for icon-only triggers; do not also set `aria-label`.
22
+ */
23
+ provides?: TooltipProvides;
24
+ children?: StateValue<ReactNode, TooltipRootState>;
25
+ }
26
+ /**
27
+ * Owns the popup id, the options and the one interaction effect. Renders no DOM, so it has no
28
+ * `ref`, `className` or `style`. The open state is browser-owned (the popover's top-layer state):
29
+ * there is no `open` prop, no action, and no `data-open`; style with `:popover-open`.
30
+ *
31
+ * Render `Tooltip.Popup` immediately after `Tooltip.Trigger` in DOM order: the HTML spec
32
+ * recommends that reading order for popovers, and some screen readers attach an adjacent tooltip
33
+ * to the wrong control otherwise. This is documented, not enforced.
34
+ */
35
+ export declare function TooltipRoot({ delay, closeDelay, disabled, provides, children }: TooltipRootProps): import("react").JSX.Element;
36
+ export declare namespace TooltipRoot {
37
+ var displayName: string;
38
+ }
39
+ //#endregion
40
+ export type { TooltipProvides };
41
+ //# sourceMappingURL=tooltip-root.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip-root.d.mts","names":[],"sources":["../../src/tooltip/tooltip-root.tsx"],"mappings":";;;;iBAWiB;WACN;;iBAGM;;;;;EAKf;;EAEA;;EAEA;;;;;;EAMA,WAAW;EACX,WAAW,WAAW,WAAW;;;;;;;;;;;wBAiBnB,cACd,OACA,YACA,UACA,UACA,YACC,mCAAgB,IAAA;yBANH"}
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { resolveStateValue } from "../internal/use-render.mjs";
3
+ import { TooltipContext } from "./tooltip-context.mjs";
4
+ import { useTooltipInteraction } from "./use-tooltip-interaction.mjs";
5
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/tooltip/tooltip-root.tsx
8
+ const isDev = process.env.NODE_ENV !== "production";
9
+ const FOCUSABLE = "a[href],area[href],button,input,select,textarea,iframe,[tabindex],[contenteditable]";
10
+ /**
11
+ * Owns the popup id, the options and the one interaction effect. Renders no DOM, so it has no
12
+ * `ref`, `className` or `style`. The open state is browser-owned (the popover's top-layer state):
13
+ * there is no `open` prop, no action, and no `data-open`; style with `:popover-open`.
14
+ *
15
+ * Render `Tooltip.Popup` immediately after `Tooltip.Trigger` in DOM order: the HTML spec
16
+ * recommends that reading order for popovers, and some screen readers attach an adjacent tooltip
17
+ * to the wrong control otherwise. This is documented, not enforced.
18
+ */
19
+ function TooltipRoot({ delay = 250, closeDelay = 150, disabled = false, provides = "description", children }) {
20
+ const popupId = useId();
21
+ const [trigger, setTriggerElement] = useState(null);
22
+ const [popup, setPopupElement] = useState(null);
23
+ const [interestForRequested, setInterestForRequested] = useState(false);
24
+ useTooltipInteraction(trigger, popup, {
25
+ delay,
26
+ closeDelay,
27
+ disabled,
28
+ interestForRequested
29
+ });
30
+ useTooltipDevWarnings(trigger, popup, provides);
31
+ const context = useMemo(() => ({
32
+ popupId,
33
+ disabled,
34
+ delay,
35
+ closeDelay,
36
+ provides,
37
+ setTriggerElement,
38
+ setPopupElement,
39
+ setInterestForRequested
40
+ }), [
41
+ popupId,
42
+ disabled,
43
+ delay,
44
+ closeDelay,
45
+ provides
46
+ ]);
47
+ return /* @__PURE__ */ jsx(TooltipContext, {
48
+ value: context,
49
+ children: resolveStateValue(children, { disabled })
50
+ });
51
+ }
52
+ TooltipRoot.displayName = "TooltipRoot";
53
+ /** Development-only checks that need the mounted elements (design 12.5). Each fires once. */
54
+ function useTooltipDevWarnings(trigger, popup, provides) {
55
+ const warned = useRef({
56
+ label: false,
57
+ focusable: false,
58
+ interactive: false
59
+ });
60
+ useEffect(() => {
61
+ if (!isDev) return;
62
+ const w = warned.current;
63
+ if (trigger !== null) {
64
+ if (provides === "name" && trigger.hasAttribute("aria-label") && !w.label) {
65
+ w.label = true;
66
+ console.error("[Tooltip] provides=\"name\" together with aria-label on the trigger announces the name twice. Remove aria-label.");
67
+ }
68
+ if (trigger.tabIndex < 0 && !w.focusable) {
69
+ w.focusable = true;
70
+ console.error("[Tooltip] The trigger is not focusable; keyboard users cannot open the tooltip. Render a <button> or a link, or set tabIndex={0}.");
71
+ }
72
+ }
73
+ if (popup !== null && popup.querySelector(FOCUSABLE) !== null && !w.interactive) {
74
+ w.interactive = true;
75
+ console.error("[Tooltip] Tooltips must not contain interactive content. Use a non-modal dialog or a popover instead.");
76
+ }
77
+ }, [
78
+ trigger,
79
+ popup,
80
+ provides
81
+ ]);
82
+ }
83
+ //#endregion
84
+ export { TooltipRoot };
85
+
86
+ //# sourceMappingURL=tooltip-root.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip-root.mjs","names":[],"sources":["../../src/tooltip/tooltip-root.tsx"],"sourcesContent":["\"use client\";\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { StateValue } from \"../internal/types.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { TooltipContext } from \"./tooltip-context.js\";\nimport type { TooltipContextValue, TooltipProvides } from \"./tooltip-context.js\";\nimport { useTooltipInteraction } from \"./use-tooltip-interaction.js\";\n\nexport type { TooltipProvides } from \"./tooltip-context.js\";\n\nexport interface TooltipRootState {\n readonly disabled: boolean;\n}\n\nexport interface TooltipRootProps {\n /**\n * Milliseconds from hover or keyboard focus to open. Default 250 (Chromium's\n * `interest-delay-start`). The same delay applies to hover and focus.\n */\n delay?: number;\n /** Milliseconds from de-hover or blur to close. Default 150 (Chromium's `interest-delay-end`). */\n closeDelay?: number;\n /** When true the tooltip never opens. The ARIA relation stays in place. */\n disabled?: boolean;\n /**\n * Which accessibility relation the tooltip provides for the trigger.\n * `\"description\"` (default): `aria-describedby`, for triggers that have their own name.\n * `\"name\"`: `aria-labelledby`, for icon-only triggers; do not also set `aria-label`.\n */\n provides?: TooltipProvides;\n children?: StateValue<ReactNode, TooltipRootState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nconst FOCUSABLE =\n \"a[href],area[href],button,input,select,textarea,iframe,[tabindex],[contenteditable]\";\n\n/**\n * Owns the popup id, the options and the one interaction effect. Renders no DOM, so it has no\n * `ref`, `className` or `style`. The open state is browser-owned (the popover's top-layer state):\n * there is no `open` prop, no action, and no `data-open`; style with `:popover-open`.\n *\n * Render `Tooltip.Popup` immediately after `Tooltip.Trigger` in DOM order: the HTML spec\n * recommends that reading order for popovers, and some screen readers attach an adjacent tooltip\n * to the wrong control otherwise. This is documented, not enforced.\n */\nexport function TooltipRoot({\n delay = 250,\n closeDelay = 150,\n disabled = false,\n provides = \"description\",\n children,\n}: TooltipRootProps) {\n const popupId = useId();\n const [trigger, setTriggerElement] = useState<HTMLElement | null>(null);\n const [popup, setPopupElement] = useState<HTMLElement | null>(null);\n const [interestForRequested, setInterestForRequested] = useState(false);\n\n useTooltipInteraction(trigger, popup, { delay, closeDelay, disabled, interestForRequested });\n useTooltipDevWarnings(trigger, popup, provides);\n\n const context = useMemo<TooltipContextValue>(\n () => ({\n popupId,\n disabled,\n delay,\n closeDelay,\n provides,\n setTriggerElement,\n setPopupElement,\n setInterestForRequested,\n }),\n [popupId, disabled, delay, closeDelay, provides],\n );\n\n const state: TooltipRootState = { disabled };\n return <TooltipContext value={context}>{resolveStateValue(children, state)}</TooltipContext>;\n}\n\nTooltipRoot.displayName = \"TooltipRoot\";\n\n/** Development-only checks that need the mounted elements (design 12.5). Each fires once. */\nfunction useTooltipDevWarnings(\n trigger: HTMLElement | null,\n popup: HTMLElement | null,\n provides: TooltipProvides,\n): void {\n const warned = useRef({ label: false, focusable: false, interactive: false });\n useEffect(() => {\n if (!isDev) return;\n const w = warned.current;\n if (trigger !== null) {\n if (provides === \"name\" && trigger.hasAttribute(\"aria-label\") && !w.label) {\n w.label = true;\n console.error(\n '[Tooltip] provides=\"name\" together with aria-label on the trigger announces the name twice. Remove aria-label.',\n );\n }\n if (trigger.tabIndex < 0 && !w.focusable) {\n w.focusable = true;\n console.error(\n \"[Tooltip] The trigger is not focusable; keyboard users cannot open the tooltip. Render a <button> or a link, or set tabIndex={0}.\",\n );\n }\n }\n if (popup !== null && popup.querySelector(FOCUSABLE) !== null && !w.interactive) {\n w.interactive = true;\n console.error(\n \"[Tooltip] Tooltips must not contain interactive content. Use a non-modal dialog or a popover instead.\",\n );\n }\n }, [trigger, popup, provides]);\n}\n"],"mappings":";;;;;;;AAkCA,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,MAAM,YACJ;;;;;;;;;;AAWF,SAAgB,YAAY,EAC1B,QAAQ,KACR,aAAa,KACb,WAAW,OACX,WAAW,eACX,YACmB;CACnB,MAAM,UAAU,MAAM;CACtB,MAAM,CAAC,SAAS,qBAAqB,SAA6B,IAAI;CACtE,MAAM,CAAC,OAAO,mBAAmB,SAA6B,IAAI;CAClE,MAAM,CAAC,sBAAsB,2BAA2B,SAAS,KAAK;CAEtE,sBAAsB,SAAS,OAAO;EAAE;EAAO;EAAY;EAAU;CAAqB,CAAC;CAC3F,sBAAsB,SAAS,OAAO,QAAQ;CAE9C,MAAM,UAAU,eACP;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,IACA;EAAC;EAAS;EAAU;EAAO;EAAY;CAAQ,CACjD;CAGA,OAAO,oBAAC,gBAAD;EAAgB,OAAO;EAAU,UAAA,kBAAkB,UAAU,EADlC,SACsC,CAAC;CAAkB,CAAA;AAC7F;AAEA,YAAY,cAAc;;AAG1B,SAAS,sBACP,SACA,OACA,UACM;CACN,MAAM,SAAS,OAAO;EAAE,OAAO;EAAO,WAAW;EAAO,aAAa;CAAM,CAAC;CAC5E,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,MAAM,IAAI,OAAO;EACjB,IAAI,YAAY,MAAM;GACpB,IAAI,aAAa,UAAU,QAAQ,aAAa,YAAY,KAAK,CAAC,EAAE,OAAO;IACzE,EAAE,QAAQ;IACV,QAAQ,MACN,kHACF;GACF;GACA,IAAI,QAAQ,WAAW,KAAK,CAAC,EAAE,WAAW;IACxC,EAAE,YAAY;IACd,QAAQ,MACN,mIACF;GACF;EACF;EACA,IAAI,UAAU,QAAQ,MAAM,cAAc,SAAS,MAAM,QAAQ,CAAC,EAAE,aAAa;GAC/E,EAAE,cAAc;GAChB,QAAQ,MACN,uGACF;EACF;CACF,GAAG;EAAC;EAAS;EAAO;CAAQ,CAAC;AAC/B"}
@@ -0,0 +1,38 @@
1
+ import { RenderProp, StateValue } from "../internal/types.mjs";
2
+ import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
3
+ //#region src/tooltip/tooltip-trigger.d.ts
4
+ export interface TooltipTriggerState {
5
+ readonly disabled: boolean;
6
+ }
7
+ export interface TooltipTriggerProps extends Omit<ComponentPropsWithRef<"button">, "className" | "style" | "children"> {
8
+ /**
9
+ * Experimental (Chromium 142+). Renders `interestfor` and `interest-delay-start/end` so the
10
+ * browser owns hover, focus, Escape and long-press; the library's JS trigger path is skipped
11
+ * where the rendered element implements `interestForElement`. No effect in other engines (the
12
+ * attribute is ignored and the JS path runs). Pair with `Tooltip.Popup experimental_hint`.
13
+ * Default `false`.
14
+ */
15
+ experimental_interestFor?: boolean;
16
+ className?: StateValue<string | undefined, TooltipTriggerState>;
17
+ style?: StateValue<CSSProperties | undefined, TooltipTriggerState>;
18
+ children?: StateValue<ReactNode, TooltipTriggerState>;
19
+ render?: RenderProp<ComponentPropsWithRef<"button">, TooltipTriggerState>;
20
+ }
21
+ /**
22
+ * The element the user hovers or focuses: a `<button type="button">` by default. `render` may
23
+ * substitute a link or a custom button; the element must be focusable (a dev warning fires when
24
+ * it is not). A natively `disabled` button never opens a tooltip; use `aria-disabled` instead.
25
+ *
26
+ * The library attaches its hover / focus listeners with `addEventListener` from `Tooltip.Root`,
27
+ * so every `on*` prop passes through untouched.
28
+ *
29
+ * Owned keys (conventions 1.2): `aria-describedby` (with `provides="description"`) or
30
+ * `aria-labelledby` (with `provides="name"`), both token-merged with the consumer's ids;
31
+ * `data-disabled`; and `interestfor` when `experimental_interestFor` is on.
32
+ */
33
+ export declare function TooltipTrigger({ experimental_interestFor, className, style, children, render, ...rest }: TooltipTriggerProps): ReactNode;
34
+ export declare namespace TooltipTrigger {
35
+ var displayName: string;
36
+ }
37
+ //#endregion
38
+ //# sourceMappingURL=tooltip-trigger.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip-trigger.d.mts","names":[],"sources":["../../src/tooltip/tooltip-trigger.tsx"],"mappings":";;;iBASiB;WACN;;iBAGM,4BAA4B,KAC3C;;;;;;;;EAUA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;wBAevC,iBACd,0BACA,WACA,OACA,UACA,WACG,QACF,sBAAmB;yBAPN"}
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { dataAttr } from "../internal/data-attr.mjs";
3
+ import { rendersButton, resolveStateValue, useRender } from "../internal/use-render.mjs";
4
+ import { useTooltipContext } from "./tooltip-context.mjs";
5
+ import { useLayoutEffect } from "react";
6
+ //#region src/tooltip/tooltip-trigger.tsx
7
+ /**
8
+ * The element the user hovers or focuses: a `<button type="button">` by default. `render` may
9
+ * substitute a link or a custom button; the element must be focusable (a dev warning fires when
10
+ * it is not). A natively `disabled` button never opens a tooltip; use `aria-disabled` instead.
11
+ *
12
+ * The library attaches its hover / focus listeners with `addEventListener` from `Tooltip.Root`,
13
+ * so every `on*` prop passes through untouched.
14
+ *
15
+ * Owned keys (conventions 1.2): `aria-describedby` (with `provides="description"`) or
16
+ * `aria-labelledby` (with `provides="name"`), both token-merged with the consumer's ids;
17
+ * `data-disabled`; and `interestfor` when `experimental_interestFor` is on.
18
+ */
19
+ function TooltipTrigger({ experimental_interestFor = false, className, style, children, render, ...rest }) {
20
+ const ctx = useTooltipContext("Tooltip.Trigger");
21
+ const { disabled, popupId, provides, setInterestForRequested } = ctx;
22
+ const native = experimental_interestFor && !disabled;
23
+ useLayoutEffect(() => {
24
+ setInterestForRequested(native);
25
+ }, [native, setInterestForRequested]);
26
+ const state = { disabled };
27
+ const libraryProps = {
28
+ ...provides === "name" ? { "aria-labelledby": popupId } : { "aria-describedby": popupId },
29
+ "data-disabled": dataAttr(disabled),
30
+ ...experimental_interestFor && { interestfor: native ? popupId : void 0 }
31
+ };
32
+ return useRender({
33
+ defaultElement: "button",
34
+ render,
35
+ state,
36
+ className,
37
+ style: native ? (s) => ({
38
+ interestDelayStart: `${ctx.delay}ms`,
39
+ interestDelayEnd: `${ctx.closeDelay}ms`,
40
+ ...resolveStateValue(style, s)
41
+ }) : style,
42
+ children,
43
+ props: rendersButton(render) ? {
44
+ type: "button",
45
+ ...rest
46
+ } : rest,
47
+ libraryProps,
48
+ ref: ctx.setTriggerElement
49
+ });
50
+ }
51
+ TooltipTrigger.displayName = "TooltipTrigger";
52
+ //#endregion
53
+ export { TooltipTrigger };
54
+
55
+ //# sourceMappingURL=tooltip-trigger.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip-trigger.mjs","names":[],"sources":["../../src/tooltip/tooltip-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useLayoutEffect } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useTooltipContext } from \"./tooltip-context.js\";\n\nexport interface TooltipTriggerState {\n readonly disabled: boolean;\n}\n\nexport interface TooltipTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"className\" | \"style\" | \"children\"\n> {\n /**\n * Experimental (Chromium 142+). Renders `interestfor` and `interest-delay-start/end` so the\n * browser owns hover, focus, Escape and long-press; the library's JS trigger path is skipped\n * where the rendered element implements `interestForElement`. No effect in other engines (the\n * attribute is ignored and the JS path runs). Pair with `Tooltip.Popup experimental_hint`.\n * Default `false`.\n */\n experimental_interestFor?: boolean;\n className?: StateValue<string | undefined, TooltipTriggerState>;\n style?: StateValue<CSSProperties | undefined, TooltipTriggerState>;\n children?: StateValue<ReactNode, TooltipTriggerState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, TooltipTriggerState>;\n}\n\n/**\n * The element the user hovers or focuses: a `<button type=\"button\">` by default. `render` may\n * substitute a link or a custom button; the element must be focusable (a dev warning fires when\n * it is not). A natively `disabled` button never opens a tooltip; use `aria-disabled` instead.\n *\n * The library attaches its hover / focus listeners with `addEventListener` from `Tooltip.Root`,\n * so every `on*` prop passes through untouched.\n *\n * Owned keys (conventions 1.2): `aria-describedby` (with `provides=\"description\"`) or\n * `aria-labelledby` (with `provides=\"name\"`), both token-merged with the consumer's ids;\n * `data-disabled`; and `interestfor` when `experimental_interestFor` is on.\n */\nexport function TooltipTrigger({\n experimental_interestFor = false,\n className,\n style,\n children,\n render,\n ...rest\n}: TooltipTriggerProps) {\n const ctx = useTooltipContext(\"Tooltip.Trigger\");\n const { disabled, popupId, provides, setInterestForRequested } = ctx;\n const native = experimental_interestFor && !disabled;\n\n useLayoutEffect(() => {\n setInterestForRequested(native);\n }, [native, setInterestForRequested]);\n\n const state: TooltipTriggerState = { disabled };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n // Only the relation in use is owned; the other one passes through untouched.\n ...(provides === \"name\" ? { \"aria-labelledby\": popupId } : { \"aria-describedby\": popupId }),\n \"data-disabled\": dataAttr(disabled),\n // Owned while the flag is on: a disabled tooltip removes the attribute, so the browser\n // cannot open it either. Unflagged, the key is omitted (conventions 1.2).\n ...(experimental_interestFor && { interestfor: native ? popupId : undefined }),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, TooltipTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n // The native delays sit under the consumer's style: the consumer wins.\n style: native\n ? (s) => ({\n interestDelayStart: `${ctx.delay}ms`,\n interestDelayEnd: `${ctx.closeDelay}ms`,\n ...resolveStateValue(style, s),\n })\n : style,\n children,\n // `type=\"button\"` only on a real button (the consumer may override it); a link or a\n // component element does not get it.\n props: rendersButton(render) ? { type: \"button\", ...rest } : rest,\n libraryProps,\n ref: ctx.setTriggerElement,\n });\n}\n\nTooltipTrigger.displayName = \"TooltipTrigger\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2CA,SAAgB,eAAe,EAC7B,2BAA2B,OAC3B,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,MAAM,kBAAkB,iBAAiB;CAC/C,MAAM,EAAE,UAAU,SAAS,UAAU,4BAA4B;CACjE,MAAM,SAAS,4BAA4B,CAAC;CAE5C,sBAAsB;EACpB,wBAAwB,MAAM;CAChC,GAAG,CAAC,QAAQ,uBAAuB,CAAC;CAEpC,MAAM,QAA6B,EAAE,SAAS;CAE9C,MAAM,eAAiE;EAErE,GAAI,aAAa,SAAS,EAAE,mBAAmB,QAAQ,IAAI,EAAE,oBAAoB,QAAQ;EACzF,iBAAiB,SAAS,QAAQ;EAGlC,GAAI,4BAA4B,EAAE,aAAa,SAAS,UAAU,KAAA,EAAU;CAC9E;CAEA,OAAO,UAAgE;EACrE,gBAAgB;EAChB;EACA;EACA;EAEA,OAAO,UACF,OAAO;GACN,oBAAoB,GAAG,IAAI,MAAM;GACjC,kBAAkB,GAAG,IAAI,WAAW;GACpC,GAAG,kBAAkB,OAAO,CAAC;EAC/B,KACA;EACJ;EAGA,OAAO,cAAc,MAAM,IAAI;GAAE,MAAM;GAAU,GAAG;EAAK,IAAI;EAC7D;EACA,KAAK,IAAI;CACX,CAAC;AACH;AAEA,eAAe,cAAc"}
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { createDelayedPair } from "../internal/use-delayed-pair.mjs";
3
+ import { useEffect, useEffectEvent } from "react";
4
+ //#region src/tooltip/use-tooltip-interaction.ts
5
+ /**
6
+ * The cross-engine JS path of design section 2.2: hover / keyboard focus with delays,
7
+ * `showPopover({ source })`, Escape and pointerdown-anywhere. Listeners go through
8
+ * `addEventListener` (conventions 7.4) so they can be feature-detected away: where the trigger
9
+ * renders `interestfor` and the element implements `interestForElement`, the browser owns the
10
+ * whole interaction and nothing is attached.
11
+ *
12
+ * Open state is browser-owned (conventions 3.5): every decision reads
13
+ * `popup.matches(":popover-open")`, never a copy. The document listeners are attached right after
14
+ * `showPopover()` (the `toggle` event is queued as a task, so waiting for it would leave a window
15
+ * in which an Escape or pointerdown does nothing) and detached when `toggle` reports a close,
16
+ * including closes the library did not perform (native light dismiss of `popover="hint"`).
17
+ */
18
+ function useTooltipInteraction(trigger, popup, options) {
19
+ const getDelays = useEffectEvent(() => ({
20
+ delay: options.delay,
21
+ closeDelay: options.closeDelay
22
+ }));
23
+ const { disabled, interestForRequested } = options;
24
+ useEffect(() => {
25
+ if (popup === null) return;
26
+ if (disabled) {
27
+ if (isOpen(popup)) popup.hidePopover();
28
+ return;
29
+ }
30
+ if (trigger === null) return;
31
+ if (interestForRequested && trigger.hasAttribute("interestfor") && "interestForElement" in trigger) return;
32
+ const doc = trigger.ownerDocument;
33
+ const timers = createDelayedPair();
34
+ const clear = timers.cancel;
35
+ const show = () => {
36
+ if (isOpen(popup) || !popup.isConnected || !trigger.isConnected) return;
37
+ popup.showPopover({ source: trigger });
38
+ listenDocument(true);
39
+ };
40
+ const hide = () => {
41
+ if (!isOpen(popup)) return;
42
+ popup.hidePopover();
43
+ listenDocument(false);
44
+ };
45
+ const showLater = () => {
46
+ timers.open(getDelays().delay, show);
47
+ };
48
+ const hideLater = () => {
49
+ timers.close(getDelays().closeDelay, hide);
50
+ };
51
+ const hideNow = () => {
52
+ clear();
53
+ hide();
54
+ };
55
+ const onKeyDown = (event) => {
56
+ if (event.key !== "Escape") return;
57
+ clear();
58
+ if (!isOpen(popup)) return;
59
+ hide();
60
+ event.preventDefault();
61
+ };
62
+ function listenDocument(listen) {
63
+ if (listen) {
64
+ doc.addEventListener("keydown", onKeyDown);
65
+ doc.addEventListener("pointerdown", hideNow, true);
66
+ } else {
67
+ doc.removeEventListener("keydown", onKeyDown);
68
+ doc.removeEventListener("pointerdown", hideNow, true);
69
+ }
70
+ }
71
+ const onToggle = () => {
72
+ listenDocument(isOpen(popup));
73
+ };
74
+ const onTriggerEnter = (event) => {
75
+ if (event.pointerType !== "touch") showLater();
76
+ };
77
+ const onTriggerFocus = () => {
78
+ if (trigger.matches(":focus-visible")) showLater();
79
+ };
80
+ const onTriggerKeyDown = (event) => {
81
+ if (event.key === "Escape") clear();
82
+ };
83
+ trigger.addEventListener("pointerenter", onTriggerEnter);
84
+ trigger.addEventListener("pointerleave", hideLater);
85
+ trigger.addEventListener("pointerdown", clear);
86
+ trigger.addEventListener("keydown", onTriggerKeyDown);
87
+ trigger.addEventListener("focus", onTriggerFocus);
88
+ trigger.addEventListener("blur", hideLater);
89
+ popup.addEventListener("pointerenter", clear);
90
+ popup.addEventListener("pointerleave", hideLater);
91
+ popup.addEventListener("toggle", onToggle);
92
+ return () => {
93
+ hideNow();
94
+ listenDocument(false);
95
+ trigger.removeEventListener("pointerenter", onTriggerEnter);
96
+ trigger.removeEventListener("pointerleave", hideLater);
97
+ trigger.removeEventListener("pointerdown", clear);
98
+ trigger.removeEventListener("keydown", onTriggerKeyDown);
99
+ trigger.removeEventListener("focus", onTriggerFocus);
100
+ trigger.removeEventListener("blur", hideLater);
101
+ popup.removeEventListener("pointerenter", clear);
102
+ popup.removeEventListener("pointerleave", hideLater);
103
+ popup.removeEventListener("toggle", onToggle);
104
+ };
105
+ }, [
106
+ trigger,
107
+ popup,
108
+ disabled,
109
+ interestForRequested
110
+ ]);
111
+ }
112
+ /** The popover's top-layer state, read from the DOM. A removed popover is never open. */
113
+ function isOpen(popup) {
114
+ return popup.isConnected && popup.matches(":popover-open");
115
+ }
116
+ //#endregion
117
+ export { useTooltipInteraction };
118
+
119
+ //# sourceMappingURL=use-tooltip-interaction.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tooltip-interaction.mjs","names":[],"sources":["../../src/tooltip/use-tooltip-interaction.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useEffectEvent } from \"react\";\nimport { createDelayedPair } from \"../internal/use-delayed-pair.js\";\n\nexport interface TooltipInteractionOptions {\n readonly delay: number;\n readonly closeDelay: number;\n readonly disabled: boolean;\n /** The Trigger renders `interestfor` (flag on and not disabled). Re-runs the detection. */\n readonly interestForRequested: boolean;\n}\n\n/**\n * The cross-engine JS path of design section 2.2: hover / keyboard focus with delays,\n * `showPopover({ source })`, Escape and pointerdown-anywhere. Listeners go through\n * `addEventListener` (conventions 7.4) so they can be feature-detected away: where the trigger\n * renders `interestfor` and the element implements `interestForElement`, the browser owns the\n * whole interaction and nothing is attached.\n *\n * Open state is browser-owned (conventions 3.5): every decision reads\n * `popup.matches(\":popover-open\")`, never a copy. The document listeners are attached right after\n * `showPopover()` (the `toggle` event is queued as a task, so waiting for it would leave a window\n * in which an Escape or pointerdown does nothing) and detached when `toggle` reports a close,\n * including closes the library did not perform (native light dismiss of `popover=\"hint\"`).\n */\nexport function useTooltipInteraction(\n trigger: HTMLElement | null,\n popup: HTMLElement | null,\n options: TooltipInteractionOptions,\n): void {\n const getDelays = useEffectEvent(() => ({\n delay: options.delay,\n closeDelay: options.closeDelay,\n }));\n const { disabled, interestForRequested } = options;\n\n useEffect(() => {\n if (popup === null) return;\n // Disabling while open hides the popup on both paths (design section 13, Disabled row). On the\n // JS path the previous run's cleanup already did; on the native path nothing else would, and\n // removing `interestfor` does not close an open popover.\n if (disabled) {\n if (isOpen(popup)) popup.hidePopover();\n return;\n }\n if (trigger === null) return;\n // Native path (conventions 8): only when the flag put `interestfor` on the element, and per\n // element, so a `render`ed `<span>` (no `interestForElement`) keeps the JS path. Nothing is\n // attached here, Escape included: the browser's interest handling and the `hint` close watcher\n // decide, so in Chromium an Escape that closes the tooltip may also close an enclosing modal\n // `<dialog>` (the library cannot `preventDefault()` a keydown it never sees).\n if (\n interestForRequested &&\n trigger.hasAttribute(\"interestfor\") &&\n \"interestForElement\" in trigger\n ) {\n return;\n }\n\n const doc = trigger.ownerDocument;\n // One pending open or close at a time (the timer helper shared with Menu's hover intent).\n const timers = createDelayedPair();\n const clear = timers.cancel;\n const show = () => {\n if (isOpen(popup) || !popup.isConnected || !trigger.isConnected) return;\n popup.showPopover({ source: trigger });\n listenDocument(true);\n };\n const hide = () => {\n // A removed popover is already hidden (`isOpen` is false), so this never throws.\n if (!isOpen(popup)) return;\n popup.hidePopover();\n listenDocument(false);\n };\n const showLater = () => {\n timers.open(getDelays().delay, show);\n };\n const hideLater = () => {\n timers.close(getDelays().closeDelay, hide);\n };\n const hideNow = () => {\n clear();\n hide();\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n clear();\n // Escape while closed passes through untouched.\n if (!isOpen(popup)) return;\n hide();\n // This Escape was the tooltip's: an enclosing modal `<dialog>` or `popover=\"auto\"` menu\n // stays open (the close request sees a canceled keydown). A second Escape reaches them.\n event.preventDefault();\n };\n function listenDocument(listen: boolean) {\n if (listen) {\n doc.addEventListener(\"keydown\", onKeyDown);\n doc.addEventListener(\"pointerdown\", hideNow, true);\n } else {\n doc.removeEventListener(\"keydown\", onKeyDown);\n doc.removeEventListener(\"pointerdown\", hideNow, true);\n }\n }\n // Follows opens and closes the library did not perform; reads the DOM, not `newState`, so a\n // toggle task that is stale by the time it runs cannot detach the listeners of an open popup.\n const onToggle = () => {\n listenDocument(isOpen(popup));\n };\n const onTriggerEnter = (event: PointerEvent) => {\n // No hover on touch; the ARIA relation still works for screen-reader focus (design 5.3).\n if (event.pointerType !== \"touch\") showLater();\n };\n const onTriggerFocus = () => {\n // Pointer focus (a click) does not open; keyboard focus does (design 5.2).\n if (trigger.matches(\":focus-visible\")) showLater();\n };\n // The document listeners exist only while the popup is open, so before that the trigger\n // itself cancels a pending open: a click or Escape before `delay` elapses must not open the\n // tooltip afterwards. Once open, the document listeners do the hiding.\n const onTriggerKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") clear();\n };\n\n trigger.addEventListener(\"pointerenter\", onTriggerEnter);\n trigger.addEventListener(\"pointerleave\", hideLater);\n trigger.addEventListener(\"pointerdown\", clear);\n trigger.addEventListener(\"keydown\", onTriggerKeyDown);\n trigger.addEventListener(\"focus\", onTriggerFocus);\n trigger.addEventListener(\"blur\", hideLater);\n popup.addEventListener(\"pointerenter\", clear);\n popup.addEventListener(\"pointerleave\", hideLater);\n popup.addEventListener(\"toggle\", onToggle);\n\n return () => {\n hideNow();\n listenDocument(false);\n trigger.removeEventListener(\"pointerenter\", onTriggerEnter);\n trigger.removeEventListener(\"pointerleave\", hideLater);\n trigger.removeEventListener(\"pointerdown\", clear);\n trigger.removeEventListener(\"keydown\", onTriggerKeyDown);\n trigger.removeEventListener(\"focus\", onTriggerFocus);\n trigger.removeEventListener(\"blur\", hideLater);\n popup.removeEventListener(\"pointerenter\", clear);\n popup.removeEventListener(\"pointerleave\", hideLater);\n popup.removeEventListener(\"toggle\", onToggle);\n };\n }, [trigger, popup, disabled, interestForRequested]);\n}\n\n/** The popover's top-layer state, read from the DOM. A removed popover is never open. */\nfunction isOpen(popup: HTMLElement): boolean {\n return popup.isConnected && popup.matches(\":popover-open\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAyBA,SAAgB,sBACd,SACA,OACA,SACM;CACN,MAAM,YAAY,sBAAsB;EACtC,OAAO,QAAQ;EACf,YAAY,QAAQ;CACtB,EAAE;CACF,MAAM,EAAE,UAAU,yBAAyB;CAE3C,gBAAgB;EACd,IAAI,UAAU,MAAM;EAIpB,IAAI,UAAU;GACZ,IAAI,OAAO,KAAK,GAAG,MAAM,YAAY;GACrC;EACF;EACA,IAAI,YAAY,MAAM;EAMtB,IACE,wBACA,QAAQ,aAAa,aAAa,KAClC,wBAAwB,SAExB;EAGF,MAAM,MAAM,QAAQ;EAEpB,MAAM,SAAS,kBAAkB;EACjC,MAAM,QAAQ,OAAO;EACrB,MAAM,aAAa;GACjB,IAAI,OAAO,KAAK,KAAK,CAAC,MAAM,eAAe,CAAC,QAAQ,aAAa;GACjE,MAAM,YAAY,EAAE,QAAQ,QAAQ,CAAC;GACrC,eAAe,IAAI;EACrB;EACA,MAAM,aAAa;GAEjB,IAAI,CAAC,OAAO,KAAK,GAAG;GACpB,MAAM,YAAY;GAClB,eAAe,KAAK;EACtB;EACA,MAAM,kBAAkB;GACtB,OAAO,KAAK,UAAU,CAAC,CAAC,OAAO,IAAI;EACrC;EACA,MAAM,kBAAkB;GACtB,OAAO,MAAM,UAAU,CAAC,CAAC,YAAY,IAAI;EAC3C;EACA,MAAM,gBAAgB;GACpB,MAAM;GACN,KAAK;EACP;EAEA,MAAM,aAAa,UAAyB;GAC1C,IAAI,MAAM,QAAQ,UAAU;GAC5B,MAAM;GAEN,IAAI,CAAC,OAAO,KAAK,GAAG;GACpB,KAAK;GAGL,MAAM,eAAe;EACvB;EACA,SAAS,eAAe,QAAiB;GACvC,IAAI,QAAQ;IACV,IAAI,iBAAiB,WAAW,SAAS;IACzC,IAAI,iBAAiB,eAAe,SAAS,IAAI;GACnD,OAAO;IACL,IAAI,oBAAoB,WAAW,SAAS;IAC5C,IAAI,oBAAoB,eAAe,SAAS,IAAI;GACtD;EACF;EAGA,MAAM,iBAAiB;GACrB,eAAe,OAAO,KAAK,CAAC;EAC9B;EACA,MAAM,kBAAkB,UAAwB;GAE9C,IAAI,MAAM,gBAAgB,SAAS,UAAU;EAC/C;EACA,MAAM,uBAAuB;GAE3B,IAAI,QAAQ,QAAQ,gBAAgB,GAAG,UAAU;EACnD;EAIA,MAAM,oBAAoB,UAAyB;GACjD,IAAI,MAAM,QAAQ,UAAU,MAAM;EACpC;EAEA,QAAQ,iBAAiB,gBAAgB,cAAc;EACvD,QAAQ,iBAAiB,gBAAgB,SAAS;EAClD,QAAQ,iBAAiB,eAAe,KAAK;EAC7C,QAAQ,iBAAiB,WAAW,gBAAgB;EACpD,QAAQ,iBAAiB,SAAS,cAAc;EAChD,QAAQ,iBAAiB,QAAQ,SAAS;EAC1C,MAAM,iBAAiB,gBAAgB,KAAK;EAC5C,MAAM,iBAAiB,gBAAgB,SAAS;EAChD,MAAM,iBAAiB,UAAU,QAAQ;EAEzC,aAAa;GACX,QAAQ;GACR,eAAe,KAAK;GACpB,QAAQ,oBAAoB,gBAAgB,cAAc;GAC1D,QAAQ,oBAAoB,gBAAgB,SAAS;GACrD,QAAQ,oBAAoB,eAAe,KAAK;GAChD,QAAQ,oBAAoB,WAAW,gBAAgB;GACvD,QAAQ,oBAAoB,SAAS,cAAc;GACnD,QAAQ,oBAAoB,QAAQ,SAAS;GAC7C,MAAM,oBAAoB,gBAAgB,KAAK;GAC/C,MAAM,oBAAoB,gBAAgB,SAAS;GACnD,MAAM,oBAAoB,UAAU,QAAQ;EAC9C;CACF,GAAG;EAAC;EAAS;EAAO;EAAU;CAAoB,CAAC;AACrD;;AAGA,SAAS,OAAO,OAA6B;CAC3C,OAAO,MAAM,eAAe,MAAM,QAAQ,eAAe;AAC3D"}
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@funstack/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Headless UI library for React — the first Async React-native UI library",
5
+ "keywords": [
6
+ "components",
7
+ "headless",
8
+ "react",
9
+ "suspense",
10
+ "transitions",
11
+ "ui"
12
+ ],
13
+ "homepage": "https://ui.funstack.work/",
14
+ "bugs": {
15
+ "url": "https://github.com/uhyo/funstack-ui/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": "uhyo <uhyo@uhy.ooo>",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/uhyo/funstack-ui.git",
22
+ "directory": "packages/ui"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "exports": {
31
+ ".": "./dist/index.mjs",
32
+ "./accordion": "./dist/accordion/index.mjs",
33
+ "./checkbox": "./dist/checkbox/index.mjs",
34
+ "./checkbox-group": "./dist/checkbox-group/index.mjs",
35
+ "./dialog": "./dist/dialog/index.mjs",
36
+ "./menu": "./dist/menu/index.mjs",
37
+ "./popover": "./dist/popover/index.mjs",
38
+ "./radio-group": "./dist/radio-group/index.mjs",
39
+ "./switch": "./dist/switch/index.mjs",
40
+ "./tabs": "./dist/tabs/index.mjs",
41
+ "./toggle": "./dist/toggle/index.mjs",
42
+ "./tooltip": "./dist/tooltip/index.mjs",
43
+ "./package.json": "./package.json"
44
+ },
45
+ "devDependencies": {
46
+ "@testing-library/dom": "^10.4.2",
47
+ "@testing-library/jest-dom": "^7.0.1",
48
+ "@testing-library/react": "^16.3.3",
49
+ "@testing-library/user-event": "^14.6.8",
50
+ "@types/node": "^26.6.5",
51
+ "@types/react": "^19.3.0",
52
+ "@types/react-dom": "^19.3.0",
53
+ "@vitest/browser-playwright": "^5.0.5",
54
+ "jsdom": "^30.1.3",
55
+ "playwright": "^1.64.1",
56
+ "react": "^19.3.1",
57
+ "react-dom": "^19.3.1",
58
+ "tsdown": "^0.23.1",
59
+ "typescript": "^7.0.2",
60
+ "vitest": "^5.0.5"
61
+ },
62
+ "peerDependencies": {
63
+ "react": ">=19.3.0",
64
+ "react-dom": ">=19.3.0"
65
+ },
66
+ "scripts": {
67
+ "build": "tsdown",
68
+ "dev": "tsdown --watch",
69
+ "test": "vitest",
70
+ "test:run": "vitest run",
71
+ "test:jsdom": "vitest run --project jsdom",
72
+ "test:browser": "vitest run --project browser",
73
+ "test:browser:dev": "vitest --project browser --browser.headless=false",
74
+ "typecheck": "tsc --noEmit"
75
+ }
6
76
  }