tempest-react-sdk 0.73.0 → 0.75.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 (246) hide show
  1. package/README.md +6 -4
  2. package/dist/br/BrazilMap.module.cjs.map +1 -1
  3. package/dist/br/BrazilMap.module.js.map +1 -1
  4. package/dist/br/MapMarkers.cjs +1 -1
  5. package/dist/br/MapMarkers.cjs.map +1 -1
  6. package/dist/br/MapMarkers.js +19 -22
  7. package/dist/br/MapMarkers.js.map +1 -1
  8. package/dist/br/MunicipalitySearch.cjs +1 -1
  9. package/dist/br/MunicipalitySearch.cjs.map +1 -1
  10. package/dist/br/MunicipalitySearch.js +66 -48
  11. package/dist/br/MunicipalitySearch.js.map +1 -1
  12. package/dist/br/MunicipalitySearch.module.cjs +1 -1
  13. package/dist/br/MunicipalitySearch.module.cjs.map +1 -1
  14. package/dist/br/MunicipalitySearch.module.js +4 -3
  15. package/dist/br/MunicipalitySearch.module.js.map +1 -1
  16. package/dist/br.d.ts +12 -0
  17. package/dist/components/AIChat/AIChat.module.cjs.map +1 -1
  18. package/dist/components/AIChat/AIChat.module.js.map +1 -1
  19. package/dist/components/AIChat/AIChatTurn.cjs +1 -1
  20. package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
  21. package/dist/components/AIChat/AIChatTurn.js +1 -1
  22. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  23. package/dist/components/AppBar/AppBar.cjs +1 -1
  24. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  25. package/dist/components/AppBar/AppBar.js +1 -0
  26. package/dist/components/AppBar/AppBar.js.map +1 -1
  27. package/dist/components/AppBar/AppBar.module.cjs +1 -1
  28. package/dist/components/AppBar/AppBar.module.cjs.map +1 -1
  29. package/dist/components/AppBar/AppBar.module.js +12 -11
  30. package/dist/components/AppBar/AppBar.module.js.map +1 -1
  31. package/dist/components/AppShell/AppShell.module.cjs.map +1 -1
  32. package/dist/components/AppShell/AppShell.module.js.map +1 -1
  33. package/dist/components/Button/Button.cjs +1 -1
  34. package/dist/components/Button/Button.cjs.map +1 -1
  35. package/dist/components/Button/Button.js +41 -36
  36. package/dist/components/Button/Button.js.map +1 -1
  37. package/dist/components/Button/Button.module.cjs.map +1 -1
  38. package/dist/components/Button/Button.module.js.map +1 -1
  39. package/dist/components/Button/ButtonContent.cjs +2 -0
  40. package/dist/components/Button/ButtonContent.cjs.map +1 -0
  41. package/dist/components/Button/ButtonContent.js +34 -0
  42. package/dist/components/Button/ButtonContent.js.map +1 -0
  43. package/dist/components/Button/ButtonSlot.cjs +2 -0
  44. package/dist/components/Button/ButtonSlot.cjs.map +1 -0
  45. package/dist/components/Button/ButtonSlot.js +36 -0
  46. package/dist/components/Button/ButtonSlot.js.map +1 -0
  47. package/dist/components/Button/button-class.cjs +2 -0
  48. package/dist/components/Button/button-class.cjs.map +1 -0
  49. package/dist/components/Button/button-class.js +13 -0
  50. package/dist/components/Button/button-class.js.map +1 -0
  51. package/dist/components/Button/link-rel.cjs +2 -0
  52. package/dist/components/Button/link-rel.cjs.map +1 -0
  53. package/dist/components/Button/link-rel.js +11 -0
  54. package/dist/components/Button/link-rel.js.map +1 -0
  55. package/dist/components/Button/slot.cjs +2 -0
  56. package/dist/components/Button/slot.cjs.map +1 -0
  57. package/dist/components/Button/slot.js +41 -0
  58. package/dist/components/Button/slot.js.map +1 -0
  59. package/dist/components/Carousel/Carousel.module.cjs.map +1 -1
  60. package/dist/components/Carousel/Carousel.module.js.map +1 -1
  61. package/dist/components/Chat/Chat.module.cjs.map +1 -1
  62. package/dist/components/Chat/Chat.module.js.map +1 -1
  63. package/dist/components/Collapsible/Collapsible.cjs +1 -1
  64. package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
  65. package/dist/components/Collapsible/Collapsible.js +1 -1
  66. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  67. package/dist/components/Combobox/Combobox.cjs +1 -1
  68. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  69. package/dist/components/Combobox/Combobox.js +18 -23
  70. package/dist/components/Combobox/Combobox.js.map +1 -1
  71. package/dist/components/Combobox/use-listbox-dismiss.cjs +2 -0
  72. package/dist/components/Combobox/use-listbox-dismiss.cjs.map +1 -0
  73. package/dist/components/Combobox/use-listbox-dismiss.js +34 -0
  74. package/dist/components/Combobox/use-listbox-dismiss.js.map +1 -0
  75. package/dist/components/Dropzone/Dropzone.module.cjs.map +1 -1
  76. package/dist/components/Dropzone/Dropzone.module.js.map +1 -1
  77. package/dist/components/FilterBar/FilterBar.module.cjs.map +1 -1
  78. package/dist/components/FilterBar/FilterBar.module.js.map +1 -1
  79. package/dist/components/FloatingActionButton/FloatingActionButton.module.cjs.map +1 -1
  80. package/dist/components/FloatingActionButton/FloatingActionButton.module.js.map +1 -1
  81. package/dist/components/Form/Form.cjs.map +1 -1
  82. package/dist/components/Form/Form.js.map +1 -1
  83. package/dist/components/ImageCropper/ImageCropper.module.cjs.map +1 -1
  84. package/dist/components/ImageCropper/ImageCropper.module.js.map +1 -1
  85. package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
  86. package/dist/components/InstallButton/InstallButton.js.map +1 -1
  87. package/dist/components/ListTile/ListTile.module.cjs.map +1 -1
  88. package/dist/components/ListTile/ListTile.module.js.map +1 -1
  89. package/dist/components/Markdown/Markdown.module.cjs.map +1 -1
  90. package/dist/components/Markdown/Markdown.module.js.map +1 -1
  91. package/dist/components/Menubar/Menubar.module.cjs.map +1 -1
  92. package/dist/components/Menubar/Menubar.module.js.map +1 -1
  93. package/dist/components/Modal/Modal.cjs +1 -1
  94. package/dist/components/Modal/Modal.cjs.map +1 -1
  95. package/dist/components/Modal/Modal.js +1 -1
  96. package/dist/components/Modal/Modal.js.map +1 -1
  97. package/dist/components/Modal/Modal.module.cjs +1 -1
  98. package/dist/components/Modal/Modal.module.cjs.map +1 -1
  99. package/dist/components/Modal/Modal.module.js +1 -1
  100. package/dist/components/Modal/Modal.module.js.map +1 -1
  101. package/dist/components/MultiSelect/MultiSelect.cjs +1 -1
  102. package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
  103. package/dist/components/MultiSelect/MultiSelect.js +55 -54
  104. package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
  105. package/dist/components/Navbar/Navbar.cjs +1 -1
  106. package/dist/components/Navbar/Navbar.cjs.map +1 -1
  107. package/dist/components/Navbar/Navbar.js +1 -0
  108. package/dist/components/Navbar/Navbar.js.map +1 -1
  109. package/dist/components/Navbar/Navbar.module.cjs +1 -1
  110. package/dist/components/Navbar/Navbar.module.cjs.map +1 -1
  111. package/dist/components/Navbar/Navbar.module.js +9 -8
  112. package/dist/components/Navbar/Navbar.module.js.map +1 -1
  113. package/dist/components/NavigationMenu/NavigationMenu.module.cjs.map +1 -1
  114. package/dist/components/NavigationMenu/NavigationMenu.module.js.map +1 -1
  115. package/dist/components/NavigationRail/NavigationRail.module.cjs.map +1 -1
  116. package/dist/components/NavigationRail/NavigationRail.module.js.map +1 -1
  117. package/dist/components/NotificationCenter/NotificationCenter.module.cjs.map +1 -1
  118. package/dist/components/NotificationCenter/NotificationCenter.module.js.map +1 -1
  119. package/dist/components/Page/Page.cjs +1 -1
  120. package/dist/components/Page/Page.cjs.map +1 -1
  121. package/dist/components/Page/Page.js +23 -39
  122. package/dist/components/Page/Page.js.map +1 -1
  123. package/dist/components/Page/Page.module.cjs +1 -1
  124. package/dist/components/Page/Page.module.cjs.map +1 -1
  125. package/dist/components/Page/Page.module.js +5 -11
  126. package/dist/components/Page/Page.module.js.map +1 -1
  127. package/dist/components/PasswordInput/PasswordInput.module.cjs.map +1 -1
  128. package/dist/components/PasswordInput/PasswordInput.module.js.map +1 -1
  129. package/dist/components/PinInput/PinInput.module.cjs.map +1 -1
  130. package/dist/components/PinInput/PinInput.module.js.map +1 -1
  131. package/dist/components/Reveal/Reveal.cjs +2 -0
  132. package/dist/components/Reveal/Reveal.cjs.map +1 -0
  133. package/dist/components/Reveal/Reveal.js +56 -0
  134. package/dist/components/Reveal/Reveal.js.map +1 -0
  135. package/dist/components/Reveal/Reveal.module.cjs +3 -0
  136. package/dist/components/Reveal/Reveal.module.cjs.map +1 -0
  137. package/dist/components/Reveal/Reveal.module.js +7 -0
  138. package/dist/components/Reveal/Reveal.module.js.map +1 -0
  139. package/dist/components/Reveal/reveal-threshold.cjs +2 -0
  140. package/dist/components/Reveal/reveal-threshold.cjs.map +1 -0
  141. package/dist/components/Reveal/reveal-threshold.js +13 -0
  142. package/dist/components/Reveal/reveal-threshold.js.map +1 -0
  143. package/dist/components/SectionHeader/SectionHeader.cjs +2 -0
  144. package/dist/components/SectionHeader/SectionHeader.cjs.map +1 -0
  145. package/dist/components/SectionHeader/SectionHeader.js +66 -0
  146. package/dist/components/SectionHeader/SectionHeader.js.map +1 -0
  147. package/dist/components/SectionHeader/SectionHeader.module.cjs +3 -0
  148. package/dist/components/SectionHeader/SectionHeader.module.cjs.map +1 -0
  149. package/dist/components/SectionHeader/SectionHeader.module.js +15 -0
  150. package/dist/components/SectionHeader/SectionHeader.module.js.map +1 -0
  151. package/dist/components/Timeline/Timeline.cjs +1 -1
  152. package/dist/components/Timeline/Timeline.cjs.map +1 -1
  153. package/dist/components/Timeline/Timeline.js +22 -15
  154. package/dist/components/Timeline/Timeline.js.map +1 -1
  155. package/dist/components/Timeline/Timeline.module.cjs +1 -1
  156. package/dist/components/Timeline/Timeline.module.cjs.map +1 -1
  157. package/dist/components/Timeline/Timeline.module.js +1 -1
  158. package/dist/components/Timeline/Timeline.module.js.map +1 -1
  159. package/dist/components/Tour/Tour.module.cjs.map +1 -1
  160. package/dist/components/Tour/Tour.module.js.map +1 -1
  161. package/dist/components/Transfer/Transfer.module.cjs.map +1 -1
  162. package/dist/components/Transfer/Transfer.module.js.map +1 -1
  163. package/dist/components/VirtualTable/VirtualTable.module.cjs.map +1 -1
  164. package/dist/components/VirtualTable/VirtualTable.module.js.map +1 -1
  165. package/dist/geo/TrajectoryMap.module.cjs.map +1 -1
  166. package/dist/geo/TrajectoryMap.module.js.map +1 -1
  167. package/dist/icons/IconPicker.module.cjs.map +1 -1
  168. package/dist/icons/IconPicker.module.js.map +1 -1
  169. package/dist/styles/Reveal.css +2 -0
  170. package/dist/styles/SectionHeader.css +2 -0
  171. package/dist/styles/actions.css +26 -24
  172. package/dist/styles/base.css +3 -1
  173. package/dist/styles/br.css +3 -3
  174. package/dist/styles/chat.css +5 -5
  175. package/dist/styles/component/AIChat.css +3 -3
  176. package/dist/styles/component/AppBar.css +1 -0
  177. package/dist/styles/component/AppShell.css +1 -1
  178. package/dist/styles/component/BrazilMap.css +2 -2
  179. package/dist/styles/component/Button.css +25 -23
  180. package/dist/styles/component/Carousel.css +3 -3
  181. package/dist/styles/component/Chat.css +2 -2
  182. package/dist/styles/component/Dropzone.css +2 -1
  183. package/dist/styles/component/FilterBar.css +2 -2
  184. package/dist/styles/component/FloatingActionButton.css +1 -1
  185. package/dist/styles/component/IconPicker.css +1 -1
  186. package/dist/styles/component/ImageCropper.css +2 -2
  187. package/dist/styles/component/ListTile.css +1 -1
  188. package/dist/styles/component/Markdown.css +1 -1
  189. package/dist/styles/component/Menubar.css +1 -1
  190. package/dist/styles/component/Modal.css +2 -2
  191. package/dist/styles/component/MunicipalitySearch.css +1 -1
  192. package/dist/styles/component/Navbar.css +1 -0
  193. package/dist/styles/component/NavigationMenu.css +1 -1
  194. package/dist/styles/component/NavigationRail.css +1 -1
  195. package/dist/styles/component/NotificationCenter.css +3 -3
  196. package/dist/styles/component/Page.css +0 -6
  197. package/dist/styles/component/PasswordInput.css +1 -1
  198. package/dist/styles/component/PinInput.css +1 -1
  199. package/dist/styles/component/Reveal.css +9 -0
  200. package/dist/styles/component/SectionHeader.css +9 -0
  201. package/dist/styles/component/Timeline.css +5 -5
  202. package/dist/styles/component/Tour.css +2 -2
  203. package/dist/styles/component/TrajectoryMap.css +1 -1
  204. package/dist/styles/component/Transfer.css +2 -2
  205. package/dist/styles/component/VirtualTable.css +2 -2
  206. package/dist/styles/core.css +3 -1
  207. package/dist/styles/data.css +15 -15
  208. package/dist/styles/feedback.css +3 -3
  209. package/dist/styles/forms.css +6 -5
  210. package/dist/styles/geo.css +1 -1
  211. package/dist/styles/icons.css +1 -1
  212. package/dist/styles/inverse.css +372 -0
  213. package/dist/styles/layout.css +9 -7
  214. package/dist/styles/manifest.json +14 -1
  215. package/dist/styles/navigation.css +8 -6
  216. package/dist/styles/overlay.css +2 -2
  217. package/dist/styles/utility.css +8 -0
  218. package/dist/styles.css +1 -1
  219. package/dist/tempest-react-sdk.cjs +1 -1
  220. package/dist/tempest-react-sdk.d.ts +473 -11
  221. package/dist/tempest-react-sdk.js +350 -346
  222. package/dist/theme/create-theme.cjs +3 -3
  223. package/dist/theme/create-theme.cjs.map +1 -1
  224. package/dist/theme/create-theme.js +72 -55
  225. package/dist/theme/create-theme.js.map +1 -1
  226. package/dist/theme/inverse-render.cjs +3 -0
  227. package/dist/theme/inverse-render.cjs.map +1 -0
  228. package/dist/theme/inverse-render.js +10 -0
  229. package/dist/theme/inverse-render.js.map +1 -0
  230. package/dist/theme/inverse-restore.cjs +5 -0
  231. package/dist/theme/inverse-restore.cjs.map +1 -0
  232. package/dist/theme/inverse-restore.js +30 -0
  233. package/dist/theme/inverse-restore.js.map +1 -0
  234. package/dist/theme/inverse-surface.cjs +2 -0
  235. package/dist/theme/inverse-surface.cjs.map +1 -0
  236. package/dist/theme/inverse-surface.js +255 -0
  237. package/dist/theme/inverse-surface.js.map +1 -0
  238. package/dist/theme/status-tokens.cjs +2 -0
  239. package/dist/theme/status-tokens.cjs.map +1 -0
  240. package/dist/theme/status-tokens.js +15 -0
  241. package/dist/theme/status-tokens.js.map +1 -0
  242. package/dist/vite/create-vite-config.cjs +1 -1
  243. package/dist/vite/create-vite-config.cjs.map +1 -1
  244. package/dist/vite/create-vite-config.js +6 -4
  245. package/dist/vite/create-vite-config.js.map +1 -1
  246. package/package.json +1 -1
package/README.md CHANGED
@@ -203,7 +203,7 @@ import "tempest-react-sdk/styles.css";
203
203
 
204
204
  This injects the design tokens (`--tempest-primary`, `--tempest-radius-md`, ...), a minimal CSS reset, and the per-component CSS Modules. Tokens live on `:root` and on `[data-tempest-theme="dark"]`, so the app can override them globally or per subtree (see [Theming](#theming-reference)).
205
205
 
206
- The reset claims the document surface too — `body { margin: 0 }` plus the theme's background and text colour, and a zero-specificity `height: 100%` chain down to `#root`. Without it an `AppShell` app opens with a spurious scrollbar and an unpainted frame around the shell in dark mode. All four are one ordinary declaration away from being overridden; see [The document surface](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/styles/#the-document-surface).
206
+ The reset claims the document surface too — `body { margin: 0 }` plus the theme's background and text colour, and a zero-specificity height chain: `height: 100%` on `html` and `body`, `min-height: 100%` on `#root`, which takes the fixed height only when it holds a single child — so a full-screen shell fills the viewport and a sticky `Navbar` placed directly in `#root` stays on screen. Without it an `AppShell` app opens with a spurious scrollbar and an unpainted frame around the shell in dark mode. All four are one ordinary declaration away from being overridden; see [The document surface](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/styles/#the-document-surface).
207
207
 
208
208
  The styles ship hashed under the `tempest_` namespace — they do **not** collide with Tailwind, Stitches, Linaria, or app-level CSS Modules.
209
209
 
@@ -229,7 +229,7 @@ export default defineConfig({ plugins: [react(), tempestStyles()] });
229
229
  import "tempest-react-sdk/styles/auto.css";
230
230
  ```
231
231
 
232
- **If your app owns its own layout,** do not reach for `core.css`: 44 of its 46 selectors dress markup you own (`html`, `body`, `#root`, `button`, `table`, `:where(ul, ol)[class]`). Dropping it is not the fix either — the components are written _against_ that reset, so without it they lose their box model, not their polish. Take the foundation in two pieces instead, which is also what `tempestStyles()` emits by default:
232
+ **If your app owns its own layout,** do not reach for `core.css`: 46 of its 48 rules dress markup you own (`html`, `body`, `#root`, `button`, `table`, `:where(ul, ol)[class]`). Dropping it is not the fix either — the components are written _against_ that reset, so without it they lose their box model, not their polish. Take the foundation in two pieces instead, which is also what `tempestStyles()` emits by default:
233
233
 
234
234
  ```ts
235
235
  import "tempest-react-sdk/styles/tokens.css"; // the 220 --tempest-* tokens
@@ -249,6 +249,8 @@ applyTheme(createTheme(themePresets.violet)); // or start from a preset
249
249
 
250
250
  The ramp is derived in OKLCH (HSL lightness is not perceptual, which is what makes generated palettes look broken for some hues) and **anchored at step `500`**, so the color you pass is the color your buttons get. `--tempest-primary-foreground` and the text-on-soft step are picked by **measured** contrast, not convention — hardcoding white breaks a yellow brand. See [Theme › createTheme](https://mauriciobenjamin700.github.io/tempest-react-sdk/theme/).
251
251
 
252
+ **A section in the brand color (opt-in).** `<Section tone="inverse">` (or `data-tempest-tone="inverse"` on any element) paints a hero, CTA band or footer in the brand and redefines every color token the components read inside it — text ≥ 7:1, muted and subtle text ≥ 4.5:1, focus ring ≥ 3:1, each measured against the fill and a lighter glow over it. Load `tempest-react-sdk/styles/inverse.css` for the SDK's blue, or pass `createTheme({ primary, inverse: createInverseSurface })` for yours; apps that do neither pay nothing. Inside the band, `data-tempest-tone="default"` (or `<Section tone="default">`) brings a region — a white `Card` in a navy hero — back to the page's tokens, light or dark. See [Theme › Inverse surface](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/theme/#inverse-surface-a-section-in-the-brand-color). `Navbar` and `AppBar` take `tone="inverse"` for a bar in the brand color whose buttons, links and focus ring invert with it; `tone="primary"` keeps painting only the background.
253
+
252
254
  **Optional layout layer.** CSS Modules cover the inside of each component; the layer _around_ them (page shell, two-column form, action row, card, a region that scrolls sideways instead of the page) is a second, opt-in stylesheet:
253
255
 
254
256
  ```ts
@@ -297,7 +299,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
297
299
  | `feature-flags` | `FeatureFlagsProvider`, `useFeatureFlag`, `useFlagValue`, `createInMemoryFlags`, `createGrowthBookFeatureFlagsAdapter`, `createLaunchDarklyFeatureFlagsAdapter`, types: `FeatureFlagsAdapter`, `FlagValue`, `GrowthBookLike`, `LDClientLike` |
298
300
  | `share` | `share`, `isShareSupported`, `shareOrDownloadBlob` (Web Share file → download fallback), types: `SharePayload`, `ShareResult`, `ShareOrDownloadOptions` |
299
301
  | `utils` | `cn`, format BR (`formatCurrency`, `formatDate`, `formatDateForInput`, `formatDateTime`, `formatPhone`, `formatCPF`, `formatPercent`), `storage`, strings (`slugify`, `truncate`, `capitalize`, `camelCase`, `kebabCase`, `pluralize`), numbers (`clamp`, `formatBytes`, `formatCompactNumber`, `percentOf`), arrays (`groupBy`, `uniqueBy`, `chunk`, `range`), objects (`pick`, `omit`, `deepMerge`, `isEmpty`), guards (`isDefined`, `isString`, `isNumber`, `isPlainObject`, `assertNever`), functions (`debounce`, `throttle`, `once`, `memoizeOne`), promises (`sleep`, `withTimeout`), `randomId`, `relativeTime`, `writeXlsx` (generic single-sheet OOXML `.xlsx` writer → `Uint8Array`), CSV (`toCsv`, `downloadCsv` — RFC 4180 escaping + BOM), `setDevBuild` (tell the SDK your build is dev — required under Vite) |
300
- | generic components | display (`CopyButton`, `RelativeTime`, `Money`, `TruncateText`, `VisuallyHidden`), headless (`Portal`, `ClickOutside`, `ConditionalWrapper`, `For`, `ErrorText`), media/content (`Image`, `DataList`, `DescriptionList`) |
302
+ | generic components | display (`CopyButton`, `RelativeTime`, `Money`, `TruncateText`, `VisuallyHidden`), motion (`Reveal`), headless (`Portal`, `ClickOutside`, `ConditionalWrapper`, `For`, `ErrorText`), media/content (`Image`, `DataList`, `DescriptionList`) |
301
303
  | Module | Exports |
302
304
  | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
303
305
  | `components` | `Avatar`, `Badge`, `Breadcrumbs`, `Button`, `Card`, `Checkbox`, `ChipInput`, `ConfirmDialog`, `Container`, `DatePicker`, `Drawer`, `EmptyState`, `ErrorState`, `FileUpload`, `Form` (`FormSection`, `FormRow`, `FormActions`), `Grid`, `Input`, `InstallBanner`, `InstallButton`, `Modal`, `Pagination`, `Progress`, `Radio`, `RadioGroup`, `SearchBar`, `Select`, `Skeleton`, `Spinner`, `Stack`, `Stepper`, `Switch`, `Table`, `Tabs`, `Textarea`, `Toast` (`ToastProvider`, `useToast`), `Tooltip`, `VirtualList` |
@@ -335,7 +337,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
335
337
  | `feature-flags` | `FeatureFlagsProvider`, `useFeatureFlag`, `useFlagValue`, `createInMemoryFlags`, `createGrowthBookFeatureFlagsAdapter`, `createLaunchDarklyFeatureFlagsAdapter`, types: `FeatureFlagsAdapter`, `FlagValue`, `GrowthBookLike`, `LDClientLike` |
336
338
  | `share` | `share`, `isShareSupported`, `shareOrDownloadBlob` (Web Share file → download fallback), types: `SharePayload`, `ShareResult`, `ShareOrDownloadOptions` |
337
339
  | `utils` | `cn`, format BR (`formatCurrency`, `formatDate`, `formatDateForInput`, `formatDateTime`, `formatPhone`, `formatCPF`, `formatPercent`), `storage`, strings (`slugify`, `truncate`, `capitalize`, `camelCase`, `kebabCase`, `pluralize`), numbers (`clamp`, `formatBytes`, `formatCompactNumber`, `percentOf`), arrays (`groupBy`, `uniqueBy`, `chunk`, `range`), objects (`pick`, `omit`, `deepMerge`, `isEmpty`), guards (`isDefined`, `isString`, `isNumber`, `isPlainObject`, `assertNever`), functions (`debounce`, `throttle`, `once`, `memoizeOne`), promises (`sleep`, `withTimeout`), `randomId`, `relativeTime`, `writeXlsx` (generic single-sheet OOXML `.xlsx` writer → `Uint8Array`), CSV (`toCsv`, `downloadCsv` — RFC 4180 escaping + BOM), `setDevBuild` (tell the SDK your build is dev — required under Vite) |
338
- | generic components | display (`CopyButton`, `RelativeTime`, `Money`, `TruncateText`, `VisuallyHidden`), headless (`Portal`, `ClickOutside`, `ConditionalWrapper`, `For`, `ErrorText`), media/content (`Image`, `DataList`, `DescriptionList`) |
340
+ | generic components | display (`CopyButton`, `RelativeTime`, `Money`, `TruncateText`, `VisuallyHidden`), motion (`Reveal`), headless (`Portal`, `ClickOutside`, `ConditionalWrapper`, `For`, `ErrorText`), media/content (`Image`, `DataList`, `DescriptionList`) |
339
341
 
340
342
  Full per-module docs are published as a bilingual MkDocs site on GitHub Pages — **[Português (BR)](https://mauriciobenjamin700.github.io/tempest-react-sdk/)** / **[English (US)](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/)** (one page per module + draw.io diagrams in [`docs/diagrams/`](./docs/diagrams)). The source markdown lives in [`docs/`](./docs) (PT-BR base files + `.en.md` translations).
341
343
 
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilMap.module.cjs","names":[],"sources":["../../src/br/BrazilMap.module.css"],"sourcesContent":[".map {\n position: relative;\n width: 100%;\n background: var(--tempest-surface-2);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n overflow: hidden;\n}\n\n.svg {\n display: block;\n width: 100%;\n}\n\n.loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n}\n\n.state {\n fill: var(--tempest-surface-3);\n stroke: var(--tempest-border-strong);\n stroke-width: 0.75;\n stroke-linejoin: round;\n transition:\n fill 0.12s ease,\n opacity 0.12s ease;\n}\n\n.state[role=\"button\"] {\n cursor: pointer;\n}\n\n.state[role=\"button\"]:hover {\n fill: var(--tempest-primary-soft);\n}\n\n.state[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-primary);\n stroke-width: 1.5;\n}\n\n.selected {\n fill: var(--tempest-primary);\n stroke: var(--tempest-primary-active);\n stroke-width: 1.25;\n}\n\n.selected:hover {\n fill: var(--tempest-primary-hover);\n}\n\n.label {\n fill: var(--tempest-text);\n font-size: 10px;\n font-weight: var(--tempest-weight-medium);\n pointer-events: none;\n user-select: none;\n}\n\n.tooltip {\n position: absolute;\n z-index: 2;\n transform: translate(12px, 12px);\n max-width: 240px;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n font-size: var(--tempest-text-xs);\n line-height: var(--tempest-leading-snug);\n pointer-events: none;\n white-space: nowrap;\n}\n\n.tooltipTitle {\n font-weight: var(--tempest-weight-semibold);\n font-size: var(--tempest-text-sm);\n}\n\n.tooltipMeta {\n color: var(--tempest-text-muted);\n}\n\n.marker {\n stroke: var(--tempest-surface);\n stroke-width: 2;\n}\n\n.marker[role=\"button\"] {\n cursor: pointer;\n}\n\n.marker[role=\"button\"]:hover {\n stroke: var(--tempest-primary-active);\n}\n\n.marker[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-primary);\n stroke-width: 3;\n}\n\n.zoomReset {\n position: absolute;\n top: var(--tempest-space-2);\n right: var(--tempest-space-2);\n padding: var(--tempest-space-1) var(--tempest-space-2);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text);\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n cursor: pointer;\n}\n\n.zoomReset:hover {\n background: var(--tempest-surface-2);\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"BrazilMap.module.cjs","names":[],"sources":["../../src/br/BrazilMap.module.css"],"sourcesContent":[".map {\n position: relative;\n width: 100%;\n background: var(--tempest-surface-2);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n overflow: hidden;\n}\n\n.svg {\n display: block;\n width: 100%;\n}\n\n.loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n}\n\n.state {\n fill: var(--tempest-surface-3);\n stroke: var(--tempest-border-strong);\n stroke-width: 0.75;\n stroke-linejoin: round;\n transition:\n fill 0.12s ease,\n opacity 0.12s ease;\n}\n\n.state[role=\"button\"] {\n cursor: pointer;\n}\n\n.state[role=\"button\"]:hover {\n fill: var(--tempest-primary-soft);\n}\n\n.state[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-focus-ring-color);\n stroke-width: 1.5;\n}\n\n.selected {\n fill: var(--tempest-primary);\n stroke: var(--tempest-primary-active);\n stroke-width: 1.25;\n}\n\n.selected:hover {\n fill: var(--tempest-primary-hover);\n}\n\n.label {\n fill: var(--tempest-text);\n font-size: 10px;\n font-weight: var(--tempest-weight-medium);\n pointer-events: none;\n user-select: none;\n}\n\n.tooltip {\n position: absolute;\n z-index: 2;\n transform: translate(12px, 12px);\n max-width: 240px;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n font-size: var(--tempest-text-xs);\n line-height: var(--tempest-leading-snug);\n pointer-events: none;\n white-space: nowrap;\n}\n\n.tooltipTitle {\n font-weight: var(--tempest-weight-semibold);\n font-size: var(--tempest-text-sm);\n}\n\n.tooltipMeta {\n color: var(--tempest-text-muted);\n}\n\n.marker {\n stroke: var(--tempest-surface);\n stroke-width: 2;\n}\n\n.marker[role=\"button\"] {\n cursor: pointer;\n}\n\n.marker[role=\"button\"]:hover {\n stroke: var(--tempest-primary-active);\n}\n\n.marker[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-focus-ring-color);\n stroke-width: 3;\n}\n\n.zoomReset {\n position: absolute;\n top: var(--tempest-space-2);\n right: var(--tempest-space-2);\n padding: var(--tempest-space-1) var(--tempest-space-2);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text);\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n cursor: pointer;\n}\n\n.zoomReset:hover {\n background: var(--tempest-surface-2);\n}\n"],"mappings":";"}
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilMap.module.js","names":[],"sources":["../../src/br/BrazilMap.module.css"],"sourcesContent":[".map {\n position: relative;\n width: 100%;\n background: var(--tempest-surface-2);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n overflow: hidden;\n}\n\n.svg {\n display: block;\n width: 100%;\n}\n\n.loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n}\n\n.state {\n fill: var(--tempest-surface-3);\n stroke: var(--tempest-border-strong);\n stroke-width: 0.75;\n stroke-linejoin: round;\n transition:\n fill 0.12s ease,\n opacity 0.12s ease;\n}\n\n.state[role=\"button\"] {\n cursor: pointer;\n}\n\n.state[role=\"button\"]:hover {\n fill: var(--tempest-primary-soft);\n}\n\n.state[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-primary);\n stroke-width: 1.5;\n}\n\n.selected {\n fill: var(--tempest-primary);\n stroke: var(--tempest-primary-active);\n stroke-width: 1.25;\n}\n\n.selected:hover {\n fill: var(--tempest-primary-hover);\n}\n\n.label {\n fill: var(--tempest-text);\n font-size: 10px;\n font-weight: var(--tempest-weight-medium);\n pointer-events: none;\n user-select: none;\n}\n\n.tooltip {\n position: absolute;\n z-index: 2;\n transform: translate(12px, 12px);\n max-width: 240px;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n font-size: var(--tempest-text-xs);\n line-height: var(--tempest-leading-snug);\n pointer-events: none;\n white-space: nowrap;\n}\n\n.tooltipTitle {\n font-weight: var(--tempest-weight-semibold);\n font-size: var(--tempest-text-sm);\n}\n\n.tooltipMeta {\n color: var(--tempest-text-muted);\n}\n\n.marker {\n stroke: var(--tempest-surface);\n stroke-width: 2;\n}\n\n.marker[role=\"button\"] {\n cursor: pointer;\n}\n\n.marker[role=\"button\"]:hover {\n stroke: var(--tempest-primary-active);\n}\n\n.marker[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-primary);\n stroke-width: 3;\n}\n\n.zoomReset {\n position: absolute;\n top: var(--tempest-space-2);\n right: var(--tempest-space-2);\n padding: var(--tempest-space-1) var(--tempest-space-2);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text);\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n cursor: pointer;\n}\n\n.zoomReset:hover {\n background: var(--tempest-surface-2);\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"BrazilMap.module.js","names":[],"sources":["../../src/br/BrazilMap.module.css"],"sourcesContent":[".map {\n position: relative;\n width: 100%;\n background: var(--tempest-surface-2);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n font-family: var(--tempest-font-sans);\n overflow: hidden;\n}\n\n.svg {\n display: block;\n width: 100%;\n}\n\n.loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n}\n\n.state {\n fill: var(--tempest-surface-3);\n stroke: var(--tempest-border-strong);\n stroke-width: 0.75;\n stroke-linejoin: round;\n transition:\n fill 0.12s ease,\n opacity 0.12s ease;\n}\n\n.state[role=\"button\"] {\n cursor: pointer;\n}\n\n.state[role=\"button\"]:hover {\n fill: var(--tempest-primary-soft);\n}\n\n.state[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-focus-ring-color);\n stroke-width: 1.5;\n}\n\n.selected {\n fill: var(--tempest-primary);\n stroke: var(--tempest-primary-active);\n stroke-width: 1.25;\n}\n\n.selected:hover {\n fill: var(--tempest-primary-hover);\n}\n\n.label {\n fill: var(--tempest-text);\n font-size: 10px;\n font-weight: var(--tempest-weight-medium);\n pointer-events: none;\n user-select: none;\n}\n\n.tooltip {\n position: absolute;\n z-index: 2;\n transform: translate(12px, 12px);\n max-width: 240px;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n background: var(--tempest-surface);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n font-size: var(--tempest-text-xs);\n line-height: var(--tempest-leading-snug);\n pointer-events: none;\n white-space: nowrap;\n}\n\n.tooltipTitle {\n font-weight: var(--tempest-weight-semibold);\n font-size: var(--tempest-text-sm);\n}\n\n.tooltipMeta {\n color: var(--tempest-text-muted);\n}\n\n.marker {\n stroke: var(--tempest-surface);\n stroke-width: 2;\n}\n\n.marker[role=\"button\"] {\n cursor: pointer;\n}\n\n.marker[role=\"button\"]:hover {\n stroke: var(--tempest-primary-active);\n}\n\n.marker[role=\"button\"]:focus-visible {\n outline: none;\n stroke: var(--tempest-focus-ring-color);\n stroke-width: 3;\n}\n\n.zoomReset {\n position: absolute;\n top: var(--tempest-space-2);\n right: var(--tempest-space-2);\n padding: var(--tempest-space-1) var(--tempest-space-2);\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text);\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-sm);\n cursor: pointer;\n}\n\n.zoomReset:hover {\n background: var(--tempest-surface-2);\n}\n"],"mappings":";"}
@@ -1,2 +1,2 @@
1
- const e=require("./BrazilMap.module.cjs");let t=require("react/jsx-runtime");function n({projection:n,markers:r,onMarkerClick:i}){return(0,t.jsx)(`g`,{className:e.default.markers,children:r.map((r,a)=>{let o=n.project(r),s=!!i;return(0,t.jsx)(`circle`,{className:e.default.marker,cx:o.x,cy:o.y,r:r.radius??6,fill:r.color??`var(--tempest-primary)`,"data-marker-id":r.id??a,tabIndex:s?0:void 0,role:s?`button`:void 0,"aria-label":r.label,onClick:s?()=>i?.(r,a):void 0,onKeyDown:s?e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i?.(r,a))}:void 0,children:r.label?(0,t.jsx)(`title`,{children:r.label}):null},r.id??a)})})}exports.MapMarkers=n;
1
+ const e=require("./BrazilMap.module.cjs");let t=require("react/jsx-runtime");function n({projection:n,markers:r,onMarkerClick:i}){return(0,t.jsx)(`g`,{children:r.map((r,a)=>{let o=n.project(r),s=!!i;return(0,t.jsx)(`circle`,{className:e.default.marker,cx:o.x,cy:o.y,r:r.radius??6,fill:r.color??`var(--tempest-primary)`,"data-marker-id":r.id??a,tabIndex:s?0:void 0,role:s?`button`:void 0,"aria-label":r.label,onClick:s?()=>i?.(r,a):void 0,onKeyDown:s?e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i?.(r,a))}:void 0,children:r.label?(0,t.jsx)(`title`,{children:r.label}):null},r.id??a)})})}exports.MapMarkers=n;
2
2
  //# sourceMappingURL=MapMarkers.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"MapMarkers.cjs","names":[],"sources":["../../src/br/MapMarkers.tsx"],"sourcesContent":["import type { FittedProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport styles from \"./BrazilMap.module.css\";\n\nexport interface MapMarkersProps {\n /** Projection used by the host map (markers must share it to line up). */\n projection: FittedProjection;\n /** Pins to plot. */\n markers: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\n/**\n * SVG overlay of point markers, projected with the host map's projection so\n * they align with its shapes. Each marker carries a native `<title>` (its\n * `label`) and is clickable when `onMarkerClick` is set.\n */\nexport function MapMarkers({ projection, markers, onMarkerClick }: MapMarkersProps) {\n return (\n <g className={styles.markers}>\n {markers.map((marker, index) => {\n const p = projection.project(marker);\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? index}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n fill={marker.color ?? \"var(--tempest-primary)\"}\n data-marker-id={marker.id ?? index}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, index) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, index);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n </g>\n );\n}\n"],"mappings":"6EAkBA,SAAgB,EAAW,CAAE,aAAY,UAAS,iBAAkC,CAChF,OACI,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAW,EAAA,QAAO,QAChB,SAAA,EAAQ,KAAK,EAAQ,IAAU,CAC5B,IAAM,EAAI,EAAW,QAAQ,CAAM,EAC7B,EAAc,EAAQ,EAC5B,OACI,EAAA,EAAA,IAAA,CAAC,SAAD,CAEI,UAAW,EAAA,QAAO,OAClB,GAAI,EAAE,EACN,GAAI,EAAE,EACN,EAAG,EAAO,QAAU,EACpB,KAAM,EAAO,OAAS,yBACtB,iBAAgB,EAAO,IAAM,EAC7B,SAAU,EAAc,EAAI,IAAA,GAC5B,KAAM,EAAc,SAAW,IAAA,GAC/B,aAAY,EAAO,MACnB,QAAS,MAAoB,IAAgB,EAAQ,CAAK,EAAI,IAAA,GAC9D,UACI,EACO,GAAM,EACC,EAAE,MAAQ,SAAW,EAAE,MAAQ,OAC/B,EAAE,eAAe,EACjB,IAAgB,EAAQ,CAAK,EAErC,EACA,IAAA,GAGT,SAAA,EAAO,OAAQ,EAAA,EAAA,IAAA,CAAC,QAAD,CAAA,SAAQ,EAAO,KAAa,CAAA,EAAI,IAC5C,EAvBC,EAAO,IAAM,CAuBd,CAEhB,CAAC,CACF,CAAA,CAEX"}
1
+ {"version":3,"file":"MapMarkers.cjs","names":[],"sources":["../../src/br/MapMarkers.tsx"],"sourcesContent":["import type { FittedProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport styles from \"./BrazilMap.module.css\";\n\nexport interface MapMarkersProps {\n /** Projection used by the host map (markers must share it to line up). */\n projection: FittedProjection;\n /** Pins to plot. */\n markers: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\n/**\n * SVG overlay of point markers, projected with the host map's projection so\n * they align with its shapes. Each marker carries a native `<title>` (its\n * `label`) and is clickable when `onMarkerClick` is set.\n */\nexport function MapMarkers({ projection, markers, onMarkerClick }: MapMarkersProps) {\n return (\n <g>\n {markers.map((marker, index) => {\n const p = projection.project(marker);\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? index}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n fill={marker.color ?? \"var(--tempest-primary)\"}\n data-marker-id={marker.id ?? index}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, index) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, index);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n </g>\n );\n}\n"],"mappings":"6EAkBA,SAAgB,EAAW,CAAE,aAAY,UAAS,iBAAkC,CAChF,OACI,EAAA,EAAA,IAAA,CAAC,IAAD,CAAA,SACK,EAAQ,KAAK,EAAQ,IAAU,CAC5B,IAAM,EAAI,EAAW,QAAQ,CAAM,EAC7B,EAAc,EAAQ,EAC5B,OACI,EAAA,EAAA,IAAA,CAAC,SAAD,CAEI,UAAW,EAAA,QAAO,OAClB,GAAI,EAAE,EACN,GAAI,EAAE,EACN,EAAG,EAAO,QAAU,EACpB,KAAM,EAAO,OAAS,yBACtB,iBAAgB,EAAO,IAAM,EAC7B,SAAU,EAAc,EAAI,IAAA,GAC5B,KAAM,EAAc,SAAW,IAAA,GAC/B,aAAY,EAAO,MACnB,QAAS,MAAoB,IAAgB,EAAQ,CAAK,EAAI,IAAA,GAC9D,UACI,EACO,GAAM,EACC,EAAE,MAAQ,SAAW,EAAE,MAAQ,OAC/B,EAAE,eAAe,EACjB,IAAgB,EAAQ,CAAK,EAErC,EACA,IAAA,GAGT,SAAA,EAAO,OAAQ,EAAA,EAAA,IAAA,CAAC,QAAD,CAAA,SAAQ,EAAO,KAAa,CAAA,EAAI,IAC5C,EAvBC,EAAO,IAAM,CAuBd,CAEhB,CAAC,CACF,CAAA,CAEX"}
@@ -2,28 +2,25 @@ import e from "./BrazilMap.module.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/br/MapMarkers.tsx
4
4
  function n({ projection: n, markers: r, onMarkerClick: i }) {
5
- return /* @__PURE__ */ t("g", {
6
- className: e.markers,
7
- children: r.map((r, a) => {
8
- let o = n.project(r), s = !!i;
9
- return /* @__PURE__ */ t("circle", {
10
- className: e.marker,
11
- cx: o.x,
12
- cy: o.y,
13
- r: r.radius ?? 6,
14
- fill: r.color ?? "var(--tempest-primary)",
15
- "data-marker-id": r.id ?? a,
16
- tabIndex: s ? 0 : void 0,
17
- role: s ? "button" : void 0,
18
- "aria-label": r.label,
19
- onClick: s ? () => i?.(r, a) : void 0,
20
- onKeyDown: s ? (e) => {
21
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), i?.(r, a));
22
- } : void 0,
23
- children: r.label ? /* @__PURE__ */ t("title", { children: r.label }) : null
24
- }, r.id ?? a);
25
- })
26
- });
5
+ return /* @__PURE__ */ t("g", { children: r.map((r, a) => {
6
+ let o = n.project(r), s = !!i;
7
+ return /* @__PURE__ */ t("circle", {
8
+ className: e.marker,
9
+ cx: o.x,
10
+ cy: o.y,
11
+ r: r.radius ?? 6,
12
+ fill: r.color ?? "var(--tempest-primary)",
13
+ "data-marker-id": r.id ?? a,
14
+ tabIndex: s ? 0 : void 0,
15
+ role: s ? "button" : void 0,
16
+ "aria-label": r.label,
17
+ onClick: s ? () => i?.(r, a) : void 0,
18
+ onKeyDown: s ? (e) => {
19
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), i?.(r, a));
20
+ } : void 0,
21
+ children: r.label ? /* @__PURE__ */ t("title", { children: r.label }) : null
22
+ }, r.id ?? a);
23
+ }) });
27
24
  }
28
25
  //#endregion
29
26
  export { n as MapMarkers };
@@ -1 +1 @@
1
- {"version":3,"file":"MapMarkers.js","names":[],"sources":["../../src/br/MapMarkers.tsx"],"sourcesContent":["import type { FittedProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport styles from \"./BrazilMap.module.css\";\n\nexport interface MapMarkersProps {\n /** Projection used by the host map (markers must share it to line up). */\n projection: FittedProjection;\n /** Pins to plot. */\n markers: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\n/**\n * SVG overlay of point markers, projected with the host map's projection so\n * they align with its shapes. Each marker carries a native `<title>` (its\n * `label`) and is clickable when `onMarkerClick` is set.\n */\nexport function MapMarkers({ projection, markers, onMarkerClick }: MapMarkersProps) {\n return (\n <g className={styles.markers}>\n {markers.map((marker, index) => {\n const p = projection.project(marker);\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? index}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n fill={marker.color ?? \"var(--tempest-primary)\"}\n data-marker-id={marker.id ?? index}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, index) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, index);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n </g>\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,EAAW,EAAE,eAAY,YAAS,oBAAkC;CAChF,OACI,kBAAC,KAAD;EAAG,WAAW,EAAO;EAChB,UAAA,EAAQ,KAAK,GAAQ,MAAU;GAC5B,IAAM,IAAI,EAAW,QAAQ,CAAM,GAC7B,IAAc,EAAQ;GAC5B,OACI,kBAAC,UAAD;IAEI,WAAW,EAAO;IAClB,IAAI,EAAE;IACN,IAAI,EAAE;IACN,GAAG,EAAO,UAAU;IACpB,MAAM,EAAO,SAAS;IACtB,kBAAgB,EAAO,MAAM;IAC7B,UAAU,IAAc,IAAI,KAAA;IAC5B,MAAM,IAAc,WAAW,KAAA;IAC/B,cAAY,EAAO;IACnB,SAAS,UAAoB,IAAgB,GAAQ,CAAK,IAAI,KAAA;IAC9D,WACI,KACO,MAAM;KACH,CAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SAC/B,EAAE,eAAe,GACjB,IAAgB,GAAQ,CAAK;IAErC,IACA,KAAA;IAGT,UAAA,EAAO,QAAQ,kBAAC,SAAD,EAAA,UAAQ,EAAO,MAAa,CAAA,IAAI;GAC5C,GAvBC,EAAO,MAAM,CAuBd;EAEhB,CAAC;CACF,CAAA;AAEX"}
1
+ {"version":3,"file":"MapMarkers.js","names":[],"sources":["../../src/br/MapMarkers.tsx"],"sourcesContent":["import type { FittedProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport styles from \"./BrazilMap.module.css\";\n\nexport interface MapMarkersProps {\n /** Projection used by the host map (markers must share it to line up). */\n projection: FittedProjection;\n /** Pins to plot. */\n markers: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\n/**\n * SVG overlay of point markers, projected with the host map's projection so\n * they align with its shapes. Each marker carries a native `<title>` (its\n * `label`) and is clickable when `onMarkerClick` is set.\n */\nexport function MapMarkers({ projection, markers, onMarkerClick }: MapMarkersProps) {\n return (\n <g>\n {markers.map((marker, index) => {\n const p = projection.project(marker);\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? index}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n fill={marker.color ?? \"var(--tempest-primary)\"}\n data-marker-id={marker.id ?? index}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, index) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, index);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n </g>\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,EAAW,EAAE,eAAY,YAAS,oBAAkC;CAChF,OACI,kBAAC,KAAD,EAAA,UACK,EAAQ,KAAK,GAAQ,MAAU;EAC5B,IAAM,IAAI,EAAW,QAAQ,CAAM,GAC7B,IAAc,EAAQ;EAC5B,OACI,kBAAC,UAAD;GAEI,WAAW,EAAO;GAClB,IAAI,EAAE;GACN,IAAI,EAAE;GACN,GAAG,EAAO,UAAU;GACpB,MAAM,EAAO,SAAS;GACtB,kBAAgB,EAAO,MAAM;GAC7B,UAAU,IAAc,IAAI,KAAA;GAC5B,MAAM,IAAc,WAAW,KAAA;GAC/B,cAAY,EAAO;GACnB,SAAS,UAAoB,IAAgB,GAAQ,CAAK,IAAI,KAAA;GAC9D,WACI,KACO,MAAM;IACH,CAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SAC/B,EAAE,eAAe,GACjB,IAAgB,GAAQ,CAAK;GAErC,IACA,KAAA;GAGT,UAAA,EAAO,QAAQ,kBAAC,SAAD,EAAA,UAAQ,EAAO,MAAa,CAAA,IAAI;EAC5C,GAvBC,EAAO,MAAM,CAuBd;CAEhB,CAAC,EACF,CAAA;AAEX"}
@@ -1,2 +1,2 @@
1
- const e=require("../components/Input/Input.cjs"),t=require("../hooks/use-debounce.cjs"),n=require("./geocode.cjs"),r=require("./MunicipalitySearch.module.cjs");let i=require("react"),a=require("react/jsx-runtime");function o({uf:o,onSelect:s,label:c,placeholder:l=`Buscar município…`,limit:u=8,debounceMs:d=250,disabled:f=!1}){let[p,m]=(0,i.useState)(``),[h,g]=(0,i.useState)([]),[_,v]=(0,i.useState)(!1),y=t.useDebounce(p,d),b=(0,i.useId)(),x=(0,i.useRef)(null);(0,i.useEffect)(()=>{let e=!0;if(!y.trim()){g([]);return}return n.searchMunicipalities(y,{uf:o,limit:u}).then(t=>{e&&g(t)}),()=>{e=!1}},[y,o,u]);function S(e){s(e),m(e.name),v(!1)}return(0,a.jsxs)(`div`,{className:r.default.search,children:[(0,a.jsx)(e.Input,{label:c,value:p,placeholder:l,disabled:f,role:`combobox`,"aria-expanded":_&&h.length>0,"aria-controls":b,autoComplete:`off`,onChange:e=>{m(e.target.value),v(!0)},onFocus:()=>v(!0),onBlur:()=>{x.current=setTimeout(()=>v(!1),120)}}),_&&h.length>0&&(0,a.jsx)(`ul`,{id:b,className:r.default.results,role:`listbox`,children:h.map(e=>(0,a.jsx)(`li`,{children:(0,a.jsxs)(`button`,{type:`button`,role:`option`,"aria-selected":!1,className:r.default.result,onMouseDown:e=>e.preventDefault(),onClick:()=>{x.current&&clearTimeout(x.current),S(e)},children:[(0,a.jsx)(`span`,{children:e.name}),(0,a.jsx)(`span`,{className:r.default.uf,children:e.uf})]})},e.id))})]})}exports.MunicipalitySearch=o;
1
+ const e=require("../utils/cn.cjs"),t=require("../components/Input/Input.cjs"),n=require("../hooks/use-debounce.cjs"),r=require("./geocode.cjs"),i=require("./MunicipalitySearch.module.cjs");let a=require("react"),o=require("react/jsx-runtime");function s(e){e.preventDefault()}function c({uf:c,onSelect:l,label:u,placeholder:d=`Buscar município…`,limit:f=8,debounceMs:p=250,disabled:m=!1}){let[h,g]=(0,a.useState)(``),[_,v]=(0,a.useState)([]),[y,b]=(0,a.useState)(!1),[x,S]=(0,a.useState)(0),C=n.useDebounce(h,p),w=(0,a.useId)(),T=y&&_.length>0,E=e=>`${w}-option-${e}`;(0,a.useEffect)(()=>{let e=!0;if(!C.trim()){v([]);return}return r.searchMunicipalities(C,{uf:c,limit:f}).then(t=>{e&&(v(t),S(0))}),()=>{e=!1}},[C,c,f]);function D(e){l(e),g(e.name),b(!1)}function O(e){if(e.key===`ArrowDown`){if(e.preventDefault(),!T){b(!0);return}S(e=>Math.min(_.length-1,e+1))}else if(e.key===`ArrowUp`)e.preventDefault(),S(e=>Math.max(0,e-1));else if(e.key===`Enter`){let t=T?_[x]:void 0;if(!t)return;e.preventDefault(),D(t)}else e.key===`Escape`&&(T&&e.preventDefault(),b(!1))}return(0,o.jsxs)(`div`,{className:i.default.search,children:[(0,o.jsx)(t.Input,{label:u,value:h,placeholder:d,disabled:m,role:`combobox`,"aria-expanded":T,"aria-controls":w,"aria-autocomplete":`list`,"aria-activedescendant":T?E(x):void 0,autoComplete:`off`,onChange:e=>{g(e.target.value),b(!0)},onFocus:()=>b(!0),onBlur:()=>b(!1),onKeyDown:O}),T&&(0,o.jsx)(`ul`,{id:w,className:i.default.results,role:`listbox`,onMouseDown:s,children:_.map((t,n)=>(0,o.jsxs)(`li`,{id:E(n),role:`option`,"aria-selected":n===x,className:e.cn(i.default.result,n===x&&i.default.active),onMouseEnter:()=>S(n),onClick:()=>D(t),children:[(0,o.jsx)(`span`,{children:t.name}),(0,o.jsx)(`span`,{className:i.default.uf,children:t.uf})]},t.id))})]})}exports.MunicipalitySearch=c;
2
2
  //# sourceMappingURL=MunicipalitySearch.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"MunicipalitySearch.cjs","names":[],"sources":["../../src/br/MunicipalitySearch.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type ReactElement } from \"react\";\nimport { Input } from \"@/components/Input\";\nimport { useDebounce } from \"@/hooks\";\nimport { searchMunicipalities, type MunicipalityCentroid } from \"./geocode\";\nimport type { UF } from \"./locations\";\nimport styles from \"./MunicipalitySearch.module.css\";\n\nexport interface MunicipalitySearchProps {\n /** Restrict the search to a single UF. */\n uf?: UF;\n /** Fired when a municipality is picked from the results. */\n onSelect: (municipality: MunicipalityCentroid) => void;\n /** Field label. */\n label?: string;\n /** Input placeholder. Default: `\"Buscar município…\"`. */\n placeholder?: string;\n /** Max results shown. Default: `8`. */\n limit?: number;\n /** Debounce in ms before searching. Default: `250`. */\n debounceMs?: number;\n /** Disable the field. */\n disabled?: boolean;\n}\n\n/**\n * Debounced municipality autocomplete backed by the offline\n * {@link searchMunicipalities} index — no network. Type a partial name, pick a\n * result, and `onSelect` fires with its `{ id, name, uf, latitude, longitude }`.\n * Wire `onSelect` to a `BrazilStateMap` `selected` to highlight it on the map.\n *\n * @example\n * const [uf, setUf] = useState<UF>(\"SP\");\n * const [city, setCity] = useState<string | null>(null);\n * <MunicipalitySearch uf={uf} onSelect={(m) => setCity(m.name)} />\n * <BrazilStateMap uf={uf} selected={city} />\n */\nexport function MunicipalitySearch({\n uf,\n onSelect,\n label,\n placeholder = \"Buscar município…\",\n limit = 8,\n debounceMs = 250,\n disabled = false,\n}: MunicipalitySearchProps): ReactElement {\n const [query, setQuery] = useState<string>(\"\");\n const [results, setResults] = useState<MunicipalityCentroid[]>([]);\n const [open, setOpen] = useState<boolean>(false);\n const debounced = useDebounce(query, debounceMs);\n const listId = useId();\n const blurTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n let active = true;\n if (!debounced.trim()) {\n setResults([]);\n return;\n }\n void searchMunicipalities(debounced, { uf, limit }).then((hits) => {\n if (active) setResults(hits);\n });\n return () => {\n active = false;\n };\n }, [debounced, uf, limit]);\n\n function choose(municipality: MunicipalityCentroid): void {\n onSelect(municipality);\n setQuery(municipality.name);\n setOpen(false);\n }\n\n return (\n <div className={styles.search}>\n <Input\n label={label}\n value={query}\n placeholder={placeholder}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={open && results.length > 0}\n aria-controls={listId}\n autoComplete=\"off\"\n onChange={(e) => {\n setQuery(e.target.value);\n setOpen(true);\n }}\n onFocus={() => setOpen(true)}\n onBlur={() => {\n // Delay so a result click registers before the list closes.\n blurTimer.current = setTimeout(() => setOpen(false), 120);\n }}\n />\n {open && results.length > 0 && (\n <ul id={listId} className={styles.results} role=\"listbox\">\n {results.map((m) => (\n <li key={m.id}>\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={false}\n className={styles.result}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n if (blurTimer.current) clearTimeout(blurTimer.current);\n choose(m);\n }}\n >\n <span>{m.name}</span>\n <span className={styles.uf}>{m.uf}</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n"],"mappings":"sNAoCA,SAAgB,EAAmB,CAC/B,KACA,WACA,QACA,cAAc,oBACd,QAAQ,EACR,aAAa,IACb,WAAW,IAC2B,CACtC,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAiB,EAAE,EACvC,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAiC,CAAC,CAAC,EAC3D,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAkB,EAAK,EACzC,EAAY,EAAA,YAAY,EAAO,CAAU,EACzC,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,GAAA,EAAY,EAAA,OAAA,CAA6C,IAAI,GAEnE,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAS,GACb,GAAI,CAAC,EAAU,KAAK,EAAG,CACnB,EAAW,CAAC,CAAC,EACb,MACJ,CAIA,OAHA,EAAK,qBAAqB,EAAW,CAAE,KAAI,OAAM,CAAC,CAAC,CAAC,KAAM,GAAS,CAC3D,GAAQ,EAAW,CAAI,CAC/B,CAAC,MACY,CACT,EAAS,EACb,CACJ,EAAG,CAAC,EAAW,EAAI,CAAK,CAAC,EAEzB,SAAS,EAAO,EAA0C,CACtD,EAAS,CAAY,EACrB,EAAS,EAAa,IAAI,EAC1B,EAAQ,EAAK,CACjB,CAEA,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAvB,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,EAAA,MAAD,CACW,QACP,MAAO,EACM,cACH,WACV,KAAK,WACL,gBAAe,GAAQ,EAAQ,OAAS,EACxC,gBAAe,EACf,aAAa,MACb,SAAW,GAAM,CACb,EAAS,EAAE,OAAO,KAAK,EACvB,EAAQ,EAAI,CAChB,EACA,YAAe,EAAQ,EAAI,EAC3B,WAAc,CAEV,EAAU,QAAU,eAAiB,EAAQ,EAAK,EAAG,GAAG,CAC5D,CACH,CAAA,EACA,GAAQ,EAAQ,OAAS,IACtB,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,GAAI,EAAQ,UAAW,EAAA,QAAO,QAAS,KAAK,UAC3C,SAAA,EAAQ,IAAK,IACV,EAAA,EAAA,IAAA,CAAC,KAAD,CAAA,UACI,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,KAAK,SACL,KAAK,SACL,gBAAe,GACf,UAAW,EAAA,QAAO,OAClB,YAAc,GAAM,EAAE,eAAe,EACrC,YAAe,CACP,EAAU,SAAS,aAAa,EAAU,OAAO,EACrD,EAAO,CAAC,CACZ,EATJ,SAAA,EAWI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAA,SAAO,EAAE,IAAW,CAAA,GACpB,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,GAAK,SAAA,EAAE,EAAS,CAAA,CACpC,CACR,CAAA,CAAA,EAfK,EAAE,EAeP,CACP,CACD,CAAA,CAEP,GAEb"}
1
+ {"version":3,"file":"MunicipalitySearch.cjs","names":[],"sources":["../../src/br/MunicipalitySearch.tsx"],"sourcesContent":["import {\n useEffect,\n useId,\n useState,\n type KeyboardEvent,\n type MouseEvent,\n type ReactElement,\n} from \"react\";\nimport { Input } from \"@/components/Input\";\nimport { useDebounce } from \"@/hooks\";\nimport { cn } from \"@/utils/cn\";\nimport { searchMunicipalities, type MunicipalityCentroid } from \"./geocode\";\nimport type { UF } from \"./locations\";\nimport styles from \"./MunicipalitySearch.module.css\";\n\nexport interface MunicipalitySearchProps {\n /** Restrict the search to a single UF. */\n uf?: UF;\n /** Fired when a municipality is picked from the results. */\n onSelect: (municipality: MunicipalityCentroid) => void;\n /** Field label. */\n label?: string;\n /** Input placeholder. Default: `\"Buscar município…\"`. */\n placeholder?: string;\n /** Max results shown. Default: `8`. */\n limit?: number;\n /** Debounce in ms before searching. Default: `250`. */\n debounceMs?: number;\n /** Disable the field. */\n disabled?: boolean;\n}\n\n/**\n * Keeps focus on the input when a press lands anywhere on the list — an option,\n * the padding or the scrollbar — so the input never blurs and the list closes\n * only when focus really leaves the field.\n *\n * @param event - The `mousedown` on the list.\n */\nfunction keepInputFocus(event: MouseEvent<HTMLUListElement>): void {\n event.preventDefault();\n}\n\n/**\n * Debounced municipality autocomplete backed by the offline\n * {@link searchMunicipalities} index — no network. Type a partial name, pick a\n * result, and `onSelect` fires with its `{ id, name, uf, latitude, longitude }`.\n * Wire `onSelect` to a `BrazilStateMap` `selected` to highlight it on the map.\n *\n * Keyboard follows the SDK's `Combobox` (the WAI-ARIA combobox pattern): focus\n * stays on the input, `ArrowDown`/`ArrowUp` move the active result — announced\n * through `aria-activedescendant` — `Enter` picks it, `Escape` closes the list\n * and `Tab` leaves the field, closing the list on the way out.\n *\n * The list used to close on a 120 ms timer after the input blurred, with each\n * result a focusable `<button>`. Measured in Chromium (gallery build, issue\n * #426): `Tab` moved focus to the first result, the timer then unmounted it and\n * focus fell to `<body>`; arrows, `Enter` and `Escape` did nothing. Results are\n * now plain options outside the tab order, and a press on the list cancels the\n * blur instead of racing it.\n *\n * @example\n * const [uf, setUf] = useState<UF>(\"SP\");\n * const [city, setCity] = useState<string | null>(null);\n * <MunicipalitySearch uf={uf} onSelect={(m) => setCity(m.name)} />\n * <BrazilStateMap uf={uf} selected={city} />\n */\nexport function MunicipalitySearch({\n uf,\n onSelect,\n label,\n placeholder = \"Buscar município…\",\n limit = 8,\n debounceMs = 250,\n disabled = false,\n}: MunicipalitySearchProps): ReactElement {\n const [query, setQuery] = useState<string>(\"\");\n const [results, setResults] = useState<MunicipalityCentroid[]>([]);\n const [open, setOpen] = useState<boolean>(false);\n const [activeIndex, setActiveIndex] = useState<number>(0);\n const debounced = useDebounce(query, debounceMs);\n const listId = useId();\n const expanded = open && results.length > 0;\n const optionId = (index: number): string => `${listId}-option-${index}`;\n\n useEffect(() => {\n let active = true;\n if (!debounced.trim()) {\n setResults([]);\n return;\n }\n void searchMunicipalities(debounced, { uf, limit }).then((hits) => {\n if (!active) return;\n setResults(hits);\n setActiveIndex(0);\n });\n return () => {\n active = false;\n };\n }, [debounced, uf, limit]);\n\n function choose(municipality: MunicipalityCentroid): void {\n onSelect(municipality);\n setQuery(municipality.name);\n setOpen(false);\n }\n\n /**\n * Keyboard model of the input, the same as `Combobox`'s.\n *\n * `Escape` calls `preventDefault()` only while the list is open, so an\n * enclosing `Modal` or `Drawer` stays open and the next `Escape` reaches it.\n * `Enter` is consumed only when it picks a result, so with the list closed\n * it still submits an enclosing form.\n *\n * @param event - The `keydown` on the input.\n */\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n if (!expanded) {\n setOpen(true);\n return;\n }\n setActiveIndex((current) => Math.min(results.length - 1, current + 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((current) => Math.max(0, current - 1));\n } else if (event.key === \"Enter\") {\n const municipality = expanded ? results[activeIndex] : undefined;\n if (!municipality) return;\n event.preventDefault();\n choose(municipality);\n } else if (event.key === \"Escape\") {\n if (expanded) event.preventDefault();\n setOpen(false);\n }\n }\n\n return (\n <div className={styles.search}>\n <Input\n label={label}\n value={query}\n placeholder={placeholder}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={expanded}\n aria-controls={listId}\n aria-autocomplete=\"list\"\n aria-activedescendant={expanded ? optionId(activeIndex) : undefined}\n autoComplete=\"off\"\n onChange={(e) => {\n setQuery(e.target.value);\n setOpen(true);\n }}\n onFocus={() => setOpen(true)}\n onBlur={() => setOpen(false)}\n onKeyDown={handleKeyDown}\n />\n {expanded && (\n <ul\n id={listId}\n className={styles.results}\n role=\"listbox\"\n onMouseDown={keepInputFocus}\n >\n {results.map((m, index) => (\n <li\n key={m.id}\n id={optionId(index)}\n role=\"option\"\n aria-selected={index === activeIndex}\n className={cn(styles.result, index === activeIndex && styles.active)}\n onMouseEnter={() => setActiveIndex(index)}\n onClick={() => choose(m)}\n >\n <span>{m.name}</span>\n <span className={styles.uf}>{m.uf}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n"],"mappings":"mPAuCA,SAAS,EAAe,EAA2C,CAC/D,EAAM,eAAe,CACzB,CA0BA,SAAgB,EAAmB,CAC/B,KACA,WACA,QACA,cAAc,oBACd,QAAQ,EACR,aAAa,IACb,WAAW,IAC2B,CACtC,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAiB,EAAE,EACvC,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAiC,CAAC,CAAC,EAC3D,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAkB,EAAK,EACzC,CAAC,EAAa,IAAA,EAAkB,EAAA,SAAA,CAAiB,CAAC,EAClD,EAAY,EAAA,YAAY,EAAO,CAAU,EACzC,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,EAAW,GAAQ,EAAQ,OAAS,EACpC,EAAY,GAA0B,GAAG,EAAO,UAAU,KAEhE,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAS,GACb,GAAI,CAAC,EAAU,KAAK,EAAG,CACnB,EAAW,CAAC,CAAC,EACb,MACJ,CAMA,OALA,EAAK,qBAAqB,EAAW,CAAE,KAAI,OAAM,CAAC,CAAC,CAAC,KAAM,GAAS,CAC1D,IACL,EAAW,CAAI,EACf,EAAe,CAAC,EACpB,CAAC,MACY,CACT,EAAS,EACb,CACJ,EAAG,CAAC,EAAW,EAAI,CAAK,CAAC,EAEzB,SAAS,EAAO,EAA0C,CACtD,EAAS,CAAY,EACrB,EAAS,EAAa,IAAI,EAC1B,EAAQ,EAAK,CACjB,CAYA,SAAS,EAAc,EAA8C,CACjE,GAAI,EAAM,MAAQ,YAAa,CAE3B,GADA,EAAM,eAAe,EACjB,CAAC,EAAU,CACX,EAAQ,EAAI,EACZ,MACJ,CACA,EAAgB,GAAY,KAAK,IAAI,EAAQ,OAAS,EAAG,EAAU,CAAC,CAAC,CACzE,MAAO,GAAI,EAAM,MAAQ,UACrB,EAAM,eAAe,EACrB,EAAgB,GAAY,KAAK,IAAI,EAAG,EAAU,CAAC,CAAC,OACjD,GAAI,EAAM,MAAQ,QAAS,CAC9B,IAAM,EAAe,EAAW,EAAQ,GAAe,IAAA,GACvD,GAAI,CAAC,EAAc,OACnB,EAAM,eAAe,EACrB,EAAO,CAAY,CACvB,MAAW,EAAM,MAAQ,WACjB,GAAU,EAAM,eAAe,EACnC,EAAQ,EAAK,EAErB,CAEA,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAvB,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,EAAA,MAAD,CACW,QACP,MAAO,EACM,cACH,WACV,KAAK,WACL,gBAAe,EACf,gBAAe,EACf,oBAAkB,OAClB,wBAAuB,EAAW,EAAS,CAAW,EAAI,IAAA,GAC1D,aAAa,MACb,SAAW,GAAM,CACb,EAAS,EAAE,OAAO,KAAK,EACvB,EAAQ,EAAI,CAChB,EACA,YAAe,EAAQ,EAAI,EAC3B,WAAc,EAAQ,EAAK,EAC3B,UAAW,CACd,CAAA,EACA,IACG,EAAA,EAAA,IAAA,CAAC,KAAD,CACI,GAAI,EACJ,UAAW,EAAA,QAAO,QAClB,KAAK,UACL,YAAa,EAEZ,SAAA,EAAQ,KAAK,EAAG,KACb,EAAA,EAAA,KAAA,CAAC,KAAD,CAEI,GAAI,EAAS,CAAK,EAClB,KAAK,SACL,gBAAe,IAAU,EACzB,UAAW,EAAA,GAAG,EAAA,QAAO,OAAQ,IAAU,GAAe,EAAA,QAAO,MAAM,EACnE,iBAAoB,EAAe,CAAK,EACxC,YAAe,EAAO,CAAC,EAP3B,SAAA,EASI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAA,SAAO,EAAE,IAAW,CAAA,GACpB,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,GAAK,SAAA,EAAE,EAAS,CAAA,CACxC,CAVK,EAAA,EAAE,EAUP,CACP,CACD,CAAA,CAEP,GAEb"}
@@ -1,74 +1,92 @@
1
- import { Input as e } from "../components/Input/Input.js";
2
- import { useDebounce as t } from "../hooks/use-debounce.js";
3
- import { searchMunicipalities as n } from "./geocode.js";
4
- import r from "./MunicipalitySearch.module.js";
5
- import { useEffect as i, useId as a, useRef as o, useState as s } from "react";
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { Input as t } from "../components/Input/Input.js";
3
+ import { useDebounce as n } from "../hooks/use-debounce.js";
4
+ import { searchMunicipalities as r } from "./geocode.js";
5
+ import i from "./MunicipalitySearch.module.js";
6
+ import { useEffect as a, useId as o, useState as s } from "react";
6
7
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
8
  //#region src/br/MunicipalitySearch.tsx
8
- function u({ uf: u, onSelect: d, label: f, placeholder: p = "Buscar município…", limit: m = 8, debounceMs: h = 250, disabled: g = !1 }) {
9
- let [_, v] = s(""), [y, b] = s([]), [x, S] = s(!1), C = t(_, h), w = a(), T = o(null);
10
- i(() => {
9
+ function u(e) {
10
+ e.preventDefault();
11
+ }
12
+ function d({ uf: d, onSelect: f, label: p, placeholder: m = "Buscar município…", limit: h = 8, debounceMs: g = 250, disabled: _ = !1 }) {
13
+ let [v, y] = s(""), [b, x] = s([]), [S, C] = s(!1), [w, T] = s(0), E = n(v, g), D = o(), O = S && b.length > 0, k = (e) => `${D}-option-${e}`;
14
+ a(() => {
11
15
  let e = !0;
12
- if (!C.trim()) {
13
- b([]);
16
+ if (!E.trim()) {
17
+ x([]);
14
18
  return;
15
19
  }
16
- return n(C, {
17
- uf: u,
18
- limit: m
20
+ return r(E, {
21
+ uf: d,
22
+ limit: h
19
23
  }).then((t) => {
20
- e && b(t);
24
+ e && (x(t), T(0));
21
25
  }), () => {
22
26
  e = !1;
23
27
  };
24
28
  }, [
25
- C,
26
- u,
27
- m
29
+ E,
30
+ d,
31
+ h
28
32
  ]);
29
- function E(e) {
30
- d(e), v(e.name), S(!1);
33
+ function A(e) {
34
+ f(e), y(e.name), C(!1);
35
+ }
36
+ function j(e) {
37
+ if (e.key === "ArrowDown") {
38
+ if (e.preventDefault(), !O) {
39
+ C(!0);
40
+ return;
41
+ }
42
+ T((e) => Math.min(b.length - 1, e + 1));
43
+ } else if (e.key === "ArrowUp") e.preventDefault(), T((e) => Math.max(0, e - 1));
44
+ else if (e.key === "Enter") {
45
+ let t = O ? b[w] : void 0;
46
+ if (!t) return;
47
+ e.preventDefault(), A(t);
48
+ } else e.key === "Escape" && (O && e.preventDefault(), C(!1));
31
49
  }
32
50
  return /* @__PURE__ */ l("div", {
33
- className: r.search,
34
- children: [/* @__PURE__ */ c(e, {
35
- label: f,
36
- value: _,
37
- placeholder: p,
38
- disabled: g,
51
+ className: i.search,
52
+ children: [/* @__PURE__ */ c(t, {
53
+ label: p,
54
+ value: v,
55
+ placeholder: m,
56
+ disabled: _,
39
57
  role: "combobox",
40
- "aria-expanded": x && y.length > 0,
41
- "aria-controls": w,
58
+ "aria-expanded": O,
59
+ "aria-controls": D,
60
+ "aria-autocomplete": "list",
61
+ "aria-activedescendant": O ? k(w) : void 0,
42
62
  autoComplete: "off",
43
63
  onChange: (e) => {
44
- v(e.target.value), S(!0);
64
+ y(e.target.value), C(!0);
45
65
  },
46
- onFocus: () => S(!0),
47
- onBlur: () => {
48
- T.current = setTimeout(() => S(!1), 120);
49
- }
50
- }), x && y.length > 0 && /* @__PURE__ */ c("ul", {
51
- id: w,
52
- className: r.results,
66
+ onFocus: () => C(!0),
67
+ onBlur: () => C(!1),
68
+ onKeyDown: j
69
+ }), O && /* @__PURE__ */ c("ul", {
70
+ id: D,
71
+ className: i.results,
53
72
  role: "listbox",
54
- children: y.map((e) => /* @__PURE__ */ c("li", { children: /* @__PURE__ */ l("button", {
55
- type: "button",
73
+ onMouseDown: u,
74
+ children: b.map((t, n) => /* @__PURE__ */ l("li", {
75
+ id: k(n),
56
76
  role: "option",
57
- "aria-selected": !1,
58
- className: r.result,
59
- onMouseDown: (e) => e.preventDefault(),
60
- onClick: () => {
61
- T.current && clearTimeout(T.current), E(e);
62
- },
63
- children: [/* @__PURE__ */ c("span", { children: e.name }), /* @__PURE__ */ c("span", {
64
- className: r.uf,
65
- children: e.uf
77
+ "aria-selected": n === w,
78
+ className: e(i.result, n === w && i.active),
79
+ onMouseEnter: () => T(n),
80
+ onClick: () => A(t),
81
+ children: [/* @__PURE__ */ c("span", { children: t.name }), /* @__PURE__ */ c("span", {
82
+ className: i.uf,
83
+ children: t.uf
66
84
  })]
67
- }) }, e.id))
85
+ }, t.id))
68
86
  })]
69
87
  });
70
88
  }
71
89
  //#endregion
72
- export { u as MunicipalitySearch };
90
+ export { d as MunicipalitySearch };
73
91
 
74
92
  //# sourceMappingURL=MunicipalitySearch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MunicipalitySearch.js","names":[],"sources":["../../src/br/MunicipalitySearch.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type ReactElement } from \"react\";\nimport { Input } from \"@/components/Input\";\nimport { useDebounce } from \"@/hooks\";\nimport { searchMunicipalities, type MunicipalityCentroid } from \"./geocode\";\nimport type { UF } from \"./locations\";\nimport styles from \"./MunicipalitySearch.module.css\";\n\nexport interface MunicipalitySearchProps {\n /** Restrict the search to a single UF. */\n uf?: UF;\n /** Fired when a municipality is picked from the results. */\n onSelect: (municipality: MunicipalityCentroid) => void;\n /** Field label. */\n label?: string;\n /** Input placeholder. Default: `\"Buscar município…\"`. */\n placeholder?: string;\n /** Max results shown. Default: `8`. */\n limit?: number;\n /** Debounce in ms before searching. Default: `250`. */\n debounceMs?: number;\n /** Disable the field. */\n disabled?: boolean;\n}\n\n/**\n * Debounced municipality autocomplete backed by the offline\n * {@link searchMunicipalities} index — no network. Type a partial name, pick a\n * result, and `onSelect` fires with its `{ id, name, uf, latitude, longitude }`.\n * Wire `onSelect` to a `BrazilStateMap` `selected` to highlight it on the map.\n *\n * @example\n * const [uf, setUf] = useState<UF>(\"SP\");\n * const [city, setCity] = useState<string | null>(null);\n * <MunicipalitySearch uf={uf} onSelect={(m) => setCity(m.name)} />\n * <BrazilStateMap uf={uf} selected={city} />\n */\nexport function MunicipalitySearch({\n uf,\n onSelect,\n label,\n placeholder = \"Buscar município…\",\n limit = 8,\n debounceMs = 250,\n disabled = false,\n}: MunicipalitySearchProps): ReactElement {\n const [query, setQuery] = useState<string>(\"\");\n const [results, setResults] = useState<MunicipalityCentroid[]>([]);\n const [open, setOpen] = useState<boolean>(false);\n const debounced = useDebounce(query, debounceMs);\n const listId = useId();\n const blurTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n let active = true;\n if (!debounced.trim()) {\n setResults([]);\n return;\n }\n void searchMunicipalities(debounced, { uf, limit }).then((hits) => {\n if (active) setResults(hits);\n });\n return () => {\n active = false;\n };\n }, [debounced, uf, limit]);\n\n function choose(municipality: MunicipalityCentroid): void {\n onSelect(municipality);\n setQuery(municipality.name);\n setOpen(false);\n }\n\n return (\n <div className={styles.search}>\n <Input\n label={label}\n value={query}\n placeholder={placeholder}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={open && results.length > 0}\n aria-controls={listId}\n autoComplete=\"off\"\n onChange={(e) => {\n setQuery(e.target.value);\n setOpen(true);\n }}\n onFocus={() => setOpen(true)}\n onBlur={() => {\n // Delay so a result click registers before the list closes.\n blurTimer.current = setTimeout(() => setOpen(false), 120);\n }}\n />\n {open && results.length > 0 && (\n <ul id={listId} className={styles.results} role=\"listbox\">\n {results.map((m) => (\n <li key={m.id}>\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={false}\n className={styles.result}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n if (blurTimer.current) clearTimeout(blurTimer.current);\n choose(m);\n }}\n >\n <span>{m.name}</span>\n <span className={styles.uf}>{m.uf}</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAoCA,SAAgB,EAAmB,EAC/B,OACA,aACA,UACA,iBAAc,qBACd,WAAQ,GACR,gBAAa,KACb,cAAW,MAC2B;CACtC,IAAM,CAAC,GAAO,KAAY,EAAiB,EAAE,GACvC,CAAC,GAAS,KAAc,EAAiC,CAAC,CAAC,GAC3D,CAAC,GAAM,KAAW,EAAkB,EAAK,GACzC,IAAY,EAAY,GAAO,CAAU,GACzC,IAAS,EAAM,GACf,IAAY,EAA6C,IAAI;CAEnE,QAAgB;EACZ,IAAI,IAAS;EACb,IAAI,CAAC,EAAU,KAAK,GAAG;GACnB,EAAW,CAAC,CAAC;GACb;EACJ;EAIA,OAHA,EAA0B,GAAW;GAAE;GAAI;EAAM,CAAC,CAAC,CAAC,MAAM,MAAS;GAC/D,AAAI,KAAQ,EAAW,CAAI;EAC/B,CAAC,SACY;GACT,IAAS;EACb;CACJ,GAAG;EAAC;EAAW;EAAI;CAAK,CAAC;CAEzB,SAAS,EAAO,GAA0C;EAGtD,AAFA,EAAS,CAAY,GACrB,EAAS,EAAa,IAAI,GAC1B,EAAQ,EAAK;CACjB;CAEA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAvB,UAAA,CACI,kBAAC,GAAD;GACW;GACP,OAAO;GACM;GACH;GACV,MAAK;GACL,iBAAe,KAAQ,EAAQ,SAAS;GACxC,iBAAe;GACf,cAAa;GACb,WAAW,MAAM;IAEb,AADA,EAAS,EAAE,OAAO,KAAK,GACvB,EAAQ,EAAI;GAChB;GACA,eAAe,EAAQ,EAAI;GAC3B,cAAc;IAEV,EAAU,UAAU,iBAAiB,EAAQ,EAAK,GAAG,GAAG;GAC5D;EACH,CAAA,GACA,KAAQ,EAAQ,SAAS,KACtB,kBAAC,MAAD;GAAI,IAAI;GAAQ,WAAW,EAAO;GAAS,MAAK;GAC3C,UAAA,EAAQ,KAAK,MACV,kBAAC,MAAD,EAAA,UACI,kBAAC,UAAD;IACI,MAAK;IACL,MAAK;IACL,iBAAe;IACf,WAAW,EAAO;IAClB,cAAc,MAAM,EAAE,eAAe;IACrC,eAAe;KAEX,AADI,EAAU,WAAS,aAAa,EAAU,OAAO,GACrD,EAAO,CAAC;IACZ;IATJ,UAAA,CAWI,kBAAC,QAAD,EAAA,UAAO,EAAE,KAAW,CAAA,GACpB,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAK,UAAA,EAAE;IAAS,CAAA,CACpC;GACR,CAAA,EAAA,GAfK,EAAE,EAeP,CACP;EACD,CAAA,CAEP;;AAEb"}
1
+ {"version":3,"file":"MunicipalitySearch.js","names":[],"sources":["../../src/br/MunicipalitySearch.tsx"],"sourcesContent":["import {\n useEffect,\n useId,\n useState,\n type KeyboardEvent,\n type MouseEvent,\n type ReactElement,\n} from \"react\";\nimport { Input } from \"@/components/Input\";\nimport { useDebounce } from \"@/hooks\";\nimport { cn } from \"@/utils/cn\";\nimport { searchMunicipalities, type MunicipalityCentroid } from \"./geocode\";\nimport type { UF } from \"./locations\";\nimport styles from \"./MunicipalitySearch.module.css\";\n\nexport interface MunicipalitySearchProps {\n /** Restrict the search to a single UF. */\n uf?: UF;\n /** Fired when a municipality is picked from the results. */\n onSelect: (municipality: MunicipalityCentroid) => void;\n /** Field label. */\n label?: string;\n /** Input placeholder. Default: `\"Buscar município…\"`. */\n placeholder?: string;\n /** Max results shown. Default: `8`. */\n limit?: number;\n /** Debounce in ms before searching. Default: `250`. */\n debounceMs?: number;\n /** Disable the field. */\n disabled?: boolean;\n}\n\n/**\n * Keeps focus on the input when a press lands anywhere on the list — an option,\n * the padding or the scrollbar — so the input never blurs and the list closes\n * only when focus really leaves the field.\n *\n * @param event - The `mousedown` on the list.\n */\nfunction keepInputFocus(event: MouseEvent<HTMLUListElement>): void {\n event.preventDefault();\n}\n\n/**\n * Debounced municipality autocomplete backed by the offline\n * {@link searchMunicipalities} index — no network. Type a partial name, pick a\n * result, and `onSelect` fires with its `{ id, name, uf, latitude, longitude }`.\n * Wire `onSelect` to a `BrazilStateMap` `selected` to highlight it on the map.\n *\n * Keyboard follows the SDK's `Combobox` (the WAI-ARIA combobox pattern): focus\n * stays on the input, `ArrowDown`/`ArrowUp` move the active result — announced\n * through `aria-activedescendant` — `Enter` picks it, `Escape` closes the list\n * and `Tab` leaves the field, closing the list on the way out.\n *\n * The list used to close on a 120 ms timer after the input blurred, with each\n * result a focusable `<button>`. Measured in Chromium (gallery build, issue\n * #426): `Tab` moved focus to the first result, the timer then unmounted it and\n * focus fell to `<body>`; arrows, `Enter` and `Escape` did nothing. Results are\n * now plain options outside the tab order, and a press on the list cancels the\n * blur instead of racing it.\n *\n * @example\n * const [uf, setUf] = useState<UF>(\"SP\");\n * const [city, setCity] = useState<string | null>(null);\n * <MunicipalitySearch uf={uf} onSelect={(m) => setCity(m.name)} />\n * <BrazilStateMap uf={uf} selected={city} />\n */\nexport function MunicipalitySearch({\n uf,\n onSelect,\n label,\n placeholder = \"Buscar município…\",\n limit = 8,\n debounceMs = 250,\n disabled = false,\n}: MunicipalitySearchProps): ReactElement {\n const [query, setQuery] = useState<string>(\"\");\n const [results, setResults] = useState<MunicipalityCentroid[]>([]);\n const [open, setOpen] = useState<boolean>(false);\n const [activeIndex, setActiveIndex] = useState<number>(0);\n const debounced = useDebounce(query, debounceMs);\n const listId = useId();\n const expanded = open && results.length > 0;\n const optionId = (index: number): string => `${listId}-option-${index}`;\n\n useEffect(() => {\n let active = true;\n if (!debounced.trim()) {\n setResults([]);\n return;\n }\n void searchMunicipalities(debounced, { uf, limit }).then((hits) => {\n if (!active) return;\n setResults(hits);\n setActiveIndex(0);\n });\n return () => {\n active = false;\n };\n }, [debounced, uf, limit]);\n\n function choose(municipality: MunicipalityCentroid): void {\n onSelect(municipality);\n setQuery(municipality.name);\n setOpen(false);\n }\n\n /**\n * Keyboard model of the input, the same as `Combobox`'s.\n *\n * `Escape` calls `preventDefault()` only while the list is open, so an\n * enclosing `Modal` or `Drawer` stays open and the next `Escape` reaches it.\n * `Enter` is consumed only when it picks a result, so with the list closed\n * it still submits an enclosing form.\n *\n * @param event - The `keydown` on the input.\n */\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n if (!expanded) {\n setOpen(true);\n return;\n }\n setActiveIndex((current) => Math.min(results.length - 1, current + 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((current) => Math.max(0, current - 1));\n } else if (event.key === \"Enter\") {\n const municipality = expanded ? results[activeIndex] : undefined;\n if (!municipality) return;\n event.preventDefault();\n choose(municipality);\n } else if (event.key === \"Escape\") {\n if (expanded) event.preventDefault();\n setOpen(false);\n }\n }\n\n return (\n <div className={styles.search}>\n <Input\n label={label}\n value={query}\n placeholder={placeholder}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={expanded}\n aria-controls={listId}\n aria-autocomplete=\"list\"\n aria-activedescendant={expanded ? optionId(activeIndex) : undefined}\n autoComplete=\"off\"\n onChange={(e) => {\n setQuery(e.target.value);\n setOpen(true);\n }}\n onFocus={() => setOpen(true)}\n onBlur={() => setOpen(false)}\n onKeyDown={handleKeyDown}\n />\n {expanded && (\n <ul\n id={listId}\n className={styles.results}\n role=\"listbox\"\n onMouseDown={keepInputFocus}\n >\n {results.map((m, index) => (\n <li\n key={m.id}\n id={optionId(index)}\n role=\"option\"\n aria-selected={index === activeIndex}\n className={cn(styles.result, index === activeIndex && styles.active)}\n onMouseEnter={() => setActiveIndex(index)}\n onClick={() => choose(m)}\n >\n <span>{m.name}</span>\n <span className={styles.uf}>{m.uf}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAuCA,SAAS,EAAe,GAA2C;CAC/D,EAAM,eAAe;AACzB;AA0BA,SAAgB,EAAmB,EAC/B,OACA,aACA,UACA,iBAAc,qBACd,WAAQ,GACR,gBAAa,KACb,cAAW,MAC2B;CACtC,IAAM,CAAC,GAAO,KAAY,EAAiB,EAAE,GACvC,CAAC,GAAS,KAAc,EAAiC,CAAC,CAAC,GAC3D,CAAC,GAAM,KAAW,EAAkB,EAAK,GACzC,CAAC,GAAa,KAAkB,EAAiB,CAAC,GAClD,IAAY,EAAY,GAAO,CAAU,GACzC,IAAS,EAAM,GACf,IAAW,KAAQ,EAAQ,SAAS,GACpC,KAAY,MAA0B,GAAG,EAAO,UAAU;CAEhE,QAAgB;EACZ,IAAI,IAAS;EACb,IAAI,CAAC,EAAU,KAAK,GAAG;GACnB,EAAW,CAAC,CAAC;GACb;EACJ;EAMA,OALA,EAA0B,GAAW;GAAE;GAAI;EAAM,CAAC,CAAC,CAAC,MAAM,MAAS;GAC1D,MACL,EAAW,CAAI,GACf,EAAe,CAAC;EACpB,CAAC,SACY;GACT,IAAS;EACb;CACJ,GAAG;EAAC;EAAW;EAAI;CAAK,CAAC;CAEzB,SAAS,EAAO,GAA0C;EAGtD,AAFA,EAAS,CAAY,GACrB,EAAS,EAAa,IAAI,GAC1B,EAAQ,EAAK;CACjB;CAYA,SAAS,EAAc,GAA8C;EACjE,IAAI,EAAM,QAAQ,aAAa;GAE3B,IADA,EAAM,eAAe,GACjB,CAAC,GAAU;IACX,EAAQ,EAAI;IACZ;GACJ;GACA,GAAgB,MAAY,KAAK,IAAI,EAAQ,SAAS,GAAG,IAAU,CAAC,CAAC;EACzE,OAAO,IAAI,EAAM,QAAQ,WAErB,AADA,EAAM,eAAe,GACrB,GAAgB,MAAY,KAAK,IAAI,GAAG,IAAU,CAAC,CAAC;OACjD,IAAI,EAAM,QAAQ,SAAS;GAC9B,IAAM,IAAe,IAAW,EAAQ,KAAe,KAAA;GACvD,IAAI,CAAC,GAAc;GAEnB,AADA,EAAM,eAAe,GACrB,EAAO,CAAY;EACvB,OAAO,AAAI,EAAM,QAAQ,aACjB,KAAU,EAAM,eAAe,GACnC,EAAQ,EAAK;CAErB;CAEA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAvB,UAAA,CACI,kBAAC,GAAD;GACW;GACP,OAAO;GACM;GACH;GACV,MAAK;GACL,iBAAe;GACf,iBAAe;GACf,qBAAkB;GAClB,yBAAuB,IAAW,EAAS,CAAW,IAAI,KAAA;GAC1D,cAAa;GACb,WAAW,MAAM;IAEb,AADA,EAAS,EAAE,OAAO,KAAK,GACvB,EAAQ,EAAI;GAChB;GACA,eAAe,EAAQ,EAAI;GAC3B,cAAc,EAAQ,EAAK;GAC3B,WAAW;EACd,CAAA,GACA,KACG,kBAAC,MAAD;GACI,IAAI;GACJ,WAAW,EAAO;GAClB,MAAK;GACL,aAAa;GAEZ,UAAA,EAAQ,KAAK,GAAG,MACb,kBAAC,MAAD;IAEI,IAAI,EAAS,CAAK;IAClB,MAAK;IACL,iBAAe,MAAU;IACzB,WAAW,EAAG,EAAO,QAAQ,MAAU,KAAe,EAAO,MAAM;IACnE,oBAAoB,EAAe,CAAK;IACxC,eAAe,EAAO,CAAC;IAP3B,UAAA,CASI,kBAAC,QAAD,EAAA,UAAO,EAAE,KAAW,CAAA,GACpB,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAK,UAAA,EAAE;IAAS,CAAA,CACxC;GAVK,GAAA,EAAE,EAUP,CACP;EACD,CAAA,CAEP;;AAEb"}
@@ -1,3 +1,3 @@
1
1
  require("../styles/component/MunicipalitySearch.css");
2
- var e=`tempest_search_iYSqC`,t=`tempest_results_-a0Jg`,n=`tempest_result_D5-no`,r=`tempest_uf_Rj15X`,i={search:e,results:t,result:n,uf:r};exports.default=i,exports.result=n,exports.results=t,exports.search=e,exports.uf=r;
2
+ var e=`tempest_search_iYSqC`,t=`tempest_results_-a0Jg`,n=`tempest_result_D5-no`,r=`tempest_active_qX2ct`,i=`tempest_uf_Rj15X`,a={search:e,results:t,result:n,active:r,uf:i};exports.active=r,exports.default=a,exports.result=n,exports.results=t,exports.search=e,exports.uf=i;
3
3
  //# sourceMappingURL=MunicipalitySearch.module.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"MunicipalitySearch.module.cjs","names":[],"sources":["../../src/br/MunicipalitySearch.module.css"],"sourcesContent":[".search {\n position: relative;\n font-family: var(--tempest-font-sans);\n}\n\n.results {\n position: absolute;\n z-index: 10;\n top: 100%;\n left: 0;\n right: 0;\n margin: var(--tempest-space-1) 0 0;\n padding: var(--tempest-space-1);\n list-style: none;\n max-height: 260px;\n overflow-y: auto;\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n}\n\n.result {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: none;\n background: transparent;\n border-radius: var(--tempest-radius-sm);\n font-size: var(--tempest-text-sm);\n color: var(--tempest-text);\n text-align: left;\n cursor: pointer;\n}\n\n.result:hover,\n.result:focus-visible {\n outline: none;\n background: var(--tempest-primary-soft);\n}\n\n.uf {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n font-weight: var(--tempest-weight-medium);\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"MunicipalitySearch.module.cjs","names":[],"sources":["../../src/br/MunicipalitySearch.module.css"],"sourcesContent":[".search {\n position: relative;\n font-family: var(--tempest-font-sans);\n}\n\n.results {\n position: absolute;\n z-index: 10;\n top: 100%;\n left: 0;\n right: 0;\n margin: var(--tempest-space-1) 0 0;\n padding: var(--tempest-space-1);\n list-style: none;\n max-height: 260px;\n overflow-y: auto;\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n}\n\n.result {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: none;\n background: transparent;\n border-radius: var(--tempest-radius-sm);\n font-size: var(--tempest-text-sm);\n color: var(--tempest-text);\n text-align: left;\n cursor: pointer;\n}\n\n.result:hover,\n.result.active {\n background: var(--tempest-primary-soft);\n}\n\n.uf {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n font-weight: var(--tempest-weight-medium);\n}\n"],"mappings":";"}
@@ -1,12 +1,13 @@
1
1
  import "../styles/component/MunicipalitySearch.css";
2
2
  //#region src/br/MunicipalitySearch.module.css
3
- var e = "tempest_search_iYSqC", t = "tempest_results_-a0Jg", n = "tempest_result_D5-no", r = "tempest_uf_Rj15X", i = {
3
+ var e = "tempest_search_iYSqC", t = "tempest_results_-a0Jg", n = "tempest_result_D5-no", r = "tempest_active_qX2ct", i = "tempest_uf_Rj15X", a = {
4
4
  search: e,
5
5
  results: t,
6
6
  result: n,
7
- uf: r
7
+ active: r,
8
+ uf: i
8
9
  };
9
10
  //#endregion
10
- export { i as default, n as result, t as results, e as search, r as uf };
11
+ export { r as active, a as default, n as result, t as results, e as search, i as uf };
11
12
 
12
13
  //# sourceMappingURL=MunicipalitySearch.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MunicipalitySearch.module.js","names":[],"sources":["../../src/br/MunicipalitySearch.module.css"],"sourcesContent":[".search {\n position: relative;\n font-family: var(--tempest-font-sans);\n}\n\n.results {\n position: absolute;\n z-index: 10;\n top: 100%;\n left: 0;\n right: 0;\n margin: var(--tempest-space-1) 0 0;\n padding: var(--tempest-space-1);\n list-style: none;\n max-height: 260px;\n overflow-y: auto;\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n}\n\n.result {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: none;\n background: transparent;\n border-radius: var(--tempest-radius-sm);\n font-size: var(--tempest-text-sm);\n color: var(--tempest-text);\n text-align: left;\n cursor: pointer;\n}\n\n.result:hover,\n.result:focus-visible {\n outline: none;\n background: var(--tempest-primary-soft);\n}\n\n.uf {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n font-weight: var(--tempest-weight-medium);\n}\n"],"mappings":";"}
1
+ {"version":3,"file":"MunicipalitySearch.module.js","names":[],"sources":["../../src/br/MunicipalitySearch.module.css"],"sourcesContent":[".search {\n position: relative;\n font-family: var(--tempest-font-sans);\n}\n\n.results {\n position: absolute;\n z-index: 10;\n top: 100%;\n left: 0;\n right: 0;\n margin: var(--tempest-space-1) 0 0;\n padding: var(--tempest-space-1);\n list-style: none;\n max-height: 260px;\n overflow-y: auto;\n background: var(--tempest-surface);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-md);\n box-shadow: var(--tempest-shadow-md, 0 4px 12px rgb(0 0 0 / 0.12));\n}\n\n.result {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n width: 100%;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: none;\n background: transparent;\n border-radius: var(--tempest-radius-sm);\n font-size: var(--tempest-text-sm);\n color: var(--tempest-text);\n text-align: left;\n cursor: pointer;\n}\n\n.result:hover,\n.result.active {\n background: var(--tempest-primary-soft);\n}\n\n.uf {\n font-size: var(--tempest-text-xs);\n color: var(--tempest-text-muted);\n font-weight: var(--tempest-weight-medium);\n}\n"],"mappings":";"}
package/dist/br.d.ts CHANGED
@@ -964,6 +964,18 @@ export declare interface MunicipalityFeature {
964
964
  * result, and `onSelect` fires with its `{ id, name, uf, latitude, longitude }`.
965
965
  * Wire `onSelect` to a `BrazilStateMap` `selected` to highlight it on the map.
966
966
  *
967
+ * Keyboard follows the SDK's `Combobox` (the WAI-ARIA combobox pattern): focus
968
+ * stays on the input, `ArrowDown`/`ArrowUp` move the active result — announced
969
+ * through `aria-activedescendant` — `Enter` picks it, `Escape` closes the list
970
+ * and `Tab` leaves the field, closing the list on the way out.
971
+ *
972
+ * The list used to close on a 120 ms timer after the input blurred, with each
973
+ * result a focusable `<button>`. Measured in Chromium (gallery build, issue
974
+ * #426): `Tab` moved focus to the first result, the timer then unmounted it and
975
+ * focus fell to `<body>`; arrows, `Enter` and `Escape` did nothing. Results are
976
+ * now plain options outside the tab order, and a press on the list cancels the
977
+ * blur instead of racing it.
978
+ *
967
979
  * @example
968
980
  * const [uf, setUf] = useState<UF>("SP");
969
981
  * const [city, setCity] = useState<string | null>(null);