tempest-react-sdk 0.47.0 → 0.49.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 (539) hide show
  1. package/dist/audio/sfx-pool.cjs +1 -1
  2. package/dist/audio/sfx-pool.cjs.map +1 -1
  3. package/dist/audio/sfx-pool.js +16 -7
  4. package/dist/audio/sfx-pool.js.map +1 -1
  5. package/dist/audio/use-sfx-pool.cjs +1 -1
  6. package/dist/audio/use-sfx-pool.cjs.map +1 -1
  7. package/dist/audio/use-sfx-pool.js +3 -8
  8. package/dist/audio/use-sfx-pool.js.map +1 -1
  9. package/dist/auth/passkey.cjs +1 -1
  10. package/dist/auth/passkey.cjs.map +1 -1
  11. package/dist/auth/passkey.js +79 -81
  12. package/dist/auth/passkey.js.map +1 -1
  13. package/dist/charts.cjs +1 -1
  14. package/dist/charts.js +4 -4
  15. package/dist/components/AIChat/AIChatTurn.cjs +1 -1
  16. package/dist/components/AIChat/AIChatTurn.js +4 -4
  17. package/dist/components/AppBar/use-sticky-body-warning.cjs.map +1 -1
  18. package/dist/components/AppBar/use-sticky-body-warning.js.map +1 -1
  19. package/dist/components/BarList/BarList.cjs +1 -1
  20. package/dist/components/BarList/BarList.cjs.map +1 -1
  21. package/dist/components/BarList/BarList.js +31 -24
  22. package/dist/components/BarList/BarList.js.map +1 -1
  23. package/dist/components/BarList/bar-list-model.cjs +1 -1
  24. package/dist/components/BarList/bar-list-model.cjs.map +1 -1
  25. package/dist/components/BarList/bar-list-model.js +5 -5
  26. package/dist/components/BarList/bar-list-model.js.map +1 -1
  27. package/dist/components/ClickOutside/ClickOutside.cjs +1 -1
  28. package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
  29. package/dist/components/ClickOutside/ClickOutside.js +13 -14
  30. package/dist/components/ClickOutside/ClickOutside.js.map +1 -1
  31. package/dist/components/DataTable/DataTable.cjs +1 -1
  32. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  33. package/dist/components/DataTable/DataTable.js +131 -129
  34. package/dist/components/DataTable/DataTable.js.map +1 -1
  35. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
  36. package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
  37. package/dist/components/FilterBar/filter-apply.cjs +1 -1
  38. package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
  39. package/dist/components/FilterBar/filter-apply.js +35 -38
  40. package/dist/components/FilterBar/filter-apply.js.map +1 -1
  41. package/dist/components/FilterBar/filter-query.cjs +1 -1
  42. package/dist/components/FilterBar/filter-query.cjs.map +1 -1
  43. package/dist/components/FilterBar/filter-query.js +16 -13
  44. package/dist/components/FilterBar/filter-query.js.map +1 -1
  45. package/dist/components/NotificationCenter/NotificationCenter.cjs +1 -1
  46. package/dist/components/NotificationCenter/NotificationCenter.js +21 -21
  47. package/dist/components/Sidebar/Sidebar.cjs +1 -1
  48. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  49. package/dist/components/Sidebar/Sidebar.js +91 -38
  50. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  51. package/dist/components/Sidebar/Sidebar.module.cjs +1 -1
  52. package/dist/components/Sidebar/Sidebar.module.cjs.map +1 -1
  53. package/dist/components/Sidebar/Sidebar.module.js +12 -8
  54. package/dist/components/Sidebar/Sidebar.module.js.map +1 -1
  55. package/dist/forms/masked-inputs.cjs +1 -1
  56. package/dist/forms/masked-inputs.js +12 -12
  57. package/dist/geo/use-position-tracker.cjs +1 -1
  58. package/dist/geo/use-position-tracker.cjs.map +1 -1
  59. package/dist/geo/use-position-tracker.js +25 -26
  60. package/dist/geo/use-position-tracker.js.map +1 -1
  61. package/dist/hooks/use-async.cjs +1 -1
  62. package/dist/hooks/use-async.cjs.map +1 -1
  63. package/dist/hooks/use-async.js +27 -30
  64. package/dist/hooks/use-async.js.map +1 -1
  65. package/dist/hooks/use-click-outside.cjs +1 -1
  66. package/dist/hooks/use-click-outside.cjs.map +1 -1
  67. package/dist/hooks/use-click-outside.js +8 -9
  68. package/dist/hooks/use-click-outside.js.map +1 -1
  69. package/dist/hooks/use-countdown.cjs +1 -1
  70. package/dist/hooks/use-countdown.cjs.map +1 -1
  71. package/dist/hooks/use-countdown.js +8 -13
  72. package/dist/hooks/use-countdown.js.map +1 -1
  73. package/dist/hooks/use-event-listener.cjs +1 -1
  74. package/dist/hooks/use-event-listener.cjs.map +1 -1
  75. package/dist/hooks/use-event-listener.js +6 -6
  76. package/dist/hooks/use-event-listener.js.map +1 -1
  77. package/dist/hooks/use-interval.cjs +1 -1
  78. package/dist/hooks/use-interval.cjs.map +1 -1
  79. package/dist/hooks/use-interval.js +5 -6
  80. package/dist/hooks/use-interval.js.map +1 -1
  81. package/dist/hooks/use-latest-ref.cjs.map +1 -1
  82. package/dist/hooks/use-latest-ref.js.map +1 -1
  83. package/dist/hooks/use-long-press-handlers.cjs +1 -1
  84. package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
  85. package/dist/hooks/use-long-press-handlers.js +8 -10
  86. package/dist/hooks/use-long-press-handlers.js.map +1 -1
  87. package/dist/hooks/use-long-press.cjs +1 -1
  88. package/dist/hooks/use-long-press.cjs.map +1 -1
  89. package/dist/hooks/use-long-press.js +6 -6
  90. package/dist/hooks/use-long-press.js.map +1 -1
  91. package/dist/hooks/use-online.cjs +1 -1
  92. package/dist/hooks/use-online.cjs.map +1 -1
  93. package/dist/hooks/use-online.js +10 -10
  94. package/dist/hooks/use-online.js.map +1 -1
  95. package/dist/hooks/use-service-worker-update.cjs +1 -1
  96. package/dist/hooks/use-service-worker-update.cjs.map +1 -1
  97. package/dist/hooks/use-service-worker-update.js +24 -25
  98. package/dist/hooks/use-service-worker-update.js.map +1 -1
  99. package/dist/hooks/use-stable-callback.cjs +1 -1
  100. package/dist/hooks/use-stable-callback.cjs.map +1 -1
  101. package/dist/hooks/use-stable-callback.js +4 -3
  102. package/dist/hooks/use-stable-callback.js.map +1 -1
  103. package/dist/hooks/use-timeout.cjs +1 -1
  104. package/dist/hooks/use-timeout.cjs.map +1 -1
  105. package/dist/hooks/use-timeout.js +5 -6
  106. package/dist/hooks/use-timeout.js.map +1 -1
  107. package/dist/hooks/use-typewriter.cjs +1 -1
  108. package/dist/hooks/use-typewriter.cjs.map +1 -1
  109. package/dist/hooks/use-typewriter.js +9 -18
  110. package/dist/hooks/use-typewriter.js.map +1 -1
  111. package/dist/http/api-client.cjs +1 -1
  112. package/dist/http/api-client.cjs.map +1 -1
  113. package/dist/http/api-client.js +55 -46
  114. package/dist/http/api-client.js.map +1 -1
  115. package/dist/http/describe-api-error.cjs +1 -1
  116. package/dist/http/describe-api-error.cjs.map +1 -1
  117. package/dist/http/describe-api-error.js +15 -14
  118. package/dist/http/describe-api-error.js.map +1 -1
  119. package/dist/http/errors.cjs +1 -1
  120. package/dist/http/errors.cjs.map +1 -1
  121. package/dist/http/errors.js +49 -20
  122. package/dist/http/errors.js.map +1 -1
  123. package/dist/http/resumable-upload.cjs +1 -1
  124. package/dist/http/resumable-upload.cjs.map +1 -1
  125. package/dist/http/resumable-upload.js +60 -61
  126. package/dist/http/resumable-upload.js.map +1 -1
  127. package/dist/http/retry.cjs +1 -1
  128. package/dist/http/retry.cjs.map +1 -1
  129. package/dist/http/retry.js +20 -16
  130. package/dist/http/retry.js.map +1 -1
  131. package/dist/http/use-describe-api-error.cjs +1 -1
  132. package/dist/http/use-describe-api-error.cjs.map +1 -1
  133. package/dist/http/use-describe-api-error.js +14 -10
  134. package/dist/http/use-describe-api-error.js.map +1 -1
  135. package/dist/i18n/create-i18n.cjs +1 -1
  136. package/dist/i18n/create-i18n.cjs.map +1 -1
  137. package/dist/i18n/create-i18n.js +6 -6
  138. package/dist/i18n/create-i18n.js.map +1 -1
  139. package/dist/icons/Icon.cjs +1 -1
  140. package/dist/icons/Icon.cjs.map +1 -1
  141. package/dist/icons/Icon.js +15 -14
  142. package/dist/icons/Icon.js.map +1 -1
  143. package/dist/icons/IconPicker.cjs +2 -0
  144. package/dist/icons/IconPicker.cjs.map +1 -0
  145. package/dist/icons/IconPicker.js +49 -0
  146. package/dist/icons/IconPicker.js.map +1 -0
  147. package/dist/icons/IconPicker.module.cjs +2 -0
  148. package/dist/icons/IconPicker.module.cjs.map +1 -0
  149. package/dist/icons/IconPicker.module.js +10 -0
  150. package/dist/icons/IconPicker.module.js.map +1 -0
  151. package/dist/icons/alias.cjs +2 -0
  152. package/dist/icons/alias.cjs.map +1 -0
  153. package/dist/icons/alias.js +9 -0
  154. package/dist/icons/alias.js.map +1 -0
  155. package/dist/icons/generated/loaders.cjs +1 -1
  156. package/dist/icons/generated/loaders.cjs.map +1 -1
  157. package/dist/icons/generated/loaders.js +273 -28
  158. package/dist/icons/generated/loaders.js.map +1 -1
  159. package/dist/icons/generated/shard-00.cjs +2 -0
  160. package/dist/icons/generated/shard-00.cjs.map +1 -0
  161. package/dist/icons/generated/shard-00.js +48 -0
  162. package/dist/icons/generated/shard-00.js.map +1 -0
  163. package/dist/icons/generated/shard-01.cjs +2 -0
  164. package/dist/icons/generated/shard-01.cjs.map +1 -0
  165. package/dist/icons/generated/shard-01.js +48 -0
  166. package/dist/icons/generated/shard-01.js.map +1 -0
  167. package/dist/icons/generated/shard-02.cjs +2 -0
  168. package/dist/icons/generated/shard-02.cjs.map +1 -0
  169. package/dist/icons/generated/shard-02.js +48 -0
  170. package/dist/icons/generated/shard-02.js.map +1 -0
  171. package/dist/icons/generated/shard-03.cjs +2 -0
  172. package/dist/icons/generated/shard-03.cjs.map +1 -0
  173. package/dist/icons/generated/shard-03.js +48 -0
  174. package/dist/icons/generated/shard-03.js.map +1 -0
  175. package/dist/icons/generated/shard-04.cjs +2 -0
  176. package/dist/icons/generated/shard-04.cjs.map +1 -0
  177. package/dist/icons/generated/shard-04.js +48 -0
  178. package/dist/icons/generated/shard-04.js.map +1 -0
  179. package/dist/icons/generated/shard-05.cjs +2 -0
  180. package/dist/icons/generated/shard-05.cjs.map +1 -0
  181. package/dist/icons/generated/shard-05.js +48 -0
  182. package/dist/icons/generated/shard-05.js.map +1 -0
  183. package/dist/icons/generated/shard-06.cjs +2 -0
  184. package/dist/icons/generated/shard-06.cjs.map +1 -0
  185. package/dist/icons/generated/shard-06.js +48 -0
  186. package/dist/icons/generated/shard-06.js.map +1 -0
  187. package/dist/icons/generated/shard-07.cjs +2 -0
  188. package/dist/icons/generated/shard-07.cjs.map +1 -0
  189. package/dist/icons/generated/shard-07.js +48 -0
  190. package/dist/icons/generated/shard-07.js.map +1 -0
  191. package/dist/icons/generated/shard-08.cjs +2 -0
  192. package/dist/icons/generated/shard-08.cjs.map +1 -0
  193. package/dist/icons/generated/shard-08.js +48 -0
  194. package/dist/icons/generated/shard-08.js.map +1 -0
  195. package/dist/icons/generated/shard-09.cjs +2 -0
  196. package/dist/icons/generated/shard-09.cjs.map +1 -0
  197. package/dist/icons/generated/shard-09.js +48 -0
  198. package/dist/icons/generated/shard-09.js.map +1 -0
  199. package/dist/icons/generated/shard-10.cjs +2 -0
  200. package/dist/icons/generated/shard-10.cjs.map +1 -0
  201. package/dist/icons/generated/shard-10.js +48 -0
  202. package/dist/icons/generated/shard-10.js.map +1 -0
  203. package/dist/icons/generated/shard-11.cjs +2 -0
  204. package/dist/icons/generated/shard-11.cjs.map +1 -0
  205. package/dist/icons/generated/shard-11.js +48 -0
  206. package/dist/icons/generated/shard-11.js.map +1 -0
  207. package/dist/icons/generated/shard-12.cjs +2 -0
  208. package/dist/icons/generated/shard-12.cjs.map +1 -0
  209. package/dist/icons/generated/shard-12.js +48 -0
  210. package/dist/icons/generated/shard-12.js.map +1 -0
  211. package/dist/icons/generated/shard-13.cjs +2 -0
  212. package/dist/icons/generated/shard-13.cjs.map +1 -0
  213. package/dist/icons/generated/shard-13.js +48 -0
  214. package/dist/icons/generated/shard-13.js.map +1 -0
  215. package/dist/icons/generated/shard-14.cjs +2 -0
  216. package/dist/icons/generated/shard-14.cjs.map +1 -0
  217. package/dist/icons/generated/shard-14.js +48 -0
  218. package/dist/icons/generated/shard-14.js.map +1 -0
  219. package/dist/icons/generated/shard-15.cjs +2 -0
  220. package/dist/icons/generated/shard-15.cjs.map +1 -0
  221. package/dist/icons/generated/shard-15.js +48 -0
  222. package/dist/icons/generated/shard-15.js.map +1 -0
  223. package/dist/icons/generated/shard-16.cjs +2 -0
  224. package/dist/icons/generated/shard-16.cjs.map +1 -0
  225. package/dist/icons/generated/shard-16.js +48 -0
  226. package/dist/icons/generated/shard-16.js.map +1 -0
  227. package/dist/icons/generated/shard-17.cjs +2 -0
  228. package/dist/icons/generated/shard-17.cjs.map +1 -0
  229. package/dist/icons/generated/shard-17.js +48 -0
  230. package/dist/icons/generated/shard-17.js.map +1 -0
  231. package/dist/icons/generated/shard-18.cjs +2 -0
  232. package/dist/icons/generated/shard-18.cjs.map +1 -0
  233. package/dist/icons/generated/shard-18.js +48 -0
  234. package/dist/icons/generated/shard-18.js.map +1 -0
  235. package/dist/icons/generated/shard-19.cjs +2 -0
  236. package/dist/icons/generated/shard-19.cjs.map +1 -0
  237. package/dist/icons/generated/shard-19.js +48 -0
  238. package/dist/icons/generated/shard-19.js.map +1 -0
  239. package/dist/icons/generated/shard-20.cjs +2 -0
  240. package/dist/icons/generated/shard-20.cjs.map +1 -0
  241. package/dist/icons/generated/shard-20.js +48 -0
  242. package/dist/icons/generated/shard-20.js.map +1 -0
  243. package/dist/icons/generated/shard-21.cjs +2 -0
  244. package/dist/icons/generated/shard-21.cjs.map +1 -0
  245. package/dist/icons/generated/shard-21.js +48 -0
  246. package/dist/icons/generated/shard-21.js.map +1 -0
  247. package/dist/icons/generated/shard-22.cjs +2 -0
  248. package/dist/icons/generated/shard-22.cjs.map +1 -0
  249. package/dist/icons/generated/shard-22.js +48 -0
  250. package/dist/icons/generated/shard-22.js.map +1 -0
  251. package/dist/icons/generated/shard-23.cjs +2 -0
  252. package/dist/icons/generated/shard-23.cjs.map +1 -0
  253. package/dist/icons/generated/shard-23.js +48 -0
  254. package/dist/icons/generated/shard-23.js.map +1 -0
  255. package/dist/icons/generated/shard-24.cjs +2 -0
  256. package/dist/icons/generated/shard-24.cjs.map +1 -0
  257. package/dist/icons/generated/shard-24.js +48 -0
  258. package/dist/icons/generated/shard-24.js.map +1 -0
  259. package/dist/icons/generated/shard-25.cjs +2 -0
  260. package/dist/icons/generated/shard-25.cjs.map +1 -0
  261. package/dist/icons/generated/shard-25.js +48 -0
  262. package/dist/icons/generated/shard-25.js.map +1 -0
  263. package/dist/icons/generated/shard-26.cjs +2 -0
  264. package/dist/icons/generated/shard-26.cjs.map +1 -0
  265. package/dist/icons/generated/shard-26.js +48 -0
  266. package/dist/icons/generated/shard-26.js.map +1 -0
  267. package/dist/icons/generated/shard-27.cjs +2 -0
  268. package/dist/icons/generated/shard-27.cjs.map +1 -0
  269. package/dist/icons/generated/shard-27.js +48 -0
  270. package/dist/icons/generated/shard-27.js.map +1 -0
  271. package/dist/icons/generated/shard-28.cjs +2 -0
  272. package/dist/icons/generated/shard-28.cjs.map +1 -0
  273. package/dist/icons/generated/shard-28.js +48 -0
  274. package/dist/icons/generated/shard-28.js.map +1 -0
  275. package/dist/icons/generated/shard-29.cjs +2 -0
  276. package/dist/icons/generated/shard-29.cjs.map +1 -0
  277. package/dist/icons/generated/shard-29.js +48 -0
  278. package/dist/icons/generated/shard-29.js.map +1 -0
  279. package/dist/icons/generated/shard-30.cjs +2 -0
  280. package/dist/icons/generated/shard-30.cjs.map +1 -0
  281. package/dist/icons/generated/shard-30.js +48 -0
  282. package/dist/icons/generated/shard-30.js.map +1 -0
  283. package/dist/icons/generated/shard-31.cjs +2 -0
  284. package/dist/icons/generated/shard-31.cjs.map +1 -0
  285. package/dist/icons/generated/shard-31.js +48 -0
  286. package/dist/icons/generated/shard-31.js.map +1 -0
  287. package/dist/icons/generated/shard-32.cjs +2 -0
  288. package/dist/icons/generated/shard-32.cjs.map +1 -0
  289. package/dist/icons/generated/shard-32.js +48 -0
  290. package/dist/icons/generated/shard-32.js.map +1 -0
  291. package/dist/icons/generated/shard-33.cjs +2 -0
  292. package/dist/icons/generated/shard-33.cjs.map +1 -0
  293. package/dist/icons/generated/shard-33.js +48 -0
  294. package/dist/icons/generated/shard-33.js.map +1 -0
  295. package/dist/icons/generated/shard-34.cjs +2 -0
  296. package/dist/icons/generated/shard-34.cjs.map +1 -0
  297. package/dist/icons/generated/shard-34.js +48 -0
  298. package/dist/icons/generated/shard-34.js.map +1 -0
  299. package/dist/icons/generated/shard-35.cjs +2 -0
  300. package/dist/icons/generated/shard-35.cjs.map +1 -0
  301. package/dist/icons/generated/shard-35.js +48 -0
  302. package/dist/icons/generated/shard-35.js.map +1 -0
  303. package/dist/icons/generated/shard-36.cjs +2 -0
  304. package/dist/icons/generated/shard-36.cjs.map +1 -0
  305. package/dist/icons/generated/shard-36.js +48 -0
  306. package/dist/icons/generated/shard-36.js.map +1 -0
  307. package/dist/icons/generated/shard-37.cjs +2 -0
  308. package/dist/icons/generated/shard-37.cjs.map +1 -0
  309. package/dist/icons/generated/shard-37.js +48 -0
  310. package/dist/icons/generated/shard-37.js.map +1 -0
  311. package/dist/icons/generated/shard-38.cjs +2 -0
  312. package/dist/icons/generated/shard-38.cjs.map +1 -0
  313. package/dist/icons/generated/shard-38.js +48 -0
  314. package/dist/icons/generated/shard-38.js.map +1 -0
  315. package/dist/icons/generated/shard-39.cjs +2 -0
  316. package/dist/icons/generated/shard-39.cjs.map +1 -0
  317. package/dist/icons/generated/shard-39.js +48 -0
  318. package/dist/icons/generated/shard-39.js.map +1 -0
  319. package/dist/icons/generated/shard-40.cjs +2 -0
  320. package/dist/icons/generated/shard-40.cjs.map +1 -0
  321. package/dist/icons/generated/shard-40.js +48 -0
  322. package/dist/icons/generated/shard-40.js.map +1 -0
  323. package/dist/icons/generated/shard-41.cjs +2 -0
  324. package/dist/icons/generated/shard-41.cjs.map +1 -0
  325. package/dist/icons/generated/shard-41.js +48 -0
  326. package/dist/icons/generated/shard-41.js.map +1 -0
  327. package/dist/icons/generated/shard-42.cjs +2 -0
  328. package/dist/icons/generated/shard-42.cjs.map +1 -0
  329. package/dist/icons/generated/shard-42.js +48 -0
  330. package/dist/icons/generated/shard-42.js.map +1 -0
  331. package/dist/icons/generated/shard-43.cjs +2 -0
  332. package/dist/icons/generated/shard-43.cjs.map +1 -0
  333. package/dist/icons/generated/shard-43.js +48 -0
  334. package/dist/icons/generated/shard-43.js.map +1 -0
  335. package/dist/icons/generated/shard-44.cjs +2 -0
  336. package/dist/icons/generated/shard-44.cjs.map +1 -0
  337. package/dist/icons/generated/shard-44.js +15 -0
  338. package/dist/icons/generated/shard-44.js.map +1 -0
  339. package/dist/icons/material-symbols.cjs +1 -1
  340. package/dist/icons/material-symbols.cjs.map +1 -1
  341. package/dist/icons/material-symbols.js +240 -1
  342. package/dist/icons/material-symbols.js.map +1 -1
  343. package/dist/icons/normalize-icon-name.cjs +2 -0
  344. package/dist/icons/normalize-icon-name.cjs.map +1 -0
  345. package/dist/icons/normalize-icon-name.js +9 -0
  346. package/dist/icons/normalize-icon-name.js.map +1 -0
  347. package/dist/icons/shard-cache.cjs +1 -1
  348. package/dist/icons/shard-cache.cjs.map +1 -1
  349. package/dist/icons/shard-cache.js +85 -32
  350. package/dist/icons/shard-cache.js.map +1 -1
  351. package/dist/icons/validate-icon-name.cjs +2 -0
  352. package/dist/icons/validate-icon-name.cjs.map +1 -0
  353. package/dist/icons/validate-icon-name.js +11 -0
  354. package/dist/icons/validate-icon-name.js.map +1 -0
  355. package/dist/icons-virtual-id.d.ts +24 -0
  356. package/dist/icons-virtual.cjs +2 -0
  357. package/dist/icons-virtual.cjs.map +1 -0
  358. package/dist/icons-virtual.d.ts +30 -22
  359. package/dist/icons-virtual.js +6 -0
  360. package/dist/icons-virtual.js.map +1 -0
  361. package/dist/icons.cjs +1 -1
  362. package/dist/icons.d.ts +282 -33
  363. package/dist/icons.js +11 -7
  364. package/dist/offline/create-offline-sync.cjs +1 -1
  365. package/dist/offline/create-offline-sync.cjs.map +1 -1
  366. package/dist/offline/create-offline-sync.js +17 -9
  367. package/dist/offline/create-offline-sync.js.map +1 -1
  368. package/dist/offline/use-offline-sync.cjs +1 -1
  369. package/dist/offline/use-offline-sync.cjs.map +1 -1
  370. package/dist/offline/use-offline-sync.js +7 -8
  371. package/dist/offline/use-offline-sync.js.map +1 -1
  372. package/dist/push/use-push-subscription.cjs +1 -1
  373. package/dist/push/use-push-subscription.cjs.map +1 -1
  374. package/dist/push/use-push-subscription.js +13 -12
  375. package/dist/push/use-push-subscription.js.map +1 -1
  376. package/dist/query/retry-policy.cjs +1 -1
  377. package/dist/query/retry-policy.cjs.map +1 -1
  378. package/dist/query/retry-policy.js +3 -3
  379. package/dist/query/retry-policy.js.map +1 -1
  380. package/dist/sse/use-event-stream.cjs +1 -1
  381. package/dist/sse/use-event-stream.cjs.map +1 -1
  382. package/dist/sse/use-event-stream.js +19 -20
  383. package/dist/sse/use-event-stream.js.map +1 -1
  384. package/dist/styles.css +1 -1
  385. package/dist/tabular/use-tabular-predictor.cjs +1 -1
  386. package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
  387. package/dist/tabular/use-tabular-predictor.js +20 -17
  388. package/dist/tabular/use-tabular-predictor.js.map +1 -1
  389. package/dist/tempest-react-sdk.cjs +1 -1
  390. package/dist/tempest-react-sdk.d.ts +458 -213
  391. package/dist/tempest-react-sdk.js +328 -329
  392. package/dist/theme/ThemeProvider.cjs +1 -1
  393. package/dist/theme/ThemeProvider.cjs.map +1 -1
  394. package/dist/theme/ThemeProvider.js +12 -17
  395. package/dist/theme/ThemeProvider.js.map +1 -1
  396. package/dist/utils/base64.cjs +2 -0
  397. package/dist/utils/base64.cjs.map +1 -0
  398. package/dist/utils/base64.js +16 -0
  399. package/dist/utils/base64.js.map +1 -0
  400. package/dist/utils/compare-values.cjs +1 -1
  401. package/dist/utils/compare-values.cjs.map +1 -1
  402. package/dist/utils/compare-values.js +4 -3
  403. package/dist/utils/compare-values.js.map +1 -1
  404. package/dist/utils/compressed-storage.cjs +1 -1
  405. package/dist/utils/compressed-storage.cjs.map +1 -1
  406. package/dist/utils/compressed-storage.js +16 -46
  407. package/dist/utils/compressed-storage.js.map +1 -1
  408. package/dist/utils/dev-mode.cjs.map +1 -1
  409. package/dist/utils/dev-mode.js.map +1 -1
  410. package/dist/utils/storage.cjs +1 -1
  411. package/dist/utils/storage.cjs.map +1 -1
  412. package/dist/utils/storage.js +37 -17
  413. package/dist/utils/storage.js.map +1 -1
  414. package/dist/vision/use-camera-stream.cjs +1 -1
  415. package/dist/vision/use-camera-stream.cjs.map +1 -1
  416. package/dist/vision/use-camera-stream.js +34 -33
  417. package/dist/vision/use-camera-stream.js.map +1 -1
  418. package/dist/vite/base-url.cjs +2 -0
  419. package/dist/vite/base-url.cjs.map +1 -0
  420. package/dist/vite/base-url.js +8 -0
  421. package/dist/vite/base-url.js.map +1 -0
  422. package/dist/vite/tempest-icons.cjs +4 -4
  423. package/dist/vite/tempest-icons.cjs.map +1 -1
  424. package/dist/vite/tempest-icons.js +26 -26
  425. package/dist/vite/tempest-icons.js.map +1 -1
  426. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  427. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  428. package/dist/vite/tempest-pwa-dev-sw.js +54 -29
  429. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  430. package/dist/vite/tempest-pwa-manifest.cjs +2 -2
  431. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  432. package/dist/vite/tempest-pwa-manifest.js +15 -14
  433. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  434. package/dist/vite.d.ts +3 -3
  435. package/dist/ws/use-web-socket.cjs +1 -1
  436. package/dist/ws/use-web-socket.cjs.map +1 -1
  437. package/dist/ws/use-web-socket.js +40 -42
  438. package/dist/ws/use-web-socket.js.map +1 -1
  439. package/package.json +5 -2
  440. package/dist/icons/generated/shard-a.cjs +0 -2
  441. package/dist/icons/generated/shard-a.cjs.map +0 -1
  442. package/dist/icons/generated/shard-a.js +0 -114
  443. package/dist/icons/generated/shard-a.js.map +0 -1
  444. package/dist/icons/generated/shard-b.cjs +0 -2
  445. package/dist/icons/generated/shard-b.cjs.map +0 -1
  446. package/dist/icons/generated/shard-b.js +0 -158
  447. package/dist/icons/generated/shard-b.js.map +0 -1
  448. package/dist/icons/generated/shard-c.cjs +0 -2
  449. package/dist/icons/generated/shard-c.cjs.map +0 -1
  450. package/dist/icons/generated/shard-c.js +0 -269
  451. package/dist/icons/generated/shard-c.js.map +0 -1
  452. package/dist/icons/generated/shard-d.cjs +0 -2
  453. package/dist/icons/generated/shard-d.cjs.map +0 -1
  454. package/dist/icons/generated/shard-d.js +0 -62
  455. package/dist/icons/generated/shard-d.js.map +0 -1
  456. package/dist/icons/generated/shard-e.cjs +0 -2
  457. package/dist/icons/generated/shard-e.cjs.map +0 -1
  458. package/dist/icons/generated/shard-e.js +0 -33
  459. package/dist/icons/generated/shard-e.js.map +0 -1
  460. package/dist/icons/generated/shard-f.cjs +0 -2
  461. package/dist/icons/generated/shard-f.cjs.map +0 -1
  462. package/dist/icons/generated/shard-f.js +0 -145
  463. package/dist/icons/generated/shard-f.js.map +0 -1
  464. package/dist/icons/generated/shard-g.cjs +0 -2
  465. package/dist/icons/generated/shard-g.cjs.map +0 -1
  466. package/dist/icons/generated/shard-g.js +0 -61
  467. package/dist/icons/generated/shard-g.js.map +0 -1
  468. package/dist/icons/generated/shard-h.cjs +0 -2
  469. package/dist/icons/generated/shard-h.cjs.map +0 -1
  470. package/dist/icons/generated/shard-h.js +0 -62
  471. package/dist/icons/generated/shard-h.js.map +0 -1
  472. package/dist/icons/generated/shard-i.cjs +0 -2
  473. package/dist/icons/generated/shard-i.cjs.map +0 -1
  474. package/dist/icons/generated/shard-i.js +0 -30
  475. package/dist/icons/generated/shard-i.js.map +0 -1
  476. package/dist/icons/generated/shard-j.cjs +0 -2
  477. package/dist/icons/generated/shard-j.cjs.map +0 -1
  478. package/dist/icons/generated/shard-j.js +0 -10
  479. package/dist/icons/generated/shard-j.js.map +0 -1
  480. package/dist/icons/generated/shard-k.cjs +0 -2
  481. package/dist/icons/generated/shard-k.cjs.map +0 -1
  482. package/dist/icons/generated/shard-k.js +0 -16
  483. package/dist/icons/generated/shard-k.js.map +0 -1
  484. package/dist/icons/generated/shard-l.cjs +0 -2
  485. package/dist/icons/generated/shard-l.cjs.map +0 -1
  486. package/dist/icons/generated/shard-l.js +0 -92
  487. package/dist/icons/generated/shard-l.js.map +0 -1
  488. package/dist/icons/generated/shard-m.cjs +0 -2
  489. package/dist/icons/generated/shard-m.cjs.map +0 -1
  490. package/dist/icons/generated/shard-m.js +0 -143
  491. package/dist/icons/generated/shard-m.js.map +0 -1
  492. package/dist/icons/generated/shard-n.cjs +0 -2
  493. package/dist/icons/generated/shard-n.cjs.map +0 -1
  494. package/dist/icons/generated/shard-n.js +0 -24
  495. package/dist/icons/generated/shard-n.js.map +0 -1
  496. package/dist/icons/generated/shard-o.cjs +0 -2
  497. package/dist/icons/generated/shard-o.cjs.map +0 -1
  498. package/dist/icons/generated/shard-o.js +0 -17
  499. package/dist/icons/generated/shard-o.js.map +0 -1
  500. package/dist/icons/generated/shard-p.cjs +0 -2
  501. package/dist/icons/generated/shard-p.cjs.map +0 -1
  502. package/dist/icons/generated/shard-p.js +0 -114
  503. package/dist/icons/generated/shard-p.js.map +0 -1
  504. package/dist/icons/generated/shard-q.cjs +0 -2
  505. package/dist/icons/generated/shard-q.cjs.map +0 -1
  506. package/dist/icons/generated/shard-q.js +0 -10
  507. package/dist/icons/generated/shard-q.js.map +0 -1
  508. package/dist/icons/generated/shard-r.cjs +0 -2
  509. package/dist/icons/generated/shard-r.cjs.map +0 -1
  510. package/dist/icons/generated/shard-r.js +0 -79
  511. package/dist/icons/generated/shard-r.js.map +0 -1
  512. package/dist/icons/generated/shard-s.cjs +0 -2
  513. package/dist/icons/generated/shard-s.cjs.map +0 -1
  514. package/dist/icons/generated/shard-s.js +0 -256
  515. package/dist/icons/generated/shard-s.js.map +0 -1
  516. package/dist/icons/generated/shard-t.cjs +0 -2
  517. package/dist/icons/generated/shard-t.cjs.map +0 -1
  518. package/dist/icons/generated/shard-t.js +0 -109
  519. package/dist/icons/generated/shard-t.js.map +0 -1
  520. package/dist/icons/generated/shard-u.cjs +0 -2
  521. package/dist/icons/generated/shard-u.cjs.map +0 -1
  522. package/dist/icons/generated/shard-u.js +0 -50
  523. package/dist/icons/generated/shard-u.js.map +0 -1
  524. package/dist/icons/generated/shard-v.cjs +0 -2
  525. package/dist/icons/generated/shard-v.cjs.map +0 -1
  526. package/dist/icons/generated/shard-v.js +0 -30
  527. package/dist/icons/generated/shard-v.js.map +0 -1
  528. package/dist/icons/generated/shard-w.cjs +0 -2
  529. package/dist/icons/generated/shard-w.cjs.map +0 -1
  530. package/dist/icons/generated/shard-w.js +0 -48
  531. package/dist/icons/generated/shard-w.js.map +0 -1
  532. package/dist/icons/generated/shard-x.cjs +0 -2
  533. package/dist/icons/generated/shard-x.cjs.map +0 -1
  534. package/dist/icons/generated/shard-x.js +0 -10
  535. package/dist/icons/generated/shard-x.js.map +0 -1
  536. package/dist/icons/generated/shard-z.cjs +0 -2
  537. package/dist/icons/generated/shard-z.cjs.map +0 -1
  538. package/dist/icons/generated/shard-z.js +0 -25
  539. package/dist/icons/generated/shard-z.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"filter-apply.cjs","names":[],"sources":["../../../src/components/FilterBar/filter-apply.ts"],"sourcesContent":["// The other half of the filter model: evaluating a `Filter[]` instead of only\n// describing it. `FilterBar` produces filters, `applyFilters` runs them over an\n// in-memory list, and `filtersToQueryParams` (filter-query.ts) hands the same set\n// to a paginated backend. Without these two the model stops one step short of\n// useful and every app writes the eleven operator branches again.\n\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete, isValueless } from \"./filter-model\";\n\n/** Matches a plain `yyyy-mm-dd`, the shape `<input type=\"date\">` produces. */\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Read a property off a row without asserting the row's shape.\n *\n * `Filter.field` is a string chosen by the app's field list, so it cannot be a\n * `keyof T`; the lookup goes through an index signature and a missing property\n * yields `undefined`, which the operators treat as an empty value.\n *\n * @param item - The row being tested.\n * @param field - Property name from the filter.\n * @returns The property value, or `undefined` when the row has no such key.\n */\nfunction readField(item: unknown, field: string): unknown {\n if (item == null || typeof item !== \"object\") return undefined;\n return (item as Record<string, unknown>)[field];\n}\n\n/**\n * Local `yyyy-mm-dd` for a value that represents a date, or `null`.\n *\n * Built from the local calendar parts rather than `toISOString`, which converts\n * to UTC first and therefore reports the next day for anything after 21:00 in\n * UTC-3. A `yyyy-mm-dd` string is returned untouched, so a filter value and a\n * row value that already agree never round-trip through `Date` at all.\n *\n * @param value - A `Date`, a date-ish string, or anything else.\n * @returns The day key, or `null` when the value does not denote a date.\n */\nfunction dayKey(value: unknown): string | null {\n if (typeof value === \"string\" && DATE_ONLY.test(value)) return value;\n\n const date =\n value instanceof Date\n ? value\n : typeof value === \"string\" && value.trim() !== \"\"\n ? new Date(value)\n : null;\n if (date === null || Number.isNaN(date.getTime())) return null;\n\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/**\n * Compare a row value against a filter value, in the row value's own type.\n *\n * The filter always carries strings — it comes from form inputs and from the\n * URL — while the row carries whatever the API returned. Comparing them as text\n * gets the two common cases wrong: `10` would sort before `9`, and a date would\n * be compared by however it happens to be formatted. So the row value picks the\n * comparison, and the filter value is coerced into it.\n *\n * Dates compare by **day**, not by instant: a row stamped `2026-03-05T13:00:00Z`\n * is `>= 2026-03-05`, which is what the person picking a date in the UI means.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Negative, zero or positive, or `null` when the two cannot be compared.\n */\nfunction compare(left: unknown, right: string): number | null {\n if (left == null) return null;\n\n if (typeof left === \"number\") {\n const parsed = Number(right);\n return Number.isNaN(parsed) ? null : left - parsed;\n }\n\n if (typeof left === \"boolean\") {\n const parsed = right === \"true\" ? true : right === \"false\" ? false : null;\n return parsed === null ? null : Number(left) - Number(parsed);\n }\n\n if (DATE_ONLY.test(right)) {\n const key = dayKey(left);\n return key === null ? null : key.localeCompare(right);\n }\n\n return String(left).localeCompare(right, undefined, { numeric: true });\n}\n\n/**\n * Equality in the row value's type, falling back to exact text.\n *\n * Case-sensitive on purpose: `eq` maps to a plain `WHERE column = value` on the\n * server side, so a case-insensitive client would quietly disagree with the\n * backend for the same filter. `contains` is the case-insensitive operator, and\n * it is also the default one for text fields, so the friendly behaviour is what\n * people get without asking.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Whether the two are equal.\n */\nfunction equals(left: unknown, right: string): boolean {\n const result = compare(left, right);\n if (result !== null) return result === 0;\n return String(left ?? \"\") === right;\n}\n\n/**\n * Is this row value empty?\n *\n * Empty means absent, blank text or an empty list — never falsy. `0` and `false`\n * are values somebody chose, and a filter that hid them would be reporting the\n * wrong count on every dashboard that tracks zeroes.\n *\n * @param value - The row value.\n * @returns Whether the value counts as empty.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * The filter's value as a list, whatever arity the operator uses.\n *\n * @param filter - The filter to read.\n * @returns Its values, trimmed of nothing — a leading space may be meaningful text.\n */\nfunction valuesOf(filter: Filter): string[] {\n if (filter.value === undefined) return [];\n return Array.isArray(filter.value) ? [...filter.value] : [String(filter.value)];\n}\n\n/**\n * Evaluate one operator against one row value.\n *\n * @param operator - The comparison to make.\n * @param left - The row value.\n * @param values - The filter values (one, two for `between`, many for `in`).\n * @returns Whether the row value satisfies the operator.\n */\nfunction matchesOperator(operator: FilterOperator, left: unknown, values: string[]): boolean {\n switch (operator) {\n case \"empty\":\n return isEmptyValue(left);\n case \"notEmpty\":\n return !isEmptyValue(left);\n case \"eq\":\n return equals(left, values[0] ?? \"\");\n case \"ne\":\n return !equals(left, values[0] ?? \"\");\n case \"contains\":\n return String(left ?? \"\")\n .toLowerCase()\n .includes((values[0] ?? \"\").toLowerCase());\n case \"in\":\n return values.some((value) => equals(left, value));\n case \"between\": {\n const [first = \"\", second = \"\"] = values;\n const low = compare(left, first);\n const high = compare(left, second);\n if (low === null || high === null) return false;\n return low >= 0 && high <= 0;\n }\n default: {\n const result = compare(left, values[0] ?? \"\");\n if (result === null) return false;\n if (operator === \"gt\") return result > 0;\n if (operator === \"gte\") return result >= 0;\n if (operator === \"lt\") return result < 0;\n return result <= 0;\n }\n }\n}\n\n/**\n * Order the two values of a `between` so the range is never empty by accident.\n *\n * A person who picks the later date first means the range between the two dates,\n * not \"no rows\". The server-side `BETWEEN` needs `(lo, hi)` in order too, which\n * is why the normalisation lives here and is shared by `filtersToQueryParams`.\n *\n * @param values - The raw pair.\n * @returns The pair, ascending.\n */\nexport function orderRange(values: readonly string[]): string[] {\n const [first = \"\", second = \"\"] = values;\n if (DATE_ONLY.test(first) && DATE_ONLY.test(second)) {\n return first <= second ? [first, second] : [second, first];\n }\n const low = Number(first);\n const high = Number(second);\n if (!Number.isNaN(low) && !Number.isNaN(high)) {\n return low <= high ? [first, second] : [second, first];\n }\n return first.localeCompare(second, undefined, { numeric: true }) <= 0\n ? [first, second]\n : [second, first];\n}\n\n/**\n * Run a filter set over an in-memory list.\n *\n * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies\n * them. Filters combine with `AND`, matching the flat model the bar builds, and\n * an incomplete filter is skipped rather than treated as a match of nothing — a\n * half-filled form should not empty the table underneath it.\n *\n * Comparison follows the row's type, not the filter's: numbers compare\n * numerically, dates compare by day, and everything else compares as text with\n * `numeric: true` so `\"item 2\"` lands before `\"item 10\"`.\n *\n * A few behaviours differ from the SQL the server-side twin produces, and the\n * difference is deliberate rather than accidental:\n *\n * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`\n * for a `NULL` column and the row drops out. Here \"is not paid\" shows the rows\n * with no status at all, which is what the chip claims.\n * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server\n * only matches `NULL`. A column that stores `\"\"` instead of `NULL` is where\n * the two disagree.\n *\n * @example\n * const visible = applyFilters(orders, filters);\n *\n * @param items - The full list.\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns A new array with the rows that satisfy every complete filter.\n */\nexport function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[] {\n const active = filters.filter(isComplete);\n if (active.length === 0) return [...items];\n\n return items.filter((item) =>\n active.every((filter) => {\n const left = readField(item, filter.field);\n if (isValueless(filter.operator)) {\n return matchesOperator(filter.operator, left, []);\n }\n const values =\n filter.operator === \"between\" ? orderRange(valuesOf(filter)) : valuesOf(filter);\n return matchesOperator(filter.operator, left, values);\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"filter-apply.cjs","names":[],"sources":["../../../src/components/FilterBar/filter-apply.ts"],"sourcesContent":["// The other half of the filter model: evaluating a `Filter[]` instead of only\n// describing it. `FilterBar` produces filters, `applyFilters` runs them over an\n// in-memory list, and `filtersToQueryParams` (filter-query.ts) hands the same set\n// to a paginated backend. Without these two the model stops one step short of\n// useful and every app writes the eleven operator branches again.\n\nimport { formatDateForInput } from \"@/utils/format\";\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\n\n/** Matches a plain `yyyy-mm-dd`, the shape `<input type=\"date\">` produces. */\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * One collator for every text comparison in this module.\n *\n * `localeCompare(value, undefined, { numeric: true })` builds a collator on each\n * call — passing an options bag opts out of the engine's cached-default fast\n * path — and this comparison runs once per row per filter. Measured at 200k\n * comparisons: 453 ms through `localeCompare` against 25 ms through a hoisted\n * collator, i.e. ~23 ms of main thread per keystroke on a 10k-row list.\n */\nconst TEXT_COLLATOR = new Intl.Collator(undefined, { numeric: true });\n\n/**\n * Read a property off a row without asserting the row's shape.\n *\n * `Filter.field` is a string chosen by the app's field list, so it cannot be a\n * `keyof T`; the lookup goes through an index signature and a missing property\n * yields `undefined`, which the operators treat as an empty value.\n *\n * @param item - The row being tested.\n * @param field - Property name from the filter.\n * @returns The property value, or `undefined` when the row has no such key.\n */\nfunction readField(item: unknown, field: string): unknown {\n if (item == null || typeof item !== \"object\") return undefined;\n return (item as Record<string, unknown>)[field];\n}\n\n/**\n * Local `yyyy-mm-dd` for a value that represents a date, or `null`.\n *\n * Delegates the formatting to {@link formatDateForInput}, which owns the rule\n * that the key is built from the local calendar parts rather than `toISOString`.\n * This adds only the narrowing from `unknown` and maps \"not a date\" to `null`\n * instead of to the empty string a date input wants.\n *\n * @param value - A `Date`, a date-ish string, or anything else.\n * @returns The day key, or `null` when the value does not denote a date.\n */\nfunction dayKey(value: unknown): string | null {\n if (value instanceof Date) return formatDateForInput(value) || null;\n if (typeof value !== \"string\" || value.trim() === \"\") return null;\n return formatDateForInput(value) || null;\n}\n\n/**\n * Compare a row value against a filter value, in the row value's own type.\n *\n * The filter always carries strings — it comes from form inputs and from the\n * URL — while the row carries whatever the API returned. Comparing them as text\n * gets the two common cases wrong: `10` would sort before `9`, and a date would\n * be compared by however it happens to be formatted. So the row value picks the\n * comparison, and the filter value is coerced into it.\n *\n * Dates compare by **day**, not by instant: a row stamped `2026-03-05T13:00:00Z`\n * is `>= 2026-03-05`, which is what the person picking a date in the UI means.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Negative, zero or positive, or `null` when the two cannot be compared.\n */\nfunction compare(left: unknown, right: string): number | null {\n if (left == null) return null;\n\n if (typeof left === \"number\") {\n const parsed = Number(right);\n return Number.isNaN(parsed) ? null : left - parsed;\n }\n\n if (typeof left === \"boolean\") {\n const parsed = right === \"true\" ? true : right === \"false\" ? false : null;\n return parsed === null ? null : Number(left) - Number(parsed);\n }\n\n if (DATE_ONLY.test(right)) {\n const key = dayKey(left);\n return key === null ? null : key.localeCompare(right);\n }\n\n return TEXT_COLLATOR.compare(String(left), right);\n}\n\n/**\n * Equality in the row value's type, falling back to exact text.\n *\n * Case-sensitive on purpose: `eq` maps to a plain `WHERE column = value` on the\n * server side, so a case-insensitive client would quietly disagree with the\n * backend for the same filter. `contains` is the case-insensitive operator, and\n * it is also the default one for text fields, so the friendly behaviour is what\n * people get without asking.\n *\n * Identical strings take a fast path out, since the row value is text in the\n * common case and the collator is the expensive part.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Whether the two are equal.\n */\nfunction equals(left: unknown, right: string): boolean {\n if (left === right) return true;\n const result = compare(left, right);\n if (result !== null) return result === 0;\n return String(left ?? \"\") === right;\n}\n\n/**\n * Is this row value empty?\n *\n * Empty means absent, blank text or an empty list — never falsy. `0` and `false`\n * are values somebody chose, and a filter that hid them would be reporting the\n * wrong count on every dashboard that tracks zeroes.\n *\n * @param value - The row value.\n * @returns Whether the value counts as empty.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * The filter's value as a list, whatever arity the operator uses.\n *\n * @param filter - The filter to read.\n * @returns Its values, trimmed of nothing — a leading space may be meaningful text.\n */\nfunction valuesOf(filter: Filter): string[] {\n if (filter.value === undefined) return [];\n return Array.isArray(filter.value) ? [...filter.value] : [String(filter.value)];\n}\n\n/**\n * Evaluate one operator against one row value.\n *\n * @param operator - The comparison to make.\n * @param left - The row value.\n * @param values - The filter values (one, two for `between`, many for `in`).\n * @returns Whether the row value satisfies the operator.\n */\nfunction matchesOperator(operator: FilterOperator, left: unknown, values: string[]): boolean {\n switch (operator) {\n case \"empty\":\n return isEmptyValue(left);\n case \"notEmpty\":\n return !isEmptyValue(left);\n case \"eq\":\n return equals(left, values[0] ?? \"\");\n case \"ne\":\n return !equals(left, values[0] ?? \"\");\n case \"contains\":\n return String(left ?? \"\")\n .toLowerCase()\n .includes((values[0] ?? \"\").toLowerCase());\n case \"in\":\n return values.some((value) => equals(left, value));\n case \"between\": {\n const [first = \"\", second = \"\"] = values;\n const low = compare(left, first);\n const high = compare(left, second);\n if (low === null || high === null) return false;\n return low >= 0 && high <= 0;\n }\n default: {\n const result = compare(left, values[0] ?? \"\");\n if (result === null) return false;\n if (operator === \"gt\") return result > 0;\n if (operator === \"gte\") return result >= 0;\n if (operator === \"lt\") return result < 0;\n return result <= 0;\n }\n }\n}\n\n/**\n * Order the two values of a `between` so the range is never empty by accident.\n *\n * A person who picks the later date first means the range between the two dates,\n * not \"no rows\". The server-side `BETWEEN` needs `(lo, hi)` in order too, which\n * is why the normalisation lives here and is shared by `filtersToQueryParams`.\n *\n * @param values - The raw pair.\n * @returns The pair, ascending.\n */\nexport function orderRange(values: readonly string[]): string[] {\n const [first = \"\", second = \"\"] = values;\n if (DATE_ONLY.test(first) && DATE_ONLY.test(second)) {\n return first <= second ? [first, second] : [second, first];\n }\n const low = Number(first);\n const high = Number(second);\n if (!Number.isNaN(low) && !Number.isNaN(high)) {\n return low <= high ? [first, second] : [second, first];\n }\n return TEXT_COLLATOR.compare(first, second) <= 0 ? [first, second] : [second, first];\n}\n\n/**\n * Run a filter set over an in-memory list.\n *\n * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies\n * them. Filters combine with `AND`, matching the flat model the bar builds, and\n * an incomplete filter is skipped rather than treated as a match of nothing — a\n * half-filled form should not empty the table underneath it.\n *\n * Comparison follows the row's type, not the filter's: numbers compare\n * numerically, dates compare by day, and everything else compares as text with\n * `numeric: true` so `\"item 2\"` lands before `\"item 10\"`.\n *\n * A few behaviours differ from the SQL the server-side twin produces, and the\n * difference is deliberate rather than accidental:\n *\n * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`\n * for a `NULL` column and the row drops out. Here \"is not paid\" shows the rows\n * with no status at all, which is what the chip claims.\n * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server\n * only matches `NULL`. A column that stores `\"\"` instead of `NULL` is where\n * the two disagree.\n *\n * @example\n * const visible = applyFilters(orders, filters);\n *\n * Each filter's values are normalised once, before the scan, rather than inside\n * the per-row predicate: the arity, the `between` ordering and the value array\n * depend only on the filter, so deriving them per row multiplied that work by\n * the row count. `empty`/`notEmpty` ignore their values entirely, which is why\n * the prepared shape does not need to distinguish them.\n *\n * @param items - The full list.\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns A new array with the rows that satisfy every complete filter.\n */\nexport function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[] {\n const active = filters.filter(isComplete).map((filter) => ({\n field: filter.field,\n operator: filter.operator,\n values: filter.operator === \"between\" ? orderRange(valuesOf(filter)) : valuesOf(filter),\n }));\n if (active.length === 0) return [...items];\n\n return items.filter((item) =>\n active.every(({ field, operator, values }) =>\n matchesOperator(operator, readField(item, field), values),\n ),\n );\n}\n"],"mappings":"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"}
@@ -1,17 +1,14 @@
1
- import { isComplete as e, isValueless as t } from "./filter-model.js";
1
+ import { isComplete as e } from "./filter-model.js";
2
+ import { formatDateForInput as t } from "../../utils/format.js";
2
3
  //#region src/components/FilterBar/filter-apply.ts
3
- var n = /^\d{4}-\d{2}-\d{2}$/;
4
- function r(e, t) {
4
+ var n = /^\d{4}-\d{2}-\d{2}$/, r = new Intl.Collator(void 0, { numeric: !0 });
5
+ function i(e, t) {
5
6
  if (!(typeof e != "object" || !e)) return e[t];
6
7
  }
7
- function i(e) {
8
- if (typeof e == "string" && n.test(e)) return e;
9
- let t = e instanceof Date ? e : typeof e == "string" && e.trim() !== "" ? new Date(e) : null;
10
- if (t === null || Number.isNaN(t.getTime())) return null;
11
- let r = `${t.getMonth() + 1}`.padStart(2, "0"), i = `${t.getDate()}`.padStart(2, "0");
12
- return `${t.getFullYear()}-${r}-${i}`;
8
+ function a(e) {
9
+ return e instanceof Date ? t(e) || null : typeof e != "string" || e.trim() === "" ? null : t(e) || null;
13
10
  }
14
- function a(e, t) {
11
+ function o(e, t) {
15
12
  if (e == null) return null;
16
13
  if (typeof e == "number") {
17
14
  let n = Number(t);
@@ -22,55 +19,55 @@ function a(e, t) {
22
19
  return n === null ? null : Number(e) - Number(n);
23
20
  }
24
21
  if (n.test(t)) {
25
- let n = i(e);
22
+ let n = a(e);
26
23
  return n === null ? null : n.localeCompare(t);
27
24
  }
28
- return String(e).localeCompare(t, void 0, { numeric: !0 });
25
+ return r.compare(String(e), t);
29
26
  }
30
- function o(e, t) {
31
- let n = a(e, t);
27
+ function s(e, t) {
28
+ if (e === t) return !0;
29
+ let n = o(e, t);
32
30
  return n === null ? String(e ?? "") === t : n === 0;
33
31
  }
34
- function s(e) {
32
+ function c(e) {
35
33
  return e == null ? !0 : typeof e == "string" ? e.trim() === "" : Array.isArray(e) ? e.length === 0 : !1;
36
34
  }
37
- function c(e) {
35
+ function l(e) {
38
36
  return e.value === void 0 ? [] : Array.isArray(e.value) ? [...e.value] : [String(e.value)];
39
37
  }
40
- function l(e, t, n) {
38
+ function u(e, t, n) {
41
39
  switch (e) {
42
- case "empty": return s(t);
43
- case "notEmpty": return !s(t);
44
- case "eq": return o(t, n[0] ?? "");
45
- case "ne": return !o(t, n[0] ?? "");
40
+ case "empty": return c(t);
41
+ case "notEmpty": return !c(t);
42
+ case "eq": return s(t, n[0] ?? "");
43
+ case "ne": return !s(t, n[0] ?? "");
46
44
  case "contains": return String(t ?? "").toLowerCase().includes((n[0] ?? "").toLowerCase());
47
- case "in": return n.some((e) => o(t, e));
45
+ case "in": return n.some((e) => s(t, e));
48
46
  case "between": {
49
- let [e = "", r = ""] = n, i = a(t, e), o = a(t, r);
50
- return i === null || o === null ? !1 : i >= 0 && o <= 0;
47
+ let [e = "", r = ""] = n, i = o(t, e), a = o(t, r);
48
+ return i === null || a === null ? !1 : i >= 0 && a <= 0;
51
49
  }
52
50
  default: {
53
- let r = a(t, n[0] ?? "");
51
+ let r = o(t, n[0] ?? "");
54
52
  return r === null ? !1 : e === "gt" ? r > 0 : e === "gte" ? r >= 0 : e === "lt" ? r < 0 : r <= 0;
55
53
  }
56
54
  }
57
55
  }
58
- function u(e) {
59
- let [t = "", r = ""] = e;
60
- if (n.test(t) && n.test(r)) return t <= r ? [t, r] : [r, t];
61
- let i = Number(t), a = Number(r);
62
- return !Number.isNaN(i) && !Number.isNaN(a) ? i <= a ? [t, r] : [r, t] : t.localeCompare(r, void 0, { numeric: !0 }) <= 0 ? [t, r] : [r, t];
56
+ function d(e) {
57
+ let [t = "", i = ""] = e;
58
+ if (n.test(t) && n.test(i)) return t <= i ? [t, i] : [i, t];
59
+ let a = Number(t), o = Number(i);
60
+ return !Number.isNaN(a) && !Number.isNaN(o) ? a <= o ? [t, i] : [i, t] : r.compare(t, i) <= 0 ? [t, i] : [i, t];
63
61
  }
64
- function d(n, i) {
65
- let a = i.filter(e);
66
- return a.length === 0 ? [...n] : n.filter((e) => a.every((n) => {
67
- let i = r(e, n.field);
68
- if (t(n.operator)) return l(n.operator, i, []);
69
- let a = n.operator === "between" ? u(c(n)) : c(n);
70
- return l(n.operator, i, a);
62
+ function f(t, n) {
63
+ let r = n.filter(e).map((e) => ({
64
+ field: e.field,
65
+ operator: e.operator,
66
+ values: e.operator === "between" ? d(l(e)) : l(e)
71
67
  }));
68
+ return r.length === 0 ? [...t] : t.filter((e) => r.every(({ field: t, operator: n, values: r }) => u(n, i(e, t), r)));
72
69
  }
73
70
  //#endregion
74
- export { d as applyFilters, u as orderRange };
71
+ export { f as applyFilters, d as orderRange };
75
72
 
76
73
  //# sourceMappingURL=filter-apply.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filter-apply.js","names":[],"sources":["../../../src/components/FilterBar/filter-apply.ts"],"sourcesContent":["// The other half of the filter model: evaluating a `Filter[]` instead of only\n// describing it. `FilterBar` produces filters, `applyFilters` runs them over an\n// in-memory list, and `filtersToQueryParams` (filter-query.ts) hands the same set\n// to a paginated backend. Without these two the model stops one step short of\n// useful and every app writes the eleven operator branches again.\n\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete, isValueless } from \"./filter-model\";\n\n/** Matches a plain `yyyy-mm-dd`, the shape `<input type=\"date\">` produces. */\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Read a property off a row without asserting the row's shape.\n *\n * `Filter.field` is a string chosen by the app's field list, so it cannot be a\n * `keyof T`; the lookup goes through an index signature and a missing property\n * yields `undefined`, which the operators treat as an empty value.\n *\n * @param item - The row being tested.\n * @param field - Property name from the filter.\n * @returns The property value, or `undefined` when the row has no such key.\n */\nfunction readField(item: unknown, field: string): unknown {\n if (item == null || typeof item !== \"object\") return undefined;\n return (item as Record<string, unknown>)[field];\n}\n\n/**\n * Local `yyyy-mm-dd` for a value that represents a date, or `null`.\n *\n * Built from the local calendar parts rather than `toISOString`, which converts\n * to UTC first and therefore reports the next day for anything after 21:00 in\n * UTC-3. A `yyyy-mm-dd` string is returned untouched, so a filter value and a\n * row value that already agree never round-trip through `Date` at all.\n *\n * @param value - A `Date`, a date-ish string, or anything else.\n * @returns The day key, or `null` when the value does not denote a date.\n */\nfunction dayKey(value: unknown): string | null {\n if (typeof value === \"string\" && DATE_ONLY.test(value)) return value;\n\n const date =\n value instanceof Date\n ? value\n : typeof value === \"string\" && value.trim() !== \"\"\n ? new Date(value)\n : null;\n if (date === null || Number.isNaN(date.getTime())) return null;\n\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/**\n * Compare a row value against a filter value, in the row value's own type.\n *\n * The filter always carries strings — it comes from form inputs and from the\n * URL — while the row carries whatever the API returned. Comparing them as text\n * gets the two common cases wrong: `10` would sort before `9`, and a date would\n * be compared by however it happens to be formatted. So the row value picks the\n * comparison, and the filter value is coerced into it.\n *\n * Dates compare by **day**, not by instant: a row stamped `2026-03-05T13:00:00Z`\n * is `>= 2026-03-05`, which is what the person picking a date in the UI means.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Negative, zero or positive, or `null` when the two cannot be compared.\n */\nfunction compare(left: unknown, right: string): number | null {\n if (left == null) return null;\n\n if (typeof left === \"number\") {\n const parsed = Number(right);\n return Number.isNaN(parsed) ? null : left - parsed;\n }\n\n if (typeof left === \"boolean\") {\n const parsed = right === \"true\" ? true : right === \"false\" ? false : null;\n return parsed === null ? null : Number(left) - Number(parsed);\n }\n\n if (DATE_ONLY.test(right)) {\n const key = dayKey(left);\n return key === null ? null : key.localeCompare(right);\n }\n\n return String(left).localeCompare(right, undefined, { numeric: true });\n}\n\n/**\n * Equality in the row value's type, falling back to exact text.\n *\n * Case-sensitive on purpose: `eq` maps to a plain `WHERE column = value` on the\n * server side, so a case-insensitive client would quietly disagree with the\n * backend for the same filter. `contains` is the case-insensitive operator, and\n * it is also the default one for text fields, so the friendly behaviour is what\n * people get without asking.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Whether the two are equal.\n */\nfunction equals(left: unknown, right: string): boolean {\n const result = compare(left, right);\n if (result !== null) return result === 0;\n return String(left ?? \"\") === right;\n}\n\n/**\n * Is this row value empty?\n *\n * Empty means absent, blank text or an empty list — never falsy. `0` and `false`\n * are values somebody chose, and a filter that hid them would be reporting the\n * wrong count on every dashboard that tracks zeroes.\n *\n * @param value - The row value.\n * @returns Whether the value counts as empty.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * The filter's value as a list, whatever arity the operator uses.\n *\n * @param filter - The filter to read.\n * @returns Its values, trimmed of nothing — a leading space may be meaningful text.\n */\nfunction valuesOf(filter: Filter): string[] {\n if (filter.value === undefined) return [];\n return Array.isArray(filter.value) ? [...filter.value] : [String(filter.value)];\n}\n\n/**\n * Evaluate one operator against one row value.\n *\n * @param operator - The comparison to make.\n * @param left - The row value.\n * @param values - The filter values (one, two for `between`, many for `in`).\n * @returns Whether the row value satisfies the operator.\n */\nfunction matchesOperator(operator: FilterOperator, left: unknown, values: string[]): boolean {\n switch (operator) {\n case \"empty\":\n return isEmptyValue(left);\n case \"notEmpty\":\n return !isEmptyValue(left);\n case \"eq\":\n return equals(left, values[0] ?? \"\");\n case \"ne\":\n return !equals(left, values[0] ?? \"\");\n case \"contains\":\n return String(left ?? \"\")\n .toLowerCase()\n .includes((values[0] ?? \"\").toLowerCase());\n case \"in\":\n return values.some((value) => equals(left, value));\n case \"between\": {\n const [first = \"\", second = \"\"] = values;\n const low = compare(left, first);\n const high = compare(left, second);\n if (low === null || high === null) return false;\n return low >= 0 && high <= 0;\n }\n default: {\n const result = compare(left, values[0] ?? \"\");\n if (result === null) return false;\n if (operator === \"gt\") return result > 0;\n if (operator === \"gte\") return result >= 0;\n if (operator === \"lt\") return result < 0;\n return result <= 0;\n }\n }\n}\n\n/**\n * Order the two values of a `between` so the range is never empty by accident.\n *\n * A person who picks the later date first means the range between the two dates,\n * not \"no rows\". The server-side `BETWEEN` needs `(lo, hi)` in order too, which\n * is why the normalisation lives here and is shared by `filtersToQueryParams`.\n *\n * @param values - The raw pair.\n * @returns The pair, ascending.\n */\nexport function orderRange(values: readonly string[]): string[] {\n const [first = \"\", second = \"\"] = values;\n if (DATE_ONLY.test(first) && DATE_ONLY.test(second)) {\n return first <= second ? [first, second] : [second, first];\n }\n const low = Number(first);\n const high = Number(second);\n if (!Number.isNaN(low) && !Number.isNaN(high)) {\n return low <= high ? [first, second] : [second, first];\n }\n return first.localeCompare(second, undefined, { numeric: true }) <= 0\n ? [first, second]\n : [second, first];\n}\n\n/**\n * Run a filter set over an in-memory list.\n *\n * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies\n * them. Filters combine with `AND`, matching the flat model the bar builds, and\n * an incomplete filter is skipped rather than treated as a match of nothing — a\n * half-filled form should not empty the table underneath it.\n *\n * Comparison follows the row's type, not the filter's: numbers compare\n * numerically, dates compare by day, and everything else compares as text with\n * `numeric: true` so `\"item 2\"` lands before `\"item 10\"`.\n *\n * A few behaviours differ from the SQL the server-side twin produces, and the\n * difference is deliberate rather than accidental:\n *\n * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`\n * for a `NULL` column and the row drops out. Here \"is not paid\" shows the rows\n * with no status at all, which is what the chip claims.\n * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server\n * only matches `NULL`. A column that stores `\"\"` instead of `NULL` is where\n * the two disagree.\n *\n * @example\n * const visible = applyFilters(orders, filters);\n *\n * @param items - The full list.\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns A new array with the rows that satisfy every complete filter.\n */\nexport function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[] {\n const active = filters.filter(isComplete);\n if (active.length === 0) return [...items];\n\n return items.filter((item) =>\n active.every((filter) => {\n const left = readField(item, filter.field);\n if (isValueless(filter.operator)) {\n return matchesOperator(filter.operator, left, []);\n }\n const values =\n filter.operator === \"between\" ? orderRange(valuesOf(filter)) : valuesOf(filter);\n return matchesOperator(filter.operator, left, values);\n }),\n );\n}\n"],"mappings":";;AAUA,IAAM,IAAY;AAalB,SAAS,EAAU,GAAe,GAAwB;CAClD,MAAgB,OAAO,KAAS,aAAhC,IACJ,OAAQ,EAAiC;AAC7C;AAaA,SAAS,EAAO,GAA+B;CAC3C,IAAI,OAAO,KAAU,YAAY,EAAU,KAAK,CAAK,GAAG,OAAO;CAE/D,IAAM,IACF,aAAiB,OACX,IACA,OAAO,KAAU,YAAY,EAAM,KAAK,MAAM,KAC5C,IAAI,KAAK,CAAK,IACd;CACZ,IAAI,MAAS,QAAQ,OAAO,MAAM,EAAK,QAAQ,CAAC,GAAG,OAAO;CAE1D,IAAM,IAAQ,GAAG,EAAK,SAAS,IAAI,IAAI,SAAS,GAAG,GAAG,GAChD,IAAM,GAAG,EAAK,QAAQ,IAAI,SAAS,GAAG,GAAG;CAC/C,OAAO,GAAG,EAAK,YAAY,EAAE,GAAG,EAAM,GAAG;AAC7C;AAkBA,SAAS,EAAQ,GAAe,GAA8B;CAC1D,IAAI,KAAQ,MAAM,OAAO;CAEzB,IAAI,OAAO,KAAS,UAAU;EAC1B,IAAM,IAAS,OAAO,CAAK;EAC3B,OAAO,OAAO,MAAM,CAAM,IAAI,OAAO,IAAO;CAChD;CAEA,IAAI,OAAO,KAAS,WAAW;EAC3B,IAAM,IAAS,MAAU,UAAgB,MAAU,WAAkB;EACrE,OAAO,MAAW,OAAO,OAAO,OAAO,CAAI,IAAI,OAAO,CAAM;CAChE;CAEA,IAAI,EAAU,KAAK,CAAK,GAAG;EACvB,IAAM,IAAM,EAAO,CAAI;EACvB,OAAO,MAAQ,OAAO,OAAO,EAAI,cAAc,CAAK;CACxD;CAEA,OAAO,OAAO,CAAI,CAAC,CAAC,cAAc,GAAO,KAAA,GAAW,EAAE,SAAS,GAAK,CAAC;AACzE;AAeA,SAAS,EAAO,GAAe,GAAwB;CACnD,IAAM,IAAS,EAAQ,GAAM,CAAK;CAElC,OADI,MAAW,OACR,OAAO,KAAQ,EAAE,MAAM,IADF,MAAW;AAE3C;AAYA,SAAS,EAAa,GAAyB;CAI3C,OAHI,KAAS,OAAa,KACtB,OAAO,KAAU,WAAiB,EAAM,KAAK,MAAM,KACnD,MAAM,QAAQ,CAAK,IAAU,EAAM,WAAW,IAC3C;AACX;AAQA,SAAS,EAAS,GAA0B;CAExC,OADI,EAAO,UAAU,KAAA,IAAkB,CAAC,IACjC,MAAM,QAAQ,EAAO,KAAK,IAAI,CAAC,GAAG,EAAO,KAAK,IAAI,CAAC,OAAO,EAAO,KAAK,CAAC;AAClF;AAUA,SAAS,EAAgB,GAA0B,GAAe,GAA2B;CACzF,QAAQ,GAAR;EACI,KAAK,SACD,OAAO,EAAa,CAAI;EAC5B,KAAK,YACD,OAAO,CAAC,EAAa,CAAI;EAC7B,KAAK,MACD,OAAO,EAAO,GAAM,EAAO,MAAM,EAAE;EACvC,KAAK,MACD,OAAO,CAAC,EAAO,GAAM,EAAO,MAAM,EAAE;EACxC,KAAK,YACD,OAAO,OAAO,KAAQ,EAAE,CAAC,CACpB,YAAY,CAAC,CACb,UAAU,EAAO,MAAM,GAAA,CAAI,YAAY,CAAC;EACjD,KAAK,MACD,OAAO,EAAO,MAAM,MAAU,EAAO,GAAM,CAAK,CAAC;EACrD,KAAK,WAAW;GACZ,IAAM,CAAC,IAAQ,IAAI,IAAS,MAAM,GAC5B,IAAM,EAAQ,GAAM,CAAK,GACzB,IAAO,EAAQ,GAAM,CAAM;GAEjC,OADI,MAAQ,QAAQ,MAAS,OAAa,KACnC,KAAO,KAAK,KAAQ;EAC/B;EACA,SAAS;GACL,IAAM,IAAS,EAAQ,GAAM,EAAO,MAAM,EAAE;GAK5C,OAJI,MAAW,OAAa,KACxB,MAAa,OAAa,IAAS,IACnC,MAAa,QAAc,KAAU,IACrC,MAAa,OAAa,IAAS,IAChC,KAAU;EACrB;CACJ;AACJ;AAYA,SAAgB,EAAW,GAAqC;CAC5D,IAAM,CAAC,IAAQ,IAAI,IAAS,MAAM;CAClC,IAAI,EAAU,KAAK,CAAK,KAAK,EAAU,KAAK,CAAM,GAC9C,OAAO,KAAS,IAAS,CAAC,GAAO,CAAM,IAAI,CAAC,GAAQ,CAAK;CAE7D,IAAM,IAAM,OAAO,CAAK,GAClB,IAAO,OAAO,CAAM;CAI1B,OAHI,CAAC,OAAO,MAAM,CAAG,KAAK,CAAC,OAAO,MAAM,CAAI,IACjC,KAAO,IAAO,CAAC,GAAO,CAAM,IAAI,CAAC,GAAQ,CAAK,IAElD,EAAM,cAAc,GAAQ,KAAA,GAAW,EAAE,SAAS,GAAK,CAAC,KAAK,IAC9D,CAAC,GAAO,CAAM,IACd,CAAC,GAAQ,CAAK;AACxB;AA+BA,SAAgB,EAAgB,GAAqB,GAAiC;CAClF,IAAM,IAAS,EAAQ,OAAO,CAAU;CAGxC,OAFI,EAAO,WAAW,IAAU,CAAC,GAAG,CAAK,IAElC,EAAM,QAAQ,MACjB,EAAO,OAAO,MAAW;EACrB,IAAM,IAAO,EAAU,GAAM,EAAO,KAAK;EACzC,IAAI,EAAY,EAAO,QAAQ,GAC3B,OAAO,EAAgB,EAAO,UAAU,GAAM,CAAC,CAAC;EAEpD,IAAM,IACF,EAAO,aAAa,YAAY,EAAW,EAAS,CAAM,CAAC,IAAI,EAAS,CAAM;EAClF,OAAO,EAAgB,EAAO,UAAU,GAAM,CAAM;CACxD,CAAC,CACL;AACJ"}
1
+ {"version":3,"file":"filter-apply.js","names":[],"sources":["../../../src/components/FilterBar/filter-apply.ts"],"sourcesContent":["// The other half of the filter model: evaluating a `Filter[]` instead of only\n// describing it. `FilterBar` produces filters, `applyFilters` runs them over an\n// in-memory list, and `filtersToQueryParams` (filter-query.ts) hands the same set\n// to a paginated backend. Without these two the model stops one step short of\n// useful and every app writes the eleven operator branches again.\n\nimport { formatDateForInput } from \"@/utils/format\";\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\n\n/** Matches a plain `yyyy-mm-dd`, the shape `<input type=\"date\">` produces. */\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * One collator for every text comparison in this module.\n *\n * `localeCompare(value, undefined, { numeric: true })` builds a collator on each\n * call — passing an options bag opts out of the engine's cached-default fast\n * path — and this comparison runs once per row per filter. Measured at 200k\n * comparisons: 453 ms through `localeCompare` against 25 ms through a hoisted\n * collator, i.e. ~23 ms of main thread per keystroke on a 10k-row list.\n */\nconst TEXT_COLLATOR = new Intl.Collator(undefined, { numeric: true });\n\n/**\n * Read a property off a row without asserting the row's shape.\n *\n * `Filter.field` is a string chosen by the app's field list, so it cannot be a\n * `keyof T`; the lookup goes through an index signature and a missing property\n * yields `undefined`, which the operators treat as an empty value.\n *\n * @param item - The row being tested.\n * @param field - Property name from the filter.\n * @returns The property value, or `undefined` when the row has no such key.\n */\nfunction readField(item: unknown, field: string): unknown {\n if (item == null || typeof item !== \"object\") return undefined;\n return (item as Record<string, unknown>)[field];\n}\n\n/**\n * Local `yyyy-mm-dd` for a value that represents a date, or `null`.\n *\n * Delegates the formatting to {@link formatDateForInput}, which owns the rule\n * that the key is built from the local calendar parts rather than `toISOString`.\n * This adds only the narrowing from `unknown` and maps \"not a date\" to `null`\n * instead of to the empty string a date input wants.\n *\n * @param value - A `Date`, a date-ish string, or anything else.\n * @returns The day key, or `null` when the value does not denote a date.\n */\nfunction dayKey(value: unknown): string | null {\n if (value instanceof Date) return formatDateForInput(value) || null;\n if (typeof value !== \"string\" || value.trim() === \"\") return null;\n return formatDateForInput(value) || null;\n}\n\n/**\n * Compare a row value against a filter value, in the row value's own type.\n *\n * The filter always carries strings — it comes from form inputs and from the\n * URL — while the row carries whatever the API returned. Comparing them as text\n * gets the two common cases wrong: `10` would sort before `9`, and a date would\n * be compared by however it happens to be formatted. So the row value picks the\n * comparison, and the filter value is coerced into it.\n *\n * Dates compare by **day**, not by instant: a row stamped `2026-03-05T13:00:00Z`\n * is `>= 2026-03-05`, which is what the person picking a date in the UI means.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Negative, zero or positive, or `null` when the two cannot be compared.\n */\nfunction compare(left: unknown, right: string): number | null {\n if (left == null) return null;\n\n if (typeof left === \"number\") {\n const parsed = Number(right);\n return Number.isNaN(parsed) ? null : left - parsed;\n }\n\n if (typeof left === \"boolean\") {\n const parsed = right === \"true\" ? true : right === \"false\" ? false : null;\n return parsed === null ? null : Number(left) - Number(parsed);\n }\n\n if (DATE_ONLY.test(right)) {\n const key = dayKey(left);\n return key === null ? null : key.localeCompare(right);\n }\n\n return TEXT_COLLATOR.compare(String(left), right);\n}\n\n/**\n * Equality in the row value's type, falling back to exact text.\n *\n * Case-sensitive on purpose: `eq` maps to a plain `WHERE column = value` on the\n * server side, so a case-insensitive client would quietly disagree with the\n * backend for the same filter. `contains` is the case-insensitive operator, and\n * it is also the default one for text fields, so the friendly behaviour is what\n * people get without asking.\n *\n * Identical strings take a fast path out, since the row value is text in the\n * common case and the collator is the expensive part.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Whether the two are equal.\n */\nfunction equals(left: unknown, right: string): boolean {\n if (left === right) return true;\n const result = compare(left, right);\n if (result !== null) return result === 0;\n return String(left ?? \"\") === right;\n}\n\n/**\n * Is this row value empty?\n *\n * Empty means absent, blank text or an empty list — never falsy. `0` and `false`\n * are values somebody chose, and a filter that hid them would be reporting the\n * wrong count on every dashboard that tracks zeroes.\n *\n * @param value - The row value.\n * @returns Whether the value counts as empty.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * The filter's value as a list, whatever arity the operator uses.\n *\n * @param filter - The filter to read.\n * @returns Its values, trimmed of nothing — a leading space may be meaningful text.\n */\nfunction valuesOf(filter: Filter): string[] {\n if (filter.value === undefined) return [];\n return Array.isArray(filter.value) ? [...filter.value] : [String(filter.value)];\n}\n\n/**\n * Evaluate one operator against one row value.\n *\n * @param operator - The comparison to make.\n * @param left - The row value.\n * @param values - The filter values (one, two for `between`, many for `in`).\n * @returns Whether the row value satisfies the operator.\n */\nfunction matchesOperator(operator: FilterOperator, left: unknown, values: string[]): boolean {\n switch (operator) {\n case \"empty\":\n return isEmptyValue(left);\n case \"notEmpty\":\n return !isEmptyValue(left);\n case \"eq\":\n return equals(left, values[0] ?? \"\");\n case \"ne\":\n return !equals(left, values[0] ?? \"\");\n case \"contains\":\n return String(left ?? \"\")\n .toLowerCase()\n .includes((values[0] ?? \"\").toLowerCase());\n case \"in\":\n return values.some((value) => equals(left, value));\n case \"between\": {\n const [first = \"\", second = \"\"] = values;\n const low = compare(left, first);\n const high = compare(left, second);\n if (low === null || high === null) return false;\n return low >= 0 && high <= 0;\n }\n default: {\n const result = compare(left, values[0] ?? \"\");\n if (result === null) return false;\n if (operator === \"gt\") return result > 0;\n if (operator === \"gte\") return result >= 0;\n if (operator === \"lt\") return result < 0;\n return result <= 0;\n }\n }\n}\n\n/**\n * Order the two values of a `between` so the range is never empty by accident.\n *\n * A person who picks the later date first means the range between the two dates,\n * not \"no rows\". The server-side `BETWEEN` needs `(lo, hi)` in order too, which\n * is why the normalisation lives here and is shared by `filtersToQueryParams`.\n *\n * @param values - The raw pair.\n * @returns The pair, ascending.\n */\nexport function orderRange(values: readonly string[]): string[] {\n const [first = \"\", second = \"\"] = values;\n if (DATE_ONLY.test(first) && DATE_ONLY.test(second)) {\n return first <= second ? [first, second] : [second, first];\n }\n const low = Number(first);\n const high = Number(second);\n if (!Number.isNaN(low) && !Number.isNaN(high)) {\n return low <= high ? [first, second] : [second, first];\n }\n return TEXT_COLLATOR.compare(first, second) <= 0 ? [first, second] : [second, first];\n}\n\n/**\n * Run a filter set over an in-memory list.\n *\n * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies\n * them. Filters combine with `AND`, matching the flat model the bar builds, and\n * an incomplete filter is skipped rather than treated as a match of nothing — a\n * half-filled form should not empty the table underneath it.\n *\n * Comparison follows the row's type, not the filter's: numbers compare\n * numerically, dates compare by day, and everything else compares as text with\n * `numeric: true` so `\"item 2\"` lands before `\"item 10\"`.\n *\n * A few behaviours differ from the SQL the server-side twin produces, and the\n * difference is deliberate rather than accidental:\n *\n * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`\n * for a `NULL` column and the row drops out. Here \"is not paid\" shows the rows\n * with no status at all, which is what the chip claims.\n * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server\n * only matches `NULL`. A column that stores `\"\"` instead of `NULL` is where\n * the two disagree.\n *\n * @example\n * const visible = applyFilters(orders, filters);\n *\n * Each filter's values are normalised once, before the scan, rather than inside\n * the per-row predicate: the arity, the `between` ordering and the value array\n * depend only on the filter, so deriving them per row multiplied that work by\n * the row count. `empty`/`notEmpty` ignore their values entirely, which is why\n * the prepared shape does not need to distinguish them.\n *\n * @param items - The full list.\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns A new array with the rows that satisfy every complete filter.\n */\nexport function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[] {\n const active = filters.filter(isComplete).map((filter) => ({\n field: filter.field,\n operator: filter.operator,\n values: filter.operator === \"between\" ? orderRange(valuesOf(filter)) : valuesOf(filter),\n }));\n if (active.length === 0) return [...items];\n\n return items.filter((item) =>\n active.every(({ field, operator, values }) =>\n matchesOperator(operator, readField(item, field), values),\n ),\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("./filter-model.cjs"),t=require("./filter-apply.cjs");var n={eq:``,ne:`__ne`,contains:`__icontains`,gt:`__gt`,gte:`__gte`,lt:`__lt`,lte:`__lte`,between:`__between`,in:`__in`,empty:`__isnull`,notEmpty:`__isnull`},r=`name`;function i(i){let a=new URLSearchParams;for(let o of i){if(!e.isComplete(o))continue;let i=n[o.operator],s=o.operator===`eq`&&o.field===r?`${o.field}__iexact`:`${o.field}${i}`;if(o.operator===`empty`||o.operator===`notEmpty`){a.append(s,o.operator===`empty`?`true`:`false`);continue}let c=Array.isArray(o.value)?[...o.value]:[String(o.value??``)];if(o.operator===`between`){for(let e of t.orderRange(c))a.append(s,e);continue}for(let e of c)a.append(s,e)}return a}exports.filtersToQueryParams=i;
1
+ const e=require("./filter-model.cjs"),t=require("./filter-apply.cjs");var n={eq:``,ne:`__ne`,contains:`__icontains`,gt:`__gt`,gte:`__gte`,lt:`__lt`,lte:`__lte`,between:`__between`,in:`__in`,empty:`__isnull`,notEmpty:`__isnull`},r=new Set([`name`]);function i(i,a={}){let o=a.operatorSuffix?{...n,...a.operatorSuffix}:n,s=a.substringColumns?new Set(a.substringColumns):r,c=new URLSearchParams;for(let n of i){if(!e.isComplete(n))continue;let r=o[n.operator],i=n.operator===`eq`&&s.has(n.field)?`${n.field}__iexact`:`${n.field}${r}`;if(n.operator===`empty`||n.operator===`notEmpty`){c.append(i,n.operator===`empty`?`true`:`false`);continue}let a=Array.isArray(n.value)?[...n.value]:[String(n.value??``)];if(n.operator===`between`){for(let e of t.orderRange(a))c.append(i,e);continue}for(let e of a)c.append(i,e)}return c}exports.filtersToQueryParams=i;
2
2
  //# sourceMappingURL=filter-query.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"filter-query.cjs","names":[],"sources":["../../../src/components/FilterBar/filter-query.ts"],"sourcesContent":["// Server-side half of the filter model: the same `Filter[]` the bar produces,\n// encoded the way a paginated backend expects to read it. Kept apart from\n// `filtersToSearchParams` (filter-model.ts) because the two answer different\n// questions — that one round-trips the UI state through the URL, this one talks\n// to an API and its shape is the API's, not ours.\n\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\nimport { orderRange } from \"./filter-apply\";\n\n/**\n * Operator suffix per filter operator, following the `<column>__<op>` convention\n * of `tempest-fastapi-sdk`.\n *\n * Ported from `build_filter_condition` in\n * `tempest_fastapi_sdk/db/expressions.py`, which is what `BaseRepository`\n * dict filters and `Q` both resolve through. An empty suffix means the bare\n * column name, which that function resolves to plain equality.\n *\n * `contains` maps to `icontains` (case-insensitive, and the backend escapes the\n * `LIKE` wildcards) and both emptiness operators map to `isnull`, whose value\n * carries the direction.\n */\nconst OPERATOR_SUFFIX: Record<FilterOperator, string> = {\n eq: \"\",\n ne: \"__ne\",\n contains: \"__icontains\",\n gt: \"__gt\",\n gte: \"__gte\",\n lt: \"__lt\",\n lte: \"__lte\",\n between: \"__between\",\n in: \"__in\",\n empty: \"__isnull\",\n notEmpty: \"__isnull\",\n};\n\n/**\n * Column name the backend treats as a substring search when it carries no\n * operator suffix.\n *\n * Ported from `build_filter_condition`, which special-cases a string `name` into\n * `ILIKE %value%`. Sending a bare `name=` for an `eq` filter would therefore ask\n * for \"contains\" while the chip says \"is\", so `eq` on this column is emitted as\n * `name__iexact` instead.\n */\nconst SUBSTRING_COLUMN = \"name\";\n\n/**\n * Encode filters as query params for a paginated backend.\n *\n * The counterpart of {@link applyFilters}: same filter set, evaluated by the\n * database instead of by the browser, which is the only option once the list is\n * paginated on the server and the page in memory is not the whole result.\n *\n * The encoding is the `<column>__<op>` convention `tempest-fastapi-sdk` already\n * reads (`BaseRepository._apply_filters` → `build_filter_condition`):\n *\n * | Operator | Param |\n * | --- | --- |\n * | `eq` | `field` (or `field__iexact` for the `name` column) |\n * | `ne` | `field__ne` |\n * | `contains` | `field__icontains` |\n * | `gt` `gte` `lt` `lte` | `field__gt` … `field__lte` |\n * | `between` | `field__between` twice, low value first |\n * | `in` | `field__in` once per value |\n * | `empty` / `notEmpty` | `field__isnull=true` / `=false` |\n *\n * Returns `URLSearchParams` rather than a plain object because `between` carries\n * a pair and `in` carries a list: an object would keep only the last value of\n * each, silently narrowing the filter. Repeated params are also how FastAPI\n * receives a `list[str]` declared with `Query`.\n *\n * Two things the backend has to hold up its end of, or the filter fails quietly:\n *\n * - **Every key must be declared.** `BasePaginationFilterSchema.get_conditions()`\n * only forwards fields the subclass declares, so a `status__ne` the schema\n * never mentions is dropped by FastAPI before the repository sees it — no\n * error, no filtering.\n * - **`isnull` matches `NULL` only.** A column that stores `\"\"` for \"no value\"\n * will not answer an `empty` filter, while {@link applyFilters} treats blank\n * text as empty.\n *\n * @example\n * const params = filtersToQueryParams(filters);\n * params.set(\"page\", String(page));\n * const data = await api.get(`/orders?${params}`);\n *\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns Params ready to append to a request URL.\n */\nexport function filtersToQueryParams(filters: readonly Filter[]): URLSearchParams {\n const params = new URLSearchParams();\n\n for (const filter of filters) {\n if (!isComplete(filter)) continue;\n\n const suffix = OPERATOR_SUFFIX[filter.operator];\n const key =\n filter.operator === \"eq\" && filter.field === SUBSTRING_COLUMN\n ? `${filter.field}__iexact`\n : `${filter.field}${suffix}`;\n\n if (filter.operator === \"empty\" || filter.operator === \"notEmpty\") {\n params.append(key, filter.operator === \"empty\" ? \"true\" : \"false\");\n continue;\n }\n\n const values = Array.isArray(filter.value)\n ? [...filter.value]\n : [String(filter.value ?? \"\")];\n\n if (filter.operator === \"between\") {\n for (const value of orderRange(values)) params.append(key, value);\n continue;\n }\n\n for (const value of values) params.append(key, value);\n }\n\n return params;\n}\n"],"mappings":"sEAuBA,IAAM,EAAkD,CACpD,GAAI,GACJ,GAAI,OACJ,SAAU,cACV,GAAI,OACJ,IAAK,QACL,GAAI,OACJ,IAAK,QACL,QAAS,YACT,GAAI,OACJ,MAAO,WACP,SAAU,UACd,EAWM,EAAmB,OA6CzB,SAAgB,EAAqB,EAA6C,CAC9E,IAAM,EAAS,IAAI,gBAEnB,IAAK,IAAM,KAAU,EAAS,CAC1B,GAAI,CAAC,EAAA,WAAW,CAAM,EAAG,SAEzB,IAAM,EAAS,EAAgB,EAAO,UAChC,EACF,EAAO,WAAa,MAAQ,EAAO,QAAU,EACvC,GAAG,EAAO,MAAM,UAChB,GAAG,EAAO,QAAQ,IAE5B,GAAI,EAAO,WAAa,SAAW,EAAO,WAAa,WAAY,CAC/D,EAAO,OAAO,EAAK,EAAO,WAAa,QAAU,OAAS,OAAO,EACjE,QACJ,CAEA,IAAM,EAAS,MAAM,QAAQ,EAAO,KAAK,EACnC,CAAC,GAAG,EAAO,KAAK,EAChB,CAAC,OAAO,EAAO,OAAS,EAAE,CAAC,EAEjC,GAAI,EAAO,WAAa,UAAW,CAC/B,IAAK,IAAM,KAAS,EAAA,WAAW,CAAM,EAAG,EAAO,OAAO,EAAK,CAAK,EAChE,QACJ,CAEA,IAAK,IAAM,KAAS,EAAQ,EAAO,OAAO,EAAK,CAAK,CACxD,CAEA,OAAO,CACX"}
1
+ {"version":3,"file":"filter-query.cjs","names":[],"sources":["../../../src/components/FilterBar/filter-query.ts"],"sourcesContent":["// Server-side half of the filter model: the same `Filter[]` the bar produces,\n// encoded the way a paginated backend expects to read it. Kept apart from\n// `filtersToSearchParams` (filter-model.ts) because the two answer different\n// questions — that one round-trips the UI state through the URL, this one talks\n// to an API and its shape is the API's, not ours.\n\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\nimport { orderRange } from \"./filter-apply\";\n\n/**\n * Operator suffix per filter operator, following the `<column>__<op>` convention\n * of `tempest-fastapi-sdk`.\n *\n * Ported from `build_filter_condition` in\n * `tempest_fastapi_sdk/db/expressions.py`, which is what `BaseRepository`\n * dict filters and `Q` both resolve through. An empty suffix means the bare\n * column name, which that function resolves to plain equality.\n *\n * `contains` maps to `icontains` (case-insensitive, and the backend escapes the\n * `LIKE` wildcards) and both emptiness operators map to `isnull`, whose value\n * carries the direction.\n */\nconst DEFAULT_OPERATOR_SUFFIX: Record<FilterOperator, string> = {\n eq: \"\",\n ne: \"__ne\",\n contains: \"__icontains\",\n gt: \"__gt\",\n gte: \"__gte\",\n lt: \"__lt\",\n lte: \"__lte\",\n between: \"__between\",\n in: \"__in\",\n empty: \"__isnull\",\n notEmpty: \"__isnull\",\n};\n\n/**\n * Columns the backend treats as a substring search when they carry no operator\n * suffix.\n *\n * Ported from `build_filter_condition`, which special-cases a string `name` into\n * `ILIKE %value%`. Sending a bare `name=` for an `eq` filter would therefore ask\n * for \"contains\" while the chip says \"is\", so `eq` on such a column is emitted as\n * `name__iexact` instead.\n */\nconst DEFAULT_SUBSTRING_COLUMNS: ReadonlySet<string> = new Set([\"name\"]);\n\n/** Overrides for the backend dialect {@link filtersToQueryParams} encodes into. */\nexport interface FiltersToQueryParamsOptions {\n /**\n * Columns whose `eq` is emitted as `<column>__iexact` rather than as the bare\n * column name. Default `[\"name\"]`, which is what `build_filter_condition`\n * special-cases.\n *\n * Pass the columns **your** backend treats that way — `nome`, `titulo`,\n * `razao_social` — or `[]` when it treats none of them specially and a bare\n * `eq` should stay bare.\n */\n substringColumns?: readonly string[];\n /**\n * Operator suffixes, merged over the `tempest-fastapi-sdk` dialect, so an\n * override names only the operators that differ.\n *\n * @example\n * // A DRF-style backend\n * filtersToQueryParams(filters, {\n * substringColumns: [],\n * operatorSuffix: { contains: \"__icontains\", ne: \"__exclude\" },\n * });\n */\n operatorSuffix?: Partial<Record<FilterOperator, string>>;\n}\n\n/**\n * Encode filters as query params for a paginated backend.\n *\n * The counterpart of {@link applyFilters}: same filter set, evaluated by the\n * database instead of by the browser, which is the only option once the list is\n * paginated on the server and the page in memory is not the whole result.\n *\n * The encoding is the `<column>__<op>` convention `tempest-fastapi-sdk` already\n * reads (`BaseRepository._apply_filters` → `build_filter_condition`):\n *\n * | Operator | Param |\n * | --- | --- |\n * | `eq` | `field` (or `field__iexact` for the `name` column) |\n * | `ne` | `field__ne` |\n * | `contains` | `field__icontains` |\n * | `gt` `gte` `lt` `lte` | `field__gt` … `field__lte` |\n * | `between` | `field__between` twice, low value first |\n * | `in` | `field__in` once per value |\n * | `empty` / `notEmpty` | `field__isnull=true` / `=false` |\n *\n * Returns `URLSearchParams` rather than a plain object because `between` carries\n * a pair and `in` carries a list: an object would keep only the last value of\n * each, silently narrowing the filter. Repeated params are also how FastAPI\n * receives a `list[str]` declared with `Query`.\n *\n * Two things the backend has to hold up its end of, or the filter fails quietly:\n *\n * - **Every key must be declared.** `BasePaginationFilterSchema.get_conditions()`\n * only forwards fields the subclass declares, so a `status__ne` the schema\n * never mentions is dropped by FastAPI before the repository sees it — no\n * error, no filtering.\n * - **`isnull` matches `NULL` only.** A column that stores `\"\"` for \"no value\"\n * will not answer an `empty` filter, while {@link applyFilters} treats blank\n * text as empty.\n *\n * The defaults are that dialect, not a universal truth. A backend that names its\n * substring column something other than `name`, or that spells its operators\n * differently, passes `options` — the alternative was an encoder whose special\n * cases were a wall for anyone not on the Tempest stack.\n *\n * @example\n * const params = filtersToQueryParams(filters);\n * params.set(\"page\", String(page));\n * const data = await api.get(`/orders?${params}`);\n *\n * @example\n * // A backend whose searchable column is `razao_social` and that spells `ne`\n * // the Django way.\n * const params = filtersToQueryParams(filters, {\n * substringColumns: [\"razao_social\"],\n * operatorSuffix: { ne: \"__exclude\" },\n * });\n *\n * @param filters - Applied filters; incomplete ones are ignored.\n * @param options - Dialect overrides. Defaults to the `tempest-fastapi-sdk` one.\n * @returns Params ready to append to a request URL.\n */\nexport function filtersToQueryParams(\n filters: readonly Filter[],\n options: FiltersToQueryParamsOptions = {},\n): URLSearchParams {\n const suffixes = options.operatorSuffix\n ? { ...DEFAULT_OPERATOR_SUFFIX, ...options.operatorSuffix }\n : DEFAULT_OPERATOR_SUFFIX;\n const substringColumns = options.substringColumns\n ? new Set(options.substringColumns)\n : DEFAULT_SUBSTRING_COLUMNS;\n\n const params = new URLSearchParams();\n\n for (const filter of filters) {\n if (!isComplete(filter)) continue;\n\n const suffix = suffixes[filter.operator];\n const key =\n filter.operator === \"eq\" && substringColumns.has(filter.field)\n ? `${filter.field}__iexact`\n : `${filter.field}${suffix}`;\n\n if (filter.operator === \"empty\" || filter.operator === \"notEmpty\") {\n params.append(key, filter.operator === \"empty\" ? \"true\" : \"false\");\n continue;\n }\n\n const values = Array.isArray(filter.value)\n ? [...filter.value]\n : [String(filter.value ?? \"\")];\n\n if (filter.operator === \"between\") {\n for (const value of orderRange(values)) params.append(key, value);\n continue;\n }\n\n for (const value of values) params.append(key, value);\n }\n\n return params;\n}\n"],"mappings":"sEAuBA,IAAM,EAA0D,CAC5D,GAAI,GACJ,GAAI,OACJ,SAAU,cACV,GAAI,OACJ,IAAK,QACL,GAAI,OACJ,IAAK,QACL,QAAS,YACT,GAAI,OACJ,MAAO,WACP,SAAU,UACd,EAWM,EAAiD,IAAI,IAAI,CAAC,MAAM,CAAC,EAqFvE,SAAgB,EACZ,EACA,EAAuC,CAAC,EACzB,CACf,IAAM,EAAW,EAAQ,eACnB,CAAE,GAAG,EAAyB,GAAG,EAAQ,cAAe,EACxD,EACA,EAAmB,EAAQ,iBAC3B,IAAI,IAAI,EAAQ,gBAAgB,EAChC,EAEA,EAAS,IAAI,gBAEnB,IAAK,IAAM,KAAU,EAAS,CAC1B,GAAI,CAAC,EAAA,WAAW,CAAM,EAAG,SAEzB,IAAM,EAAS,EAAS,EAAO,UACzB,EACF,EAAO,WAAa,MAAQ,EAAiB,IAAI,EAAO,KAAK,EACvD,GAAG,EAAO,MAAM,UAChB,GAAG,EAAO,QAAQ,IAE5B,GAAI,EAAO,WAAa,SAAW,EAAO,WAAa,WAAY,CAC/D,EAAO,OAAO,EAAK,EAAO,WAAa,QAAU,OAAS,OAAO,EACjE,QACJ,CAEA,IAAM,EAAS,MAAM,QAAQ,EAAO,KAAK,EACnC,CAAC,GAAG,EAAO,KAAK,EAChB,CAAC,OAAO,EAAO,OAAS,EAAE,CAAC,EAEjC,GAAI,EAAO,WAAa,UAAW,CAC/B,IAAK,IAAM,KAAS,EAAA,WAAW,CAAM,EAAG,EAAO,OAAO,EAAK,CAAK,EAChE,QACJ,CAEA,IAAK,IAAM,KAAS,EAAQ,EAAO,OAAO,EAAK,CAAK,CACxD,CAEA,OAAO,CACX"}
@@ -13,24 +13,27 @@ var n = {
13
13
  in: "__in",
14
14
  empty: "__isnull",
15
15
  notEmpty: "__isnull"
16
- }, r = "name";
17
- function i(i) {
18
- let a = new URLSearchParams();
19
- for (let o of i) {
20
- if (!e(o)) continue;
21
- let i = n[o.operator], s = o.operator === "eq" && o.field === r ? `${o.field}__iexact` : `${o.field}${i}`;
22
- if (o.operator === "empty" || o.operator === "notEmpty") {
23
- a.append(s, o.operator === "empty" ? "true" : "false");
16
+ }, r = /* @__PURE__ */ new Set(["name"]);
17
+ function i(i, a = {}) {
18
+ let o = a.operatorSuffix ? {
19
+ ...n,
20
+ ...a.operatorSuffix
21
+ } : n, s = a.substringColumns ? new Set(a.substringColumns) : r, c = new URLSearchParams();
22
+ for (let n of i) {
23
+ if (!e(n)) continue;
24
+ let r = o[n.operator], i = n.operator === "eq" && s.has(n.field) ? `${n.field}__iexact` : `${n.field}${r}`;
25
+ if (n.operator === "empty" || n.operator === "notEmpty") {
26
+ c.append(i, n.operator === "empty" ? "true" : "false");
24
27
  continue;
25
28
  }
26
- let c = Array.isArray(o.value) ? [...o.value] : [String(o.value ?? "")];
27
- if (o.operator === "between") {
28
- for (let e of t(c)) a.append(s, e);
29
+ let a = Array.isArray(n.value) ? [...n.value] : [String(n.value ?? "")];
30
+ if (n.operator === "between") {
31
+ for (let e of t(a)) c.append(i, e);
29
32
  continue;
30
33
  }
31
- for (let e of c) a.append(s, e);
34
+ for (let e of a) c.append(i, e);
32
35
  }
33
- return a;
36
+ return c;
34
37
  }
35
38
  //#endregion
36
39
  export { i as filtersToQueryParams };
@@ -1 +1 @@
1
- {"version":3,"file":"filter-query.js","names":[],"sources":["../../../src/components/FilterBar/filter-query.ts"],"sourcesContent":["// Server-side half of the filter model: the same `Filter[]` the bar produces,\n// encoded the way a paginated backend expects to read it. Kept apart from\n// `filtersToSearchParams` (filter-model.ts) because the two answer different\n// questions — that one round-trips the UI state through the URL, this one talks\n// to an API and its shape is the API's, not ours.\n\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\nimport { orderRange } from \"./filter-apply\";\n\n/**\n * Operator suffix per filter operator, following the `<column>__<op>` convention\n * of `tempest-fastapi-sdk`.\n *\n * Ported from `build_filter_condition` in\n * `tempest_fastapi_sdk/db/expressions.py`, which is what `BaseRepository`\n * dict filters and `Q` both resolve through. An empty suffix means the bare\n * column name, which that function resolves to plain equality.\n *\n * `contains` maps to `icontains` (case-insensitive, and the backend escapes the\n * `LIKE` wildcards) and both emptiness operators map to `isnull`, whose value\n * carries the direction.\n */\nconst OPERATOR_SUFFIX: Record<FilterOperator, string> = {\n eq: \"\",\n ne: \"__ne\",\n contains: \"__icontains\",\n gt: \"__gt\",\n gte: \"__gte\",\n lt: \"__lt\",\n lte: \"__lte\",\n between: \"__between\",\n in: \"__in\",\n empty: \"__isnull\",\n notEmpty: \"__isnull\",\n};\n\n/**\n * Column name the backend treats as a substring search when it carries no\n * operator suffix.\n *\n * Ported from `build_filter_condition`, which special-cases a string `name` into\n * `ILIKE %value%`. Sending a bare `name=` for an `eq` filter would therefore ask\n * for \"contains\" while the chip says \"is\", so `eq` on this column is emitted as\n * `name__iexact` instead.\n */\nconst SUBSTRING_COLUMN = \"name\";\n\n/**\n * Encode filters as query params for a paginated backend.\n *\n * The counterpart of {@link applyFilters}: same filter set, evaluated by the\n * database instead of by the browser, which is the only option once the list is\n * paginated on the server and the page in memory is not the whole result.\n *\n * The encoding is the `<column>__<op>` convention `tempest-fastapi-sdk` already\n * reads (`BaseRepository._apply_filters` → `build_filter_condition`):\n *\n * | Operator | Param |\n * | --- | --- |\n * | `eq` | `field` (or `field__iexact` for the `name` column) |\n * | `ne` | `field__ne` |\n * | `contains` | `field__icontains` |\n * | `gt` `gte` `lt` `lte` | `field__gt` … `field__lte` |\n * | `between` | `field__between` twice, low value first |\n * | `in` | `field__in` once per value |\n * | `empty` / `notEmpty` | `field__isnull=true` / `=false` |\n *\n * Returns `URLSearchParams` rather than a plain object because `between` carries\n * a pair and `in` carries a list: an object would keep only the last value of\n * each, silently narrowing the filter. Repeated params are also how FastAPI\n * receives a `list[str]` declared with `Query`.\n *\n * Two things the backend has to hold up its end of, or the filter fails quietly:\n *\n * - **Every key must be declared.** `BasePaginationFilterSchema.get_conditions()`\n * only forwards fields the subclass declares, so a `status__ne` the schema\n * never mentions is dropped by FastAPI before the repository sees it — no\n * error, no filtering.\n * - **`isnull` matches `NULL` only.** A column that stores `\"\"` for \"no value\"\n * will not answer an `empty` filter, while {@link applyFilters} treats blank\n * text as empty.\n *\n * @example\n * const params = filtersToQueryParams(filters);\n * params.set(\"page\", String(page));\n * const data = await api.get(`/orders?${params}`);\n *\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns Params ready to append to a request URL.\n */\nexport function filtersToQueryParams(filters: readonly Filter[]): URLSearchParams {\n const params = new URLSearchParams();\n\n for (const filter of filters) {\n if (!isComplete(filter)) continue;\n\n const suffix = OPERATOR_SUFFIX[filter.operator];\n const key =\n filter.operator === \"eq\" && filter.field === SUBSTRING_COLUMN\n ? `${filter.field}__iexact`\n : `${filter.field}${suffix}`;\n\n if (filter.operator === \"empty\" || filter.operator === \"notEmpty\") {\n params.append(key, filter.operator === \"empty\" ? \"true\" : \"false\");\n continue;\n }\n\n const values = Array.isArray(filter.value)\n ? [...filter.value]\n : [String(filter.value ?? \"\")];\n\n if (filter.operator === \"between\") {\n for (const value of orderRange(values)) params.append(key, value);\n continue;\n }\n\n for (const value of values) params.append(key, value);\n }\n\n return params;\n}\n"],"mappings":";;;AAuBA,IAAM,IAAkD;CACpD,IAAI;CACJ,IAAI;CACJ,UAAU;CACV,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,KAAK;CACL,SAAS;CACT,IAAI;CACJ,OAAO;CACP,UAAU;AACd,GAWM,IAAmB;AA6CzB,SAAgB,EAAqB,GAA6C;CAC9E,IAAM,IAAS,IAAI,gBAAgB;CAEnC,KAAK,IAAM,KAAU,GAAS;EAC1B,IAAI,CAAC,EAAW,CAAM,GAAG;EAEzB,IAAM,IAAS,EAAgB,EAAO,WAChC,IACF,EAAO,aAAa,QAAQ,EAAO,UAAU,IACvC,GAAG,EAAO,MAAM,YAChB,GAAG,EAAO,QAAQ;EAE5B,IAAI,EAAO,aAAa,WAAW,EAAO,aAAa,YAAY;GAC/D,EAAO,OAAO,GAAK,EAAO,aAAa,UAAU,SAAS,OAAO;GACjE;EACJ;EAEA,IAAM,IAAS,MAAM,QAAQ,EAAO,KAAK,IACnC,CAAC,GAAG,EAAO,KAAK,IAChB,CAAC,OAAO,EAAO,SAAS,EAAE,CAAC;EAEjC,IAAI,EAAO,aAAa,WAAW;GAC/B,KAAK,IAAM,KAAS,EAAW,CAAM,GAAG,EAAO,OAAO,GAAK,CAAK;GAChE;EACJ;EAEA,KAAK,IAAM,KAAS,GAAQ,EAAO,OAAO,GAAK,CAAK;CACxD;CAEA,OAAO;AACX"}
1
+ {"version":3,"file":"filter-query.js","names":[],"sources":["../../../src/components/FilterBar/filter-query.ts"],"sourcesContent":["// Server-side half of the filter model: the same `Filter[]` the bar produces,\n// encoded the way a paginated backend expects to read it. Kept apart from\n// `filtersToSearchParams` (filter-model.ts) because the two answer different\n// questions — that one round-trips the UI state through the URL, this one talks\n// to an API and its shape is the API's, not ours.\n\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\nimport { orderRange } from \"./filter-apply\";\n\n/**\n * Operator suffix per filter operator, following the `<column>__<op>` convention\n * of `tempest-fastapi-sdk`.\n *\n * Ported from `build_filter_condition` in\n * `tempest_fastapi_sdk/db/expressions.py`, which is what `BaseRepository`\n * dict filters and `Q` both resolve through. An empty suffix means the bare\n * column name, which that function resolves to plain equality.\n *\n * `contains` maps to `icontains` (case-insensitive, and the backend escapes the\n * `LIKE` wildcards) and both emptiness operators map to `isnull`, whose value\n * carries the direction.\n */\nconst DEFAULT_OPERATOR_SUFFIX: Record<FilterOperator, string> = {\n eq: \"\",\n ne: \"__ne\",\n contains: \"__icontains\",\n gt: \"__gt\",\n gte: \"__gte\",\n lt: \"__lt\",\n lte: \"__lte\",\n between: \"__between\",\n in: \"__in\",\n empty: \"__isnull\",\n notEmpty: \"__isnull\",\n};\n\n/**\n * Columns the backend treats as a substring search when they carry no operator\n * suffix.\n *\n * Ported from `build_filter_condition`, which special-cases a string `name` into\n * `ILIKE %value%`. Sending a bare `name=` for an `eq` filter would therefore ask\n * for \"contains\" while the chip says \"is\", so `eq` on such a column is emitted as\n * `name__iexact` instead.\n */\nconst DEFAULT_SUBSTRING_COLUMNS: ReadonlySet<string> = new Set([\"name\"]);\n\n/** Overrides for the backend dialect {@link filtersToQueryParams} encodes into. */\nexport interface FiltersToQueryParamsOptions {\n /**\n * Columns whose `eq` is emitted as `<column>__iexact` rather than as the bare\n * column name. Default `[\"name\"]`, which is what `build_filter_condition`\n * special-cases.\n *\n * Pass the columns **your** backend treats that way — `nome`, `titulo`,\n * `razao_social` — or `[]` when it treats none of them specially and a bare\n * `eq` should stay bare.\n */\n substringColumns?: readonly string[];\n /**\n * Operator suffixes, merged over the `tempest-fastapi-sdk` dialect, so an\n * override names only the operators that differ.\n *\n * @example\n * // A DRF-style backend\n * filtersToQueryParams(filters, {\n * substringColumns: [],\n * operatorSuffix: { contains: \"__icontains\", ne: \"__exclude\" },\n * });\n */\n operatorSuffix?: Partial<Record<FilterOperator, string>>;\n}\n\n/**\n * Encode filters as query params for a paginated backend.\n *\n * The counterpart of {@link applyFilters}: same filter set, evaluated by the\n * database instead of by the browser, which is the only option once the list is\n * paginated on the server and the page in memory is not the whole result.\n *\n * The encoding is the `<column>__<op>` convention `tempest-fastapi-sdk` already\n * reads (`BaseRepository._apply_filters` → `build_filter_condition`):\n *\n * | Operator | Param |\n * | --- | --- |\n * | `eq` | `field` (or `field__iexact` for the `name` column) |\n * | `ne` | `field__ne` |\n * | `contains` | `field__icontains` |\n * | `gt` `gte` `lt` `lte` | `field__gt` … `field__lte` |\n * | `between` | `field__between` twice, low value first |\n * | `in` | `field__in` once per value |\n * | `empty` / `notEmpty` | `field__isnull=true` / `=false` |\n *\n * Returns `URLSearchParams` rather than a plain object because `between` carries\n * a pair and `in` carries a list: an object would keep only the last value of\n * each, silently narrowing the filter. Repeated params are also how FastAPI\n * receives a `list[str]` declared with `Query`.\n *\n * Two things the backend has to hold up its end of, or the filter fails quietly:\n *\n * - **Every key must be declared.** `BasePaginationFilterSchema.get_conditions()`\n * only forwards fields the subclass declares, so a `status__ne` the schema\n * never mentions is dropped by FastAPI before the repository sees it — no\n * error, no filtering.\n * - **`isnull` matches `NULL` only.** A column that stores `\"\"` for \"no value\"\n * will not answer an `empty` filter, while {@link applyFilters} treats blank\n * text as empty.\n *\n * The defaults are that dialect, not a universal truth. A backend that names its\n * substring column something other than `name`, or that spells its operators\n * differently, passes `options` — the alternative was an encoder whose special\n * cases were a wall for anyone not on the Tempest stack.\n *\n * @example\n * const params = filtersToQueryParams(filters);\n * params.set(\"page\", String(page));\n * const data = await api.get(`/orders?${params}`);\n *\n * @example\n * // A backend whose searchable column is `razao_social` and that spells `ne`\n * // the Django way.\n * const params = filtersToQueryParams(filters, {\n * substringColumns: [\"razao_social\"],\n * operatorSuffix: { ne: \"__exclude\" },\n * });\n *\n * @param filters - Applied filters; incomplete ones are ignored.\n * @param options - Dialect overrides. Defaults to the `tempest-fastapi-sdk` one.\n * @returns Params ready to append to a request URL.\n */\nexport function filtersToQueryParams(\n filters: readonly Filter[],\n options: FiltersToQueryParamsOptions = {},\n): URLSearchParams {\n const suffixes = options.operatorSuffix\n ? { ...DEFAULT_OPERATOR_SUFFIX, ...options.operatorSuffix }\n : DEFAULT_OPERATOR_SUFFIX;\n const substringColumns = options.substringColumns\n ? new Set(options.substringColumns)\n : DEFAULT_SUBSTRING_COLUMNS;\n\n const params = new URLSearchParams();\n\n for (const filter of filters) {\n if (!isComplete(filter)) continue;\n\n const suffix = suffixes[filter.operator];\n const key =\n filter.operator === \"eq\" && substringColumns.has(filter.field)\n ? `${filter.field}__iexact`\n : `${filter.field}${suffix}`;\n\n if (filter.operator === \"empty\" || filter.operator === \"notEmpty\") {\n params.append(key, filter.operator === \"empty\" ? \"true\" : \"false\");\n continue;\n }\n\n const values = Array.isArray(filter.value)\n ? [...filter.value]\n : [String(filter.value ?? \"\")];\n\n if (filter.operator === \"between\") {\n for (const value of orderRange(values)) params.append(key, value);\n continue;\n }\n\n for (const value of values) params.append(key, value);\n }\n\n return params;\n}\n"],"mappings":";;;AAuBA,IAAM,IAA0D;CAC5D,IAAI;CACJ,IAAI;CACJ,UAAU;CACV,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,KAAK;CACL,SAAS;CACT,IAAI;CACJ,OAAO;CACP,UAAU;AACd,GAWM,oBAAiD,IAAI,IAAI,CAAC,MAAM,CAAC;AAqFvE,SAAgB,EACZ,GACA,IAAuC,CAAC,GACzB;CACf,IAAM,IAAW,EAAQ,iBACnB;EAAE,GAAG;EAAyB,GAAG,EAAQ;CAAe,IACxD,GACA,IAAmB,EAAQ,mBAC3B,IAAI,IAAI,EAAQ,gBAAgB,IAChC,GAEA,IAAS,IAAI,gBAAgB;CAEnC,KAAK,IAAM,KAAU,GAAS;EAC1B,IAAI,CAAC,EAAW,CAAM,GAAG;EAEzB,IAAM,IAAS,EAAS,EAAO,WACzB,IACF,EAAO,aAAa,QAAQ,EAAiB,IAAI,EAAO,KAAK,IACvD,GAAG,EAAO,MAAM,YAChB,GAAG,EAAO,QAAQ;EAE5B,IAAI,EAAO,aAAa,WAAW,EAAO,aAAa,YAAY;GAC/D,EAAO,OAAO,GAAK,EAAO,aAAa,UAAU,SAAS,OAAO;GACjE;EACJ;EAEA,IAAM,IAAS,MAAM,QAAQ,EAAO,KAAK,IACnC,CAAC,GAAG,EAAO,KAAK,IAChB,CAAC,OAAO,EAAO,SAAS,EAAE,CAAC;EAEjC,IAAI,EAAO,aAAa,WAAW;GAC/B,KAAK,IAAM,KAAS,EAAW,CAAM,GAAG,EAAO,OAAO,GAAK,CAAK;GAChE;EACJ;EAEA,KAAK,IAAM,KAAS,GAAQ,EAAO,OAAO,GAAK,CAAK;CACxD;CAEA,OAAO;AACX"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../../utils/relative-time.cjs"),n=require("../Badge/Badge.cjs"),r=require("../EmptyState/EmptyState.cjs"),i=require("./NotificationCenter.module.cjs");let a=require("react/jsx-runtime");function o({items:o,title:s=`Notificações`,onSelect:c,onMarkRead:l,onMarkAllRead:u,onDismiss:d,renderIcon:f,locale:p=`pt-BR`,emptyState:m,now:h,className:g,..._}){let v=o.filter(e=>!e.read).length,y=h??new Date().getTime(),b=e=>{e.read||l?.(e.id),c?.(e)},x=!!(c||l);return(0,a.jsxs)(`div`,{className:e.cn(i.default.panel,g),..._,children:[(s||u&&v>0)&&(0,a.jsxs)(`header`,{className:i.default.header,children:[s?(0,a.jsxs)(`h2`,{className:i.default.title,children:[s,v>0&&(0,a.jsx)(n.Badge,{variant:`primary`,"aria-label":`${v} não lidas`,children:v})]}):(0,a.jsx)(`span`,{}),u&&v>0&&(0,a.jsx)(`button`,{type:`button`,className:i.default.headerAction,onClick:u,children:`Marcar todas como lidas`})]}),o.length===0?m??(0,a.jsx)(r.EmptyState,{title:`Nenhuma notificação`}):(0,a.jsx)(`ul`,{className:i.default.list,children:o.map(n=>(0,a.jsxs)(`li`,{className:e.cn(i.default.item,!n.read&&i.default.unread),"aria-current":n.read?void 0:`true`,children:[f&&(0,a.jsx)(`span`,{className:i.default.icon,children:f(n)}),(0,a.jsxs)(`div`,{className:i.default.body,children:[x?(0,a.jsxs)(`button`,{type:`button`,className:i.default.trigger,onClick:()=>b(n),children:[(0,a.jsx)(`span`,{className:i.default.itemTitle,children:n.title}),n.body&&(0,a.jsx)(`span`,{className:i.default.itemBody,children:n.body})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(`span`,{className:i.default.itemTitle,children:n.title}),n.body&&(0,a.jsx)(`span`,{className:i.default.itemBody,children:n.body})]}),(0,a.jsx)(`time`,{className:i.default.time,dateTime:new Date(n.receivedAt).toISOString(),children:t.relativeTime(n.receivedAt,{locale:p,now:y})})]}),d&&(0,a.jsx)(`button`,{type:`button`,className:i.default.dismiss,onClick:()=>d(n.id),"aria-label":`Descartar: ${n.title}`,children:`×`})]},n.id))})]})}exports.NotificationCenter=o;
1
+ const e=require("../../utils/cn.cjs"),t=require("../Badge/Badge.cjs"),n=require("../EmptyState/EmptyState.cjs"),r=require("../../utils/relative-time.cjs"),i=require("./NotificationCenter.module.cjs");let a=require("react/jsx-runtime");function o({items:o,title:s=`Notificações`,onSelect:c,onMarkRead:l,onMarkAllRead:u,onDismiss:d,renderIcon:f,locale:p=`pt-BR`,emptyState:m,now:h,className:g,..._}){let v=o.filter(e=>!e.read).length,y=h??new Date().getTime(),b=e=>{e.read||l?.(e.id),c?.(e)},x=!!(c||l);return(0,a.jsxs)(`div`,{className:e.cn(i.default.panel,g),..._,children:[(s||u&&v>0)&&(0,a.jsxs)(`header`,{className:i.default.header,children:[s?(0,a.jsxs)(`h2`,{className:i.default.title,children:[s,v>0&&(0,a.jsx)(t.Badge,{variant:`primary`,"aria-label":`${v} não lidas`,children:v})]}):(0,a.jsx)(`span`,{}),u&&v>0&&(0,a.jsx)(`button`,{type:`button`,className:i.default.headerAction,onClick:u,children:`Marcar todas como lidas`})]}),o.length===0?m??(0,a.jsx)(n.EmptyState,{title:`Nenhuma notificação`}):(0,a.jsx)(`ul`,{className:i.default.list,children:o.map(t=>(0,a.jsxs)(`li`,{className:e.cn(i.default.item,!t.read&&i.default.unread),"aria-current":t.read?void 0:`true`,children:[f&&(0,a.jsx)(`span`,{className:i.default.icon,children:f(t)}),(0,a.jsxs)(`div`,{className:i.default.body,children:[x?(0,a.jsxs)(`button`,{type:`button`,className:i.default.trigger,onClick:()=>b(t),children:[(0,a.jsx)(`span`,{className:i.default.itemTitle,children:t.title}),t.body&&(0,a.jsx)(`span`,{className:i.default.itemBody,children:t.body})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(`span`,{className:i.default.itemTitle,children:t.title}),t.body&&(0,a.jsx)(`span`,{className:i.default.itemBody,children:t.body})]}),(0,a.jsx)(`time`,{className:i.default.time,dateTime:new Date(t.receivedAt).toISOString(),children:r.relativeTime(t.receivedAt,{locale:p,now:y})})]}),d&&(0,a.jsx)(`button`,{type:`button`,className:i.default.dismiss,onClick:()=>d(t.id),"aria-label":`Descartar: ${t.title}`,children:`×`})]},t.id))})]})}exports.NotificationCenter=o;
2
2
  //# sourceMappingURL=NotificationCenter.cjs.map
@@ -1,7 +1,7 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
- import { relativeTime as t } from "../../utils/relative-time.js";
3
- import { Badge as n } from "../Badge/Badge.js";
4
- import { EmptyState as r } from "../EmptyState/EmptyState.js";
2
+ import { Badge as t } from "../Badge/Badge.js";
3
+ import { EmptyState as n } from "../EmptyState/EmptyState.js";
4
+ import { relativeTime as r } from "../../utils/relative-time.js";
5
5
  import i from "./NotificationCenter.module.js";
6
6
  import { Fragment as a, jsx as o, jsxs as s } from "react/jsx-runtime";
7
7
  //#region src/components/NotificationCenter/NotificationCenter.tsx
@@ -16,7 +16,7 @@ function c({ items: c, title: l = "Notificações", onSelect: u, onMarkRead: d,
16
16
  className: i.header,
17
17
  children: [l ? /* @__PURE__ */ s("h2", {
18
18
  className: i.title,
19
- children: [l, b > 0 && /* @__PURE__ */ o(n, {
19
+ children: [l, b > 0 && /* @__PURE__ */ o(t, {
20
20
  variant: "primary",
21
21
  "aria-label": `${b} não lidas`,
22
22
  children: b
@@ -27,39 +27,39 @@ function c({ items: c, title: l = "Notificações", onSelect: u, onMarkRead: d,
27
27
  onClick: f,
28
28
  children: "Marcar todas como lidas"
29
29
  })]
30
- }), c.length === 0 ? g ?? /* @__PURE__ */ o(r, { title: "Nenhuma notificação" }) : /* @__PURE__ */ o("ul", {
30
+ }), c.length === 0 ? g ?? /* @__PURE__ */ o(n, { title: "Nenhuma notificação" }) : /* @__PURE__ */ o("ul", {
31
31
  className: i.list,
32
- children: c.map((n) => /* @__PURE__ */ s("li", {
33
- className: e(i.item, !n.read && i.unread),
34
- "aria-current": n.read ? void 0 : "true",
32
+ children: c.map((t) => /* @__PURE__ */ s("li", {
33
+ className: e(i.item, !t.read && i.unread),
34
+ "aria-current": t.read ? void 0 : "true",
35
35
  children: [
36
36
  m && /* @__PURE__ */ o("span", {
37
37
  className: i.icon,
38
- children: m(n)
38
+ children: m(t)
39
39
  }),
40
40
  /* @__PURE__ */ s("div", {
41
41
  className: i.body,
42
42
  children: [C ? /* @__PURE__ */ s("button", {
43
43
  type: "button",
44
44
  className: i.trigger,
45
- onClick: () => S(n),
45
+ onClick: () => S(t),
46
46
  children: [/* @__PURE__ */ o("span", {
47
47
  className: i.itemTitle,
48
- children: n.title
49
- }), n.body && /* @__PURE__ */ o("span", {
48
+ children: t.title
49
+ }), t.body && /* @__PURE__ */ o("span", {
50
50
  className: i.itemBody,
51
- children: n.body
51
+ children: t.body
52
52
  })]
53
53
  }) : /* @__PURE__ */ s(a, { children: [/* @__PURE__ */ o("span", {
54
54
  className: i.itemTitle,
55
- children: n.title
56
- }), n.body && /* @__PURE__ */ o("span", {
55
+ children: t.title
56
+ }), t.body && /* @__PURE__ */ o("span", {
57
57
  className: i.itemBody,
58
- children: n.body
58
+ children: t.body
59
59
  })] }), /* @__PURE__ */ o("time", {
60
60
  className: i.time,
61
- dateTime: new Date(n.receivedAt).toISOString(),
62
- children: t(n.receivedAt, {
61
+ dateTime: new Date(t.receivedAt).toISOString(),
62
+ children: r(t.receivedAt, {
63
63
  locale: h,
64
64
  now: x
65
65
  })
@@ -68,12 +68,12 @@ function c({ items: c, title: l = "Notificações", onSelect: u, onMarkRead: d,
68
68
  p && /* @__PURE__ */ o("button", {
69
69
  type: "button",
70
70
  className: i.dismiss,
71
- onClick: () => p(n.id),
72
- "aria-label": `Descartar: ${n.title}`,
71
+ onClick: () => p(t.id),
72
+ "aria-label": `Descartar: ${t.title}`,
73
73
  children: "×"
74
74
  })
75
75
  ]
76
- }, n.id))
76
+ }, t.id))
77
77
  })]
78
78
  });
79
79
  }
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("./Sidebar.module.cjs");let n=require("react/jsx-runtime");function r({header:r,items:i,value:a,onChange:o,footer:s,collapsed:c=!1,width:l=240,collapsedWidth:u=64,className:d,style:f,...p}){let m=typeof(c?u:l)==`number`?`${c?u:l}px`:c?u:l;return(0,n.jsxs)(`aside`,{className:e.cn(t.default.sidebar,c&&t.default.collapsed,d),style:{width:m,...f},...p,children:[r&&(0,n.jsx)(`div`,{className:t.default.header,children:r}),(0,n.jsx)(`nav`,{className:t.default.nav,"aria-label":`Navegação lateral`,children:i.map(r=>{let i=r.key===a;return(0,n.jsxs)(`button`,{type:`button`,className:e.cn(t.default.item,i&&t.default.active),"aria-current":i?`page`:void 0,disabled:r.disabled,onClick:()=>o?.(r.key),title:c&&typeof r.label==`string`?r.label:void 0,children:[r.icon&&(0,n.jsx)(`span`,{className:t.default.icon,children:r.icon}),!c&&(0,n.jsx)(`span`,{className:t.default.label,children:r.label}),!c&&r.badge!==void 0&&(0,n.jsx)(`span`,{className:t.default.badge,children:r.badge})]},r.key)})}),s&&(0,n.jsx)(`div`,{className:t.default.footer,children:s})]})}exports.Sidebar=r;
1
+ const e=require("../../utils/cn.cjs"),t=require("./Sidebar.module.cjs");let n=require("react"),r=require("react/jsx-runtime");function i(e){let t=[],n;for(let r of e){if(r.type===`separator`){t.push({kind:`separator`,key:r.key}),n=void 0;continue}if(r.type===`section`){n={kind:`items`,key:r.key,section:r,items:[]},t.push(n);continue}n||(n={kind:`items`,key:`loose-${r.key}`,items:[]},t.push(n)),n.items.push(r)}return t}function a({header:a,items:o,value:s,onChange:c,footer:l,collapsed:u=!1,width:d=240,collapsedWidth:f=64,className:p,style:m,...h}){let g=(0,n.useId)(),_=i(o),v=typeof(u?f:d)==`number`?`${u?f:d}px`:u?f:d,y=n=>{let i=n.key===s,a=u&&typeof n.label==`string`?n.label:void 0,o=(0,r.jsxs)(r.Fragment,{children:[n.icon&&(0,r.jsx)(`span`,{className:t.default.icon,children:n.icon}),!u&&(0,r.jsx)(`span`,{className:t.default.label,children:n.label}),!u&&n.badge!==void 0&&(0,r.jsx)(`span`,{className:t.default.badge,children:n.badge})]});return n.href&&!n.disabled?(0,r.jsx)(`a`,{href:n.href,className:e.cn(t.default.item,i&&t.default.active),"aria-current":i?`page`:void 0,onClick:()=>c?.(n.key),title:a,children:o},n.key):(0,r.jsx)(`button`,{type:`button`,className:e.cn(t.default.item,i&&t.default.active),"aria-current":i?`page`:void 0,disabled:n.disabled,onClick:()=>c?.(n.key),title:a,children:o},n.key)};return(0,r.jsxs)(`aside`,{className:e.cn(t.default.sidebar,u&&t.default.collapsed,p),style:{width:v,...m},...h,children:[a&&(0,r.jsx)(`div`,{className:t.default.header,children:a}),(0,r.jsx)(`nav`,{className:t.default.nav,"aria-label":`Navegação lateral`,children:_.map(n=>{if(n.kind===`separator`)return(0,r.jsx)(`hr`,{className:t.default.separator},n.key);if(!n.section)return(0,r.jsx)(`div`,{className:t.default.group,children:n.items.map(y)},n.key);let i=`${g}-${n.key}`;return(0,r.jsxs)(`div`,{role:`group`,"aria-labelledby":i,className:t.default.group,children:[(0,r.jsx)(`div`,{id:i,role:`presentation`,className:e.cn(t.default.section,u&&t.default.sectionCollapsed),children:n.section.label}),n.items.map(y)]},n.key)})}),l&&(0,r.jsx)(`div`,{className:t.default.footer,children:l})]})}exports.Sidebar=a;
2
2
  //# sourceMappingURL=Sidebar.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sidebar.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n href?: string;\n}\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation items. */\n items: SidebarItem[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {items.map((item) => {\n const active = item.key === value;\n return (\n <button\n key={item.key}\n type=\"button\"\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n disabled={item.disabled}\n onClick={() => onChange?.(item.key)}\n title={\n collapsed && typeof item.label === \"string\" ? item.label : undefined\n }\n >\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </button>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"2GAoDA,SAAgB,EAAQ,CACpB,SACA,QACA,QACA,WACA,SACA,YAAY,GACZ,QAAQ,IACR,iBAAiB,GACjB,YACA,QACA,GAAG,GACU,CACb,IAAM,EACF,OAAQ,EAAY,EAAiB,IAAW,SAC1C,GAAG,EAAY,EAAiB,EAAM,IACtC,EACE,EACA,EACZ,OACI,EAAA,EAAA,KAAA,CAAC,QAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAa,EAAA,QAAO,UAAW,CAAS,EACtE,MAAO,CAAE,MAAO,EAAY,GAAG,CAAM,EACrC,GAAI,EAHR,SAAA,CAKK,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,GACvD,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,IAAK,aAAW,oBAClC,SAAA,EAAM,IAAK,GAAS,CACjB,IAAM,EAAS,EAAK,MAAQ,EAC5B,OACI,EAAA,EAAA,KAAA,CAAC,SAAD,CAEI,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,GAAU,EAAA,QAAO,MAAM,EAClD,eAAc,EAAS,OAAS,IAAA,GAChC,SAAU,EAAK,SACf,YAAe,IAAW,EAAK,GAAG,EAClC,MACI,GAAa,OAAO,EAAK,OAAU,SAAW,EAAK,MAAQ,IAAA,GARnE,SAAA,CAWK,EAAK,OAAQ,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAO,SAAA,EAAK,IAAW,CAAA,EAC5D,CAAC,IAAa,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAQ,SAAA,EAAK,KAAY,CAAA,EAC/D,CAAC,GAAa,EAAK,QAAU,IAAA,KAC1B,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAQ,SAAA,EAAK,KAAY,CAAA,CAEjD,CAfC,EAAA,EAAK,GAeN,CAEhB,CAAC,CACA,CAAA,EACJ,IAAU,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAS,SAAA,CAAY,CAAA,CACpD,GAEf"}
1
+ {"version":3,"file":"Sidebar.cjs","names":[],"sources":["../../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — header, items, footer are the three regions,\n * value/onChange the selection, and collapsed/width/collapsedWidth the two layout\n * modes it switches between.\n */\nimport { useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarItem {\n key: string;\n label: ReactNode;\n icon?: ReactNode;\n badge?: ReactNode;\n disabled?: boolean;\n /**\n * Renders the entry as a link to this URL instead of a button.\n *\n * `onChange` still fires on click, so an app that tracks the active key keeps\n * working — the difference is that the entry now behaves like navigation:\n * middle-click, ctrl-click and \"copy link address\" do what the user expects,\n * and a screen reader announces a link rather than a button.\n *\n * A `disabled` entry ignores this and stays a `<button disabled>`: there is no\n * disabled state for an anchor, and dropping the `href` to fake one leaves a\n * link that announces itself as actionable and is not.\n */\n href?: string;\n}\n\n/**\n * A section heading. Every item after it belongs to the section, until the next\n * section or separator.\n */\nexport interface SidebarSection {\n type: \"section\";\n key: string;\n label: ReactNode;\n}\n\n/** A plain divider, for splitting the list without naming the parts. */\nexport interface SidebarSeparator {\n type: \"separator\";\n key: string;\n}\n\n/**\n * One entry of the navigation list.\n *\n * An entry with no `type` is an item, which is what keeps a plain\n * `SidebarItem[]` a valid `SidebarEntry[]`: adding sections to this component\n * needed no change at any existing call site.\n */\nexport type SidebarEntry = ({ type?: \"item\" } & SidebarItem) | SidebarSection | SidebarSeparator;\n\nexport interface SidebarProps extends Omit<HTMLAttributes<HTMLElement>, \"onChange\"> {\n /** Top slot — typically the logo + brand. */\n header?: ReactNode;\n /** Navigation entries. An entry with no `type` is an item. */\n items: SidebarEntry[];\n /** Active item key. */\n value?: string;\n /** Fires when an item is clicked. Receives the item's `key`. */\n onChange?: (key: string) => void;\n /** Bottom slot — typically settings/profile/logout. */\n footer?: ReactNode;\n /** Collapsed mode — only icons visible. Default `false`. */\n collapsed?: boolean;\n /** Width when expanded, in pixels or any CSS length. Default `240px`. */\n width?: number | string;\n /** Width when collapsed, in pixels or any CSS length. Default `64px`. */\n collapsedWidth?: number | string;\n}\n\n/** A run of items, optionally under a section heading. */\ninterface ItemBlock {\n kind: \"items\";\n key: string;\n section?: SidebarSection;\n items: SidebarItem[];\n}\n\n/** A standalone divider between blocks. */\ninterface SeparatorBlock {\n kind: \"separator\";\n key: string;\n}\n\ntype Block = ItemBlock | SeparatorBlock;\n\n/**\n * Fold the flat entry list into the blocks that get rendered.\n *\n * A section opens a block that swallows every following item; a separator closes\n * whatever is open, so items after it are loose again. Items before the first\n * section are loose too — which is the whole existing behaviour, and why a list\n * with no sections comes out of here as one unnamed block.\n *\n * @param entries - The `items` prop, as given.\n * @returns Blocks in render order.\n */\nfunction toBlocks(entries: readonly SidebarEntry[]): Block[] {\n const blocks: Block[] = [];\n let open: ItemBlock | undefined;\n\n for (const entry of entries) {\n if (entry.type === \"separator\") {\n blocks.push({ kind: \"separator\", key: entry.key });\n open = undefined;\n continue;\n }\n if (entry.type === \"section\") {\n open = { kind: \"items\", key: entry.key, section: entry, items: [] };\n blocks.push(open);\n continue;\n }\n if (!open) {\n open = { kind: \"items\", key: `loose-${entry.key}`, items: [] };\n blocks.push(open);\n }\n open.items.push(entry);\n }\n return blocks;\n}\n\n/**\n * Desktop sidebar navigation. Pair with `<Show above=\"md\">` and a `Drawer`\n * for mobile.\n *\n * Sixteen screens in one flat list is a wall, so `items` also takes section\n * headings and separators. A section renders as a labelled `role=\"group\"`, which\n * is what tells a screen reader \"Monitoring, group, 3 items\" — the reason it is\n * not a styled `disabled` item, which would announce an unavailable button and\n * stay in the navigation order.\n *\n * @example\n * const [tab, setTab] = useState(\"home\");\n * <Show above=\"md\">\n * <Sidebar\n * header={<Brand />}\n * items={[{ key: \"home\", label: \"Home\", icon: <Home /> }]}\n * value={tab}\n * onChange={setTab}\n * />\n * </Show>\n *\n * @example\n * <Sidebar\n * items={[\n * { type: \"section\", key: \"monitoring\", label: \"Monitoring\" },\n * { key: \"overview\", label: \"Overview\", href: \"/overview\" },\n * { key: \"activity\", label: \"Activity\", href: \"/activity\" },\n * { type: \"section\", key: \"admin\", label: \"Administration\" },\n * { key: \"settings\", label: \"Settings\", href: \"/settings\" },\n * ]}\n * value={tab}\n * onChange={setTab}\n * />\n */\nexport function Sidebar({\n header,\n items,\n value,\n onChange,\n footer,\n collapsed = false,\n width = 240,\n collapsedWidth = 64,\n className,\n style,\n ...props\n}: SidebarProps) {\n const baseId = useId();\n const blocks = toBlocks(items);\n const finalWidth =\n typeof (collapsed ? collapsedWidth : width) === \"number\"\n ? `${collapsed ? collapsedWidth : width}px`\n : collapsed\n ? collapsedWidth\n : width;\n\n /**\n * Render one navigation entry.\n *\n * @param item - The entry to render.\n * @returns The button, or the anchor when the entry carries an `href`.\n */\n const renderItem = (item: SidebarItem): ReactNode => {\n const active = item.key === value;\n const title = collapsed && typeof item.label === \"string\" ? item.label : undefined;\n const body = (\n <>\n {item.icon && <span className={styles.icon}>{item.icon}</span>}\n {!collapsed && <span className={styles.label}>{item.label}</span>}\n {!collapsed && item.badge !== undefined && (\n <span className={styles.badge}>{item.badge}</span>\n )}\n </>\n );\n\n if (item.href && !item.disabled) {\n return (\n <a\n key={item.key}\n href={item.href}\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n onClick={() => onChange?.(item.key)}\n title={title}\n >\n {body}\n </a>\n );\n }\n\n return (\n <button\n key={item.key}\n type=\"button\"\n className={cn(styles.item, active && styles.active)}\n aria-current={active ? \"page\" : undefined}\n disabled={item.disabled}\n onClick={() => onChange?.(item.key)}\n title={title}\n >\n {body}\n </button>\n );\n };\n\n return (\n <aside\n className={cn(styles.sidebar, collapsed && styles.collapsed, className)}\n style={{ width: finalWidth, ...style }}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <nav className={styles.nav} aria-label=\"Navegação lateral\">\n {blocks.map((block) => {\n if (block.kind === \"separator\") {\n return <hr key={block.key} className={styles.separator} />;\n }\n if (!block.section) {\n return (\n <div key={block.key} className={styles.group}>\n {block.items.map(renderItem)}\n </div>\n );\n }\n const labelId = `${baseId}-${block.key}`;\n return (\n <div\n key={block.key}\n role=\"group\"\n aria-labelledby={labelId}\n className={styles.group}\n >\n <div\n id={labelId}\n role=\"presentation\"\n className={cn(styles.section, collapsed && styles.sectionCollapsed)}\n >\n {block.section.label}\n </div>\n {block.items.map(renderItem)}\n </div>\n );\n })}\n </nav>\n {footer && <div className={styles.footer}>{footer}</div>}\n </aside>\n );\n}\n"],"mappings":"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"}