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
@@ -1,144 +1,168 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
- import t from "./DropdownMenu.module.js";
3
- import { useCallback 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 { Portal as t } from "../Portal/Portal.js";
3
+ import { useAnchorPosition as n } from "../Portal/anchor-position.js";
4
+ import r from "./DropdownMenu.module.js";
5
+ import { useCallback as i, useEffect as a, useId as o, useRef as s, useState as c } from "react";
6
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
5
7
  //#region src/components/DropdownMenu/DropdownMenu.tsx
6
- function l(e) {
8
+ function d(e) {
7
9
  switch (e) {
8
- case "bottom-end": return t.bottomEnd;
9
- case "top-start": return t.topStart;
10
- case "top-end": return t.topEnd;
11
- default: return t.bottomStart;
10
+ case "bottom-end": return r.bottomEnd;
11
+ case "top-start": return r.topStart;
12
+ case "top-end": return r.topEnd;
13
+ default: return r.bottomStart;
12
14
  }
13
15
  }
14
- function u(e) {
16
+ var f = {
17
+ "bottom-start": ["bottom", "start"],
18
+ "bottom-end": ["bottom", "end"],
19
+ "top-start": ["top", "start"],
20
+ "top-end": ["top", "end"]
21
+ }, p = 4;
22
+ function m(e) {
15
23
  return (e.type === "item" || e.type === "checkbox") && !e.disabled;
16
24
  }
17
- function d({ trigger: d, items: f, placement: p = "bottom-start", className: m }) {
18
- let [h, g] = o(!1), [_, v] = o(-1), y = i(), b = a(null), x = a([]), S = a(null), C = f.map((e, t) => u(e) ? t : -1).filter((e) => e !== -1), w = n(() => {
19
- b.current?.querySelector("[aria-haspopup='menu']")?.focus();
20
- }, []), T = n((e) => {
21
- g(!1), v(-1), e && w();
22
- }, [w]), E = n((e) => {
23
- v(e), x.current[e]?.focus();
24
- }, []), D = (e) => {
25
- if (C.length === 0) return;
26
- let t = (C.indexOf(_) + e + C.length) % C.length;
27
- E(C[t] ?? -1);
28
- };
29
- r(() => {
30
- if (!h || S.current === null) return;
31
- let e = S.current === "last" ? C.at(-1) : C[0];
32
- S.current = null, e !== void 0 && E(e);
25
+ function h({ trigger: h, items: g, placement: _ = "bottom-start", portal: v = !0, className: y }) {
26
+ let [b, x] = c(!1), [S, C] = c(-1), w = o(), [T, E] = c(null), [D, O] = c(null), k = s([]), A = s(null), j = g.map((e, t) => m(e) ? t : -1).filter((e) => e !== -1), M = i(() => {
27
+ T?.querySelector("[aria-haspopup='menu']")?.focus();
28
+ }, [T]), N = i((e) => {
29
+ x(!1), C(-1), e && M();
30
+ }, [M]), P = i((e) => {
31
+ C(e), k.current[e]?.focus();
32
+ }, []), F = (e) => {
33
+ if (j.length === 0) return;
34
+ let t = (j.indexOf(S) + e + j.length) % j.length;
35
+ P(j[t] ?? -1);
36
+ }, [I, L] = f[_], R = n({
37
+ anchor: T,
38
+ floating: D,
39
+ side: I,
40
+ align: L,
41
+ offset: p,
42
+ enabled: v && b
43
+ });
44
+ a(() => {
45
+ if (!b || !D || A.current === null) return;
46
+ let e = A.current === "last" ? j.at(-1) : j[0];
47
+ A.current = null, e !== void 0 && P(e);
33
48
  }, [
34
- h,
35
- C,
36
- E
37
- ]), r(() => {
38
- if (!h) return;
49
+ b,
50
+ D,
51
+ j,
52
+ P
53
+ ]), a(() => {
54
+ if (!b) return;
39
55
  let e = (e) => {
40
- b.current && !b.current.contains(e.target) && T(!1);
56
+ let t = e.target;
57
+ T?.contains(t) || D?.contains(t) || N(!1);
41
58
  };
42
59
  return window.addEventListener("mousedown", e), () => window.removeEventListener("mousedown", e);
43
- }, [h, T]);
44
- let O = (e) => {
45
- S.current = e, g(!0);
46
- }, k = (e) => {
47
- if (d.props.onKeyDown?.(e), !e.defaultPrevented) {
48
- if (e.key === "ArrowDown" || !h && (e.key === "Enter" || e.key === " ")) {
49
- e.preventDefault(), O("first");
60
+ }, [
61
+ b,
62
+ N,
63
+ T,
64
+ D
65
+ ]);
66
+ let z = (e) => {
67
+ A.current = e, x(!0);
68
+ }, B = (e) => {
69
+ if (h.props.onKeyDown?.(e), !e.defaultPrevented) {
70
+ if (e.key === "ArrowDown" || !b && (e.key === "Enter" || e.key === " ")) {
71
+ e.preventDefault(), z("first");
50
72
  return;
51
73
  }
52
- e.key === "ArrowUp" && (e.preventDefault(), O("last"));
74
+ e.key === "ArrowUp" && (e.preventDefault(), z("last"));
53
75
  }
54
- }, A = (e) => {
76
+ }, V = (e) => {
55
77
  switch (e.key) {
56
78
  case "ArrowDown":
57
- e.preventDefault(), D(1);
79
+ e.preventDefault(), F(1);
58
80
  break;
59
81
  case "ArrowUp":
60
- e.preventDefault(), D(-1);
82
+ e.preventDefault(), F(-1);
61
83
  break;
62
84
  case "Home":
63
- e.preventDefault(), C[0] !== void 0 && E(C[0]);
85
+ e.preventDefault(), j[0] !== void 0 && P(j[0]);
64
86
  break;
65
87
  case "End": {
66
88
  e.preventDefault();
67
- let t = C.at(-1);
68
- t !== void 0 && E(t);
89
+ let t = j.at(-1);
90
+ t !== void 0 && P(t);
69
91
  break;
70
92
  }
71
93
  case "Escape":
72
- e.preventDefault(), T(!0);
94
+ e.preventDefault(), N(!0);
73
95
  break;
74
- case "Tab": T(!1);
96
+ case "Tab": N(!0);
75
97
  }
76
- }, j = (e) => {
77
- if (d.props.onClick?.(e), !e.defaultPrevented) {
78
- if (h) {
79
- T(!1);
98
+ }, H = (e) => {
99
+ if (h.props.onClick?.(e), !e.defaultPrevented) {
100
+ if (b) {
101
+ N(!1);
80
102
  return;
81
103
  }
82
- O("first");
104
+ z("first");
83
105
  }
84
- }, M = {
85
- ...d,
106
+ }, U = {
107
+ ...h,
86
108
  props: {
87
- ...d.props,
88
- onClick: j,
89
- onKeyDown: k,
90
- "aria-expanded": h,
91
- "aria-controls": y,
109
+ ...h.props,
110
+ onClick: H,
111
+ onKeyDown: B,
112
+ "aria-expanded": b,
113
+ "aria-controls": w,
92
114
  "aria-haspopup": "menu"
93
115
  }
94
- }, N = (e) => {
95
- e.onSelect(), e.type !== "checkbox" && T(!0);
96
- };
97
- return /* @__PURE__ */ c("span", {
98
- ref: b,
99
- className: t.root,
100
- children: [M, h && /* @__PURE__ */ s("ul", {
101
- id: y,
102
- role: "menu",
103
- className: e(t.menu, l(p), m),
104
- onKeyDown: A,
105
- children: f.map((n, r) => {
106
- if (n.type === "separator") return /* @__PURE__ */ s("li", {
107
- role: "separator",
108
- className: t.separator,
109
- "aria-hidden": !0
110
- }, n.id);
111
- if (n.type === "label") return /* @__PURE__ */ s("li", {
112
- role: "presentation",
113
- className: t.label,
114
- children: n.label
115
- }, n.id);
116
- let i = n.type === "checkbox";
117
- return /* @__PURE__ */ s("li", {
118
- role: "none",
119
- children: /* @__PURE__ */ c("button", {
120
- ref: (e) => {
121
- x.current[r] = e;
122
- },
123
- type: "button",
124
- role: i ? "menuitemcheckbox" : "menuitem",
125
- "aria-checked": i ? n.checked : void 0,
126
- tabIndex: _ === r ? 0 : -1,
127
- className: e(t.item, n.type === "item" && n.danger && t.danger, _ === r && t.active),
128
- disabled: n.disabled,
129
- onClick: () => N(n),
130
- onMouseEnter: () => v(r),
131
- children: [n.icon && /* @__PURE__ */ s("span", {
132
- "aria-hidden": !0,
133
- children: n.icon
134
- }), n.label]
135
- })
136
- }, n.id);
137
- })
138
- })]
116
+ }, W = (e) => {
117
+ e.onSelect(), e.type !== "checkbox" && N(!0);
118
+ }, G = /* @__PURE__ */ l("ul", {
119
+ ref: O,
120
+ id: w,
121
+ role: "menu",
122
+ className: e(r.menu, v ? r.portalled : d(_), y),
123
+ style: R,
124
+ onKeyDown: V,
125
+ children: g.map((t, n) => {
126
+ if (t.type === "separator") return /* @__PURE__ */ l("li", {
127
+ role: "separator",
128
+ className: r.separator,
129
+ "aria-hidden": !0
130
+ }, t.id);
131
+ if (t.type === "label") return /* @__PURE__ */ l("li", {
132
+ role: "presentation",
133
+ className: r.label,
134
+ children: t.label
135
+ }, t.id);
136
+ let i = t.type === "checkbox";
137
+ return /* @__PURE__ */ l("li", {
138
+ role: "none",
139
+ children: /* @__PURE__ */ u("button", {
140
+ ref: (e) => {
141
+ k.current[n] = e;
142
+ },
143
+ type: "button",
144
+ role: i ? "menuitemcheckbox" : "menuitem",
145
+ "aria-checked": i ? t.checked : void 0,
146
+ tabIndex: S === n ? 0 : -1,
147
+ className: e(r.item, t.type === "item" && t.danger && r.danger, S === n && r.active),
148
+ disabled: t.disabled,
149
+ onClick: () => W(t),
150
+ onMouseEnter: () => C(n),
151
+ children: [t.icon && /* @__PURE__ */ l("span", {
152
+ "aria-hidden": !0,
153
+ children: t.icon
154
+ }), t.label]
155
+ })
156
+ }, t.id);
157
+ })
158
+ });
159
+ return /* @__PURE__ */ u("span", {
160
+ ref: E,
161
+ className: r.root,
162
+ children: [U, b && (v ? /* @__PURE__ */ l(t, { children: G }) : G)]
139
163
  });
140
164
  }
141
165
  //#endregion
142
- export { d as DropdownMenu };
166
+ export { h as DropdownMenu };
143
167
 
144
168
  //# sourceMappingURL=DropdownMenu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.js","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — the body owns placement, outside-\n * click, and the whole APG menu-button keyboard model, which has to see the entire\n * entry list to know where the next stop is. Splitting it would move the focus\n * bookkeeping away from the list it indexes into.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent as ReactKeyboardEvent, ReactElement, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DropdownMenu.module.css\";\n\nexport type DropdownMenuPlacement = \"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\";\n\nexport type DropdownMenuEntry =\n | {\n type: \"item\";\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n danger?: boolean;\n disabled?: boolean;\n onSelect: () => void;\n }\n | {\n /**\n * An entry that carries on/off state.\n *\n * Rendered as `role=\"menuitemcheckbox\"` with `aria-checked`, which is how\n * a screen reader announces \"checked\" instead of leaving the state\n * invisible. Selecting it does **not** close the menu: toggling two\n * settings in a row is the ordinary case, and closing after the first\n * would make the second a second trip.\n */\n type: \"checkbox\";\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n checked: boolean;\n disabled?: boolean;\n onSelect: () => void;\n }\n | { type: \"separator\"; id: string }\n | { type: \"label\"; id: string; label: ReactNode };\n\n/** The entry kinds a user can move focus to and activate. */\ntype SelectableEntry = Extract<DropdownMenuEntry, { type: \"item\" | \"checkbox\" }>;\n\nexport interface DropdownMenuProps {\n trigger: ReactElement<{\n onClick?: (e: React.MouseEvent) => void;\n onKeyDown?: (e: ReactKeyboardEvent) => void;\n \"aria-expanded\"?: boolean;\n \"aria-controls\"?: string;\n \"aria-haspopup\"?: boolean | \"menu\";\n }>;\n items: DropdownMenuEntry[];\n placement?: DropdownMenuPlacement;\n className?: string;\n}\n\nfunction placementClass(placement: DropdownMenuPlacement): string {\n switch (placement) {\n case \"bottom-end\":\n return styles.bottomEnd;\n case \"top-start\":\n return styles.topStart;\n case \"top-end\":\n return styles.topEnd;\n case \"bottom-start\":\n default:\n return styles.bottomStart;\n }\n}\n\n/** Whether an entry can take focus — an item or checkbox that is not disabled. */\nfunction isSelectable(entry: DropdownMenuEntry): entry is SelectableEntry {\n return (entry.type === \"item\" || entry.type === \"checkbox\") && !entry.disabled;\n}\n\n/**\n * Dropdown menu — a list of actions anchored to a trigger, following the\n * [APG menu button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/).\n *\n * `role=\"menu\"` is a promise about the keyboard, and this component keeps it:\n *\n * - `Enter`, `Space` or `ArrowDown` on the trigger opens and focuses the first\n * entry; `ArrowUp` opens and focuses the last.\n * - `ArrowUp` / `ArrowDown` move with wrap, `Home` / `End` jump to the ends.\n * - `Escape` closes and returns focus to the trigger, so the next `Tab` continues\n * from where the user was rather than from the top of the document.\n * - `Tab` closes the menu and lets the page's own tab order take over.\n * - Focus is managed: entries carry `tabIndex={-1}` and only the active one is\n * `0`, which is what stops `Tab` from walking the menu one entry at a time.\n *\n * Disabled entries, separators and labels are skipped by every movement.\n */\nexport function DropdownMenu({\n trigger,\n items,\n placement = \"bottom-start\",\n className,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const id = useId();\n const rootRef = useRef<HTMLSpanElement>(null);\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);\n const pendingFocus = useRef<\"first\" | \"last\" | null>(null);\n\n const selectable = items\n .map((entry, index) => (isSelectable(entry) ? index : -1))\n .filter((index) => index !== -1);\n\n /**\n * Focus the trigger again.\n *\n * Found through the `aria-haspopup` this component puts on it, rather than\n * through a ref: `ref` lives in different places in React 18 and 19, and a\n * consumer's custom trigger is under no obligation to forward one. The\n * attribute is on the element either way.\n *\n * @returns Nothing.\n */\n const focusTrigger = useCallback((): void => {\n rootRef.current?.querySelector<HTMLElement>(\"[aria-haspopup='menu']\")?.focus();\n }, []);\n\n const close = useCallback(\n (restoreFocus: boolean): void => {\n setOpen(false);\n setActiveIndex(-1);\n if (restoreFocus) focusTrigger();\n },\n [focusTrigger],\n );\n\n const focusIndex = useCallback((index: number): void => {\n setActiveIndex(index);\n itemRefs.current[index]?.focus();\n }, []);\n\n /**\n * Move focus to the entry `step` positions away, wrapping at both ends.\n *\n * Walks the selectable positions rather than the raw list, so separators,\n * labels and disabled entries are never a stop.\n */\n const move = (step: number): void => {\n if (selectable.length === 0) return;\n const current = selectable.indexOf(activeIndex);\n const next = (current + step + selectable.length) % selectable.length;\n focusIndex(selectable[next] ?? -1);\n };\n\n useEffect(() => {\n if (!open || pendingFocus.current === null) return;\n const target = pendingFocus.current === \"last\" ? selectable.at(-1) : selectable[0];\n pendingFocus.current = null;\n if (target !== undefined) focusIndex(target);\n }, [open, selectable, focusIndex]);\n\n useEffect(() => {\n if (!open) return;\n const onDown = (event: MouseEvent): void => {\n if (rootRef.current && !rootRef.current.contains(event.target as Node)) close(false);\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => window.removeEventListener(\"mousedown\", onDown);\n }, [open, close]);\n\n const openWith = (edge: \"first\" | \"last\"): void => {\n pendingFocus.current = edge;\n setOpen(true);\n };\n\n const handleTriggerKeyDown = (event: ReactKeyboardEvent): void => {\n trigger.props.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key === \"ArrowDown\" || (!open && (event.key === \"Enter\" || event.key === \" \"))) {\n event.preventDefault();\n openWith(\"first\");\n return;\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n openWith(\"last\");\n }\n };\n\n /**\n * Keyboard model of the open menu.\n *\n * Handled on the list rather than on `window`: focus is inside the menu by\n * then, so the keys arrive here by bubbling and cannot be pre-empted by an\n * unrelated global listener — which is how the arrows used to go missing in a\n * host application.\n */\n const handleMenuKeyDown = (event: ReactKeyboardEvent<HTMLUListElement>): void => {\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n move(1);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n move(-1);\n break;\n case \"Home\":\n event.preventDefault();\n if (selectable[0] !== undefined) focusIndex(selectable[0]);\n break;\n case \"End\": {\n event.preventDefault();\n const last = selectable.at(-1);\n if (last !== undefined) focusIndex(last);\n break;\n }\n case \"Escape\":\n event.preventDefault();\n close(true);\n break;\n case \"Tab\":\n close(false);\n break;\n default:\n break;\n }\n };\n\n const handleTriggerClick = (event: React.MouseEvent): void => {\n trigger.props.onClick?.(event);\n if (event.defaultPrevented) return;\n if (open) {\n close(false);\n return;\n }\n openWith(\"first\");\n };\n\n const triggerClone = {\n ...trigger,\n props: {\n ...trigger.props,\n onClick: handleTriggerClick,\n onKeyDown: handleTriggerKeyDown,\n \"aria-expanded\": open,\n \"aria-controls\": id,\n \"aria-haspopup\": \"menu\" as const,\n },\n } as ReactElement;\n\n const handleSelect = (entry: SelectableEntry): void => {\n entry.onSelect();\n if (entry.type === \"checkbox\") return;\n close(true);\n };\n\n return (\n <span ref={rootRef} className={styles.root}>\n {triggerClone}\n {open && (\n <ul\n id={id}\n role=\"menu\"\n className={cn(styles.menu, placementClass(placement), className)}\n onKeyDown={handleMenuKeyDown}\n >\n {items.map((entry, index) => {\n if (entry.type === \"separator\") {\n return (\n <li\n key={entry.id}\n role=\"separator\"\n className={styles.separator}\n aria-hidden\n />\n );\n }\n if (entry.type === \"label\") {\n return (\n <li key={entry.id} role=\"presentation\" className={styles.label}>\n {entry.label}\n </li>\n );\n }\n const checkbox = entry.type === \"checkbox\";\n return (\n <li key={entry.id} role=\"none\">\n <button\n ref={(el) => {\n itemRefs.current[index] = el;\n }}\n type=\"button\"\n role={checkbox ? \"menuitemcheckbox\" : \"menuitem\"}\n aria-checked={checkbox ? entry.checked : undefined}\n tabIndex={activeIndex === index ? 0 : -1}\n className={cn(\n styles.item,\n entry.type === \"item\" && entry.danger && styles.danger,\n activeIndex === index && styles.active,\n )}\n disabled={entry.disabled}\n onClick={() => handleSelect(entry)}\n onMouseEnter={() => setActiveIndex(index)}\n >\n {entry.icon && <span aria-hidden>{entry.icon}</span>}\n {entry.label}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </span>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DropdownMenu.js","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — the body owns placement, outside-\n * click, and the whole APG menu-button keyboard model, which has to see the entire\n * entry list to know where the next stop is. Splitting it would move the focus\n * bookkeeping away from the list it indexes into.\n */\nimport { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent as ReactKeyboardEvent, ReactElement, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport type { AnchorAlign, AnchorSide } from \"@/components/Portal/anchor-position\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DropdownMenu.module.css\";\n\nexport type DropdownMenuPlacement = \"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\";\n\nexport type DropdownMenuEntry =\n | {\n type: \"item\";\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n danger?: boolean;\n disabled?: boolean;\n onSelect: () => void;\n }\n | {\n /**\n * An entry that carries on/off state.\n *\n * Rendered as `role=\"menuitemcheckbox\"` with `aria-checked`, which is how\n * a screen reader announces \"checked\" instead of leaving the state\n * invisible. Selecting it does **not** close the menu: toggling two\n * settings in a row is the ordinary case, and closing after the first\n * would make the second a second trip.\n */\n type: \"checkbox\";\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n checked: boolean;\n disabled?: boolean;\n onSelect: () => void;\n }\n | { type: \"separator\"; id: string }\n | { type: \"label\"; id: string; label: ReactNode };\n\n/** The entry kinds a user can move focus to and activate. */\ntype SelectableEntry = Extract<DropdownMenuEntry, { type: \"item\" | \"checkbox\" }>;\n\nexport interface DropdownMenuProps {\n trigger: ReactElement<{\n onClick?: (e: React.MouseEvent) => void;\n onKeyDown?: (e: ReactKeyboardEvent) => void;\n \"aria-expanded\"?: boolean;\n \"aria-controls\"?: string;\n \"aria-haspopup\"?: boolean | \"menu\";\n }>;\n items: DropdownMenuEntry[];\n placement?: DropdownMenuPlacement;\n /**\n * Render the menu in a portal, positioned against the trigger. Default `true`.\n *\n * Without it the menu is `position: absolute` inside the trigger's wrapper,\n * and any ancestor with `overflow` other than `visible` clips it. The ordinary\n * victim is the action column of a `DataTable`: its wrapper scrolls\n * horizontally, `overflow-x: auto` forces `overflow-y` to `auto` too, and the\n * menu of the last row showed one of three entries (measured in Chrome at\n * 1440 px). In a portal the menu escapes the clip, flips above the trigger\n * when there is no room below, and follows the trigger on scroll and resize.\n *\n * The portalled menu stacks at `--tempest-z-popover`, above\n * `--tempest-z-modal`, so a menu opened inside a `Modal` still paints on top.\n * `false` keeps the in-flow menu, for a container that must own the menu's\n * stacking or clipping on purpose.\n */\n portal?: boolean;\n className?: string;\n}\n\nfunction placementClass(placement: DropdownMenuPlacement): string {\n switch (placement) {\n case \"bottom-end\":\n return styles.bottomEnd;\n case \"top-start\":\n return styles.topStart;\n case \"top-end\":\n return styles.topEnd;\n case \"bottom-start\":\n default:\n return styles.bottomStart;\n }\n}\n\nconst PLACEMENT_GEOMETRY: Record<DropdownMenuPlacement, [AnchorSide, AnchorAlign]> = {\n \"bottom-start\": [\"bottom\", \"start\"],\n \"bottom-end\": [\"bottom\", \"end\"],\n \"top-start\": [\"top\", \"start\"],\n \"top-end\": [\"top\", \"end\"],\n};\n\n/** Gap between trigger and menu, in px — the in-flow CSS uses the same 4 px. */\nconst MENU_OFFSET = 4;\n\n/** Whether an entry can take focus — an item or checkbox that is not disabled. */\nfunction isSelectable(entry: DropdownMenuEntry): entry is SelectableEntry {\n return (entry.type === \"item\" || entry.type === \"checkbox\") && !entry.disabled;\n}\n\n/**\n * Dropdown menu — a list of actions anchored to a trigger, following the\n * [APG menu button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/).\n *\n * `role=\"menu\"` is a promise about the keyboard, and this component keeps it:\n *\n * - `Enter`, `Space` or `ArrowDown` on the trigger opens and focuses the first\n * entry; `ArrowUp` opens and focuses the last.\n * - `ArrowUp` / `ArrowDown` move with wrap, `Home` / `End` jump to the ends.\n * - `Escape` closes and returns focus to the trigger, so the next `Tab` continues\n * from where the user was rather than from the top of the document.\n * - `Tab` closes the menu and lets the page's own tab order take over, counted\n * from the trigger: focus goes back to the trigger synchronously and the key's\n * default action then moves on from there. Without that, a portalled menu —\n * the last thing in `body` — would hand `Tab` to whatever follows it, which\n * inside a `Modal` is outside the dialog.\n * - Focus is managed: entries carry `tabIndex={-1}` and only the active one is\n * `0`, which is what stops `Tab` from walking the menu one entry at a time.\n *\n * Disabled entries, separators and labels are skipped by every movement.\n */\nexport function DropdownMenu({\n trigger,\n items,\n placement = \"bottom-start\",\n portal = true,\n className,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const id = useId();\n const [rootNode, setRootNode] = useState<HTMLSpanElement | null>(null);\n const [menuNode, setMenuNode] = useState<HTMLUListElement | null>(null);\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);\n const pendingFocus = useRef<\"first\" | \"last\" | null>(null);\n\n const selectable = items\n .map((entry, index) => (isSelectable(entry) ? index : -1))\n .filter((index) => index !== -1);\n\n /**\n * Focus the trigger again.\n *\n * Found through the `aria-haspopup` this component puts on it, rather than\n * through a ref: `ref` lives in different places in React 18 and 19, and a\n * consumer's custom trigger is under no obligation to forward one. The\n * attribute is on the element either way.\n *\n * @returns Nothing.\n */\n const focusTrigger = useCallback((): void => {\n rootNode?.querySelector<HTMLElement>(\"[aria-haspopup='menu']\")?.focus();\n }, [rootNode]);\n\n const close = useCallback(\n (restoreFocus: boolean): void => {\n setOpen(false);\n setActiveIndex(-1);\n if (restoreFocus) focusTrigger();\n },\n [focusTrigger],\n );\n\n const focusIndex = useCallback((index: number): void => {\n setActiveIndex(index);\n itemRefs.current[index]?.focus();\n }, []);\n\n /**\n * Move focus to the entry `step` positions away, wrapping at both ends.\n *\n * Walks the selectable positions rather than the raw list, so separators,\n * labels and disabled entries are never a stop.\n */\n const move = (step: number): void => {\n if (selectable.length === 0) return;\n const current = selectable.indexOf(activeIndex);\n const next = (current + step + selectable.length) % selectable.length;\n focusIndex(selectable[next] ?? -1);\n };\n\n const [side, align] = PLACEMENT_GEOMETRY[placement];\n const floatingStyle = useAnchorPosition({\n anchor: rootNode,\n floating: menuNode,\n side,\n align,\n offset: MENU_OFFSET,\n enabled: portal && open,\n });\n\n /**\n * Focus the entry the opening key asked for.\n *\n * Keyed off the menu node rather than off `open` alone: in a portal the menu\n * mounts one commit after `open` flips, and an effect that ran on `open` would\n * find no entries to focus.\n */\n useEffect(() => {\n if (!open || !menuNode || pendingFocus.current === null) return;\n const target = pendingFocus.current === \"last\" ? selectable.at(-1) : selectable[0];\n pendingFocus.current = null;\n if (target !== undefined) focusIndex(target);\n }, [open, menuNode, selectable, focusIndex]);\n\n /**\n * Close on a press outside both the trigger and the menu.\n *\n * The menu is checked separately because in a portal it is not inside the\n * trigger's wrapper: testing the wrapper alone would treat a press on an entry\n * as outside and close the menu before the entry's click lands.\n */\n useEffect(() => {\n if (!open) return;\n const onDown = (event: MouseEvent): void => {\n const target = event.target as Node;\n if (rootNode?.contains(target) || menuNode?.contains(target)) return;\n close(false);\n };\n window.addEventListener(\"mousedown\", onDown);\n return () => window.removeEventListener(\"mousedown\", onDown);\n }, [open, close, rootNode, menuNode]);\n\n const openWith = (edge: \"first\" | \"last\"): void => {\n pendingFocus.current = edge;\n setOpen(true);\n };\n\n const handleTriggerKeyDown = (event: ReactKeyboardEvent): void => {\n trigger.props.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key === \"ArrowDown\" || (!open && (event.key === \"Enter\" || event.key === \" \"))) {\n event.preventDefault();\n openWith(\"first\");\n return;\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n openWith(\"last\");\n }\n };\n\n /**\n * Keyboard model of the open menu.\n *\n * Handled on the list rather than on `window`: focus is inside the menu by\n * then, so the keys arrive here by bubbling and cannot be pre-empted by an\n * unrelated global listener — which is how the arrows used to go missing in a\n * host application.\n */\n const handleMenuKeyDown = (event: ReactKeyboardEvent<HTMLUListElement>): void => {\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n move(1);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n move(-1);\n break;\n case \"Home\":\n event.preventDefault();\n if (selectable[0] !== undefined) focusIndex(selectable[0]);\n break;\n case \"End\": {\n event.preventDefault();\n const last = selectable.at(-1);\n if (last !== undefined) focusIndex(last);\n break;\n }\n case \"Escape\":\n event.preventDefault();\n close(true);\n break;\n case \"Tab\":\n close(true);\n break;\n default:\n break;\n }\n };\n\n const handleTriggerClick = (event: React.MouseEvent): void => {\n trigger.props.onClick?.(event);\n if (event.defaultPrevented) return;\n if (open) {\n close(false);\n return;\n }\n openWith(\"first\");\n };\n\n const triggerClone = {\n ...trigger,\n props: {\n ...trigger.props,\n onClick: handleTriggerClick,\n onKeyDown: handleTriggerKeyDown,\n \"aria-expanded\": open,\n \"aria-controls\": id,\n \"aria-haspopup\": \"menu\" as const,\n },\n } as ReactElement;\n\n const handleSelect = (entry: SelectableEntry): void => {\n entry.onSelect();\n if (entry.type === \"checkbox\") return;\n close(true);\n };\n\n const menu = (\n <ul\n ref={setMenuNode}\n id={id}\n role=\"menu\"\n className={cn(\n styles.menu,\n portal ? styles.portalled : placementClass(placement),\n className,\n )}\n style={floatingStyle}\n onKeyDown={handleMenuKeyDown}\n >\n {items.map((entry, index) => {\n if (entry.type === \"separator\") {\n return (\n <li\n key={entry.id}\n role=\"separator\"\n className={styles.separator}\n aria-hidden\n />\n );\n }\n if (entry.type === \"label\") {\n return (\n <li key={entry.id} role=\"presentation\" className={styles.label}>\n {entry.label}\n </li>\n );\n }\n const checkbox = entry.type === \"checkbox\";\n return (\n <li key={entry.id} role=\"none\">\n <button\n ref={(el) => {\n itemRefs.current[index] = el;\n }}\n type=\"button\"\n role={checkbox ? \"menuitemcheckbox\" : \"menuitem\"}\n aria-checked={checkbox ? entry.checked : undefined}\n tabIndex={activeIndex === index ? 0 : -1}\n className={cn(\n styles.item,\n entry.type === \"item\" && entry.danger && styles.danger,\n activeIndex === index && styles.active,\n )}\n disabled={entry.disabled}\n onClick={() => handleSelect(entry)}\n onMouseEnter={() => setActiveIndex(index)}\n >\n {entry.icon && <span aria-hidden>{entry.icon}</span>}\n {entry.label}\n </button>\n </li>\n );\n })}\n </ul>\n );\n\n return (\n <span ref={setRootNode} className={styles.root}>\n {triggerClone}\n {open && (portal ? <Portal>{menu}</Portal> : menu)}\n </span>\n );\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  require("../../styles/component/DropdownMenu.css");
2
- var e=`tempest_root_5ZetV`,t=`tempest_menu_-RiRW`,n=`tempest_tempest-menu-in_uCs9q`,r=`tempest_bottomStart_z60h9`,i=`tempest_bottomEnd_b1tmU`,a=`tempest_topStart_A0em2`,o=`tempest_topEnd_cFA5-`,s=`tempest_item_T-QPZ`,c=`tempest_active_WNqoX`,l=`tempest_danger_C0jN3`,u=`tempest_separator_RimQa`,d=`tempest_label_6yP76`,f={root:e,menu:t,tempestMenuIn:n,bottomStart:r,bottomEnd:i,topStart:a,topEnd:o,item:s,active:c,danger:l,separator:u,label:d};exports.active=c,exports.bottomEnd=i,exports.bottomStart=r,exports.danger=l,exports.default=f,exports.item=s,exports.label=d,exports.menu=t,exports.root=e,exports.separator=u,exports.tempestMenuIn=n,exports.topEnd=o,exports.topStart=a;
2
+ var e=`tempest_root_5ZetV`,t=`tempest_menu_-RiRW`,n=`tempest_tempest-menu-in_uCs9q`,r=`tempest_portalled_b7vCf`,i=`tempest_bottomStart_z60h9`,a=`tempest_bottomEnd_b1tmU`,o=`tempest_topStart_A0em2`,s=`tempest_topEnd_cFA5-`,c=`tempest_item_T-QPZ`,l=`tempest_active_WNqoX`,u=`tempest_danger_C0jN3`,d=`tempest_separator_RimQa`,f=`tempest_label_6yP76`,p={root:e,menu:t,tempestMenuIn:n,portalled:r,bottomStart:i,bottomEnd:a,topStart:o,topEnd:s,item:c,active:l,danger:u,separator:d,label:f};exports.active=l,exports.bottomEnd=a,exports.bottomStart=i,exports.danger=u,exports.default=p,exports.item=c,exports.label=f,exports.menu=t,exports.portalled=r,exports.root=e,exports.separator=d,exports.tempestMenuIn=n,exports.topEnd=s,exports.topStart=o;
3
3
  //# sourceMappingURL=DropdownMenu.module.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.module.cjs","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.menu {\n position: absolute;\n z-index: var(--tempest-z-dropdown);\n min-width: 180px;\n max-width: 320px;\n background-color: var(--tempest-bg);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n list-style: none;\n margin: 0;\n animation: tempest-menu-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.bottomStart {\n top: calc(100% + 4px);\n left: 0;\n}\n.bottomEnd {\n top: calc(100% + 4px);\n right: 0;\n}\n.topStart {\n bottom: calc(100% + 4px);\n left: 0;\n}\n.topEnd {\n bottom: calc(100% + 4px);\n right: 0;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .item:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.item:focus,\n.item.active {\n outline: none;\n background-color: var(--tempest-primary-soft);\n color: var(--tempest-primary-on-soft);\n box-shadow: inset 3px 0 0 0 var(--tempest-selected-indicator);\n}\n\n.item:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n\n.item.danger {\n color: var(--tempest-danger);\n}\n\n.separator {\n height: 1px;\n background-color: var(--tempest-border);\n margin: var(--tempest-space-1) 0;\n}\n\n.label {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text-muted);\n text-transform: uppercase;\n letter-spacing: var(--tempest-tracking-wider);\n}\n\n@keyframes tempest-menu-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .menu {\n animation: none;\n }\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"DropdownMenu.module.cjs","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.menu {\n position: absolute;\n z-index: var(--tempest-z-dropdown);\n min-width: 180px;\n max-width: 320px;\n background-color: var(--tempest-bg);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n list-style: none;\n margin: 0;\n animation: tempest-menu-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.portalled {\n z-index: var(--tempest-z-popover);\n}\n\n.bottomStart {\n top: calc(100% + 4px);\n left: 0;\n}\n.bottomEnd {\n top: calc(100% + 4px);\n right: 0;\n}\n.topStart {\n bottom: calc(100% + 4px);\n left: 0;\n}\n.topEnd {\n bottom: calc(100% + 4px);\n right: 0;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .item:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.item:focus,\n.item.active {\n outline: none;\n background-color: var(--tempest-primary-soft);\n color: var(--tempest-primary-on-soft);\n box-shadow: inset 3px 0 0 0 var(--tempest-selected-indicator);\n}\n\n.item:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n\n.item.danger {\n color: var(--tempest-danger);\n}\n\n.separator {\n height: 1px;\n background-color: var(--tempest-border);\n margin: var(--tempest-space-1) 0;\n}\n\n.label {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text-muted);\n text-transform: uppercase;\n letter-spacing: var(--tempest-tracking-wider);\n}\n\n@keyframes tempest-menu-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .menu {\n animation: none;\n }\n}\n"],"mappings":";"}
@@ -1,20 +1,21 @@
1
1
  import "../../styles/component/DropdownMenu.css";
2
2
  //#region src/components/DropdownMenu/DropdownMenu.module.css
3
- var e = "tempest_root_5ZetV", t = "tempest_menu_-RiRW", n = "tempest_tempest-menu-in_uCs9q", r = "tempest_bottomStart_z60h9", i = "tempest_bottomEnd_b1tmU", a = "tempest_topStart_A0em2", o = "tempest_topEnd_cFA5-", s = "tempest_item_T-QPZ", c = "tempest_active_WNqoX", l = "tempest_danger_C0jN3", u = "tempest_separator_RimQa", d = "tempest_label_6yP76", f = {
3
+ var e = "tempest_root_5ZetV", t = "tempest_menu_-RiRW", n = "tempest_tempest-menu-in_uCs9q", r = "tempest_portalled_b7vCf", i = "tempest_bottomStart_z60h9", a = "tempest_bottomEnd_b1tmU", o = "tempest_topStart_A0em2", s = "tempest_topEnd_cFA5-", c = "tempest_item_T-QPZ", l = "tempest_active_WNqoX", u = "tempest_danger_C0jN3", d = "tempest_separator_RimQa", f = "tempest_label_6yP76", p = {
4
4
  root: e,
5
5
  menu: t,
6
6
  tempestMenuIn: n,
7
- bottomStart: r,
8
- bottomEnd: i,
9
- topStart: a,
10
- topEnd: o,
11
- item: s,
12
- active: c,
13
- danger: l,
14
- separator: u,
15
- label: d
7
+ portalled: r,
8
+ bottomStart: i,
9
+ bottomEnd: a,
10
+ topStart: o,
11
+ topEnd: s,
12
+ item: c,
13
+ active: l,
14
+ danger: u,
15
+ separator: d,
16
+ label: f
16
17
  };
17
18
  //#endregion
18
- export { c as active, i as bottomEnd, r as bottomStart, l as danger, f as default, s as item, d as label, t as menu, e as root, u as separator, n as tempestMenuIn, o as topEnd, a as topStart };
19
+ export { l as active, a as bottomEnd, i as bottomStart, u as danger, p as default, c as item, f as label, t as menu, r as portalled, e as root, d as separator, n as tempestMenuIn, s as topEnd, o as topStart };
19
20
 
20
21
  //# sourceMappingURL=DropdownMenu.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.module.js","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.menu {\n position: absolute;\n z-index: var(--tempest-z-dropdown);\n min-width: 180px;\n max-width: 320px;\n background-color: var(--tempest-bg);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n list-style: none;\n margin: 0;\n animation: tempest-menu-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.bottomStart {\n top: calc(100% + 4px);\n left: 0;\n}\n.bottomEnd {\n top: calc(100% + 4px);\n right: 0;\n}\n.topStart {\n bottom: calc(100% + 4px);\n left: 0;\n}\n.topEnd {\n bottom: calc(100% + 4px);\n right: 0;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .item:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.item:focus,\n.item.active {\n outline: none;\n background-color: var(--tempest-primary-soft);\n color: var(--tempest-primary-on-soft);\n box-shadow: inset 3px 0 0 0 var(--tempest-selected-indicator);\n}\n\n.item:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n\n.item.danger {\n color: var(--tempest-danger);\n}\n\n.separator {\n height: 1px;\n background-color: var(--tempest-border);\n margin: var(--tempest-space-1) 0;\n}\n\n.label {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text-muted);\n text-transform: uppercase;\n letter-spacing: var(--tempest-tracking-wider);\n}\n\n@keyframes tempest-menu-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .menu {\n animation: none;\n }\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"DropdownMenu.module.js","names":[],"sources":["../../../src/components/DropdownMenu/DropdownMenu.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.menu {\n position: absolute;\n z-index: var(--tempest-z-dropdown);\n min-width: 180px;\n max-width: 320px;\n background-color: var(--tempest-bg);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-1);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n list-style: none;\n margin: 0;\n animation: tempest-menu-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.portalled {\n z-index: var(--tempest-z-popover);\n}\n\n.bottomStart {\n top: calc(100% + 4px);\n left: 0;\n}\n.bottomEnd {\n top: calc(100% + 4px);\n right: 0;\n}\n.topStart {\n bottom: calc(100% + 4px);\n left: 0;\n}\n.topEnd {\n bottom: calc(100% + 4px);\n right: 0;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: none;\n border: none;\n border-radius: var(--tempest-radius-sm);\n color: var(--tempest-text);\n font-family: inherit;\n font-size: inherit;\n line-height: var(--tempest-leading-snug);\n text-align: left;\n cursor: pointer;\n transition: var(--tempest-transition-color);\n}\n\n@media (hover: hover) and (pointer: fine) {\n .item:hover:not(:disabled) {\n background-color: var(--tempest-surface);\n }\n}\n\n.item:focus,\n.item.active {\n outline: none;\n background-color: var(--tempest-primary-soft);\n color: var(--tempest-primary-on-soft);\n box-shadow: inset 3px 0 0 0 var(--tempest-selected-indicator);\n}\n\n.item:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n\n.item.danger {\n color: var(--tempest-danger);\n}\n\n.separator {\n height: 1px;\n background-color: var(--tempest-border);\n margin: var(--tempest-space-1) 0;\n}\n\n.label {\n padding: var(--tempest-space-1) var(--tempest-space-3);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n color: var(--tempest-text-muted);\n text-transform: uppercase;\n letter-spacing: var(--tempest-tracking-wider);\n}\n\n@keyframes tempest-menu-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .menu {\n animation: none;\n }\n}\n"],"mappings":";"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("../Button/Button.cjs"),n=require("../Card/Card.cjs"),r=require("../Layout/Layout.cjs"),i=require("./FilterPanel.module.cjs");let a=require("react"),o=require("react/jsx-runtime");var s={"pt-BR":{title:`Filtros`,clear:`Limpar filtros`,apply:`Filtrar`},en:{title:`Filters`,clear:`Clear filters`,apply:`Apply filters`}};function c({children:c,title:l,actions:u,onClear:d,onApply:f,applyDisabled:p=!1,locale:m=`pt-BR`,labels:h,minFieldWidth:g=`12rem`,columns:_,className:v,style:y,...b}){let x=(0,a.useId)(),S={...s[m],...h},C=l??S.title,w=e=>{e.preventDefault(),f?.()},T=u!==void 0||d!==void 0||f!==void 0?(0,o.jsxs)(`div`,{className:i.default.actions,children:[u,d&&(0,o.jsx)(t.Button,{type:`button`,variant:`ghost`,size:`sm`,onClick:d,children:S.clear}),f&&(0,o.jsx)(t.Button,{type:`submit`,size:`sm`,form:x,disabled:p,children:S.apply})]}):void 0,E=(0,o.jsx)(r.Grid,{className:i.default.fields,columns:_??`repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))`,children:c});return(0,o.jsx)(n.Card,{flush:!0,role:`search`,"aria-label":typeof C==`string`?C:void 0,title:C,actions:T,className:e.cn(i.default.panel,v),style:{"--tempest-filter-panel-min":g,...y},...b,children:f?(0,o.jsx)(`form`,{id:x,className:i.default.form,onSubmit:w,noValidate:!0,children:E}):E})}exports.FilterPanel=c;
2
+ //# sourceMappingURL=FilterPanel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.cjs","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.tsx"],"sourcesContent":["import {\n useId,\n type CSSProperties,\n type FormEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { Button } from \"../Button\";\nimport { Card } from \"../Card\";\nimport { Grid, type ResponsiveValue } from \"../Layout\";\nimport styles from \"./FilterPanel.module.css\";\n\n/** Labels the panel writes itself. */\nexport interface FilterPanelLabels {\n /** Default title. `\"Filtros\"` / `\"Filters\"`. */\n title: string;\n /** The clear button. `\"Limpar filtros\"` / `\"Clear filters\"`. */\n clear: string;\n /** The apply button. `\"Filtrar\"` / `\"Apply filters\"`. */\n apply: string;\n}\n\n/** Built-in labels per locale. */\nconst LABELS: Record<\"pt-BR\" | \"en\", FilterPanelLabels> = {\n \"pt-BR\": { title: \"Filtros\", clear: \"Limpar filtros\", apply: \"Filtrar\" },\n en: { title: \"Filters\", clear: \"Clear filters\", apply: \"Apply filters\" },\n};\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"title\" | \"children\" | \"onSubmit\";\n\nexport interface FilterPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The filter controls the screen declares — `Input`, `Select`, a date picker. One grid cell each. */\n children: ReactNode;\n /** Heading. Default from `locale`. A string renders as an `h3`; a node renders as given. */\n title?: ReactNode;\n /** Header actions that are not fields — \"Atualizar a cada 30s\", \"Exportar CSV\". */\n actions?: ReactNode;\n /** Return the screen to its initial filters. Renders the clear button only when set. */\n onClear?: () => void;\n /**\n * Publish the draft. When set, the fields sit in a `<form>`: the apply button\n * submits it and Enter in any text field applies.\n */\n onApply?: () => void;\n /**\n * Disable the apply button — pass `!filters.isDirty`. Enter follows the button:\n * it is the form's default button, so a disabled one blocks implicit submission\n * too, and nothing is re-applied when nothing changed.\n */\n applyDisabled?: boolean;\n /** Locale for the default title and button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Override any of the labels. */\n labels?: Partial<FilterPanelLabels>;\n /**\n * Narrowest a field may get before the grid drops a column. Default `\"12rem\"`.\n * The count follows the **panel's** width, so the same panel fits a page and a\n * sidebar.\n */\n minFieldWidth?: string;\n /**\n * Fixed columns per viewport, as in `Grid` — `{ mobile: 1, tablet: 2, desktop: 4 }`.\n * Replaces `minFieldWidth`, and follows the **viewport**, not the panel.\n */\n columns?: ResponsiveValue<number | string>;\n}\n\n/**\n * The frame around a fixed set of filters: a card with a title, a grid of the\n * fields the screen declares, and the actions that belong to no field — clear,\n * apply, export.\n *\n * It holds no state and has no opinion on the fields. `FilterBar` is for ad hoc\n * querying (the user picks field, operator and value); this is for the set a\n * screen already knows — município, UF, situação, a search box. Pair it with\n * `useDraftFilters`: the fields edit `draft`, `onApply` and `onClear` are the\n * hook's `apply` and `clear`.\n *\n * What it settles, each measured in Chromium against the `Card` + `Grid`\n * composition it replaces:\n *\n * - **The grid follows the panel, not the viewport.** `Grid` switches columns on\n * viewport breakpoints, so the same filters in a 360 px column of a 1280 px\n * screen got four 57.5 px fields. The default here is\n * `repeat(auto-fill, minmax(min(100%, minFieldWidth), 1fr))`: the column count\n * comes from the room the panel has, and `min(100%, …)` keeps a floor wider than\n * the phone from overflowing it.\n * - **Cells align to the top.** A field showing an error is taller; with the grid\n * default (`stretch`), its row-mates' boxes grew to match.\n * - **The header wraps.** Three actions next to the title measured 527 px in a\n * 308 px header at 390 px wide, and scrolled the whole page sideways. `Card`'s\n * header now wraps, and so do the actions inside it.\n * - **Clear and apply have a fixed place**, at the end of the header, apply last.\n *\n * @example\n * const filters = useDraftFilters({ search: \"\", uf: \"\" });\n * <FilterPanel\n * onApply={() => filters.apply()}\n * onClear={filters.clear}\n * applyDisabled={!filters.isDirty}\n * >\n * <Input label=\"Busca\" value={filters.draft.search}\n * onChange={(e) => filters.set({ search: e.target.value })} />\n * <Select label=\"UF\" options={UFS} value={filters.draft.uf}\n * onChange={(e) => filters.set({ uf: e.target.value })} />\n * </FilterPanel>\n */\nexport function FilterPanel({\n children,\n title,\n actions,\n onClear,\n onApply,\n applyDisabled = false,\n locale = \"pt-BR\",\n labels,\n minFieldWidth = \"12rem\",\n columns,\n className,\n style,\n ...props\n}: FilterPanelProps) {\n const formId = useId();\n const text: FilterPanelLabels = { ...LABELS[locale], ...labels };\n const heading = title ?? text.title;\n\n const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {\n event.preventDefault();\n onApply?.();\n };\n\n const hasActions = actions !== undefined || onClear !== undefined || onApply !== undefined;\n const headerActions = hasActions ? (\n <div className={styles.actions}>\n {actions}\n {onClear && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onClear}>\n {text.clear}\n </Button>\n )}\n {onApply && (\n <Button type=\"submit\" size=\"sm\" form={formId} disabled={applyDisabled}>\n {text.apply}\n </Button>\n )}\n </div>\n ) : undefined;\n\n const fields = (\n <Grid\n className={styles.fields}\n columns={\n columns ??\n \"repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))\"\n }\n >\n {children}\n </Grid>\n );\n\n return (\n <Card\n flush\n role=\"search\"\n aria-label={typeof heading === \"string\" ? heading : undefined}\n title={heading}\n actions={headerActions}\n className={cn(styles.panel, className)}\n style={{ \"--tempest-filter-panel-min\": minFieldWidth, ...style } as CSSProperties}\n {...props}\n >\n {onApply ? (\n <form id={formId} className={styles.form} onSubmit={handleSubmit} noValidate>\n {fields}\n </form>\n ) : (\n fields\n )}\n </Card>\n );\n}\n"],"mappings":"oOA0BA,IAAM,EAAoD,CACtD,QAAS,CAAE,MAAO,UAAW,MAAO,iBAAkB,MAAO,SAAU,EACvE,GAAI,CAAE,MAAO,UAAW,MAAO,gBAAiB,MAAO,eAAgB,CAC3E,EAkFA,SAAgB,EAAY,CACxB,WACA,QACA,UACA,UACA,UACA,gBAAgB,GAChB,SAAS,QACT,SACA,gBAAgB,QAChB,UACA,YACA,QACA,GAAG,GACc,CACjB,IAAM,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,EAA0B,CAAE,GAAG,EAAO,GAAS,GAAG,CAAO,EACzD,EAAU,GAAS,EAAK,MAExB,EAAgB,GAA4C,CAC9D,EAAM,eAAe,EACrB,IAAU,CACd,EAGM,EADa,IAAY,IAAA,IAAa,IAAY,IAAA,IAAa,IAAY,IAAA,IAE7E,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,QAAvB,SAAA,CACK,EACA,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAQ,KAAK,SAAS,QAAQ,QAAQ,KAAK,KAAK,QAAS,EACpD,SAAA,EAAK,KACF,CAAA,EAEX,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAQ,KAAK,SAAS,KAAK,KAAK,KAAM,EAAQ,SAAU,EACnD,SAAA,EAAK,KACF,CAAA,CAEX,CACL,CAAA,EAAA,IAAA,GAEE,GACF,EAAA,EAAA,IAAA,CAAC,EAAA,KAAD,CACI,UAAW,EAAA,QAAO,OAClB,QACI,GACA,6EAGH,UACC,CAAA,EAGV,OACI,EAAA,EAAA,IAAA,CAAC,EAAA,KAAD,CACI,MAAA,GACA,KAAK,SACL,aAAY,OAAO,GAAY,SAAW,EAAU,IAAA,GACpD,MAAO,EACP,QAAS,EACT,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,CAAS,EACrC,MAAO,CAAE,6BAA8B,EAAe,GAAG,CAAM,EAC/D,GAAI,EAEH,SAAA,GACG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,GAAI,EAAQ,UAAW,EAAA,QAAO,KAAM,SAAU,EAAc,WAAA,GAC7D,SAAA,CACC,CAAA,EAEN,CAEF,CAAA,CAEd"}
@@ -0,0 +1,75 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { Button as t } from "../Button/Button.js";
3
+ import { Card as n } from "../Card/Card.js";
4
+ import { Grid as r } from "../Layout/Layout.js";
5
+ import i from "./FilterPanel.module.js";
6
+ import { useId as a } from "react";
7
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ //#region src/components/FilterPanel/FilterPanel.tsx
9
+ var c = {
10
+ "pt-BR": {
11
+ title: "Filtros",
12
+ clear: "Limpar filtros",
13
+ apply: "Filtrar"
14
+ },
15
+ en: {
16
+ title: "Filters",
17
+ clear: "Clear filters",
18
+ apply: "Apply filters"
19
+ }
20
+ };
21
+ function l({ children: l, title: u, actions: d, onClear: f, onApply: p, applyDisabled: m = !1, locale: h = "pt-BR", labels: g, minFieldWidth: _ = "12rem", columns: v, className: y, style: b, ...x }) {
22
+ let S = a(), C = {
23
+ ...c[h],
24
+ ...g
25
+ }, w = u ?? C.title, T = (e) => {
26
+ e.preventDefault(), p?.();
27
+ }, E = d !== void 0 || f !== void 0 || p !== void 0 ? /* @__PURE__ */ s("div", {
28
+ className: i.actions,
29
+ children: [
30
+ d,
31
+ f && /* @__PURE__ */ o(t, {
32
+ type: "button",
33
+ variant: "ghost",
34
+ size: "sm",
35
+ onClick: f,
36
+ children: C.clear
37
+ }),
38
+ p && /* @__PURE__ */ o(t, {
39
+ type: "submit",
40
+ size: "sm",
41
+ form: S,
42
+ disabled: m,
43
+ children: C.apply
44
+ })
45
+ ]
46
+ }) : void 0, D = /* @__PURE__ */ o(r, {
47
+ className: i.fields,
48
+ columns: v ?? "repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))",
49
+ children: l
50
+ });
51
+ return /* @__PURE__ */ o(n, {
52
+ flush: !0,
53
+ role: "search",
54
+ "aria-label": typeof w == "string" ? w : void 0,
55
+ title: w,
56
+ actions: E,
57
+ className: e(i.panel, y),
58
+ style: {
59
+ "--tempest-filter-panel-min": _,
60
+ ...b
61
+ },
62
+ ...x,
63
+ children: p ? /* @__PURE__ */ o("form", {
64
+ id: S,
65
+ className: i.form,
66
+ onSubmit: T,
67
+ noValidate: !0,
68
+ children: D
69
+ }) : D
70
+ });
71
+ }
72
+ //#endregion
73
+ export { l as FilterPanel };
74
+
75
+ //# sourceMappingURL=FilterPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.js","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.tsx"],"sourcesContent":["import {\n useId,\n type CSSProperties,\n type FormEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { Button } from \"../Button\";\nimport { Card } from \"../Card\";\nimport { Grid, type ResponsiveValue } from \"../Layout\";\nimport styles from \"./FilterPanel.module.css\";\n\n/** Labels the panel writes itself. */\nexport interface FilterPanelLabels {\n /** Default title. `\"Filtros\"` / `\"Filters\"`. */\n title: string;\n /** The clear button. `\"Limpar filtros\"` / `\"Clear filters\"`. */\n clear: string;\n /** The apply button. `\"Filtrar\"` / `\"Apply filters\"`. */\n apply: string;\n}\n\n/** Built-in labels per locale. */\nconst LABELS: Record<\"pt-BR\" | \"en\", FilterPanelLabels> = {\n \"pt-BR\": { title: \"Filtros\", clear: \"Limpar filtros\", apply: \"Filtrar\" },\n en: { title: \"Filters\", clear: \"Clear filters\", apply: \"Apply filters\" },\n};\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"title\" | \"children\" | \"onSubmit\";\n\nexport interface FilterPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The filter controls the screen declares — `Input`, `Select`, a date picker. One grid cell each. */\n children: ReactNode;\n /** Heading. Default from `locale`. A string renders as an `h3`; a node renders as given. */\n title?: ReactNode;\n /** Header actions that are not fields — \"Atualizar a cada 30s\", \"Exportar CSV\". */\n actions?: ReactNode;\n /** Return the screen to its initial filters. Renders the clear button only when set. */\n onClear?: () => void;\n /**\n * Publish the draft. When set, the fields sit in a `<form>`: the apply button\n * submits it and Enter in any text field applies.\n */\n onApply?: () => void;\n /**\n * Disable the apply button — pass `!filters.isDirty`. Enter follows the button:\n * it is the form's default button, so a disabled one blocks implicit submission\n * too, and nothing is re-applied when nothing changed.\n */\n applyDisabled?: boolean;\n /** Locale for the default title and button labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Override any of the labels. */\n labels?: Partial<FilterPanelLabels>;\n /**\n * Narrowest a field may get before the grid drops a column. Default `\"12rem\"`.\n * The count follows the **panel's** width, so the same panel fits a page and a\n * sidebar.\n */\n minFieldWidth?: string;\n /**\n * Fixed columns per viewport, as in `Grid` — `{ mobile: 1, tablet: 2, desktop: 4 }`.\n * Replaces `minFieldWidth`, and follows the **viewport**, not the panel.\n */\n columns?: ResponsiveValue<number | string>;\n}\n\n/**\n * The frame around a fixed set of filters: a card with a title, a grid of the\n * fields the screen declares, and the actions that belong to no field — clear,\n * apply, export.\n *\n * It holds no state and has no opinion on the fields. `FilterBar` is for ad hoc\n * querying (the user picks field, operator and value); this is for the set a\n * screen already knows — município, UF, situação, a search box. Pair it with\n * `useDraftFilters`: the fields edit `draft`, `onApply` and `onClear` are the\n * hook's `apply` and `clear`.\n *\n * What it settles, each measured in Chromium against the `Card` + `Grid`\n * composition it replaces:\n *\n * - **The grid follows the panel, not the viewport.** `Grid` switches columns on\n * viewport breakpoints, so the same filters in a 360 px column of a 1280 px\n * screen got four 57.5 px fields. The default here is\n * `repeat(auto-fill, minmax(min(100%, minFieldWidth), 1fr))`: the column count\n * comes from the room the panel has, and `min(100%, …)` keeps a floor wider than\n * the phone from overflowing it.\n * - **Cells align to the top.** A field showing an error is taller; with the grid\n * default (`stretch`), its row-mates' boxes grew to match.\n * - **The header wraps.** Three actions next to the title measured 527 px in a\n * 308 px header at 390 px wide, and scrolled the whole page sideways. `Card`'s\n * header now wraps, and so do the actions inside it.\n * - **Clear and apply have a fixed place**, at the end of the header, apply last.\n *\n * @example\n * const filters = useDraftFilters({ search: \"\", uf: \"\" });\n * <FilterPanel\n * onApply={() => filters.apply()}\n * onClear={filters.clear}\n * applyDisabled={!filters.isDirty}\n * >\n * <Input label=\"Busca\" value={filters.draft.search}\n * onChange={(e) => filters.set({ search: e.target.value })} />\n * <Select label=\"UF\" options={UFS} value={filters.draft.uf}\n * onChange={(e) => filters.set({ uf: e.target.value })} />\n * </FilterPanel>\n */\nexport function FilterPanel({\n children,\n title,\n actions,\n onClear,\n onApply,\n applyDisabled = false,\n locale = \"pt-BR\",\n labels,\n minFieldWidth = \"12rem\",\n columns,\n className,\n style,\n ...props\n}: FilterPanelProps) {\n const formId = useId();\n const text: FilterPanelLabels = { ...LABELS[locale], ...labels };\n const heading = title ?? text.title;\n\n const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {\n event.preventDefault();\n onApply?.();\n };\n\n const hasActions = actions !== undefined || onClear !== undefined || onApply !== undefined;\n const headerActions = hasActions ? (\n <div className={styles.actions}>\n {actions}\n {onClear && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onClear}>\n {text.clear}\n </Button>\n )}\n {onApply && (\n <Button type=\"submit\" size=\"sm\" form={formId} disabled={applyDisabled}>\n {text.apply}\n </Button>\n )}\n </div>\n ) : undefined;\n\n const fields = (\n <Grid\n className={styles.fields}\n columns={\n columns ??\n \"repeat(auto-fill, minmax(min(100%, var(--tempest-filter-panel-min)), 1fr))\"\n }\n >\n {children}\n </Grid>\n );\n\n return (\n <Card\n flush\n role=\"search\"\n aria-label={typeof heading === \"string\" ? heading : undefined}\n title={heading}\n actions={headerActions}\n className={cn(styles.panel, className)}\n style={{ \"--tempest-filter-panel-min\": minFieldWidth, ...style } as CSSProperties}\n {...props}\n >\n {onApply ? (\n <form id={formId} className={styles.form} onSubmit={handleSubmit} noValidate>\n {fields}\n </form>\n ) : (\n fields\n )}\n </Card>\n );\n}\n"],"mappings":";;;;;;;;AA0BA,IAAM,IAAoD;CACtD,SAAS;EAAE,OAAO;EAAW,OAAO;EAAkB,OAAO;CAAU;CACvE,IAAI;EAAE,OAAO;EAAW,OAAO;EAAiB,OAAO;CAAgB;AAC3E;AAkFA,SAAgB,EAAY,EACxB,aACA,UACA,YACA,YACA,YACA,mBAAgB,IAChB,YAAS,SACT,WACA,mBAAgB,SAChB,YACA,cACA,UACA,GAAG,KACc;CACjB,IAAM,IAAS,EAAM,GACf,IAA0B;EAAE,GAAG,EAAO;EAAS,GAAG;CAAO,GACzD,IAAU,KAAS,EAAK,OAExB,KAAgB,MAA4C;EAE9D,AADA,EAAM,eAAe,GACrB,IAAU;CACd,GAGM,IADa,MAAY,KAAA,KAAa,MAAY,KAAA,KAAa,MAAY,KAAA,IAE7E,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAvB,UAAA;GACK;GACA,KACG,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAQ,MAAK;IAAK,SAAS;IACpD,UAAA,EAAK;GACF,CAAA;GAEX,KACG,kBAAC,GAAD;IAAQ,MAAK;IAAS,MAAK;IAAK,MAAM;IAAQ,UAAU;IACnD,UAAA,EAAK;GACF,CAAA;EAEX;CACL,CAAA,IAAA,KAAA,GAEE,IACF,kBAAC,GAAD;EACI,WAAW,EAAO;EAClB,SACI,KACA;EAGH;CACC,CAAA;CAGV,OACI,kBAAC,GAAD;EACI,OAAA;EACA,MAAK;EACL,cAAY,OAAO,KAAY,WAAW,IAAU,KAAA;EACpD,OAAO;EACP,SAAS;EACT,WAAW,EAAG,EAAO,OAAO,CAAS;EACrC,OAAO;GAAE,8BAA8B;GAAe,GAAG;EAAM;EAC/D,GAAI;EAEH,UAAA,IACG,kBAAC,QAAD;GAAM,IAAI;GAAQ,WAAW,EAAO;GAAM,UAAU;GAAc,YAAA;GAC7D,UAAA;EACC,CAAA,IAEN;CAEF,CAAA;AAEd"}
@@ -0,0 +1,3 @@
1
+ require("../../styles/component/FilterPanel.css");
2
+ var e=`tempest_panel_ab2ba`,t=`tempest_actions_9a-Sm`,n=`tempest_form_bMDhN`,r=`tempest_fields_M30-g`,i={panel:e,actions:t,form:n,fields:r};exports.actions=t,exports.default=i,exports.fields=r,exports.form=n,exports.panel=e;
3
+ //# sourceMappingURL=FilterPanel.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.module.cjs","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.module.css"],"sourcesContent":[".panel {\n min-width: 0;\n}\n\n/*\n * Actions share the header row with the title on a wide screen and move under it,\n * wrapping among themselves, when they do not fit. Apply stays last.\n */\n.actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--tempest-space-2);\n margin-left: auto;\n}\n\n.form {\n margin: 0;\n}\n\n/*\n * Top alignment keeps a field's box its own height: the grid default stretches every\n * cell of a row to the tallest one, which is the field showing an error.\n */\n.fields {\n align-items: start;\n}\n"],"mappings":";"}
@@ -0,0 +1,12 @@
1
+ import "../../styles/component/FilterPanel.css";
2
+ //#region src/components/FilterPanel/FilterPanel.module.css
3
+ var e = "tempest_panel_ab2ba", t = "tempest_actions_9a-Sm", n = "tempest_form_bMDhN", r = "tempest_fields_M30-g", i = {
4
+ panel: e,
5
+ actions: t,
6
+ form: n,
7
+ fields: r
8
+ };
9
+ //#endregion
10
+ export { t as actions, i as default, r as fields, n as form, e as panel };
11
+
12
+ //# sourceMappingURL=FilterPanel.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.module.js","names":[],"sources":["../../../src/components/FilterPanel/FilterPanel.module.css"],"sourcesContent":[".panel {\n min-width: 0;\n}\n\n/*\n * Actions share the header row with the title on a wide screen and move under it,\n * wrapping among themselves, when they do not fit. Apply stays last.\n */\n.actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--tempest-space-2);\n margin-left: auto;\n}\n\n.form {\n margin: 0;\n}\n\n/*\n * Top alignment keeps a field's box its own height: the grid default stretches every\n * cell of a row to the tallest one, which is the field showing an error.\n */\n.fields {\n align-items: start;\n}\n"],"mappings":";"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("./HoverCard.module.cjs");let n=require("react"),r=require("react/jsx-runtime");function i({trigger:i,children:a,openDelay:o=300,closeDelay:s=150,placement:c=`bottom`,className:l,...u}){let[d,f]=(0,n.useState)(!1),p=(0,n.useId)(),m=(0,n.useRef)(null),h=(0,n.useCallback)(()=>{m.current!==null&&(clearTimeout(m.current),m.current=null)},[]),g=(0,n.useCallback)(()=>{h(),m.current=setTimeout(()=>f(!0),o)},[h,o]),_=(0,n.useCallback)(()=>{h(),m.current=setTimeout(()=>f(!1),s)},[h,s]);return(0,n.useEffect)(()=>h,[h]),(0,r.jsxs)(`span`,{className:t.default.root,onMouseEnter:g,onMouseLeave:_,onFocus:g,onBlur:_,children:[(0,r.jsx)(`span`,{"aria-describedby":d?p:void 0,className:t.default.trigger,children:i}),d&&(0,r.jsx)(`div`,{id:p,role:`dialog`,className:e.cn(t.default.card,t.default[c],l),...u,children:a})]})}exports.HoverCard=i;
1
+ const e=require("../../utils/cn.cjs"),t=require("../Portal/Portal.cjs"),n=require("../Portal/anchor-position.cjs"),r=require("../Portal/tab-bridge.cjs"),i=require("./HoverCard.module.cjs");let a=require("react"),o=require("react/jsx-runtime");var s=8;function c({trigger:c,children:l,openDelay:u=300,closeDelay:d=150,placement:f=`bottom`,portal:p=!0,className:m,style:h,...g}){let[_,v]=(0,a.useState)(!1),[y,b]=(0,a.useState)(null),[x,S]=(0,a.useState)(null),C=n.useAnchorPosition({anchor:y,floating:x,side:f,align:`center`,offset:s,enabled:p&&_});r.usePortalTabOrder({enabled:p&&_,anchor:y,panel:x});let w=(0,a.useId)(),T=(0,a.useRef)(null),E=(0,a.useCallback)(()=>{T.current!==null&&(clearTimeout(T.current),T.current=null)},[]),D=(0,a.useCallback)(()=>{E(),T.current=setTimeout(()=>v(!0),u)},[E,u]),O=(0,a.useCallback)(()=>{E(),T.current=setTimeout(()=>v(!1),d)},[E,d]);(0,a.useEffect)(()=>E,[E]);let k=(0,o.jsx)(`div`,{ref:S,id:w,role:`dialog`,className:e.cn(i.default.card,i.default[f],p&&i.default.portalled,m),style:{...h,...C},...g,children:l});return(0,o.jsxs)(`span`,{ref:b,className:i.default.root,onMouseEnter:D,onMouseLeave:O,onFocus:D,onBlur:O,children:[(0,o.jsx)(`span`,{"aria-describedby":_?w:void 0,className:i.default.trigger,children:c}),_&&(p?(0,o.jsx)(t.Portal,{children:k}):k)]})}exports.HoverCard=c;
2
2
  //# sourceMappingURL=HoverCard.cjs.map