tempest-react-sdk 0.66.0 → 0.68.0

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 (232) hide show
  1. package/README.md +97 -93
  2. package/dist/auth/create-tempest-auth.cjs +1 -1
  3. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  4. package/dist/auth/create-tempest-auth.js +41 -40
  5. package/dist/auth/create-tempest-auth.js.map +1 -1
  6. package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
  7. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  8. package/dist/components/BottomSheet/BottomSheet.js +35 -40
  9. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  10. package/dist/components/Card/Card.module.cjs.map +1 -1
  11. package/dist/components/Card/Card.module.js.map +1 -1
  12. package/dist/components/Combobox/Combobox.cjs +1 -1
  13. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  14. package/dist/components/Combobox/Combobox.js +96 -74
  15. package/dist/components/Combobox/Combobox.js.map +1 -1
  16. package/dist/components/Combobox/Combobox.module.cjs +1 -1
  17. package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
  18. package/dist/components/Combobox/Combobox.module.js +4 -3
  19. package/dist/components/Combobox/Combobox.module.js.map +1 -1
  20. package/dist/components/Command/Command.cjs +1 -1
  21. package/dist/components/Command/Command.js +4 -4
  22. package/dist/components/ContextMenu/ContextMenu.cjs +1 -1
  23. package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
  24. package/dist/components/ContextMenu/ContextMenu.js +80 -83
  25. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  26. package/dist/components/DataTable/DataTable.cjs +1 -1
  27. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  28. package/dist/components/DataTable/DataTable.js +138 -127
  29. package/dist/components/DataTable/DataTable.js.map +1 -1
  30. package/dist/components/DataTable/use-dev-warnings.cjs +1 -1
  31. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
  32. package/dist/components/DataTable/use-dev-warnings.js +5 -3
  33. package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
  34. package/dist/components/Drawer/Drawer.cjs +1 -1
  35. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  36. package/dist/components/Drawer/Drawer.js +46 -46
  37. package/dist/components/Drawer/Drawer.js.map +1 -1
  38. package/dist/components/DropdownMenu/DropdownMenu.cjs +1 -1
  39. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  40. package/dist/components/DropdownMenu/DropdownMenu.js +129 -105
  41. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  42. package/dist/components/DropdownMenu/DropdownMenu.module.cjs +1 -1
  43. package/dist/components/DropdownMenu/DropdownMenu.module.cjs.map +1 -1
  44. package/dist/components/DropdownMenu/DropdownMenu.module.js +12 -11
  45. package/dist/components/DropdownMenu/DropdownMenu.module.js.map +1 -1
  46. package/dist/components/FilterPanel/FilterPanel.cjs +2 -0
  47. package/dist/components/FilterPanel/FilterPanel.cjs.map +1 -0
  48. package/dist/components/FilterPanel/FilterPanel.js +75 -0
  49. package/dist/components/FilterPanel/FilterPanel.js.map +1 -0
  50. package/dist/components/FilterPanel/FilterPanel.module.cjs +3 -0
  51. package/dist/components/FilterPanel/FilterPanel.module.cjs.map +1 -0
  52. package/dist/components/FilterPanel/FilterPanel.module.js +12 -0
  53. package/dist/components/FilterPanel/FilterPanel.module.js.map +1 -0
  54. package/dist/components/HoverCard/HoverCard.cjs +1 -1
  55. package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
  56. package/dist/components/HoverCard/HoverCard.js +54 -29
  57. package/dist/components/HoverCard/HoverCard.js.map +1 -1
  58. package/dist/components/HoverCard/HoverCard.module.cjs +1 -1
  59. package/dist/components/HoverCard/HoverCard.module.cjs.map +1 -1
  60. package/dist/components/HoverCard/HoverCard.module.js +4 -3
  61. package/dist/components/HoverCard/HoverCard.module.js.map +1 -1
  62. package/dist/components/Lightbox/Lightbox.cjs +1 -1
  63. package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
  64. package/dist/components/Lightbox/Lightbox.js +84 -87
  65. package/dist/components/Lightbox/Lightbox.js.map +1 -1
  66. package/dist/components/Menubar/Menubar.cjs +1 -1
  67. package/dist/components/Menubar/Menubar.cjs.map +1 -1
  68. package/dist/components/Menubar/Menubar.js +86 -65
  69. package/dist/components/Menubar/Menubar.js.map +1 -1
  70. package/dist/components/Menubar/Menubar.module.cjs +1 -1
  71. package/dist/components/Menubar/Menubar.module.cjs.map +1 -1
  72. package/dist/components/Menubar/Menubar.module.js +4 -3
  73. package/dist/components/Menubar/Menubar.module.js.map +1 -1
  74. package/dist/components/Modal/Modal.cjs +1 -1
  75. package/dist/components/Modal/Modal.cjs.map +1 -1
  76. package/dist/components/Modal/Modal.js +47 -51
  77. package/dist/components/Modal/Modal.js.map +1 -1
  78. package/dist/components/MultiSelect/MultiSelect.cjs +1 -1
  79. package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
  80. package/dist/components/MultiSelect/MultiSelect.js +113 -96
  81. package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
  82. package/dist/components/MultiSelect/MultiSelect.module.cjs +1 -1
  83. package/dist/components/MultiSelect/MultiSelect.module.cjs.map +1 -1
  84. package/dist/components/MultiSelect/MultiSelect.module.js +4 -3
  85. package/dist/components/MultiSelect/MultiSelect.module.js.map +1 -1
  86. package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
  87. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  88. package/dist/components/NavigationMenu/NavigationMenu.js +83 -61
  89. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  90. package/dist/components/NavigationMenu/NavigationMenu.module.cjs +1 -1
  91. package/dist/components/NavigationMenu/NavigationMenu.module.cjs.map +1 -1
  92. package/dist/components/NavigationMenu/NavigationMenu.module.js +4 -3
  93. package/dist/components/NavigationMenu/NavigationMenu.module.js.map +1 -1
  94. package/dist/components/Pagination/Pagination.cjs +1 -1
  95. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  96. package/dist/components/Pagination/Pagination.js +31 -30
  97. package/dist/components/Pagination/Pagination.js.map +1 -1
  98. package/dist/components/Popover/Popover.cjs +1 -1
  99. package/dist/components/Popover/Popover.cjs.map +1 -1
  100. package/dist/components/Popover/Popover.js +57 -36
  101. package/dist/components/Popover/Popover.js.map +1 -1
  102. package/dist/components/Popover/Popover.module.cjs +1 -1
  103. package/dist/components/Popover/Popover.module.cjs.map +1 -1
  104. package/dist/components/Popover/Popover.module.js +7 -6
  105. package/dist/components/Popover/Popover.module.js.map +1 -1
  106. package/dist/components/Portal/anchor-position.cjs +2 -0
  107. package/dist/components/Portal/anchor-position.cjs.map +1 -0
  108. package/dist/components/Portal/anchor-position.js +99 -0
  109. package/dist/components/Portal/anchor-position.js.map +1 -0
  110. package/dist/components/Portal/escape-layer.cjs +2 -0
  111. package/dist/components/Portal/escape-layer.cjs.map +1 -0
  112. package/dist/components/Portal/escape-layer.js +30 -0
  113. package/dist/components/Portal/escape-layer.js.map +1 -0
  114. package/dist/components/Portal/tab-bridge.cjs +2 -0
  115. package/dist/components/Portal/tab-bridge.cjs.map +1 -0
  116. package/dist/components/Portal/tab-bridge.js +37 -0
  117. package/dist/components/Portal/tab-bridge.js.map +1 -0
  118. package/dist/components/Select/CaretIcon.cjs +2 -0
  119. package/dist/components/Select/CaretIcon.cjs.map +1 -0
  120. package/dist/components/Select/CaretIcon.js +21 -0
  121. package/dist/components/Select/CaretIcon.js.map +1 -0
  122. package/dist/components/Select/Select.cjs +1 -1
  123. package/dist/components/Select/Select.cjs.map +1 -1
  124. package/dist/components/Select/Select.js +33 -47
  125. package/dist/components/Select/Select.js.map +1 -1
  126. package/dist/components/Select/Select.module.cjs +1 -1
  127. package/dist/components/Select/Select.module.cjs.map +1 -1
  128. package/dist/components/Select/Select.module.js +4 -3
  129. package/dist/components/Select/Select.module.js.map +1 -1
  130. package/dist/components/Select/options.cjs +2 -0
  131. package/dist/components/Select/options.cjs.map +1 -0
  132. package/dist/components/Select/options.js +25 -0
  133. package/dist/components/Select/options.js.map +1 -0
  134. package/dist/components/Sidebar/Sidebar.cjs +1 -1
  135. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  136. package/dist/components/Sidebar/Sidebar.js +49 -43
  137. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  138. package/dist/components/Sidebar/active-nav-key.cjs +2 -0
  139. package/dist/components/Sidebar/active-nav-key.cjs.map +1 -0
  140. package/dist/components/Sidebar/active-nav-key.js +22 -0
  141. package/dist/components/Sidebar/active-nav-key.js.map +1 -0
  142. package/dist/components/Tooltip/Tooltip.cjs +1 -1
  143. package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
  144. package/dist/components/Tooltip/Tooltip.js +43 -29
  145. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  146. package/dist/components/Tooltip/Tooltip.module.cjs +1 -1
  147. package/dist/components/Tooltip/Tooltip.module.cjs.map +1 -1
  148. package/dist/components/Tooltip/Tooltip.module.js +4 -3
  149. package/dist/components/Tooltip/Tooltip.module.js.map +1 -1
  150. package/dist/hooks/tab-sequence.cjs +2 -0
  151. package/dist/hooks/tab-sequence.cjs.map +1 -0
  152. package/dist/hooks/tab-sequence.js +53 -0
  153. package/dist/hooks/tab-sequence.js.map +1 -0
  154. package/dist/hooks/use-deep-memo.cjs +1 -1
  155. package/dist/hooks/use-deep-memo.cjs.map +1 -1
  156. package/dist/hooks/use-deep-memo.js +1 -1
  157. package/dist/hooks/use-deep-memo.js.map +1 -1
  158. package/dist/hooks/use-draft-filters.cjs +2 -0
  159. package/dist/hooks/use-draft-filters.cjs.map +1 -0
  160. package/dist/hooks/use-draft-filters.js +49 -0
  161. package/dist/hooks/use-draft-filters.js.map +1 -0
  162. package/dist/hooks/use-focus-trap.cjs +1 -1
  163. package/dist/hooks/use-focus-trap.cjs.map +1 -1
  164. package/dist/hooks/use-focus-trap.js +31 -34
  165. package/dist/hooks/use-focus-trap.js.map +1 -1
  166. package/dist/hooks/use-sortable.cjs +1 -1
  167. package/dist/hooks/use-sortable.cjs.map +1 -1
  168. package/dist/hooks/use-sortable.js +3 -3
  169. package/dist/hooks/use-sortable.js.map +1 -1
  170. package/dist/http/api-client.cjs +1 -1
  171. package/dist/http/api-client.cjs.map +1 -1
  172. package/dist/http/api-client.js +92 -80
  173. package/dist/http/api-client.js.map +1 -1
  174. package/dist/http/credential-scope.cjs +1 -1
  175. package/dist/http/credential-scope.cjs.map +1 -1
  176. package/dist/http/credential-scope.js +5 -3
  177. package/dist/http/credential-scope.js.map +1 -1
  178. package/dist/http/csrf.cjs +2 -0
  179. package/dist/http/csrf.cjs.map +1 -0
  180. package/dist/http/csrf.js +46 -0
  181. package/dist/http/csrf.js.map +1 -0
  182. package/dist/http/describe-api-error.cjs +1 -1
  183. package/dist/http/describe-api-error.cjs.map +1 -1
  184. package/dist/http/describe-api-error.js +4 -2
  185. package/dist/http/describe-api-error.js.map +1 -1
  186. package/dist/http/resumable-upload.cjs +1 -1
  187. package/dist/http/resumable-upload.cjs.map +1 -1
  188. package/dist/http/resumable-upload.js +131 -123
  189. package/dist/http/resumable-upload.js.map +1 -1
  190. package/dist/http/upload-with-progress.cjs +1 -1
  191. package/dist/http/upload-with-progress.cjs.map +1 -1
  192. package/dist/http/upload-with-progress.js +41 -33
  193. package/dist/http/upload-with-progress.js.map +1 -1
  194. package/dist/query/use-paginated-query.cjs +1 -1
  195. package/dist/query/use-paginated-query.cjs.map +1 -1
  196. package/dist/query/use-paginated-query.js +47 -30
  197. package/dist/query/use-paginated-query.js.map +1 -1
  198. package/dist/styles/FilterPanel.css +2 -0
  199. package/dist/styles/actions.css +3 -0
  200. package/dist/styles/advanced.css +1 -0
  201. package/dist/styles/component/Card.css +1 -1
  202. package/dist/styles/component/Combobox.css +5 -3
  203. package/dist/styles/component/DropdownMenu.css +1 -0
  204. package/dist/styles/component/FilterPanel.css +5 -0
  205. package/dist/styles/component/HoverCard.css +1 -0
  206. package/dist/styles/component/Menubar.css +1 -0
  207. package/dist/styles/component/MultiSelect.css +1 -0
  208. package/dist/styles/component/NavigationMenu.css +1 -0
  209. package/dist/styles/component/Popover.css +1 -0
  210. package/dist/styles/component/Select.css +13 -6
  211. package/dist/styles/component/Tooltip.css +1 -0
  212. package/dist/styles/core.css +4 -4
  213. package/dist/styles/data.css +4 -0
  214. package/dist/styles/forms.css +19 -9
  215. package/dist/styles/identity.css +1 -1
  216. package/dist/styles/manifest.json +6 -0
  217. package/dist/styles/navigation.css +2 -0
  218. package/dist/styles/tokens.css +4 -4
  219. package/dist/styles.css +1 -1
  220. package/dist/tempest-react-sdk.cjs +1 -1
  221. package/dist/tempest-react-sdk.d.ts +805 -35
  222. package/dist/tempest-react-sdk.js +347 -342
  223. package/dist/vite/tempest-csp.cjs +3 -0
  224. package/dist/vite/tempest-csp.cjs.map +1 -0
  225. package/dist/vite/tempest-csp.js +189 -0
  226. package/dist/vite/tempest-csp.js.map +1 -0
  227. package/dist/vite.cjs +1 -1
  228. package/dist/vite.d.ts +145 -0
  229. package/dist/vite.js +2 -1
  230. package/package.json +2 -2
  231. package/template/vite.config.ts +8 -1
  232. package/template-pwa/vite.config.ts +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-nav-key.js","names":[],"sources":["../../../src/components/Sidebar/active-nav-key.ts"],"sourcesContent":["/**\n * Reduce a path or a key to the form two of them are compared in.\n *\n * The query string and the hash are dropped, because `/users?tab=2` is still the\n * `/users` screen, and trailing slashes are dropped, because `/users/` and\n * `/users` are the same route to every router this SDK pairs with. The root keeps\n * its single slash — stripping it would leave an empty string, which is the\n * \"nothing matched\" answer, not a path.\n *\n * @param path - A pathname, an `href`, or a key used as a URL.\n * @returns The comparable form of `path`.\n */\nfunction normalizePath(path: string): string {\n const cut = path.search(/[?#]/);\n const bare = cut === -1 ? path : path.slice(0, cut);\n const trimmed = bare.replace(/\\/+$/, \"\");\n return trimmed === \"\" && bare.startsWith(\"/\") ? \"/\" : trimmed;\n}\n\n/**\n * Whether `candidate` names the screen at `pathname` or one of its ancestors.\n *\n * The match is on **segment boundaries**, not on string prefixes: `/users` covers\n * `/users/3` but not `/users-admin`. The root `/` covers only itself — the same\n * rule react-router's `NavLink` applies — because a root that prefixed every path\n * would light \"Home\" on every screen the menu does not list.\n *\n * @param pathname - The normalized current path.\n * @param candidate - The normalized key or `href`.\n * @returns `true` when the candidate covers the path.\n */\nfunction covers(pathname: string, candidate: string): boolean {\n if (candidate === \"/\") {\n return pathname === \"/\";\n }\n return pathname === candidate || pathname.startsWith(`${candidate}/`);\n}\n\n/**\n * Index of the candidate that owns `pathname`: the longest one covering it.\n *\n * Longest wins so that `/dashboard` does not stay highlighted on\n * `/dashboard/tips/42` — with a first-match rule the order of the menu array would\n * decide, and nobody reading the menu knows it carries meaning. On a tie (`/users`\n * and `/users/` both present), the first one wins.\n *\n * @param pathname - The current route, as the router reports it.\n * @param candidates - One string per navigable entry, in menu order.\n * @returns The winning index, or `-1` when nothing covers the path.\n */\nexport function activeNavIndex(pathname: string, candidates: readonly string[]): number {\n const path = normalizePath(pathname);\n let winner = -1;\n let winnerLength = -1;\n candidates.forEach((raw, index) => {\n const candidate = normalizePath(raw);\n if (candidate !== \"\" && covers(path, candidate) && candidate.length > winnerLength) {\n winner = index;\n winnerLength = candidate.length;\n }\n });\n return winner;\n}\n\n/**\n * Resolve which navigation key is active for the current route.\n *\n * A menu whose keys are URLs almost never has a key equal to the pathname —\n * `/dashboard/tips/42` has to highlight `/dashboard/tips`. This picks the\n * **longest key that covers the path on a segment boundary**, which is what fixes\n * the two ways the one-line `keys.find((k) => pathname.startsWith(k))` goes wrong:\n * `/dashboard` stays lit on every screen of the panel (the array order decides),\n * and `/dashboard/tip` lights `/dashboard/tips`.\n *\n * The rules:\n *\n * - Query string and hash are ignored, on both sides.\n * - A trailing slash, on the route or on the key, changes nothing.\n * - `/` is active only on `/` itself, as with react-router's `NavLink`.\n * - The comparison is case-sensitive, as URL paths are.\n * - No match returns `\"\"` — never `undefined`, never a throw — so the result can\n * go straight into a `value` prop.\n *\n * The keys and the pathname have to live in the same space: with a router\n * `basename`, pass `useLocation().pathname` (which has the basename stripped) and\n * keys without it.\n *\n * Framework-agnostic on purpose: it takes a string, so it works with\n * react-router, with `window.location.pathname`, and for `BottomNavigation`,\n * `NavigationRail` or a `Drawer` menu just as for `Sidebar` — which does the same\n * resolution itself with `match=\"route\"`.\n *\n * @param pathname - The current route, e.g. `useLocation().pathname`.\n * @param keys - The navigation keys, each one a URL path.\n * @returns The active key exactly as given in `keys`, or `\"\"` when none covers\n * the route.\n *\n * @example\n * const { pathname } = useLocation();\n * const value = activeNavKey(pathname, [\"/dashboard\", \"/dashboard/tips\"]);\n * // \"/dashboard/tips/42\" → \"/dashboard/tips\"\n * // \"/dashboard/tip\" → \"/dashboard\"\n */\nexport function activeNavKey(pathname: string, keys: readonly string[]): string {\n return keys[activeNavIndex(pathname, keys)] ?? \"\";\n}\n"],"mappings":";AAYA,SAAS,EAAc,GAAsB;CACzC,IAAM,IAAM,EAAK,OAAO,MAAM,GACxB,IAAO,MAAQ,KAAK,IAAO,EAAK,MAAM,GAAG,CAAG,GAC5C,IAAU,EAAK,QAAQ,QAAQ,EAAE;CACvC,OAAO,MAAY,MAAM,EAAK,WAAW,GAAG,IAAI,MAAM;AAC1D;AAcA,SAAS,EAAO,GAAkB,GAA4B;CAI1D,OAHI,MAAc,MACP,MAAa,MAEjB,MAAa,KAAa,EAAS,WAAW,GAAG,EAAU,EAAE;AACxE;AAcA,SAAgB,EAAe,GAAkB,GAAuC;CACpF,IAAM,IAAO,EAAc,CAAQ,GAC/B,IAAS,IACT,IAAe;CAQnB,OAPA,EAAW,SAAS,GAAK,MAAU;EAC/B,IAAM,IAAY,EAAc,CAAG;EACnC,AAAI,MAAc,MAAM,EAAO,GAAM,CAAS,KAAK,EAAU,SAAS,MAClE,IAAS,GACT,IAAe,EAAU;CAEjC,CAAC,GACM;AACX;AAyCA,SAAgB,EAAa,GAAkB,GAAiC;CAC5E,OAAO,EAAK,EAAe,GAAU,CAAI,MAAM;AACnD"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("./Tooltip.module.cjs");let n=require("react"),r=require("react/jsx-runtime");function i({content:i,placement:a=`top`,children:o,disabled:s=!1,openDelay:c=150}){let[l,u]=(0,n.useState)(!1),d=(0,n.useId)(),f=(0,n.useRef)(null);function p(){f.current!==null&&(clearTimeout(f.current),f.current=null)}function m(){s||(p(),f.current=setTimeout(()=>u(!0),c))}function h(){p(),u(!1)}(0,n.useEffect)(()=>()=>{p()},[]);let g=(0,n.cloneElement)(o,{onMouseEnter:m,onMouseLeave:h,onFocus:m,onBlur:h,"aria-describedby":l?d:void 0});return(0,r.jsxs)(`span`,{className:t.default.trigger,children:[g,l&&(0,r.jsx)(`span`,{id:d,role:`tooltip`,className:e.cn(t.default.bubble,t.default[a]),children:i})]})}exports.Tooltip=i;
1
+ const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../Portal/Portal.cjs"),r=require("../Portal/anchor-position.cjs"),i=require("./Tooltip.module.cjs");let a=require("react"),o=require("react/jsx-runtime");var s=8;function c({content:c,placement:l=`top`,children:u,disabled:d=!1,openDelay:f=150,portal:p=!0}){let[m,h]=(0,a.useState)(!1),[g,_]=(0,a.useState)(null),[v,y]=(0,a.useState)(null),b=r.useAnchorPosition({anchor:g,floating:v,side:l,align:`center`,offset:s,enabled:p&&m}),x=(0,a.useId)(),S=(0,a.useRef)(null);function C(){S.current!==null&&(clearTimeout(S.current),S.current=null)}function w(){d||(C(),S.current=setTimeout(()=>h(!0),f))}function T(){C(),h(!1)}(0,a.useEffect)(()=>()=>{C()},[]),t.useEscapeLayer(m,T);let E=(0,a.cloneElement)(u,{onMouseEnter:w,onMouseLeave:T,onFocus:w,onBlur:T,"aria-describedby":m?x:void 0}),D=(0,o.jsx)(`span`,{ref:y,id:x,role:`tooltip`,className:e.cn(i.default.bubble,i.default[l],p&&i.default.portalled),style:b,children:c});return(0,o.jsxs)(`span`,{ref:_,className:i.default.trigger,children:[E,m&&(p?(0,o.jsx)(n.Portal,{children:D}):D)]})}exports.Tooltip=c;
2
2
  //# sourceMappingURL=Tooltip.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n}\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n return (\n <span className={styles.trigger}>\n {trigger}\n {open && (\n <span\n id={tooltipId}\n role=\"tooltip\"\n className={cn(styles.bubble, styles[placement])}\n >\n {content}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":"8HA2BA,SAAgB,EAAQ,CACpB,UACA,YAAY,MACZ,WACA,WAAW,GACX,YAAY,KACC,CACb,GAAM,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAkB,EAAK,EACzC,GAAA,EAAY,EAAA,MAAA,CAAM,EASlB,GAAA,EAAW,EAAA,OAAA,CAA6C,IAAI,EAElE,SAAS,GAAmB,CACpB,EAAS,UAAY,OACrB,aAAa,EAAS,OAAO,EAC7B,EAAS,QAAU,KAE3B,CAEA,SAAS,GAAa,CACd,IACJ,EAAW,EACX,EAAS,QAAU,eAAiB,EAAQ,EAAI,EAAG,CAAS,EAChE,CAEA,SAAS,GAAa,CAClB,EAAW,EACX,EAAQ,EAAK,CACjB,EAEA,EAAA,EAAA,UAAA,SACiB,CACT,EAAW,CACf,EACD,CAAC,CAAC,EAEL,IAAM,GAAA,EAAU,EAAA,aAAA,CAAa,EAAU,CACnC,aAAc,EACd,aAAc,EACd,QAAS,EACT,OAAQ,EACR,mBAAoB,EAAO,EAAY,IAAA,EAC3C,CAA4B,EAE5B,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,QAAxB,SAAA,CACK,EACA,IACG,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,GAAI,EACJ,KAAK,UACL,UAAW,EAAA,GAAG,EAAA,QAAO,OAAQ,EAAA,QAAO,EAAU,EAE7C,SAAA,CACC,CAAA,CAER,GAEd"}
1
+ {"version":3,"file":"Tooltip.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n /**\n * Render the bubble in a portal, positioned against the trigger. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the bubble —\n * a `DataTable` cell cut it 23 px short (measured in Chrome at 1440 px). In a\n * portal it escapes the clip and flips to the opposite side at the viewport\n * edge. `false` keeps the in-flow bubble.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and bubble, in px — the in-flow CSS uses the same 8 px. */\nconst TOOLTIP_OFFSET = 8;\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n *\n * `Escape` hides it without moving the pointer or the focus, which is what\n * WCAG 2.2 SC 1.4.13 (Content on Hover or Focus) asks of content that appears on\n * hover. It is an `Escape` layer like the overlays, so inside a `Modal` the first\n * press hides the tooltip and the second closes the modal.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n portal = true,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const [triggerNode, setTriggerNode] = useState<HTMLSpanElement | null>(null);\n const [bubbleNode, setBubbleNode] = useState<HTMLSpanElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: triggerNode,\n floating: bubbleNode,\n side: placement,\n align: \"center\",\n offset: TOOLTIP_OFFSET,\n enabled: portal && open,\n });\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n useEscapeLayer(open, hide);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n const bubble = (\n <span\n ref={setBubbleNode}\n id={tooltipId}\n role=\"tooltip\"\n className={cn(styles.bubble, styles[placement], portal && styles.portalled)}\n style={floatingStyle}\n >\n {content}\n </span>\n );\n\n return (\n <span ref={setTriggerNode} className={styles.trigger}>\n {trigger}\n {open && (portal ? <Portal>{bubble}</Portal> : bubble)}\n </span>\n );\n}\n"],"mappings":"mPAmCA,IAAM,EAAiB,EAYvB,SAAgB,EAAQ,CACpB,UACA,YAAY,MACZ,WACA,WAAW,GACX,YAAY,IACZ,SAAS,IACI,CACb,GAAM,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAkB,EAAK,EACzC,CAAC,EAAa,IAAA,EAAkB,EAAA,SAAA,CAAiC,IAAI,EACrE,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAiC,IAAI,EACnE,EAAgB,EAAA,kBAAkB,CACpC,OAAQ,EACR,SAAU,EACV,KAAM,EACN,MAAO,SACP,OAAQ,EACR,QAAS,GAAU,CACvB,CAAC,EACK,GAAA,EAAY,EAAA,MAAA,CAAM,EASlB,GAAA,EAAW,EAAA,OAAA,CAA6C,IAAI,EAElE,SAAS,GAAmB,CACpB,EAAS,UAAY,OACrB,aAAa,EAAS,OAAO,EAC7B,EAAS,QAAU,KAE3B,CAEA,SAAS,GAAa,CACd,IACJ,EAAW,EACX,EAAS,QAAU,eAAiB,EAAQ,EAAI,EAAG,CAAS,EAChE,CAEA,SAAS,GAAa,CAClB,EAAW,EACX,EAAQ,EAAK,CACjB,EAEA,EAAA,EAAA,UAAA,SACiB,CACT,EAAW,CACf,EACD,CAAC,CAAC,EAEL,EAAA,eAAe,EAAM,CAAI,EAEzB,IAAM,GAAA,EAAU,EAAA,aAAA,CAAa,EAAU,CACnC,aAAc,EACd,aAAc,EACd,QAAS,EACT,OAAQ,EACR,mBAAoB,EAAO,EAAY,IAAA,EAC3C,CAA4B,EAEtB,GACF,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,IAAK,EACL,GAAI,EACJ,KAAK,UACL,UAAW,EAAA,GAAG,EAAA,QAAO,OAAQ,EAAA,QAAO,GAAY,GAAU,EAAA,QAAO,SAAS,EAC1E,MAAO,EAEN,SAAA,CACC,CAAA,EAGV,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,IAAK,EAAgB,UAAW,EAAA,QAAO,QAA7C,SAAA,CACK,EACA,IAAS,GAAS,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAA,SAAS,CAAe,CAAA,EAAI,EAC7C,GAEd"}
@@ -1,40 +1,54 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
- import t from "./Tooltip.module.js";
3
- import { cloneElement as n, useEffect as r, useId as i, useRef as a, useState as o } from "react";
4
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
2
+ import { useEscapeLayer as t } from "../Portal/escape-layer.js";
3
+ import { Portal as n } from "../Portal/Portal.js";
4
+ import { useAnchorPosition as r } from "../Portal/anchor-position.js";
5
+ import i from "./Tooltip.module.js";
6
+ import { cloneElement as a, useEffect as o, useId as s, useRef as c, useState as l } from "react";
7
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
5
8
  //#region src/components/Tooltip/Tooltip.tsx
6
- function l({ content: l, placement: u = "top", children: d, disabled: f = !1, openDelay: p = 150 }) {
7
- let [m, h] = o(!1), g = i(), _ = a(null);
8
- function v() {
9
- _.current !== null && (clearTimeout(_.current), _.current = null);
9
+ var f = 8;
10
+ function p({ content: p, placement: m = "top", children: h, disabled: g = !1, openDelay: _ = 150, portal: v = !0 }) {
11
+ let [y, b] = l(!1), [x, S] = l(null), [C, w] = l(null), T = r({
12
+ anchor: x,
13
+ floating: C,
14
+ side: m,
15
+ align: "center",
16
+ offset: f,
17
+ enabled: v && y
18
+ }), E = s(), D = c(null);
19
+ function O() {
20
+ D.current !== null && (clearTimeout(D.current), D.current = null);
10
21
  }
11
- function y() {
12
- f || (v(), _.current = setTimeout(() => h(!0), p));
22
+ function k() {
23
+ g || (O(), D.current = setTimeout(() => b(!0), _));
13
24
  }
14
- function b() {
15
- v(), h(!1);
25
+ function A() {
26
+ O(), b(!1);
16
27
  }
17
- r(() => () => {
18
- v();
19
- }, []);
20
- let x = n(d, {
21
- onMouseEnter: y,
22
- onMouseLeave: b,
23
- onFocus: y,
24
- onBlur: b,
25
- "aria-describedby": m ? g : void 0
28
+ o(() => () => {
29
+ O();
30
+ }, []), t(y, A);
31
+ let j = a(h, {
32
+ onMouseEnter: k,
33
+ onMouseLeave: A,
34
+ onFocus: k,
35
+ onBlur: A,
36
+ "aria-describedby": y ? E : void 0
37
+ }), M = /* @__PURE__ */ u("span", {
38
+ ref: w,
39
+ id: E,
40
+ role: "tooltip",
41
+ className: e(i.bubble, i[m], v && i.portalled),
42
+ style: T,
43
+ children: p
26
44
  });
27
- return /* @__PURE__ */ c("span", {
28
- className: t.trigger,
29
- children: [x, m && /* @__PURE__ */ s("span", {
30
- id: g,
31
- role: "tooltip",
32
- className: e(t.bubble, t[u]),
33
- children: l
34
- })]
45
+ return /* @__PURE__ */ d("span", {
46
+ ref: S,
47
+ className: i.trigger,
48
+ children: [j, y && (v ? /* @__PURE__ */ u(n, { children: M }) : M)]
35
49
  });
36
50
  }
37
51
  //#endregion
38
- export { l as Tooltip };
52
+ export { p as Tooltip };
39
53
 
40
54
  //# sourceMappingURL=Tooltip.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n}\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n return (\n <span className={styles.trigger}>\n {trigger}\n {open && (\n <span\n id={tooltipId}\n role=\"tooltip\"\n className={cn(styles.bubble, styles[placement])}\n >\n {content}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;AA2BA,SAAgB,EAAQ,EACpB,YACA,eAAY,OACZ,aACA,cAAW,IACX,eAAY,OACC;CACb,IAAM,CAAC,GAAM,KAAW,EAAkB,EAAK,GACzC,IAAY,EAAM,GASlB,IAAW,EAA6C,IAAI;CAElE,SAAS,IAAmB;EACxB,AAAI,EAAS,YAAY,SACrB,aAAa,EAAS,OAAO,GAC7B,EAAS,UAAU;CAE3B;CAEA,SAAS,IAAa;EACd,MACJ,EAAW,GACX,EAAS,UAAU,iBAAiB,EAAQ,EAAI,GAAG,CAAS;CAChE;CAEA,SAAS,IAAa;EAElB,AADA,EAAW,GACX,EAAQ,EAAK;CACjB;CAEA,cACiB;EACT,EAAW;CACf,GACD,CAAC,CAAC;CAEL,IAAM,IAAU,EAAa,GAAU;EACnC,cAAc;EACd,cAAc;EACd,SAAS;EACT,QAAQ;EACR,oBAAoB,IAAO,IAAY,KAAA;CAC3C,CAA4B;CAE5B,OACI,kBAAC,QAAD;EAAM,WAAW,EAAO;EAAxB,UAAA,CACK,GACA,KACG,kBAAC,QAAD;GACI,IAAI;GACJ,MAAK;GACL,WAAW,EAAG,EAAO,QAAQ,EAAO,EAAU;GAE7C,UAAA;EACC,CAAA,CAER;;AAEd"}
1
+ {"version":3,"file":"Tooltip.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the handlers below read the timer ref, and they only\n run on user events (hover/focus), never during render */\nimport { cloneElement, useEffect, useId, useRef, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /** Tooltip content. Plain text recommended; rich content also works. */\n content: ReactNode;\n /** Placement relative to the trigger. Default: `\"top\"`. */\n placement?: TooltipPlacement;\n /** Single React element to attach hover/focus listeners to. */\n children: ReactElement;\n /** Disable the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Delay (ms) before showing the tooltip. Default: 150. */\n openDelay?: number;\n /**\n * Render the bubble in a portal, positioned against the trigger. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the bubble —\n * a `DataTable` cell cut it 23 px short (measured in Chrome at 1440 px). In a\n * portal it escapes the clip and flips to the opposite side at the viewport\n * edge. `false` keeps the in-flow bubble.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and bubble, in px — the in-flow CSS uses the same 8 px. */\nconst TOOLTIP_OFFSET = 8;\n\n/**\n * Lightweight tooltip. Shows on hover and on focus (keyboard-friendly). Wraps\n * a single child element via `cloneElement`, so the trigger keeps its own ref\n * and props.\n *\n * `Escape` hides it without moving the pointer or the focus, which is what\n * WCAG 2.2 SC 1.4.13 (Content on Hover or Focus) asks of content that appears on\n * hover. It is an `Escape` layer like the overlays, so inside a `Modal` the first\n * press hides the tooltip and the second closes the modal.\n */\nexport function Tooltip({\n content,\n placement = \"top\",\n children,\n disabled = false,\n openDelay = 150,\n portal = true,\n}: TooltipProps) {\n const [open, setOpen] = useState<boolean>(false);\n const [triggerNode, setTriggerNode] = useState<HTMLSpanElement | null>(null);\n const [bubbleNode, setBubbleNode] = useState<HTMLSpanElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: triggerNode,\n floating: bubbleNode,\n side: placement,\n align: \"center\",\n offset: TOOLTIP_OFFSET,\n enabled: portal && open,\n });\n const tooltipId = useId();\n /**\n * Handle of the pending open timer.\n *\n * A ref, not a `let` in the component body: that binding is recreated on every\n * render, so any re-render between `show` and `hide` (a parent update, a state\n * change elsewhere) dropped the handle and `clearTimeout` silently cancelled\n * nothing — the tooltip then opened after the pointer had already left.\n */\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n function clearTimer(): void {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }\n\n function show(): void {\n if (disabled) return;\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }\n\n function hide(): void {\n clearTimer();\n setOpen(false);\n }\n\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, []);\n\n useEscapeLayer(open, hide);\n\n const trigger = cloneElement(children, {\n onMouseEnter: show,\n onMouseLeave: hide,\n onFocus: show,\n onBlur: hide,\n \"aria-describedby\": open ? tooltipId : undefined,\n } as Record<string, unknown>);\n\n const bubble = (\n <span\n ref={setBubbleNode}\n id={tooltipId}\n role=\"tooltip\"\n className={cn(styles.bubble, styles[placement], portal && styles.portalled)}\n style={floatingStyle}\n >\n {content}\n </span>\n );\n\n return (\n <span ref={setTriggerNode} className={styles.trigger}>\n {trigger}\n {open && (portal ? <Portal>{bubble}</Portal> : bubble)}\n </span>\n );\n}\n"],"mappings":";;;;;;;;AAmCA,IAAM,IAAiB;AAYvB,SAAgB,EAAQ,EACpB,YACA,eAAY,OACZ,aACA,cAAW,IACX,eAAY,KACZ,YAAS,MACI;CACb,IAAM,CAAC,GAAM,KAAW,EAAkB,EAAK,GACzC,CAAC,GAAa,KAAkB,EAAiC,IAAI,GACrE,CAAC,GAAY,KAAiB,EAAiC,IAAI,GACnE,IAAgB,EAAkB;EACpC,QAAQ;EACR,UAAU;EACV,MAAM;EACN,OAAO;EACP,QAAQ;EACR,SAAS,KAAU;CACvB,CAAC,GACK,IAAY,EAAM,GASlB,IAAW,EAA6C,IAAI;CAElE,SAAS,IAAmB;EACxB,AAAI,EAAS,YAAY,SACrB,aAAa,EAAS,OAAO,GAC7B,EAAS,UAAU;CAE3B;CAEA,SAAS,IAAa;EACd,MACJ,EAAW,GACX,EAAS,UAAU,iBAAiB,EAAQ,EAAI,GAAG,CAAS;CAChE;CAEA,SAAS,IAAa;EAElB,AADA,EAAW,GACX,EAAQ,EAAK;CACjB;CAQA,AANA,cACiB;EACT,EAAW;CACf,GACD,CAAC,CAAC,GAEL,EAAe,GAAM,CAAI;CAEzB,IAAM,IAAU,EAAa,GAAU;EACnC,cAAc;EACd,cAAc;EACd,SAAS;EACT,QAAQ;EACR,oBAAoB,IAAO,IAAY,KAAA;CAC3C,CAA4B,GAEtB,IACF,kBAAC,QAAD;EACI,KAAK;EACL,IAAI;EACJ,MAAK;EACL,WAAW,EAAG,EAAO,QAAQ,EAAO,IAAY,KAAU,EAAO,SAAS;EAC1E,OAAO;EAEN,UAAA;CACC,CAAA;CAGV,OACI,kBAAC,QAAD;EAAM,KAAK;EAAgB,WAAW,EAAO;EAA7C,UAAA,CACK,GACA,MAAS,IAAS,kBAAC,GAAD,EAAA,UAAS,EAAe,CAAA,IAAI,EAC7C;;AAEd"}
@@ -1,3 +1,3 @@
1
1
  require("../../styles/component/Tooltip.css");
2
- var e=`tempest_trigger_Dmc5E`,t=`tempest_bubble_TPGHB`,n=`tempest_tempest-tooltip-in_csYeZ`,r=`tempest_top_m2tnn`,i=`tempest_bottom_9Twz4`,a=`tempest_left_UPdrG`,o=`tempest_right_CV--T`,s={trigger:e,bubble:t,tempestTooltipIn:n,top:r,bottom:i,left:a,right:o};exports.bottom=i,exports.bubble=t,exports.default=s,exports.left=a,exports.right=o,exports.tempestTooltipIn=n,exports.top=r,exports.trigger=e;
2
+ var e=`tempest_trigger_Dmc5E`,t=`tempest_bubble_TPGHB`,n=`tempest_tempest-tooltip-in_csYeZ`,r=`tempest_top_m2tnn`,i=`tempest_bottom_9Twz4`,a=`tempest_left_UPdrG`,o=`tempest_right_CV--T`,s=`tempest_portalled_MImIY`,c={trigger:e,bubble:t,tempestTooltipIn:n,top:r,bottom:i,left:a,right:o,portalled:s};exports.bottom=i,exports.bubble=t,exports.default=c,exports.left=a,exports.portalled=s,exports.right=o,exports.tempestTooltipIn=n,exports.top=r,exports.trigger=e;
3
3
  //# sourceMappingURL=Tooltip.module.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.module.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"Tooltip.module.cjs","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.portalled {\n right: auto;\n bottom: auto;\n transform: none;\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
@@ -1,15 +1,16 @@
1
1
  import "../../styles/component/Tooltip.css";
2
2
  //#region src/components/Tooltip/Tooltip.module.css
3
- var e = "tempest_trigger_Dmc5E", t = "tempest_bubble_TPGHB", n = "tempest_tempest-tooltip-in_csYeZ", r = "tempest_top_m2tnn", i = "tempest_bottom_9Twz4", a = "tempest_left_UPdrG", o = "tempest_right_CV--T", s = {
3
+ var e = "tempest_trigger_Dmc5E", t = "tempest_bubble_TPGHB", n = "tempest_tempest-tooltip-in_csYeZ", r = "tempest_top_m2tnn", i = "tempest_bottom_9Twz4", a = "tempest_left_UPdrG", o = "tempest_right_CV--T", s = "tempest_portalled_MImIY", c = {
4
4
  trigger: e,
5
5
  bubble: t,
6
6
  tempestTooltipIn: n,
7
7
  top: r,
8
8
  bottom: i,
9
9
  left: a,
10
- right: o
10
+ right: o,
11
+ portalled: s
11
12
  };
12
13
  //#endregion
13
- export { i as bottom, t as bubble, s as default, a as left, o as right, n as tempestTooltipIn, r as top, e as trigger };
14
+ export { i as bottom, t as bubble, c as default, a as left, s as portalled, o as right, n as tempestTooltipIn, r as top, e as trigger };
14
15
 
15
16
  //# sourceMappingURL=Tooltip.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.module.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"Tooltip.module.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.module.css"],"sourcesContent":[".trigger {\n display: inline-flex;\n position: relative;\n}\n\n.bubble {\n position: absolute;\n z-index: var(--tempest-z-tooltip);\n background-color: var(--tempest-gray-900);\n color: #ffffff;\n padding: 6px 10px;\n border-radius: var(--tempest-radius-sm);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n white-space: nowrap;\n pointer-events: none;\n box-shadow: var(--tempest-shadow-lg);\n animation: tempest-tooltip-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.portalled {\n right: auto;\n bottom: auto;\n transform: none;\n}\n\n@keyframes tempest-tooltip-in {\n from {\n opacity: 0;\n transform: scale(0.94) translate(var(--tempest-tx, 0), var(--tempest-ty, 0));\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .bubble {\n animation: none;\n }\n}\n\n/* Hide tooltip on touch / no-hover devices — tap+hold leaves\n * tooltip stuck visible and competes with native long-press menus. */\n@media (hover: none) {\n .bubble {\n display: none;\n }\n}\n"],"mappings":";"}
@@ -0,0 +1,2 @@
1
+ var e=[`a[href]`,`button:not([disabled])`,`textarea:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`[tabindex]:not([tabindex='-1'])`].join(`,`),t=new Set;function n(e){return t.add(e),()=>{t.delete(e)}}function r(t){return Array.from(t.querySelectorAll(e)).filter(e=>{if(e.tabIndex<0||e.hasAttribute(`aria-hidden`))return!1;let t=window.getComputedStyle(e);return t.display!==`none`&&t.visibility!==`hidden`})}function i(e,t){let n=t.filter(t=>e.contains(t.anchor)&&!e.contains(t.panel));return{owned:n,rest:t.filter(e=>!n.includes(e))}}function a(e){let n=[e],r=[...t];for(let e=0;e<n.length;e+=1){let{owned:t,rest:a}=i(n[e],r);n.push(...t.map(e=>e.panel)),r=a}return n}function o(e,n=[...t]){let a=r(e),{owned:s,rest:c}=i(e,n),l=new Map;for(let e of s){let t=-1;a.forEach((n,r)=>{((n.compareDocumentPosition(e.anchor)&Node.DOCUMENT_POSITION_FOLLOWING)!==0||e.anchor.contains(n))&&(t=r)}),l.set(t,[...l.get(t)??[],...o(e.panel,c)])}let u=[...l.get(-1)??[]];return a.forEach((e,t)=>{u.push(e,...l.get(t)??[])}),u}exports.FOCUSABLE_SELECTOR=e,exports.registerPortalLayer=n,exports.tabRegion=a,exports.tabSequence=o,exports.tabbables=r;
2
+ //# sourceMappingURL=tab-sequence.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab-sequence.cjs","names":[],"sources":["../../src/hooks/tab-sequence.ts"],"sourcesContent":["/** Elements that can take keyboard focus — shared by the focus trap and the portal tab order. */\nexport const FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\n/** A portalled panel and the element of the page it was opened from. */\nexport interface PortalLayer {\n /** Wrapper of the trigger — the spot in the page the panel belongs to. */\n anchor: HTMLElement;\n /** The panel, mounted at the end of `body`. */\n panel: HTMLElement;\n}\n\nconst layers = new Set<PortalLayer>();\n\n/**\n * Declare a portalled panel as belonging to the spot of its anchor.\n *\n * A portal takes the panel out of the subtree it was opened from, so a focus\n * trap around that subtree would see the panel as \"outside\" and pull focus away\n * from it. Registered layers are what {@link tabSequence} and\n * {@link tabRegion} splice back in.\n *\n * @param layer - The anchor and the panel.\n * @returns A function that removes the registration.\n */\nexport function registerPortalLayer(layer: PortalLayer): () => void {\n layers.add(layer);\n return () => {\n layers.delete(layer);\n };\n}\n\n/**\n * The elements `Tab` can stop on inside `root`, in document order.\n *\n * @param root - Where to look.\n * @returns The tabbable descendants that are not disabled, hidden or removed\n * from the tab order.\n */\nexport function tabbables(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((el) => {\n if (el.tabIndex < 0 || el.hasAttribute(\"aria-hidden\")) return false;\n const style = window.getComputedStyle(el);\n return style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n}\n\n/**\n * Split the layers into those opened from inside `root` and the rest.\n *\n * @param root - The subtree the layers are matched against.\n * @param pending - The layers not yet assigned to a subtree.\n * @returns The layers whose anchor lives in `root` (and whose panel does not),\n * and the remaining ones.\n */\nfunction ownedBy(\n root: HTMLElement,\n pending: PortalLayer[],\n): { owned: PortalLayer[]; rest: PortalLayer[] } {\n const owned = pending.filter(\n (layer) => root.contains(layer.anchor) && !root.contains(layer.panel),\n );\n return { owned, rest: pending.filter((layer) => !owned.includes(layer)) };\n}\n\n/**\n * Every subtree focus may sit in while `root` is trapped: `root` itself plus the\n * panels opened from it, transitively (a popover opened from a popover).\n *\n * @param root - The trapped container.\n * @returns `root` followed by the panels that belong to it.\n */\nexport function tabRegion(root: HTMLElement): HTMLElement[] {\n const region: HTMLElement[] = [root];\n let pending = [...layers];\n for (let index = 0; index < region.length; index += 1) {\n const { owned, rest } = ownedBy(region[index]!, pending);\n region.push(...owned.map((layer) => layer.panel));\n pending = rest;\n }\n return region;\n}\n\n/**\n * The order `Tab` walks inside `root`, with each owned portalled panel spliced\n * right after the last stop of its anchor — where it would sit had it rendered\n * in flow. That is the same order `usePortalTabOrder` restores between the\n * trigger and the panel, so the trap wraps at the same ends the user walks.\n *\n * @param root - The trapped container.\n * @param pending - Layers not yet placed; defaults to every registered one.\n * @returns The tab stops of `root` and of its panels, in walking order.\n */\nexport function tabSequence(\n root: HTMLElement,\n pending: PortalLayer[] = [...layers],\n): HTMLElement[] {\n const native = tabbables(root);\n const { owned, rest } = ownedBy(root, pending);\n const inserted = new Map<number, HTMLElement[]>();\n for (const layer of owned) {\n let at = -1;\n native.forEach((el, index) => {\n const precedes =\n (el.compareDocumentPosition(layer.anchor) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;\n if (precedes || layer.anchor.contains(el)) at = index;\n });\n inserted.set(at, [...(inserted.get(at) ?? []), ...tabSequence(layer.panel, rest)]);\n }\n const sequence: HTMLElement[] = [...(inserted.get(-1) ?? [])];\n native.forEach((el, index) => {\n sequence.push(el, ...(inserted.get(index) ?? []));\n });\n return sequence;\n}\n"],"mappings":"AACA,IAAa,EAAqB,CAC9B,UACA,yBACA,2BACA,wBACA,yBACA,iCACJ,CAAC,CAAC,KAAK,GAAG,EAUJ,EAAS,IAAI,IAanB,SAAgB,EAAoB,EAAgC,CAEhE,OADA,EAAO,IAAI,CAAK,MACH,CACT,EAAO,OAAO,CAAK,CACvB,CACJ,CASA,SAAgB,EAAU,EAAiC,CACvD,OAAO,MAAM,KAAK,EAAK,iBAA8B,CAAkB,CAAC,CAAC,CAAC,OAAQ,GAAO,CACrF,GAAI,EAAG,SAAW,GAAK,EAAG,aAAa,aAAa,EAAG,MAAO,GAC9D,IAAM,EAAQ,OAAO,iBAAiB,CAAE,EACxC,OAAO,EAAM,UAAY,QAAU,EAAM,aAAe,QAC5D,CAAC,CACL,CAUA,SAAS,EACL,EACA,EAC6C,CAC7C,IAAM,EAAQ,EAAQ,OACjB,GAAU,EAAK,SAAS,EAAM,MAAM,GAAK,CAAC,EAAK,SAAS,EAAM,KAAK,CACxE,EACA,MAAO,CAAE,QAAO,KAAM,EAAQ,OAAQ,GAAU,CAAC,EAAM,SAAS,CAAK,CAAC,CAAE,CAC5E,CASA,SAAgB,EAAU,EAAkC,CACxD,IAAM,EAAwB,CAAC,CAAI,EAC/B,EAAU,CAAC,GAAG,CAAM,EACxB,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAO,OAAQ,GAAS,EAAG,CACnD,GAAM,CAAE,QAAO,QAAS,EAAQ,EAAO,GAAS,CAAO,EACvD,EAAO,KAAK,GAAG,EAAM,IAAK,GAAU,EAAM,KAAK,CAAC,EAChD,EAAU,CACd,CACA,OAAO,CACX,CAYA,SAAgB,EACZ,EACA,EAAyB,CAAC,GAAG,CAAM,EACtB,CACb,IAAM,EAAS,EAAU,CAAI,EACvB,CAAE,QAAO,QAAS,EAAQ,EAAM,CAAO,EACvC,EAAW,IAAI,IACrB,IAAK,IAAM,KAAS,EAAO,CACvB,IAAI,EAAK,GACT,EAAO,SAAS,EAAI,IAAU,GAErB,EAAG,wBAAwB,EAAM,MAAM,EAAI,KAAK,+BAAiC,GACtE,EAAM,OAAO,SAAS,CAAE,KAAG,EAAK,EACpD,CAAC,EACD,EAAS,IAAI,EAAI,CAAC,GAAI,EAAS,IAAI,CAAE,GAAK,CAAC,EAAI,GAAG,EAAY,EAAM,MAAO,CAAI,CAAC,CAAC,CACrF,CACA,IAAM,EAA0B,CAAC,GAAI,EAAS,IAAI,EAAE,GAAK,CAAC,CAAE,EAI5D,OAHA,EAAO,SAAS,EAAI,IAAU,CAC1B,EAAS,KAAK,EAAI,GAAI,EAAS,IAAI,CAAK,GAAK,CAAC,CAAE,CACpD,CAAC,EACM,CACX"}
@@ -0,0 +1,53 @@
1
+ //#region src/hooks/tab-sequence.ts
2
+ var e = [
3
+ "a[href]",
4
+ "button:not([disabled])",
5
+ "textarea:not([disabled])",
6
+ "input:not([disabled])",
7
+ "select:not([disabled])",
8
+ "[tabindex]:not([tabindex='-1'])"
9
+ ].join(","), t = /* @__PURE__ */ new Set();
10
+ function n(e) {
11
+ return t.add(e), () => {
12
+ t.delete(e);
13
+ };
14
+ }
15
+ function r(t) {
16
+ return Array.from(t.querySelectorAll(e)).filter((e) => {
17
+ if (e.tabIndex < 0 || e.hasAttribute("aria-hidden")) return !1;
18
+ let t = window.getComputedStyle(e);
19
+ return t.display !== "none" && t.visibility !== "hidden";
20
+ });
21
+ }
22
+ function i(e, t) {
23
+ let n = t.filter((t) => e.contains(t.anchor) && !e.contains(t.panel));
24
+ return {
25
+ owned: n,
26
+ rest: t.filter((e) => !n.includes(e))
27
+ };
28
+ }
29
+ function a(e) {
30
+ let n = [e], r = [...t];
31
+ for (let e = 0; e < n.length; e += 1) {
32
+ let { owned: t, rest: a } = i(n[e], r);
33
+ n.push(...t.map((e) => e.panel)), r = a;
34
+ }
35
+ return n;
36
+ }
37
+ function o(e, n = [...t]) {
38
+ let a = r(e), { owned: s, rest: c } = i(e, n), l = /* @__PURE__ */ new Map();
39
+ for (let e of s) {
40
+ let t = -1;
41
+ a.forEach((n, r) => {
42
+ ((n.compareDocumentPosition(e.anchor) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 || e.anchor.contains(n)) && (t = r);
43
+ }), l.set(t, [...l.get(t) ?? [], ...o(e.panel, c)]);
44
+ }
45
+ let u = [...l.get(-1) ?? []];
46
+ return a.forEach((e, t) => {
47
+ u.push(e, ...l.get(t) ?? []);
48
+ }), u;
49
+ }
50
+ //#endregion
51
+ export { e as FOCUSABLE_SELECTOR, n as registerPortalLayer, a as tabRegion, o as tabSequence, r as tabbables };
52
+
53
+ //# sourceMappingURL=tab-sequence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab-sequence.js","names":[],"sources":["../../src/hooks/tab-sequence.ts"],"sourcesContent":["/** Elements that can take keyboard focus — shared by the focus trap and the portal tab order. */\nexport const FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\n/** A portalled panel and the element of the page it was opened from. */\nexport interface PortalLayer {\n /** Wrapper of the trigger — the spot in the page the panel belongs to. */\n anchor: HTMLElement;\n /** The panel, mounted at the end of `body`. */\n panel: HTMLElement;\n}\n\nconst layers = new Set<PortalLayer>();\n\n/**\n * Declare a portalled panel as belonging to the spot of its anchor.\n *\n * A portal takes the panel out of the subtree it was opened from, so a focus\n * trap around that subtree would see the panel as \"outside\" and pull focus away\n * from it. Registered layers are what {@link tabSequence} and\n * {@link tabRegion} splice back in.\n *\n * @param layer - The anchor and the panel.\n * @returns A function that removes the registration.\n */\nexport function registerPortalLayer(layer: PortalLayer): () => void {\n layers.add(layer);\n return () => {\n layers.delete(layer);\n };\n}\n\n/**\n * The elements `Tab` can stop on inside `root`, in document order.\n *\n * @param root - Where to look.\n * @returns The tabbable descendants that are not disabled, hidden or removed\n * from the tab order.\n */\nexport function tabbables(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((el) => {\n if (el.tabIndex < 0 || el.hasAttribute(\"aria-hidden\")) return false;\n const style = window.getComputedStyle(el);\n return style.display !== \"none\" && style.visibility !== \"hidden\";\n });\n}\n\n/**\n * Split the layers into those opened from inside `root` and the rest.\n *\n * @param root - The subtree the layers are matched against.\n * @param pending - The layers not yet assigned to a subtree.\n * @returns The layers whose anchor lives in `root` (and whose panel does not),\n * and the remaining ones.\n */\nfunction ownedBy(\n root: HTMLElement,\n pending: PortalLayer[],\n): { owned: PortalLayer[]; rest: PortalLayer[] } {\n const owned = pending.filter(\n (layer) => root.contains(layer.anchor) && !root.contains(layer.panel),\n );\n return { owned, rest: pending.filter((layer) => !owned.includes(layer)) };\n}\n\n/**\n * Every subtree focus may sit in while `root` is trapped: `root` itself plus the\n * panels opened from it, transitively (a popover opened from a popover).\n *\n * @param root - The trapped container.\n * @returns `root` followed by the panels that belong to it.\n */\nexport function tabRegion(root: HTMLElement): HTMLElement[] {\n const region: HTMLElement[] = [root];\n let pending = [...layers];\n for (let index = 0; index < region.length; index += 1) {\n const { owned, rest } = ownedBy(region[index]!, pending);\n region.push(...owned.map((layer) => layer.panel));\n pending = rest;\n }\n return region;\n}\n\n/**\n * The order `Tab` walks inside `root`, with each owned portalled panel spliced\n * right after the last stop of its anchor — where it would sit had it rendered\n * in flow. That is the same order `usePortalTabOrder` restores between the\n * trigger and the panel, so the trap wraps at the same ends the user walks.\n *\n * @param root - The trapped container.\n * @param pending - Layers not yet placed; defaults to every registered one.\n * @returns The tab stops of `root` and of its panels, in walking order.\n */\nexport function tabSequence(\n root: HTMLElement,\n pending: PortalLayer[] = [...layers],\n): HTMLElement[] {\n const native = tabbables(root);\n const { owned, rest } = ownedBy(root, pending);\n const inserted = new Map<number, HTMLElement[]>();\n for (const layer of owned) {\n let at = -1;\n native.forEach((el, index) => {\n const precedes =\n (el.compareDocumentPosition(layer.anchor) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;\n if (precedes || layer.anchor.contains(el)) at = index;\n });\n inserted.set(at, [...(inserted.get(at) ?? []), ...tabSequence(layer.panel, rest)]);\n }\n const sequence: HTMLElement[] = [...(inserted.get(-1) ?? [])];\n native.forEach((el, index) => {\n sequence.push(el, ...(inserted.get(index) ?? []));\n });\n return sequence;\n}\n"],"mappings":";AACA,IAAa,IAAqB;CAC9B;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC,CAAC,KAAK,GAAG,GAUJ,oBAAS,IAAI,IAAiB;AAapC,SAAgB,EAAoB,GAAgC;CAEhE,OADA,EAAO,IAAI,CAAK,SACH;EACT,EAAO,OAAO,CAAK;CACvB;AACJ;AASA,SAAgB,EAAU,GAAiC;CACvD,OAAO,MAAM,KAAK,EAAK,iBAA8B,CAAkB,CAAC,CAAC,CAAC,QAAQ,MAAO;EACrF,IAAI,EAAG,WAAW,KAAK,EAAG,aAAa,aAAa,GAAG,OAAO;EAC9D,IAAM,IAAQ,OAAO,iBAAiB,CAAE;EACxC,OAAO,EAAM,YAAY,UAAU,EAAM,eAAe;CAC5D,CAAC;AACL;AAUA,SAAS,EACL,GACA,GAC6C;CAC7C,IAAM,IAAQ,EAAQ,QACjB,MAAU,EAAK,SAAS,EAAM,MAAM,KAAK,CAAC,EAAK,SAAS,EAAM,KAAK,CACxE;CACA,OAAO;EAAE;EAAO,MAAM,EAAQ,QAAQ,MAAU,CAAC,EAAM,SAAS,CAAK,CAAC;CAAE;AAC5E;AASA,SAAgB,EAAU,GAAkC;CACxD,IAAM,IAAwB,CAAC,CAAI,GAC/B,IAAU,CAAC,GAAG,CAAM;CACxB,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAO,QAAQ,KAAS,GAAG;EACnD,IAAM,EAAE,UAAO,YAAS,EAAQ,EAAO,IAAS,CAAO;EAEvD,AADA,EAAO,KAAK,GAAG,EAAM,KAAK,MAAU,EAAM,KAAK,CAAC,GAChD,IAAU;CACd;CACA,OAAO;AACX;AAYA,SAAgB,EACZ,GACA,IAAyB,CAAC,GAAG,CAAM,GACtB;CACb,IAAM,IAAS,EAAU,CAAI,GACvB,EAAE,UAAO,YAAS,EAAQ,GAAM,CAAO,GACvC,oBAAW,IAAI,IAA2B;CAChD,KAAK,IAAM,KAAS,GAAO;EACvB,IAAI,IAAK;EAMT,AALA,EAAO,SAAS,GAAI,MAAU;GAG1B,EADK,EAAG,wBAAwB,EAAM,MAAM,IAAI,KAAK,iCAAiC,KACtE,EAAM,OAAO,SAAS,CAAE,OAAG,IAAK;EACpD,CAAC,GACD,EAAS,IAAI,GAAI,CAAC,GAAI,EAAS,IAAI,CAAE,KAAK,CAAC,GAAI,GAAG,EAAY,EAAM,OAAO,CAAI,CAAC,CAAC;CACrF;CACA,IAAM,IAA0B,CAAC,GAAI,EAAS,IAAI,EAAE,KAAK,CAAC,CAAE;CAI5D,OAHA,EAAO,SAAS,GAAI,MAAU;EAC1B,EAAS,KAAK,GAAI,GAAI,EAAS,IAAI,CAAK,KAAK,CAAC,CAAE;CACpD,CAAC,GACM;AACX"}
@@ -1,2 +1,2 @@
1
- let e=require("react");function t(e,n){if(Object.is(e,n))return!0;if(typeof e!=`object`||typeof n!=`object`||e===null||n===null||Array.isArray(e)!==Array.isArray(n))return!1;if(Array.isArray(e)&&Array.isArray(n))return e.length===n.length&&e.every((e,r)=>t(e,n[r]));let r=Object.keys(e),i=Object.keys(n);return r.length===i.length&&r.every(r=>t(e[r],n[r]))}function n(n){let r=(0,e.useRef)(n);return t(r.current,n)||(r.current=n),r.current}exports.useDeepMemo=n;
1
+ let e=require("react");function t(e,n){if(Object.is(e,n))return!0;if(typeof e!=`object`||typeof n!=`object`||e===null||n===null||Array.isArray(e)!==Array.isArray(n))return!1;if(Array.isArray(e)&&Array.isArray(n))return e.length===n.length&&e.every((e,r)=>t(e,n[r]));let r=Object.keys(e),i=Object.keys(n);return r.length===i.length&&r.every(r=>t(e[r],n[r]))}function n(n){let r=(0,e.useRef)(n);return t(r.current,n)||(r.current=n),r.current}exports.deepEqual=t,exports.useDeepMemo=n;
2
2
  //# sourceMappingURL=use-deep-memo.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-deep-memo.cjs","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":"uBAGA,SAAS,EAAU,EAAY,EAAqB,CAChD,GAAI,OAAO,GAAG,EAAG,CAAC,EAAG,MAAO,GAE5B,GADI,OAAO,GAAM,UAAY,OAAO,GAAM,UAAY,IAAM,MAAQ,IAAM,MACtE,MAAM,QAAQ,CAAC,IAAM,MAAM,QAAQ,CAAC,EAAG,MAAO,GAClD,GAAI,MAAM,QAAQ,CAAC,GAAK,MAAM,QAAQ,CAAC,EAEnC,OADI,EAAE,SAAW,EAAE,QACZ,EAAE,OAAO,EAAO,IAAU,EAAU,EAAO,EAAE,EAAM,CAAC,EAE/D,IAAM,EAAQ,OAAO,KAAK,CAAW,EAC/B,EAAQ,OAAO,KAAK,CAAW,EAErC,OADI,EAAM,SAAW,EAAM,QACpB,EAAM,MAAO,GAChB,EAAW,EAA8B,GAAO,EAA8B,EAAI,CACtF,CACJ,CAYA,SAAgB,EAAe,EAAa,CACxC,IAAM,GAAA,EAAM,EAAA,OAAA,CAAU,CAAK,EAI3B,OAHK,EAAU,EAAI,QAAS,CAAK,IAC7B,EAAI,QAAU,GAEX,EAAI,OACf"}
1
+ {"version":3,"file":"use-deep-memo.cjs","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\n/**\n * Structural equality over plain objects and arrays, the comparison `useDeepMemo`\n * and `useDraftFilters` share. Internal: not re-exported from the barrel.\n *\n * @param a - First value.\n * @param b - Second value.\n * @returns Whether both values have the same shape and leaves.\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":"uBAWA,SAAgB,EAAU,EAAY,EAAqB,CACvD,GAAI,OAAO,GAAG,EAAG,CAAC,EAAG,MAAO,GAE5B,GADI,OAAO,GAAM,UAAY,OAAO,GAAM,UAAY,IAAM,MAAQ,IAAM,MACtE,MAAM,QAAQ,CAAC,IAAM,MAAM,QAAQ,CAAC,EAAG,MAAO,GAClD,GAAI,MAAM,QAAQ,CAAC,GAAK,MAAM,QAAQ,CAAC,EAEnC,OADI,EAAE,SAAW,EAAE,QACZ,EAAE,OAAO,EAAO,IAAU,EAAU,EAAO,EAAE,EAAM,CAAC,EAE/D,IAAM,EAAQ,OAAO,KAAK,CAAW,EAC/B,EAAQ,OAAO,KAAK,CAAW,EAErC,OADI,EAAM,SAAW,EAAM,QACpB,EAAM,MAAO,GAChB,EAAW,EAA8B,GAAO,EAA8B,EAAI,CACtF,CACJ,CAYA,SAAgB,EAAe,EAAa,CACxC,IAAM,GAAA,EAAM,EAAA,OAAA,CAAU,CAAK,EAI3B,OAHK,EAAU,EAAI,QAAS,CAAK,IAC7B,EAAI,QAAU,GAEX,EAAI,OACf"}
@@ -12,6 +12,6 @@ function n(n) {
12
12
  return t(r.current, n) || (r.current = n), r.current;
13
13
  }
14
14
  //#endregion
15
- export { n as useDeepMemo };
15
+ export { t as deepEqual, n as useDeepMemo };
16
16
 
17
17
  //# sourceMappingURL=use-deep-memo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-deep-memo.js","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":";;AAGA,SAAS,EAAU,GAAY,GAAqB;CAChD,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,OAAO;CAE5B,IADI,OAAO,KAAM,YAAY,OAAO,KAAM,YAAY,MAAM,QAAQ,MAAM,QACtE,MAAM,QAAQ,CAAC,MAAM,MAAM,QAAQ,CAAC,GAAG,OAAO;CAClD,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAEnC,OADI,EAAE,WAAW,EAAE,UACZ,EAAE,OAAO,GAAO,MAAU,EAAU,GAAO,EAAE,EAAM,CAAC;CAE/D,IAAM,IAAQ,OAAO,KAAK,CAAW,GAC/B,IAAQ,OAAO,KAAK,CAAW;CAErC,OADI,EAAM,WAAW,EAAM,UACpB,EAAM,OAAO,MAChB,EAAW,EAA8B,IAAO,EAA8B,EAAI,CACtF;AACJ;AAYA,SAAgB,EAAe,GAAa;CACxC,IAAM,IAAM,EAAU,CAAK;CAI3B,OAHK,EAAU,EAAI,SAAS,CAAK,MAC7B,EAAI,UAAU,IAEX,EAAI;AACf"}
1
+ {"version":3,"file":"use-deep-memo.js","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\n/**\n * Structural equality over plain objects and arrays, the comparison `useDeepMemo`\n * and `useDraftFilters` share. Internal: not re-exported from the barrel.\n *\n * @param a - First value.\n * @param b - Second value.\n * @returns Whether both values have the same shape and leaves.\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":";;AAWA,SAAgB,EAAU,GAAY,GAAqB;CACvD,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,OAAO;CAE5B,IADI,OAAO,KAAM,YAAY,OAAO,KAAM,YAAY,MAAM,QAAQ,MAAM,QACtE,MAAM,QAAQ,CAAC,MAAM,MAAM,QAAQ,CAAC,GAAG,OAAO;CAClD,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAEnC,OADI,EAAE,WAAW,EAAE,UACZ,EAAE,OAAO,GAAO,MAAU,EAAU,GAAO,EAAE,EAAM,CAAC;CAE/D,IAAM,IAAQ,OAAO,KAAK,CAAW,GAC/B,IAAQ,OAAO,KAAK,CAAW;CAErC,OADI,EAAM,WAAW,EAAM,UACpB,EAAM,OAAO,MAChB,EAAW,EAA8B,IAAO,EAA8B,EAAI,CACtF;AACJ;AAYA,SAAgB,EAAe,GAAa;CACxC,IAAM,IAAM,EAAU,CAAK;CAI3B,OAHK,EAAU,EAAI,SAAS,CAAK,MAC7B,EAAI,UAAU,IAEX,EAAI;AACf"}
@@ -0,0 +1,2 @@
1
+ const e=require("./use-deep-memo.cjs"),t=require("./use-latest-ref.cjs");let n=require("react");function r(e,t){return typeof t==`function`?t(e):{...e,...t}}function i(i,a={}){let[o,s]=(0,n.useState)(()=>({draft:i,applied:i})),c=(0,n.useRef)(o.applied),l=(0,n.useRef)(o),u=t.useLatestRef(a),d=(0,n.useCallback)((t,n)=>(u.current.isEqual??e.deepEqual)(t,n),[u]),f=(0,n.useCallback)(e=>{l.current=e,s(e)},[]),p=(0,n.useCallback)(e=>{let t=l.current;f({draft:r(t.draft,e),applied:t.applied})},[f]),m=(0,n.useCallback)(e=>{let t=l.current,n=d(e,t.applied)?t.applied:e;f({draft:e,applied:n}),u.current.onApply?.(n)},[f,d,u]),h=(0,n.useCallback)(e=>{let t=l.current.draft;m(e===void 0?t:r(t,e))},[m]),g=(0,n.useCallback)(()=>m(c.current),[m]);return{draft:o.draft,applied:o.applied,isDirty:!(a.isEqual??e.deepEqual)(o.draft,o.applied),set:p,apply:h,clear:g}}exports.useDraftFilters=i;
2
+ //# sourceMappingURL=use-draft-filters.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-draft-filters.cjs","names":[],"sources":["../../src/hooks/use-draft-filters.ts"],"sourcesContent":["import { useCallback, useRef, useState } from \"react\";\n\nimport { deepEqual } from \"./use-deep-memo\";\nimport { useLatestRef } from \"./use-latest-ref\";\n\n/** An edit to the draft: a partial object merged over it, or an updater. */\nexport type DraftFiltersPatch<T> = Partial<T> | ((draft: T) => T);\n\nexport interface UseDraftFiltersOptions<T> {\n /**\n * Called with the applied value on every `apply()` and `clear()` — the place to\n * reset a page number you keep yourself. `usePaginatedQuery` does not need it:\n * it goes back to page 1 on its own when `applied` changes its `queryKey`.\n */\n onApply?: (applied: T) => void;\n /** Equality behind `isDirty` and the stable `applied`. Default: structural. */\n isEqual?: (a: T, b: T) => boolean;\n}\n\nexport interface UseDraftFiltersResult<T> {\n /** What the filter controls edit. Changes on every keystroke. */\n draft: T;\n /**\n * What the query reads. Changes only on `apply()`/`clear()`, and keeps its\n * reference when the value applied is equal to the current one — safe to put\n * in a `queryKey`.\n */\n applied: T;\n /** Whether the draft differs from what is applied — enable the apply button on it. */\n isDirty: boolean;\n /** Edit the draft, merging a partial object or running an updater. Never applies. */\n set: (patch: DraftFiltersPatch<T>) => void;\n /**\n * Publish the draft. With a patch, merge it into the draft **and** publish the\n * result in the same call — \"filter by this code\" from a table row.\n */\n apply: (patch?: DraftFiltersPatch<T>) => void;\n /** Reset the draft and the applied value to the initial one, together. */\n clear: () => void;\n}\n\n/** Both halves, kept in one state so they can never be committed apart. */\ninterface DraftFiltersState<T> {\n draft: T;\n applied: T;\n}\n\n/**\n * Resolve a patch against a draft.\n *\n * @param draft - The current draft.\n * @param patch - A partial object to merge, or an updater.\n * @returns The next draft.\n */\nfunction patchDraft<T>(draft: T, patch: DraftFiltersPatch<T>): T {\n return typeof patch === \"function\" ? patch(draft) : { ...draft, ...patch };\n}\n\n/**\n * Draft vs applied state for a server-side filter: the controls edit a draft\n * freely, and the query only sees it when the user applies (a click, or Enter).\n *\n * A server-side filter cannot react to every keystroke — each character typed in\n * a search box would be a request. Every admin screen then writes the same pair of\n * `useState`s, and gets the same three details wrong:\n *\n * - **Clearing moves both halves.** Resetting only the draft leaves a filtered\n * list under an empty bar; resetting only the applied value leaves the opposite.\n * - **Applying a value that is not in state yet.** \"Filter by this code\" has to\n * write the draft and publish it in one gesture; calling `setDraft(x)` and then\n * publishing `draft` reads the previous value. `apply(patch)` computes the next\n * draft from a ref that every call keeps current, so it never depends on the\n * order React flushes state in.\n * - **Resetting the page.** Filtering while on page 7 of a result that now has two\n * pages shows an empty table. `usePaginatedQuery` handles it by itself when\n * `applied` is in its `queryKey`; a page kept elsewhere resets in `onApply`.\n *\n * There is no effect in here: applying happens inside the event that asked for it,\n * so one click is one request. `applied` keeps its reference while nothing new is\n * applied, so it can sit in a `queryKey` without a phantom refetch.\n *\n * Like `useState`, the initial value is read on the first render only; `clear()`\n * returns to it.\n *\n * @param initial - The empty filter form: what the screen shows and queries first.\n * @param options - `onApply` and `isEqual`.\n * @returns The draft, the applied value and the gestures between them.\n *\n * @example\n * const filters = useDraftFilters({ code: \"\", level: \"all\" });\n * const logs = usePaginatedQuery({\n * queryKey: [\"logs\", filters.applied],\n * queryFn: (params) => api.listLogs({ ...params, ...toQuery(filters.applied) }),\n * });\n * // <Input value={filters.draft.code} onChange={(e) => filters.set({ code: e.target.value })} />\n * // <Button disabled={!filters.isDirty} onClick={() => filters.apply()}>Filtrar</Button>\n */\nexport function useDraftFilters<T extends object>(\n initial: T,\n options: UseDraftFiltersOptions<T> = {},\n): UseDraftFiltersResult<T> {\n const [state, setState] = useState<DraftFiltersState<T>>(() => ({\n draft: initial,\n applied: initial,\n }));\n const initialRef = useRef<T>(state.applied);\n const stateRef = useRef<DraftFiltersState<T>>(state);\n const optionsRef = useLatestRef(options);\n\n const equal = useCallback(\n (a: T, b: T): boolean => (optionsRef.current.isEqual ?? deepEqual)(a, b),\n [optionsRef],\n );\n\n const commit = useCallback((next: DraftFiltersState<T>): void => {\n stateRef.current = next;\n setState(next);\n }, []);\n\n const set = useCallback(\n (patch: DraftFiltersPatch<T>): void => {\n const current = stateRef.current;\n commit({ draft: patchDraft(current.draft, patch), applied: current.applied });\n },\n [commit],\n );\n\n const publish = useCallback(\n (next: T): void => {\n const current = stateRef.current;\n const applied = equal(next, current.applied) ? current.applied : next;\n commit({ draft: next, applied });\n optionsRef.current.onApply?.(applied);\n },\n [commit, equal, optionsRef],\n );\n\n const apply = useCallback(\n (patch?: DraftFiltersPatch<T>): void => {\n const draft = stateRef.current.draft;\n publish(patch === undefined ? draft : patchDraft(draft, patch));\n },\n [publish],\n );\n\n const clear = useCallback((): void => publish(initialRef.current), [publish]);\n\n return {\n draft: state.draft,\n applied: state.applied,\n isDirty: !(options.isEqual ?? deepEqual)(state.draft, state.applied),\n set,\n apply,\n clear,\n };\n}\n"],"mappings":"gGAsDA,SAAS,EAAc,EAAU,EAAgC,CAC7D,OAAO,OAAO,GAAU,WAAa,EAAM,CAAK,EAAI,CAAE,GAAG,EAAO,GAAG,CAAM,CAC7E,CAyCA,SAAgB,EACZ,EACA,EAAqC,CAAC,EACd,CACxB,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,MAAsC,CAC5D,MAAO,EACP,QAAS,CACb,EAAE,EACI,GAAA,EAAa,EAAA,OAAA,CAAU,EAAM,OAAO,EACpC,GAAA,EAAW,EAAA,OAAA,CAA6B,CAAK,EAC7C,EAAa,EAAA,aAAa,CAAO,EAEjC,GAAA,EAAQ,EAAA,YAAA,EACT,EAAM,KAAmB,EAAW,QAAQ,SAAW,EAAA,UAAA,CAAW,EAAG,CAAC,EACvE,CAAC,CAAU,CACf,EAEM,GAAA,EAAS,EAAA,YAAA,CAAa,GAAqC,CAC7D,EAAS,QAAU,EACnB,EAAS,CAAI,CACjB,EAAG,CAAC,CAAC,EAEC,GAAA,EAAM,EAAA,YAAA,CACP,GAAsC,CACnC,IAAM,EAAU,EAAS,QACzB,EAAO,CAAE,MAAO,EAAW,EAAQ,MAAO,CAAK,EAAG,QAAS,EAAQ,OAAQ,CAAC,CAChF,EACA,CAAC,CAAM,CACX,EAEM,GAAA,EAAU,EAAA,YAAA,CACX,GAAkB,CACf,IAAM,EAAU,EAAS,QACnB,EAAU,EAAM,EAAM,EAAQ,OAAO,EAAI,EAAQ,QAAU,EACjE,EAAO,CAAE,MAAO,EAAM,SAAQ,CAAC,EAC/B,EAAW,QAAQ,UAAU,CAAO,CACxC,EACA,CAAC,EAAQ,EAAO,CAAU,CAC9B,EAEM,GAAA,EAAQ,EAAA,YAAA,CACT,GAAuC,CACpC,IAAM,EAAQ,EAAS,QAAQ,MAC/B,EAAQ,IAAU,IAAA,GAAY,EAAQ,EAAW,EAAO,CAAK,CAAC,CAClE,EACA,CAAC,CAAO,CACZ,EAEM,GAAA,EAAQ,EAAA,YAAA,KAAwB,EAAQ,EAAW,OAAO,EAAG,CAAC,CAAO,CAAC,EAE5E,MAAO,CACH,MAAO,EAAM,MACb,QAAS,EAAM,QACf,QAAS,EAAE,EAAQ,SAAW,EAAA,UAAA,CAAW,EAAM,MAAO,EAAM,OAAO,EACnE,MACA,QACA,OACJ,CACJ"}
@@ -0,0 +1,49 @@
1
+ import { deepEqual as e } from "./use-deep-memo.js";
2
+ import { useLatestRef as t } from "./use-latest-ref.js";
3
+ import { useCallback as n, useRef as r, useState as i } from "react";
4
+ //#region src/hooks/use-draft-filters.ts
5
+ function a(e, t) {
6
+ return typeof t == "function" ? t(e) : {
7
+ ...e,
8
+ ...t
9
+ };
10
+ }
11
+ function o(o, s = {}) {
12
+ let [c, l] = i(() => ({
13
+ draft: o,
14
+ applied: o
15
+ })), u = r(c.applied), d = r(c), f = t(s), p = n((t, n) => (f.current.isEqual ?? e)(t, n), [f]), m = n((e) => {
16
+ d.current = e, l(e);
17
+ }, []), h = n((e) => {
18
+ let t = d.current;
19
+ m({
20
+ draft: a(t.draft, e),
21
+ applied: t.applied
22
+ });
23
+ }, [m]), g = n((e) => {
24
+ let t = d.current, n = p(e, t.applied) ? t.applied : e;
25
+ m({
26
+ draft: e,
27
+ applied: n
28
+ }), f.current.onApply?.(n);
29
+ }, [
30
+ m,
31
+ p,
32
+ f
33
+ ]), _ = n((e) => {
34
+ let t = d.current.draft;
35
+ g(e === void 0 ? t : a(t, e));
36
+ }, [g]), v = n(() => g(u.current), [g]);
37
+ return {
38
+ draft: c.draft,
39
+ applied: c.applied,
40
+ isDirty: !(s.isEqual ?? e)(c.draft, c.applied),
41
+ set: h,
42
+ apply: _,
43
+ clear: v
44
+ };
45
+ }
46
+ //#endregion
47
+ export { o as useDraftFilters };
48
+
49
+ //# sourceMappingURL=use-draft-filters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-draft-filters.js","names":[],"sources":["../../src/hooks/use-draft-filters.ts"],"sourcesContent":["import { useCallback, useRef, useState } from \"react\";\n\nimport { deepEqual } from \"./use-deep-memo\";\nimport { useLatestRef } from \"./use-latest-ref\";\n\n/** An edit to the draft: a partial object merged over it, or an updater. */\nexport type DraftFiltersPatch<T> = Partial<T> | ((draft: T) => T);\n\nexport interface UseDraftFiltersOptions<T> {\n /**\n * Called with the applied value on every `apply()` and `clear()` — the place to\n * reset a page number you keep yourself. `usePaginatedQuery` does not need it:\n * it goes back to page 1 on its own when `applied` changes its `queryKey`.\n */\n onApply?: (applied: T) => void;\n /** Equality behind `isDirty` and the stable `applied`. Default: structural. */\n isEqual?: (a: T, b: T) => boolean;\n}\n\nexport interface UseDraftFiltersResult<T> {\n /** What the filter controls edit. Changes on every keystroke. */\n draft: T;\n /**\n * What the query reads. Changes only on `apply()`/`clear()`, and keeps its\n * reference when the value applied is equal to the current one — safe to put\n * in a `queryKey`.\n */\n applied: T;\n /** Whether the draft differs from what is applied — enable the apply button on it. */\n isDirty: boolean;\n /** Edit the draft, merging a partial object or running an updater. Never applies. */\n set: (patch: DraftFiltersPatch<T>) => void;\n /**\n * Publish the draft. With a patch, merge it into the draft **and** publish the\n * result in the same call — \"filter by this code\" from a table row.\n */\n apply: (patch?: DraftFiltersPatch<T>) => void;\n /** Reset the draft and the applied value to the initial one, together. */\n clear: () => void;\n}\n\n/** Both halves, kept in one state so they can never be committed apart. */\ninterface DraftFiltersState<T> {\n draft: T;\n applied: T;\n}\n\n/**\n * Resolve a patch against a draft.\n *\n * @param draft - The current draft.\n * @param patch - A partial object to merge, or an updater.\n * @returns The next draft.\n */\nfunction patchDraft<T>(draft: T, patch: DraftFiltersPatch<T>): T {\n return typeof patch === \"function\" ? patch(draft) : { ...draft, ...patch };\n}\n\n/**\n * Draft vs applied state for a server-side filter: the controls edit a draft\n * freely, and the query only sees it when the user applies (a click, or Enter).\n *\n * A server-side filter cannot react to every keystroke — each character typed in\n * a search box would be a request. Every admin screen then writes the same pair of\n * `useState`s, and gets the same three details wrong:\n *\n * - **Clearing moves both halves.** Resetting only the draft leaves a filtered\n * list under an empty bar; resetting only the applied value leaves the opposite.\n * - **Applying a value that is not in state yet.** \"Filter by this code\" has to\n * write the draft and publish it in one gesture; calling `setDraft(x)` and then\n * publishing `draft` reads the previous value. `apply(patch)` computes the next\n * draft from a ref that every call keeps current, so it never depends on the\n * order React flushes state in.\n * - **Resetting the page.** Filtering while on page 7 of a result that now has two\n * pages shows an empty table. `usePaginatedQuery` handles it by itself when\n * `applied` is in its `queryKey`; a page kept elsewhere resets in `onApply`.\n *\n * There is no effect in here: applying happens inside the event that asked for it,\n * so one click is one request. `applied` keeps its reference while nothing new is\n * applied, so it can sit in a `queryKey` without a phantom refetch.\n *\n * Like `useState`, the initial value is read on the first render only; `clear()`\n * returns to it.\n *\n * @param initial - The empty filter form: what the screen shows and queries first.\n * @param options - `onApply` and `isEqual`.\n * @returns The draft, the applied value and the gestures between them.\n *\n * @example\n * const filters = useDraftFilters({ code: \"\", level: \"all\" });\n * const logs = usePaginatedQuery({\n * queryKey: [\"logs\", filters.applied],\n * queryFn: (params) => api.listLogs({ ...params, ...toQuery(filters.applied) }),\n * });\n * // <Input value={filters.draft.code} onChange={(e) => filters.set({ code: e.target.value })} />\n * // <Button disabled={!filters.isDirty} onClick={() => filters.apply()}>Filtrar</Button>\n */\nexport function useDraftFilters<T extends object>(\n initial: T,\n options: UseDraftFiltersOptions<T> = {},\n): UseDraftFiltersResult<T> {\n const [state, setState] = useState<DraftFiltersState<T>>(() => ({\n draft: initial,\n applied: initial,\n }));\n const initialRef = useRef<T>(state.applied);\n const stateRef = useRef<DraftFiltersState<T>>(state);\n const optionsRef = useLatestRef(options);\n\n const equal = useCallback(\n (a: T, b: T): boolean => (optionsRef.current.isEqual ?? deepEqual)(a, b),\n [optionsRef],\n );\n\n const commit = useCallback((next: DraftFiltersState<T>): void => {\n stateRef.current = next;\n setState(next);\n }, []);\n\n const set = useCallback(\n (patch: DraftFiltersPatch<T>): void => {\n const current = stateRef.current;\n commit({ draft: patchDraft(current.draft, patch), applied: current.applied });\n },\n [commit],\n );\n\n const publish = useCallback(\n (next: T): void => {\n const current = stateRef.current;\n const applied = equal(next, current.applied) ? current.applied : next;\n commit({ draft: next, applied });\n optionsRef.current.onApply?.(applied);\n },\n [commit, equal, optionsRef],\n );\n\n const apply = useCallback(\n (patch?: DraftFiltersPatch<T>): void => {\n const draft = stateRef.current.draft;\n publish(patch === undefined ? draft : patchDraft(draft, patch));\n },\n [publish],\n );\n\n const clear = useCallback((): void => publish(initialRef.current), [publish]);\n\n return {\n draft: state.draft,\n applied: state.applied,\n isDirty: !(options.isEqual ?? deepEqual)(state.draft, state.applied),\n set,\n apply,\n clear,\n };\n}\n"],"mappings":";;;;AAsDA,SAAS,EAAc,GAAU,GAAgC;CAC7D,OAAO,OAAO,KAAU,aAAa,EAAM,CAAK,IAAI;EAAE,GAAG;EAAO,GAAG;CAAM;AAC7E;AAyCA,SAAgB,EACZ,GACA,IAAqC,CAAC,GACd;CACxB,IAAM,CAAC,GAAO,KAAY,SAAsC;EAC5D,OAAO;EACP,SAAS;CACb,EAAE,GACI,IAAa,EAAU,EAAM,OAAO,GACpC,IAAW,EAA6B,CAAK,GAC7C,IAAa,EAAa,CAAO,GAEjC,IAAQ,GACT,GAAM,OAAmB,EAAW,QAAQ,WAAW,EAAA,CAAW,GAAG,CAAC,GACvE,CAAC,CAAU,CACf,GAEM,IAAS,GAAa,MAAqC;EAE7D,AADA,EAAS,UAAU,GACnB,EAAS,CAAI;CACjB,GAAG,CAAC,CAAC,GAEC,IAAM,GACP,MAAsC;EACnC,IAAM,IAAU,EAAS;EACzB,EAAO;GAAE,OAAO,EAAW,EAAQ,OAAO,CAAK;GAAG,SAAS,EAAQ;EAAQ,CAAC;CAChF,GACA,CAAC,CAAM,CACX,GAEM,IAAU,GACX,MAAkB;EACf,IAAM,IAAU,EAAS,SACnB,IAAU,EAAM,GAAM,EAAQ,OAAO,IAAI,EAAQ,UAAU;EAEjE,AADA,EAAO;GAAE,OAAO;GAAM;EAAQ,CAAC,GAC/B,EAAW,QAAQ,UAAU,CAAO;CACxC,GACA;EAAC;EAAQ;EAAO;CAAU,CAC9B,GAEM,IAAQ,GACT,MAAuC;EACpC,IAAM,IAAQ,EAAS,QAAQ;EAC/B,EAAQ,MAAU,KAAA,IAAY,IAAQ,EAAW,GAAO,CAAK,CAAC;CAClE,GACA,CAAC,CAAO,CACZ,GAEM,IAAQ,QAAwB,EAAQ,EAAW,OAAO,GAAG,CAAC,CAAO,CAAC;CAE5E,OAAO;EACH,OAAO,EAAM;EACb,SAAS,EAAM;EACf,SAAS,EAAE,EAAQ,WAAW,EAAA,CAAW,EAAM,OAAO,EAAM,OAAO;EACnE;EACA;EACA;CACJ;AACJ"}
@@ -1,2 +1,2 @@
1
- let e=require("react");var t=[`a[href]`,`button:not([disabled])`,`textarea:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`[tabindex]:not([tabindex='-1'])`].join(`,`);function n(n,r){(0,e.useEffect)(()=>{if(!r)return;let e=n.current;if(!e)return;let i=document.activeElement;function a(){return Array.from(e?.querySelectorAll(t)??[]).filter(e=>{if(e.hasAttribute(`aria-hidden`))return!1;let t=typeof window<`u`?window.getComputedStyle(e):null;return!t||t.display!==`none`&&t.visibility!==`hidden`})}function o(t){if(t.key!==`Tab`)return;let n=a();if(n.length===0){t.preventDefault();return}let r=n[0],i=n[n.length-1],o=document.activeElement;t.shiftKey?(o===r||!o||!e?.contains(o))&&(t.preventDefault(),i.focus()):o===i&&(t.preventDefault(),r.focus())}return a()[0]?.focus(),document.addEventListener(`keydown`,o),()=>{document.removeEventListener(`keydown`,o),i?.focus?.()}},[n,r])}exports.useFocusTrap=n;
1
+ const e=require("./tab-sequence.cjs");let t=require("react");var n=[];function r(r,i){let[a,o]=(0,t.useState)(!1),[s,c]=(0,t.useState)(null);i!==a&&(o(i),i&&typeof document<`u`&&c(document.activeElement)),(0,t.useEffect)(()=>{if(!i)return;let t=r.current;if(!t)return;let a={container:t};n.push(a),t.contains(document.activeElement)||(e.tabSequence(t)[0]??t).focus();let o=r=>{if(r.key!==`Tab`||n.at(-1)!==a)return;let i=e.tabSequence(t),o=i[0],s=i.at(-1),c=document.activeElement,l=c!==null&&e.tabRegion(t).some(e=>e.contains(c));if(r.defaultPrevented){l||((r.shiftKey?s:o)??t).focus();return}if(!o||!s){r.preventDefault(),t.focus();return}let u=r.shiftKey?c===o||c===t:c===s;(!l||u)&&(r.preventDefault(),(r.shiftKey?s:o).focus())};return document.addEventListener(`keydown`,o),()=>{document.removeEventListener(`keydown`,o),n.splice(n.indexOf(a),1),s?.isConnected&&s.focus()}},[r,i,s])}exports.useFocusTrap=r;
2
2
  //# sourceMappingURL=use-focus-trap.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-focus-trap.cjs","names":[],"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { RefObject } from \"react\";\n\nconst FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\n/**\n * Trap keyboard focus inside `containerRef` while `active` is true. Cycles\n * Tab and Shift+Tab between the first and last focusable descendants. Pair\n * with Modal/Drawer for fully-accessible overlays.\n */\nexport function useFocusTrap(containerRef: RefObject<HTMLElement | null>, active: boolean): void {\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n\n function getFocusable(): HTMLElement[] {\n return Array.from(\n container?.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR) ?? [],\n ).filter((el) => {\n if (el.hasAttribute(\"aria-hidden\")) return false;\n const style = typeof window !== \"undefined\" ? window.getComputedStyle(el) : null;\n if (style && (style.display === \"none\" || style.visibility === \"hidden\")) {\n return false;\n }\n return true;\n });\n }\n\n function handleKeydown(event: KeyboardEvent): void {\n if (event.key !== \"Tab\") return;\n const elements = getFocusable();\n if (elements.length === 0) {\n event.preventDefault();\n return;\n }\n const first = elements[0]!;\n const last = elements[elements.length - 1]!;\n const current = document.activeElement as HTMLElement | null;\n if (event.shiftKey) {\n if (current === first || !current || !container?.contains(current)) {\n event.preventDefault();\n last.focus();\n }\n } else {\n if (current === last) {\n event.preventDefault();\n first.focus();\n }\n }\n }\n\n const focusable = getFocusable();\n focusable[0]?.focus();\n document.addEventListener(\"keydown\", handleKeydown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeydown);\n previouslyFocused?.focus?.();\n };\n }, [containerRef, active]);\n}\n"],"mappings":"uBAGA,IAAM,EAAqB,CACvB,UACA,yBACA,2BACA,wBACA,yBACA,iCACJ,CAAC,CAAC,KAAK,GAAG,EAOV,SAAgB,EAAa,EAA6C,EAAuB,EAC7F,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAQ,OACb,IAAM,EAAY,EAAa,QAC/B,GAAI,CAAC,EAAW,OAEhB,IAAM,EAAoB,SAAS,cAEnC,SAAS,GAA8B,CACnC,OAAO,MAAM,KACT,GAAW,iBAA8B,CAAkB,GAAK,CAAC,CACrE,CAAC,CAAC,OAAQ,GAAO,CACb,GAAI,EAAG,aAAa,aAAa,EAAG,MAAO,GAC3C,IAAM,EAAQ,OAAO,OAAW,IAAc,OAAO,iBAAiB,CAAE,EAAI,KAI5E,MAHI,IAAU,EAAM,UAAY,QAAU,EAAM,aAAe,QAInE,CAAC,CACL,CAEA,SAAS,EAAc,EAA4B,CAC/C,GAAI,EAAM,MAAQ,MAAO,OACzB,IAAM,EAAW,EAAa,EAC9B,GAAI,EAAS,SAAW,EAAG,CACvB,EAAM,eAAe,EACrB,MACJ,CACA,IAAM,EAAQ,EAAS,GACjB,EAAO,EAAS,EAAS,OAAS,GAClC,EAAU,SAAS,cACrB,EAAM,UACF,IAAY,GAAS,CAAC,GAAW,CAAC,GAAW,SAAS,CAAO,KAC7D,EAAM,eAAe,EACrB,EAAK,MAAM,GAGX,IAAY,IACZ,EAAM,eAAe,EACrB,EAAM,MAAM,EAGxB,CAKA,OAFA,EAAA,CAAA,CAAU,EAAE,EAAE,MAAM,EACpB,SAAS,iBAAiB,UAAW,CAAa,MACrC,CACT,SAAS,oBAAoB,UAAW,CAAa,EACrD,GAAmB,QAAQ,CAC/B,CACJ,EAAG,CAAC,EAAc,CAAM,CAAC,CAC7B"}
1
+ {"version":3,"file":"use-focus-trap.cjs","names":[],"sources":["../../src/hooks/use-focus-trap.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\nimport { tabRegion, tabSequence } from \"./tab-sequence\";\n\nexport { FOCUSABLE_SELECTOR } from \"./tab-sequence\";\n\n/** One armed trap; the identity is what the stack compares. */\ninterface TrapEntry {\n container: HTMLElement;\n}\n\nconst stack: TrapEntry[] = [];\n\n/**\n * Trap keyboard focus inside `containerRef` while `active` is true.\n *\n * - **On activation** focus moves to the first tab stop, or to the container\n * itself when it has none (give it `tabIndex={-1}`). Content that already put\n * focus inside — an `autoFocus` input — is left where it is.\n * - **`Tab` / `Shift+Tab`** wrap between the first and the last stop, and pull\n * focus back in when it sits outside.\n * - **On release** focus returns to the element that held it when the trap\n * armed. That element is read during the render that arms the trap, before\n * React commits: an `autoFocus` child takes focus in the commit, ahead of any\n * effect, and reading it there would \"restore\" to the child that is about to\n * unmount — which dropped focus on `body` in `Command`.\n *\n * **Portalled panels stay reachable.** A `Popover` or `HoverCard` opened from\n * inside the container renders at the end of `body`, outside it. Those panels\n * register themselves (see `usePortalTabOrder`), and the trap treats them as\n * part of the container, spliced right after their trigger. A `Tab` another\n * layer already consumed is not moved again, unless it moved focus out of the\n * container and its panels — the portal bridge leaving the last panel stop for\n * whatever follows the trap in the page.\n *\n * **Traps stack.** Only the most recent active trap handles `Tab`, so a modal\n * opened over another owns the keyboard until it closes, and then hands focus\n * back to the element it was opened from, inside the one below.\n *\n * The listener is on `document` in the bubble phase, after React's own\n * handlers: a menu that closes on `Tab` and returns focus to its trigger has\n * already done so when the trap looks at where focus is.\n *\n * @param containerRef - The element focus is confined to.\n * @param active - Whether the trap is armed.\n * @returns Nothing.\n */\nexport function useFocusTrap(containerRef: RefObject<HTMLElement | null>, active: boolean): void {\n const [armed, setArmed] = useState(false);\n const [opener, setOpener] = useState<HTMLElement | null>(null);\n if (active !== armed) {\n setArmed(active);\n if (active && typeof document !== \"undefined\") {\n setOpener(document.activeElement as HTMLElement | null);\n }\n }\n\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n const entry: TrapEntry = { container };\n stack.push(entry);\n\n if (!container.contains(document.activeElement)) {\n (tabSequence(container)[0] ?? container).focus();\n }\n\n const handleKeydown = (event: KeyboardEvent): void => {\n if (event.key !== \"Tab\" || stack.at(-1) !== entry) return;\n const sequence = tabSequence(container);\n const first = sequence[0];\n const last = sequence.at(-1);\n const current = document.activeElement as HTMLElement | null;\n const inside =\n current !== null && tabRegion(container).some((root) => root.contains(current));\n\n if (event.defaultPrevented) {\n if (!inside) ((event.shiftKey ? last : first) ?? container).focus();\n return;\n }\n if (!first || !last) {\n event.preventDefault();\n container.focus();\n return;\n }\n const atEnd = event.shiftKey\n ? current === first || current === container\n : current === last;\n if (!inside || atEnd) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeydown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeydown);\n stack.splice(stack.indexOf(entry), 1);\n if (opener?.isConnected) opener.focus();\n };\n }, [containerRef, active, opener]);\n}\n"],"mappings":"6DAWA,IAAM,EAAqB,CAAC,EAoC5B,SAAgB,EAAa,EAA6C,EAAuB,CAC7F,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAS,EAAK,EAClC,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA6B,IAAI,EACzD,IAAW,IACX,EAAS,CAAM,EACX,GAAU,OAAO,SAAa,KAC9B,EAAU,SAAS,aAAmC,IAI9D,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAQ,OACb,IAAM,EAAY,EAAa,QAC/B,GAAI,CAAC,EAAW,OAEhB,IAAM,EAAmB,CAAE,WAAU,EACrC,EAAM,KAAK,CAAK,EAEX,EAAU,SAAS,SAAS,aAAa,IACzC,EAAA,YAAY,CAAS,CAAC,CAAC,IAAM,EAAA,CAAW,MAAM,EAGnD,IAAM,EAAiB,GAA+B,CAClD,GAAI,EAAM,MAAQ,OAAS,EAAM,GAAG,EAAE,IAAM,EAAO,OACnD,IAAM,EAAW,EAAA,YAAY,CAAS,EAChC,EAAQ,EAAS,GACjB,EAAO,EAAS,GAAG,EAAE,EACrB,EAAU,SAAS,cACnB,EACF,IAAY,MAAQ,EAAA,UAAU,CAAS,CAAC,CAAC,KAAM,GAAS,EAAK,SAAS,CAAO,CAAC,EAElF,GAAI,EAAM,iBAAkB,CACnB,KAAU,EAAM,SAAW,EAAO,IAAU,EAAA,CAAW,MAAM,EAClE,MACJ,CACA,GAAI,CAAC,GAAS,CAAC,EAAM,CACjB,EAAM,eAAe,EACrB,EAAU,MAAM,EAChB,MACJ,CACA,IAAM,EAAQ,EAAM,SACd,IAAY,GAAS,IAAY,EACjC,IAAY,GACd,CAAC,GAAU,KACX,EAAM,eAAe,GACpB,EAAM,SAAW,EAAO,EAAA,CAAO,MAAM,EAE9C,EAGA,OADA,SAAS,iBAAiB,UAAW,CAAa,MACrC,CACT,SAAS,oBAAoB,UAAW,CAAa,EACrD,EAAM,OAAO,EAAM,QAAQ,CAAK,EAAG,CAAC,EAChC,GAAQ,aAAa,EAAO,MAAM,CAC1C,CACJ,EAAG,CAAC,EAAc,EAAQ,CAAM,CAAC,CACrC"}
@@ -1,42 +1,39 @@
1
- import { useEffect as e } from "react";
1
+ import { tabRegion as e, tabSequence as t } from "./tab-sequence.js";
2
+ import { useEffect as n, useState as r } from "react";
2
3
  //#region src/hooks/use-focus-trap.ts
3
- var t = [
4
- "a[href]",
5
- "button:not([disabled])",
6
- "textarea:not([disabled])",
7
- "input:not([disabled])",
8
- "select:not([disabled])",
9
- "[tabindex]:not([tabindex='-1'])"
10
- ].join(",");
11
- function n(n, r) {
12
- e(() => {
13
- if (!r) return;
14
- let e = n.current;
15
- if (!e) return;
16
- let i = document.activeElement;
17
- function a() {
18
- return Array.from(e?.querySelectorAll(t) ?? []).filter((e) => {
19
- if (e.hasAttribute("aria-hidden")) return !1;
20
- let t = typeof window < "u" ? window.getComputedStyle(e) : null;
21
- return !t || t.display !== "none" && t.visibility !== "hidden";
22
- });
23
- }
24
- function o(t) {
25
- if (t.key !== "Tab") return;
26
- let n = a();
27
- if (n.length === 0) {
28
- t.preventDefault();
4
+ var i = [];
5
+ function a(a, o) {
6
+ let [s, c] = r(!1), [l, u] = r(null);
7
+ o !== s && (c(o), o && typeof document < "u" && u(document.activeElement)), n(() => {
8
+ if (!o) return;
9
+ let n = a.current;
10
+ if (!n) return;
11
+ let r = { container: n };
12
+ i.push(r), n.contains(document.activeElement) || (t(n)[0] ?? n).focus();
13
+ let s = (a) => {
14
+ if (a.key !== "Tab" || i.at(-1) !== r) return;
15
+ let o = t(n), s = o[0], c = o.at(-1), l = document.activeElement, u = l !== null && e(n).some((e) => e.contains(l));
16
+ if (a.defaultPrevented) {
17
+ u || ((a.shiftKey ? c : s) ?? n).focus();
29
18
  return;
30
19
  }
31
- let r = n[0], i = n[n.length - 1], o = document.activeElement;
32
- t.shiftKey ? (o === r || !o || !e?.contains(o)) && (t.preventDefault(), i.focus()) : o === i && (t.preventDefault(), r.focus());
33
- }
34
- return a()[0]?.focus(), document.addEventListener("keydown", o), () => {
35
- document.removeEventListener("keydown", o), i?.focus?.();
20
+ if (!s || !c) {
21
+ a.preventDefault(), n.focus();
22
+ return;
23
+ }
24
+ let d = a.shiftKey ? l === s || l === n : l === c;
25
+ (!u || d) && (a.preventDefault(), (a.shiftKey ? c : s).focus());
26
+ };
27
+ return document.addEventListener("keydown", s), () => {
28
+ document.removeEventListener("keydown", s), i.splice(i.indexOf(r), 1), l?.isConnected && l.focus();
36
29
  };
37
- }, [n, r]);
30
+ }, [
31
+ a,
32
+ o,
33
+ l
34
+ ]);
38
35
  }
39
36
  //#endregion
40
- export { n as useFocusTrap };
37
+ export { a as useFocusTrap };
41
38
 
42
39
  //# sourceMappingURL=use-focus-trap.js.map