tempest-react-sdk 0.80.0 → 0.82.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 (247) hide show
  1. package/README.md +40 -10
  2. package/bin/create-tempest-app.e2e.test.mjs +31 -0
  3. package/bin/create-tempest-app.mjs +11 -1
  4. package/bin/lib/component/generate.test.mjs +13 -1
  5. package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
  6. package/bin/lib/doctor/vitest.mjs +120 -0
  7. package/bin/lib/doctor/vitest.test.mjs +139 -0
  8. package/bin/lib/fs/contain.mjs +7 -0
  9. package/bin/lib/openapi/escape.mjs +68 -4
  10. package/bin/lib/openapi/generate.hostile.test.mjs +150 -1
  11. package/bin/lib/openapi/generate.mjs +78 -13
  12. package/bin/lib/openapi/generate.test.mjs +13 -1
  13. package/bin/lib/openapi/pattern-risk.mjs +134 -0
  14. package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
  15. package/bin/tempest-gen.test.mjs +100 -0
  16. package/bin/tempest.mjs +24 -8
  17. package/dist/access/use-can.cjs +1 -1
  18. package/dist/access/use-can.cjs.map +1 -1
  19. package/dist/access/use-can.js +25 -28
  20. package/dist/access/use-can.js.map +1 -1
  21. package/dist/auth/create-tempest-auth.cjs +1 -1
  22. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  23. package/dist/auth/create-tempest-auth.js +234 -122
  24. package/dist/auth/create-tempest-auth.js.map +1 -1
  25. package/dist/auth/session-identity.cjs +2 -0
  26. package/dist/auth/session-identity.cjs.map +1 -0
  27. package/dist/auth/session-identity.js +12 -0
  28. package/dist/auth/session-identity.js.map +1 -0
  29. package/dist/br/boleto.cjs +1 -1
  30. package/dist/br/boleto.cjs.map +1 -1
  31. package/dist/br/boleto.js +2 -1
  32. package/dist/br/boleto.js.map +1 -1
  33. package/dist/br.d.ts +22 -3
  34. package/dist/components/AuthForm/AuthFieldControl.cjs +2 -0
  35. package/dist/components/AuthForm/AuthFieldControl.cjs.map +1 -0
  36. package/dist/components/AuthForm/AuthFieldControl.js +85 -0
  37. package/dist/components/AuthForm/AuthFieldControl.js.map +1 -0
  38. package/dist/components/AuthForm/AuthForm.cjs +2 -0
  39. package/dist/components/AuthForm/AuthForm.cjs.map +1 -0
  40. package/dist/components/AuthForm/AuthForm.js +83 -0
  41. package/dist/components/AuthForm/AuthForm.js.map +1 -0
  42. package/dist/components/AuthForm/AuthForm.module.cjs +3 -0
  43. package/dist/components/AuthForm/AuthForm.module.cjs.map +1 -0
  44. package/dist/components/AuthForm/AuthForm.module.js +14 -0
  45. package/dist/components/AuthForm/AuthForm.module.js.map +1 -0
  46. package/dist/components/AuthForm/AuthLinks.cjs +2 -0
  47. package/dist/components/AuthForm/AuthLinks.cjs.map +1 -0
  48. package/dist/components/AuthForm/AuthLinks.js +32 -0
  49. package/dist/components/AuthForm/AuthLinks.js.map +1 -0
  50. package/dist/components/AuthForm/ForgotPasswordForm.cjs +2 -0
  51. package/dist/components/AuthForm/ForgotPasswordForm.cjs.map +1 -0
  52. package/dist/components/AuthForm/ForgotPasswordForm.js +32 -0
  53. package/dist/components/AuthForm/ForgotPasswordForm.js.map +1 -0
  54. package/dist/components/AuthForm/LoginForm.cjs +2 -0
  55. package/dist/components/AuthForm/LoginForm.cjs.map +1 -0
  56. package/dist/components/AuthForm/LoginForm.js +39 -0
  57. package/dist/components/AuthForm/LoginForm.js.map +1 -0
  58. package/dist/components/AuthForm/ResetPasswordForm.cjs +2 -0
  59. package/dist/components/AuthForm/ResetPasswordForm.cjs.map +1 -0
  60. package/dist/components/AuthForm/ResetPasswordForm.js +39 -0
  61. package/dist/components/AuthForm/ResetPasswordForm.js.map +1 -0
  62. package/dist/components/AuthForm/SignUpForm.cjs +2 -0
  63. package/dist/components/AuthForm/SignUpForm.cjs.map +1 -0
  64. package/dist/components/AuthForm/SignUpForm.js +32 -0
  65. package/dist/components/AuthForm/SignUpForm.js.map +1 -0
  66. package/dist/components/AuthForm/auth-defaults.cjs +2 -0
  67. package/dist/components/AuthForm/auth-defaults.cjs.map +1 -0
  68. package/dist/components/AuthForm/auth-defaults.js +54 -0
  69. package/dist/components/AuthForm/auth-defaults.js.map +1 -0
  70. package/dist/components/AuthForm/auth-schemas.cjs +2 -0
  71. package/dist/components/AuthForm/auth-schemas.cjs.map +1 -0
  72. package/dist/components/AuthForm/auth-schemas.js +26 -0
  73. package/dist/components/AuthForm/auth-schemas.js.map +1 -0
  74. package/dist/components/AuthPage/AuthPage.cjs +2 -0
  75. package/dist/components/AuthPage/AuthPage.cjs.map +1 -0
  76. package/dist/components/AuthPage/AuthPage.js +97 -0
  77. package/dist/components/AuthPage/AuthPage.js.map +1 -0
  78. package/dist/components/AuthPage/AuthPage.module.cjs +3 -0
  79. package/dist/components/AuthPage/AuthPage.module.cjs.map +1 -0
  80. package/dist/components/AuthPage/AuthPage.module.js +24 -0
  81. package/dist/components/AuthPage/AuthPage.module.js.map +1 -0
  82. package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
  83. package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
  84. package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
  85. package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  86. package/dist/components/Button/Button.cjs +1 -1
  87. package/dist/components/Button/Button.cjs.map +1 -1
  88. package/dist/components/Button/Button.js +41 -40
  89. package/dist/components/Button/Button.js.map +1 -1
  90. package/dist/components/Chat/ChatAttachments.cjs +1 -1
  91. package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
  92. package/dist/components/Chat/ChatAttachments.js +38 -38
  93. package/dist/components/FilterBar/filter-apply.cjs +1 -1
  94. package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
  95. package/dist/components/FilterBar/filter-apply.js +7 -7
  96. package/dist/components/Form/Form.cjs +1 -1
  97. package/dist/components/Form/Form.cjs.map +1 -1
  98. package/dist/components/Form/Form.js +30 -32
  99. package/dist/components/Form/Form.js.map +1 -1
  100. package/dist/components/Layout/Layout.cjs +1 -1
  101. package/dist/components/Layout/Layout.cjs.map +1 -1
  102. package/dist/components/Layout/Layout.js +25 -24
  103. package/dist/components/Layout/Layout.js.map +1 -1
  104. package/dist/components/Layout/gap.cjs +2 -0
  105. package/dist/components/Layout/gap.cjs.map +1 -0
  106. package/dist/components/Layout/gap.js +15 -0
  107. package/dist/components/Layout/gap.js.map +1 -0
  108. package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
  109. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  110. package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
  111. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  112. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  113. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  114. package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
  115. package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
  116. package/dist/components/Sidebar/SidebarEntry.js +30 -29
  117. package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
  118. package/dist/data/create-data-provider.cjs +1 -1
  119. package/dist/data/create-data-provider.cjs.map +1 -1
  120. package/dist/data/create-data-provider.js +1 -2
  121. package/dist/data/create-data-provider.js.map +1 -1
  122. package/dist/forms/index.js +7 -7
  123. package/dist/forms/masked-inputs.cjs +1 -1
  124. package/dist/forms/masked-inputs.cjs.map +1 -1
  125. package/dist/forms/masked-inputs.js +15 -15
  126. package/dist/forms/validate-form.cjs +1 -1
  127. package/dist/forms/validate-form.cjs.map +1 -1
  128. package/dist/forms/validate-form.js +6 -1
  129. package/dist/forms/validate-form.js.map +1 -1
  130. package/dist/forms/zod-resolver.cjs +1 -1
  131. package/dist/forms/zod-resolver.cjs.map +1 -1
  132. package/dist/forms/zod-resolver.js +8 -3
  133. package/dist/forms/zod-resolver.js.map +1 -1
  134. package/dist/http/api-client.cjs +1 -1
  135. package/dist/http/api-client.cjs.map +1 -1
  136. package/dist/http/api-client.js +137 -116
  137. package/dist/http/api-client.js.map +1 -1
  138. package/dist/http/credential-scope.cjs +1 -1
  139. package/dist/http/credential-scope.cjs.map +1 -1
  140. package/dist/http/credential-scope.js +33 -9
  141. package/dist/http/credential-scope.js.map +1 -1
  142. package/dist/http/presigned-post.cjs.map +1 -1
  143. package/dist/http/presigned-post.js.map +1 -1
  144. package/dist/http/resumable-upload.cjs +1 -1
  145. package/dist/http/resumable-upload.cjs.map +1 -1
  146. package/dist/http/resumable-upload.js +140 -134
  147. package/dist/http/resumable-upload.js.map +1 -1
  148. package/dist/http/upload-with-progress.cjs +1 -1
  149. package/dist/http/upload-with-progress.cjs.map +1 -1
  150. package/dist/http/upload-with-progress.js +17 -17
  151. package/dist/http/upload-with-progress.js.map +1 -1
  152. package/dist/http/use-describe-api-error.cjs +1 -1
  153. package/dist/http/use-describe-api-error.cjs.map +1 -1
  154. package/dist/http/use-describe-api-error.js +9 -9
  155. package/dist/oauth/GoogleSignIn.cjs +1 -1
  156. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  157. package/dist/oauth/GoogleSignIn.js +5 -4
  158. package/dist/oauth/GoogleSignIn.js.map +1 -1
  159. package/dist/offline/create-offline-sync.cjs +1 -1
  160. package/dist/offline/create-offline-sync.cjs.map +1 -1
  161. package/dist/offline/create-offline-sync.js +108 -68
  162. package/dist/offline/create-offline-sync.js.map +1 -1
  163. package/dist/query/offline-persistence.cjs +1 -1
  164. package/dist/query/offline-persistence.cjs.map +1 -1
  165. package/dist/query/offline-persistence.js +54 -44
  166. package/dist/query/offline-persistence.js.map +1 -1
  167. package/dist/sse/create-event-stream.cjs +1 -1
  168. package/dist/sse/create-event-stream.cjs.map +1 -1
  169. package/dist/sse/create-event-stream.js +48 -41
  170. package/dist/sse/create-event-stream.js.map +1 -1
  171. package/dist/sse/ticket-event-stream.cjs +1 -1
  172. package/dist/sse/ticket-event-stream.cjs.map +1 -1
  173. package/dist/sse/ticket-event-stream.js +48 -19
  174. package/dist/sse/ticket-event-stream.js.map +1 -1
  175. package/dist/sse/use-event-stream.cjs +1 -1
  176. package/dist/sse/use-event-stream.cjs.map +1 -1
  177. package/dist/sse/use-event-stream.js +19 -14
  178. package/dist/sse/use-event-stream.js.map +1 -1
  179. package/dist/styles/AuthForm.css +2 -0
  180. package/dist/styles/AuthPage.css +2 -0
  181. package/dist/styles/component/AuthForm.css +9 -0
  182. package/dist/styles/component/AuthPage.css +29 -0
  183. package/dist/styles/forms.css +36 -0
  184. package/dist/styles/manifest.json +63 -0
  185. package/dist/styles.css +1 -1
  186. package/dist/sw/background-sync.cjs +1 -1
  187. package/dist/sw/background-sync.cjs.map +1 -1
  188. package/dist/sw/background-sync.js +132 -112
  189. package/dist/sw/background-sync.js.map +1 -1
  190. package/dist/sw/cache.cjs +1 -1
  191. package/dist/sw/cache.cjs.map +1 -1
  192. package/dist/sw/cache.js +36 -30
  193. package/dist/sw/cache.js.map +1 -1
  194. package/dist/sw/create-push-handler.cjs +1 -1
  195. package/dist/sw/create-push-handler.cjs.map +1 -1
  196. package/dist/sw/create-push-handler.js +20 -10
  197. package/dist/sw/create-push-handler.js.map +1 -1
  198. package/dist/sw.d.ts +93 -28
  199. package/dist/tabular/compact.cjs +1 -1
  200. package/dist/tabular/compact.cjs.map +1 -1
  201. package/dist/tabular/compact.js +35 -16
  202. package/dist/tabular/compact.js.map +1 -1
  203. package/dist/tabular/manifest.cjs +1 -1
  204. package/dist/tabular/manifest.cjs.map +1 -1
  205. package/dist/tabular/manifest.js +33 -15
  206. package/dist/tabular/manifest.js.map +1 -1
  207. package/dist/tabular.d.ts +11 -1
  208. package/dist/tempest-react-sdk.cjs +1 -1
  209. package/dist/tempest-react-sdk.d.ts +1057 -83
  210. package/dist/tempest-react-sdk.js +373 -365
  211. package/dist/theme/initial-theme.cjs +4 -4
  212. package/dist/theme/initial-theme.cjs.map +1 -1
  213. package/dist/theme/initial-theme.js +8 -5
  214. package/dist/theme/initial-theme.js.map +1 -1
  215. package/dist/utils/compressed-storage.cjs +1 -1
  216. package/dist/utils/compressed-storage.cjs.map +1 -1
  217. package/dist/utils/compressed-storage.js +13 -7
  218. package/dist/utils/compressed-storage.js.map +1 -1
  219. package/dist/utils/format.cjs +1 -1
  220. package/dist/utils/format.cjs.map +1 -1
  221. package/dist/utils/format.js +25 -25
  222. package/dist/utils/numbers.cjs +1 -1
  223. package/dist/utils/numbers.cjs.map +1 -1
  224. package/dist/utils/numbers.js +7 -7
  225. package/dist/utils/objects.cjs +1 -1
  226. package/dist/utils/objects.cjs.map +1 -1
  227. package/dist/utils/objects.js +13 -7
  228. package/dist/utils/objects.js.map +1 -1
  229. package/dist/utils/safe-url.cjs +1 -1
  230. package/dist/utils/safe-url.cjs.map +1 -1
  231. package/dist/utils/safe-url.js +4 -1
  232. package/dist/utils/safe-url.js.map +1 -1
  233. package/dist/webrtc/peer-mesh.cjs +1 -1
  234. package/dist/webrtc/peer-mesh.cjs.map +1 -1
  235. package/dist/webrtc/peer-mesh.js +7 -1
  236. package/dist/webrtc/peer-mesh.js.map +1 -1
  237. package/dist/ws/create-web-socket.cjs +1 -1
  238. package/dist/ws/create-web-socket.cjs.map +1 -1
  239. package/dist/ws/create-web-socket.js +77 -61
  240. package/dist/ws/create-web-socket.js.map +1 -1
  241. package/dist/ws/use-web-socket.cjs +1 -1
  242. package/dist/ws/use-web-socket.cjs.map +1 -1
  243. package/dist/ws/use-web-socket.js +4 -3
  244. package/dist/ws/use-web-socket.js.map +1 -1
  245. package/package.json +1 -1
  246. package/template-pwa/src/pwa.tsx +42 -0
  247. package/template-pwa/src/sw.ts +15 -10
@@ -1,11 +1,11 @@
1
- const e=require("../utils/storage.cjs");function t(t={}){let{storageKey:n=`tempest-theme`,defaultTheme:r=`system`}=t;if(typeof window>`u`)return r===`dark`?`dark`:`light`;let i=e.storage.getRaw(n)??r;return i===`dark`||i===`light`?i:window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}function n(e={}){let t=e.storageKey??`tempest-theme`,n=e.defaultTheme??`system`;return`
1
+ const e=require("../utils/storage.cjs");function t(t={}){let{storageKey:n=`tempest-theme`,defaultTheme:r=`system`}=t;if(typeof window>`u`)return r===`dark`?`dark`:`light`;let i=e.storage.getRaw(n)??r;return i===`dark`||i===`light`?i:window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}function n(e){return JSON.stringify(e).replace(/[<>&\u2028\u2029]/g,e=>`\\u${e.charCodeAt(0).toString(16).padStart(4,`0`)}`)}function r(e={}){let t=e.storageKey??`tempest-theme`,r=e.defaultTheme??`system`;return`
2
2
  (function(){try{
3
- var key=${JSON.stringify(t)};
4
- var def=${JSON.stringify(n)};
3
+ var key=${n(t)};
4
+ var def=${n(r)};
5
5
  var stored=localStorage.getItem(key);
6
6
  var mode=stored||def;
7
7
  var resolved=mode==="dark"||mode==="light"?mode:(matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light");
8
8
  document.documentElement.setAttribute("data-tempest-theme",resolved);
9
9
  }catch(e){}})();
10
- `.trim()}exports.getInitialTheme=t,exports.themeInitScript=n;
10
+ `.trim()}exports.getInitialTheme=t,exports.themeInitScript=r;
11
11
  //# sourceMappingURL=initial-theme.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"initial-theme.cjs","names":["storage","require_storage","getInitialTheme","options","defaultTheme","mode","storageKey","themeInitScript"],"sources":["../../src/theme/initial-theme.ts"],"sourcesContent":["import { storage } from \"@/utils/storage\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface GetInitialThemeOptions {\n /** localStorage key. Default: `\"tempest-theme\"`. */\n storageKey?: string;\n /** Theme used when nothing is stored and the user has no system preference. */\n defaultTheme?: ThemeMode;\n}\n\n/**\n * Inline-safe theme resolution intended for early bootstrap (e.g. inside the\n * `<head>` script that prevents the flash of incorrect theme). Reads from\n * localStorage and falls back to `prefers-color-scheme`.\n */\nexport function getInitialTheme(options: GetInitialThemeOptions = {}): ResolvedTheme {\n const { storageKey = \"tempest-theme\", defaultTheme = \"system\" } = options;\n\n if (typeof window === \"undefined\") {\n return defaultTheme === \"dark\" ? \"dark\" : \"light\";\n }\n\n const stored = storage.getRaw(storageKey) as ThemeMode | null;\n const mode: ThemeMode = stored ?? defaultTheme;\n if (mode === \"dark\" || mode === \"light\") return mode;\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * Plain HTML snippet that sets `data-tempest-theme` on `<html>` before React\n * hydrates. Inline this in `<head>` to avoid a flash of the wrong theme on\n * first paint.\n *\n * @example\n * <script dangerouslySetInnerHTML={{ __html: themeInitScript() }} />\n */\nexport function themeInitScript(options: GetInitialThemeOptions = {}): string {\n const storageKey = options.storageKey ?? \"tempest-theme\";\n const defaultTheme = options.defaultTheme ?? \"system\";\n return `\n(function(){try{\n var key=${JSON.stringify(storageKey)};\n var def=${JSON.stringify(defaultTheme)};\n var stored=localStorage.getItem(key);\n var mode=stored||def;\n var resolved=mode===\"dark\"||mode===\"light\"?mode:(matchMedia(\"(prefers-color-scheme: dark)\").matches?\"dark\":\"light\");\n document.documentElement.setAttribute(\"data-tempest-theme\",resolved);\n}catch(e){}})();\n`.trim();\n}\n"],"mappings":"A,wCAeA,SAAgBE,EAAgB,EAAkC,CAAC,EAAkB,CACjF,GAAM,CAAE,aAAa,gBAAiB,eAAe,UAAaC,EAElE,GAAI,OAAO,OAAW,IAClB,OAAOC,IAAiB,OAAS,OAAS,QAI9C,IAAMC,EADSL,EAAAA,QAAQ,OAAOM,CACN,GAAUF,EAGlC,OAFIC,IAAS,QAAUA,IAAS,QAAgBA,EAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,QAAU,OAAS,OAChF,CAUA,SAAgBE,EAAgB,EAAkC,CAAC,EAAW,CAC1E,IAAMD,EAAaH,EAAQ,YAAc,gBACnCC,EAAeD,EAAQ,cAAgB,SAC7C,MAAO;;YAEC,KAAK,UAAUG,CAAU,EAAE;YAC3B,KAAK,UAAUF,CAAY,EAAE;;;;;;EAMvC,KAAK,CACP,C"}
1
+ {"version":3,"file":"initial-theme.cjs","names":["storage","require_storage","getInitialTheme","options","defaultTheme","mode","storageKey","scriptLiteral","value","char","themeInitScript"],"sources":["../../src/theme/initial-theme.ts"],"sourcesContent":["import { storage } from \"@/utils/storage\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface GetInitialThemeOptions {\n /** localStorage key. Default: `\"tempest-theme\"`. */\n storageKey?: string;\n /** Theme used when nothing is stored and the user has no system preference. */\n defaultTheme?: ThemeMode;\n}\n\n/**\n * Inline-safe theme resolution intended for early bootstrap (e.g. inside the\n * `<head>` script that prevents the flash of incorrect theme). Reads from\n * localStorage and falls back to `prefers-color-scheme`.\n */\nexport function getInitialTheme(options: GetInitialThemeOptions = {}): ResolvedTheme {\n const { storageKey = \"tempest-theme\", defaultTheme = \"system\" } = options;\n\n if (typeof window === \"undefined\") {\n return defaultTheme === \"dark\" ? \"dark\" : \"light\";\n }\n\n const stored = storage.getRaw(storageKey) as ThemeMode | null;\n const mode: ThemeMode = stored ?? defaultTheme;\n if (mode === \"dark\" || mode === \"light\") return mode;\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * A string as a JavaScript literal that is also safe inside an HTML `<script>`.\n *\n * `JSON.stringify` produces a valid literal but leaves `<`, `>`, `&` and the\n * line separators U+2028/U+2029 as they are, and the HTML parser ends a script\n * element at the first `</script` whatever JavaScript thinks of it: a\n * `storageKey` of `\"</script><script>alert(1)</script>\"` closed the snippet and\n * ran the second script. Each of those characters is written as its `\\uXXXX`\n * escape, which JavaScript reads back to the same string.\n *\n * @param value - The option value to embed.\n * @returns The quoted, escaped literal.\n */\nfunction scriptLiteral(value: string): string {\n return JSON.stringify(value).replace(\n /[<>&\\u2028\\u2029]/g,\n (char) => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, \"0\")}`,\n );\n}\n\n/**\n * Plain HTML snippet that sets `data-tempest-theme` on `<html>` before React\n * hydrates. Inline this in `<head>` to avoid a flash of the wrong theme on\n * first paint.\n *\n * Both options are embedded through an escaper that keeps them inside the\n * `<script>` element whatever they contain, so the snippet is safe to inline\n * even when the storage key comes from configuration.\n *\n * @example\n * <script dangerouslySetInnerHTML={{ __html: themeInitScript() }} />\n */\nexport function themeInitScript(options: GetInitialThemeOptions = {}): string {\n const storageKey = options.storageKey ?? \"tempest-theme\";\n const defaultTheme = options.defaultTheme ?? \"system\";\n return `\n(function(){try{\n var key=${scriptLiteral(storageKey)};\n var def=${scriptLiteral(defaultTheme)};\n var stored=localStorage.getItem(key);\n var mode=stored||def;\n var resolved=mode===\"dark\"||mode===\"light\"?mode:(matchMedia(\"(prefers-color-scheme: dark)\").matches?\"dark\":\"light\");\n document.documentElement.setAttribute(\"data-tempest-theme\",resolved);\n}catch(e){}})();\n`.trim();\n}\n"],"mappings":"A,wCAeA,SAAgBE,EAAgB,EAAkC,CAAC,EAAkB,CACjF,GAAM,CAAE,aAAa,gBAAiB,eAAe,UAAaC,EAElE,GAAI,OAAO,OAAW,IAClB,OAAOC,IAAiB,OAAS,OAAS,QAI9C,IAAMC,EADSL,EAAAA,QAAQ,OAAOM,CACN,GAAUF,EAGlC,OAFIC,IAAS,QAAUA,IAAS,QAAgBA,EAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,QAAU,OAAS,OAChF,CAeA,SAASE,EAAc,EAAuB,CAC1C,OAAO,KAAK,UAAUC,CAAK,CAAC,CAAC,QACzB,qBACC,GAAS,MAAMC,EAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,GACnE,CACJ,CAcA,SAAgBC,EAAgB,EAAkC,CAAC,EAAW,CAC1E,IAAMJ,EAAaH,EAAQ,YAAc,gBACnCC,EAAeD,EAAQ,cAAgB,SAC7C,MAAO;;YAECI,EAAcD,CAAU,EAAE;YAC1BC,EAAcH,CAAY,EAAE;;;;;;EAMtC,KAAK,CACP,C"}
@@ -6,12 +6,15 @@ function t(t = {}) {
6
6
  let i = e.getRaw(n) ?? r;
7
7
  return i === "dark" || i === "light" ? i : window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
8
8
  }
9
- function n(e = {}) {
10
- let t = e.storageKey ?? "tempest-theme", n = e.defaultTheme ?? "system";
9
+ function n(e) {
10
+ return JSON.stringify(e).replace(/[<>&\u2028\u2029]/g, (e) => `\\u${e.charCodeAt(0).toString(16).padStart(4, "0")}`);
11
+ }
12
+ function r(e = {}) {
13
+ let t = e.storageKey ?? "tempest-theme", r = e.defaultTheme ?? "system";
11
14
  return `
12
15
  (function(){try{
13
- var key=${JSON.stringify(t)};
14
- var def=${JSON.stringify(n)};
16
+ var key=${n(t)};
17
+ var def=${n(r)};
15
18
  var stored=localStorage.getItem(key);
16
19
  var mode=stored||def;
17
20
  var resolved=mode==="dark"||mode==="light"?mode:(matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light");
@@ -20,6 +23,6 @@ function n(e = {}) {
20
23
  `.trim();
21
24
  }
22
25
  //#endregion
23
- export { t as getInitialTheme, n as themeInitScript };
26
+ export { t as getInitialTheme, r as themeInitScript };
24
27
 
25
28
  //# sourceMappingURL=initial-theme.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"initial-theme.js","names":["getInitialTheme","options","defaultTheme","mode","storage","storageKey","themeInitScript"],"sources":["../../src/theme/initial-theme.ts"],"sourcesContent":["import { storage } from \"@/utils/storage\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface GetInitialThemeOptions {\n /** localStorage key. Default: `\"tempest-theme\"`. */\n storageKey?: string;\n /** Theme used when nothing is stored and the user has no system preference. */\n defaultTheme?: ThemeMode;\n}\n\n/**\n * Inline-safe theme resolution intended for early bootstrap (e.g. inside the\n * `<head>` script that prevents the flash of incorrect theme). Reads from\n * localStorage and falls back to `prefers-color-scheme`.\n */\nexport function getInitialTheme(options: GetInitialThemeOptions = {}): ResolvedTheme {\n const { storageKey = \"tempest-theme\", defaultTheme = \"system\" } = options;\n\n if (typeof window === \"undefined\") {\n return defaultTheme === \"dark\" ? \"dark\" : \"light\";\n }\n\n const stored = storage.getRaw(storageKey) as ThemeMode | null;\n const mode: ThemeMode = stored ?? defaultTheme;\n if (mode === \"dark\" || mode === \"light\") return mode;\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * Plain HTML snippet that sets `data-tempest-theme` on `<html>` before React\n * hydrates. Inline this in `<head>` to avoid a flash of the wrong theme on\n * first paint.\n *\n * @example\n * <script dangerouslySetInnerHTML={{ __html: themeInitScript() }} />\n */\nexport function themeInitScript(options: GetInitialThemeOptions = {}): string {\n const storageKey = options.storageKey ?? \"tempest-theme\";\n const defaultTheme = options.defaultTheme ?? \"system\";\n return `\n(function(){try{\n var key=${JSON.stringify(storageKey)};\n var def=${JSON.stringify(defaultTheme)};\n var stored=localStorage.getItem(key);\n var mode=stored||def;\n var resolved=mode===\"dark\"||mode===\"light\"?mode:(matchMedia(\"(prefers-color-scheme: dark)\").matches?\"dark\":\"light\");\n document.documentElement.setAttribute(\"data-tempest-theme\",resolved);\n}catch(e){}})();\n`.trim();\n}\n"],"mappings":";;AAeA,SAAgBA,EAAgB,IAAkC,CAAC,GAAkB;CACjF,IAAM,EAAE,gBAAa,iBAAiB,kBAAe,aAAaC;CAElE,IAAI,OAAO,SAAW,KAClB,OAAOC,MAAiB,SAAS,SAAS;CAI9C,IAAMC,IADSC,EAAQ,OAAOC,CACN,KAAUH;CAGlC,OAFIC,MAAS,UAAUA,MAAS,UAAgBA,IAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,UAAU,SAAS;AAChF;AAUA,SAAgBG,EAAgB,IAAkC,CAAC,GAAW;CAC1E,IAAMD,IAAaJ,EAAQ,cAAc,iBACnCC,IAAeD,EAAQ,gBAAgB;CAC7C,OAAO;;YAEC,KAAK,UAAUI,CAAU,EAAE;YAC3B,KAAK,UAAUH,CAAY,EAAE;;;;;;EAMvC,KAAK;AACP"}
1
+ {"version":3,"file":"initial-theme.js","names":["getInitialTheme","options","defaultTheme","mode","storage","storageKey","scriptLiteral","value","char","themeInitScript"],"sources":["../../src/theme/initial-theme.ts"],"sourcesContent":["import { storage } from \"@/utils/storage\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface GetInitialThemeOptions {\n /** localStorage key. Default: `\"tempest-theme\"`. */\n storageKey?: string;\n /** Theme used when nothing is stored and the user has no system preference. */\n defaultTheme?: ThemeMode;\n}\n\n/**\n * Inline-safe theme resolution intended for early bootstrap (e.g. inside the\n * `<head>` script that prevents the flash of incorrect theme). Reads from\n * localStorage and falls back to `prefers-color-scheme`.\n */\nexport function getInitialTheme(options: GetInitialThemeOptions = {}): ResolvedTheme {\n const { storageKey = \"tempest-theme\", defaultTheme = \"system\" } = options;\n\n if (typeof window === \"undefined\") {\n return defaultTheme === \"dark\" ? \"dark\" : \"light\";\n }\n\n const stored = storage.getRaw(storageKey) as ThemeMode | null;\n const mode: ThemeMode = stored ?? defaultTheme;\n if (mode === \"dark\" || mode === \"light\") return mode;\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * A string as a JavaScript literal that is also safe inside an HTML `<script>`.\n *\n * `JSON.stringify` produces a valid literal but leaves `<`, `>`, `&` and the\n * line separators U+2028/U+2029 as they are, and the HTML parser ends a script\n * element at the first `</script` whatever JavaScript thinks of it: a\n * `storageKey` of `\"</script><script>alert(1)</script>\"` closed the snippet and\n * ran the second script. Each of those characters is written as its `\\uXXXX`\n * escape, which JavaScript reads back to the same string.\n *\n * @param value - The option value to embed.\n * @returns The quoted, escaped literal.\n */\nfunction scriptLiteral(value: string): string {\n return JSON.stringify(value).replace(\n /[<>&\\u2028\\u2029]/g,\n (char) => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, \"0\")}`,\n );\n}\n\n/**\n * Plain HTML snippet that sets `data-tempest-theme` on `<html>` before React\n * hydrates. Inline this in `<head>` to avoid a flash of the wrong theme on\n * first paint.\n *\n * Both options are embedded through an escaper that keeps them inside the\n * `<script>` element whatever they contain, so the snippet is safe to inline\n * even when the storage key comes from configuration.\n *\n * @example\n * <script dangerouslySetInnerHTML={{ __html: themeInitScript() }} />\n */\nexport function themeInitScript(options: GetInitialThemeOptions = {}): string {\n const storageKey = options.storageKey ?? \"tempest-theme\";\n const defaultTheme = options.defaultTheme ?? \"system\";\n return `\n(function(){try{\n var key=${scriptLiteral(storageKey)};\n var def=${scriptLiteral(defaultTheme)};\n var stored=localStorage.getItem(key);\n var mode=stored||def;\n var resolved=mode===\"dark\"||mode===\"light\"?mode:(matchMedia(\"(prefers-color-scheme: dark)\").matches?\"dark\":\"light\");\n document.documentElement.setAttribute(\"data-tempest-theme\",resolved);\n}catch(e){}})();\n`.trim();\n}\n"],"mappings":";;AAeA,SAAgBA,EAAgB,IAAkC,CAAC,GAAkB;CACjF,IAAM,EAAE,gBAAa,iBAAiB,kBAAe,aAAaC;CAElE,IAAI,OAAO,SAAW,KAClB,OAAOC,MAAiB,SAAS,SAAS;CAI9C,IAAMC,IADSC,EAAQ,OAAOC,CACN,KAAUH;CAGlC,OAFIC,MAAS,UAAUA,MAAS,UAAgBA,IAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,UAAU,SAAS;AAChF;AAeA,SAASG,EAAc,GAAuB;CAC1C,OAAO,KAAK,UAAUC,CAAK,CAAC,CAAC,QACzB,uBACC,MAAS,MAAMC,EAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,GACnE;AACJ;AAcA,SAAgBC,EAAgB,IAAkC,CAAC,GAAW;CAC1E,IAAMJ,IAAaJ,EAAQ,cAAc,iBACnCC,IAAeD,EAAQ,gBAAgB;CAC7C,OAAO;;YAECK,EAAcD,CAAU,EAAE;YAC1BC,EAAcJ,CAAY,EAAE;;;;;;EAMtC,KAAK;AACP"}
@@ -1,2 +1,2 @@
1
- const e=require("./storage.cjs"),t=require("./base64.cjs");let n=require("fflate");var r=`~tgz1:`;function i(e){let i=JSON.stringify(e),a=(0,n.gzipSync)(new TextEncoder().encode(i));return`${r}${t.bytesToBase64(a)}`}function a(e){if(!e.startsWith(r))return JSON.parse(e);let i=(0,n.gunzipSync)(t.base64ToBytes(e.slice(6)));return JSON.parse(new TextDecoder().decode(i))}var o={serialize:i,deserialize:a},s=e.createJsonStorage(o);exports.compressToString=i,exports.compressedStorage=s,exports.compressedStorageCodec=o,exports.decompressFromString=a;
1
+ const e=require("./storage.cjs"),t=require("./base64.cjs");let n=require("fflate");var r=`~tgz1:`;function i(e){let i=JSON.stringify(e),a=(0,n.gzipSync)(new TextEncoder().encode(i));return`${r}${t.bytesToBase64(a)}`}var a=67108864;function o(e){if(!e.startsWith(r))return JSON.parse(e);let i=t.base64ToBytes(e.slice(6));if(i.byteLength>=4){let e=new DataView(i.buffer,i.byteOffset,i.byteLength).getUint32(i.byteLength-4,!0);if(e>a)throw RangeError(`Compressed value declares ${e} bytes once inflated, above the ${a}-byte limit.`)}let o=(0,n.gunzipSync)(i);return JSON.parse(new TextDecoder().decode(o))}var s={serialize:i,deserialize:o},c=e.createJsonStorage(s);exports.compressToString=i,exports.compressedStorage=c,exports.compressedStorageCodec=s,exports.decompressFromString=o;
2
2
  //# sourceMappingURL=compressed-storage.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"compressed-storage.cjs","names":["gzipSync","bytesToBase64","gunzipSync","base64ToBytes","createJsonStorage","require_storage","require_base64","fflate","MARKER","compressToString","json","value","bytes","decompressFromString","raw","compressedStorageCodec","compressedStorage"],"sources":["../../src/utils/compressed-storage.ts"],"sourcesContent":["import { gunzipSync, gzipSync } from \"fflate\";\n\nimport { base64ToBytes, bytesToBase64 } from \"./base64\";\n\nimport { createJsonStorage, type JsonStorage, type StorageCodec } from \"./storage\";\n\n/**\n * Gzip-backed `localStorage`, for payloads that would otherwise eat the quota.\n *\n * Kept in its own module rather than folded into {@link storage} so that\n * importing the plain typed wrapper never drags `fflate` into the bundle.\n *\n * @tempest-limits empty-catch — every operation here is best-effort by\n * contract, exactly like {@link storage}. `localStorage` throws on quota\n * exhaustion, in Safari private mode, and when a cross-origin frame has storage\n * blocked; a caller persisting state has no recovery to run and no message to\n * show, so the write is dropped and the value stays in memory for the session.\n */\n\n/**\n * Prefix stamped on every value this module writes.\n *\n * It makes the format self-describing, which is what lets a read fall back to\n * plain JSON: a key that predates compression — or that a degraded write stored\n * uncompressed — is still readable, so turning compression on for an existing\n * key does not orphan the data already there. The character also cannot open a\n * JSON document, so the two cases can never be confused.\n */\nconst MARKER = \"~tgz1:\";\n\n/**\n * Serialize a value to a gzipped, base64 string carrying the format marker.\n *\n * Base64 costs a third more characters than the raw compressed bytes, and\n * `localStorage` bills two bytes per character on top of that. Packing the\n * bytes into UTF-16 code units directly would be denser, but lone surrogates\n * survive neither every storage implementation nor a JSON round-trip, and a\n * save that decodes to garbage is far worse than one that is bigger. Even with\n * that overhead a typical JSON document lands well under a third of its\n * uncompressed size.\n *\n * @typeParam T - The value being stored.\n * @param value - Any JSON-serializable value.\n * @returns The encoded string, ready for `localStorage`.\n */\nexport function compressToString<T>(value: T): string {\n const json = JSON.stringify(value);\n const bytes = gzipSync(new TextEncoder().encode(json));\n return `${MARKER}${bytesToBase64(bytes)}`;\n}\n\n/**\n * Decode a string produced by {@link compressToString}.\n *\n * A string without the marker is parsed as plain JSON, so values written before\n * compression was enabled — or by a write that fell back after `gzipSync`\n * failed — still read back.\n *\n * @typeParam T - The expected value shape.\n * @param raw - The stored string.\n * @returns The decoded value.\n * @throws If the payload is neither valid compressed data nor valid JSON.\n */\nexport function decompressFromString<T>(raw: string): T {\n if (!raw.startsWith(MARKER)) return JSON.parse(raw) as T;\n const bytes = gunzipSync(base64ToBytes(raw.slice(MARKER.length)));\n return JSON.parse(new TextDecoder().decode(bytes)) as T;\n}\n\n/**\n * Codec pair for {@link useLocalStorage}, so a compressed key gets the hook's\n * cross-tab sync and SSR guard for free.\n *\n * @example\n * const [save, setSave] = useLocalStorage(\"save\", EMPTY_SAVE, compressedStorageCodec);\n */\nexport const compressedStorageCodec: StorageCodec = {\n serialize: compressToString,\n deserialize: decompressFromString,\n};\n\n/**\n * Typed `localStorage` wrapper that gzips what it writes.\n *\n * {@link createJsonStorage} with {@link compressedStorageCodec}, so it is the\n * same implementation as {@link storage} and genuinely interchangeable with it —\n * `get`, `set` and `remove`, differing only in how the value is encoded. It used\n * to be a hand-written copy that had no `remove`, which made that promise false\n * for anybody who took the docstring at its word.\n *\n * When compression itself fails the value is written as plain JSON rather than\n * dropped: a slightly larger record still loads, an absent one does not. Only a\n * storage-level failure — quota, blocked storage — loses the write.\n */\nexport const compressedStorage: JsonStorage = createJsonStorage(compressedStorageCodec);\n"],"mappings":"A,mFA4BA,IAAMQ,EAAS,SAiBf,SAAgBC,EAAoB,EAAkB,CAClD,IAAMC,EAAO,KAAK,UAAUC,CAAK,EAC3BC,GAAA,EAAQZ,EAAAA,SAAAA,CAAS,IAAI,YAAY,CAAC,CAAC,OAAOU,CAAI,CAAC,EACrD,MAAO,GAAGF,IAASP,EAAAA,cAAcW,CAAK,GAC1C,CAcA,SAAgBC,EAAwB,EAAgB,CACpD,GAAI,CAACC,EAAI,WAAWN,CAAM,EAAG,OAAO,KAAK,MAAMM,CAAG,EAClD,IAAMF,GAAA,EAAQV,EAAAA,WAAAA,CAAWC,EAAAA,cAAcW,EAAI,MAAM,CAAa,CAAC,CAAC,EAChE,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAOF,CAAK,CAAC,CACrD,CASA,IAAaG,EAAuC,CAChD,UAAWN,EACX,YAAaI,CACjB,EAeaG,EAAiCZ,EAAAA,kBAAkBW,CAAsB,E"}
1
+ {"version":3,"file":"compressed-storage.cjs","names":["gzipSync","bytesToBase64","base64ToBytes","gunzipSync","createJsonStorage","require_storage","require_base64","fflate","MARKER","compressToString","json","value","bytes","MAX_DECOMPRESSED_BYTES","decompressFromString","raw","compressed","size","compressedStorageCodec","compressedStorage"],"sources":["../../src/utils/compressed-storage.ts"],"sourcesContent":["import { gunzipSync, gzipSync } from \"fflate\";\n\nimport { base64ToBytes, bytesToBase64 } from \"./base64\";\n\nimport { createJsonStorage, type JsonStorage, type StorageCodec } from \"./storage\";\n\n/**\n * Gzip-backed `localStorage`, for payloads that would otherwise eat the quota.\n *\n * Kept in its own module rather than folded into {@link storage} so that\n * importing the plain typed wrapper never drags `fflate` into the bundle.\n *\n * @tempest-limits empty-catch — every operation here is best-effort by\n * contract, exactly like {@link storage}. `localStorage` throws on quota\n * exhaustion, in Safari private mode, and when a cross-origin frame has storage\n * blocked; a caller persisting state has no recovery to run and no message to\n * show, so the write is dropped and the value stays in memory for the session.\n */\n\n/**\n * Prefix stamped on every value this module writes.\n *\n * It makes the format self-describing, which is what lets a read fall back to\n * plain JSON: a key that predates compression — or that a degraded write stored\n * uncompressed — is still readable, so turning compression on for an existing\n * key does not orphan the data already there. The character also cannot open a\n * JSON document, so the two cases can never be confused.\n */\nconst MARKER = \"~tgz1:\";\n\n/**\n * Serialize a value to a gzipped, base64 string carrying the format marker.\n *\n * Base64 costs a third more characters than the raw compressed bytes, and\n * `localStorage` bills two bytes per character on top of that. Packing the\n * bytes into UTF-16 code units directly would be denser, but lone surrogates\n * survive neither every storage implementation nor a JSON round-trip, and a\n * save that decodes to garbage is far worse than one that is bigger. Even with\n * that overhead a typical JSON document lands well under a third of its\n * uncompressed size.\n *\n * @typeParam T - The value being stored.\n * @param value - Any JSON-serializable value.\n * @returns The encoded string, ready for `localStorage`.\n */\nexport function compressToString<T>(value: T): string {\n const json = JSON.stringify(value);\n const bytes = gzipSync(new TextEncoder().encode(json));\n return `${MARKER}${bytesToBase64(bytes)}`;\n}\n\n/**\n * Largest decompressed size {@link decompressFromString} will allocate: 64 MiB.\n *\n * Deflate reaches ~1000:1 on repetitive input (measured with fflate: 20 MB of\n * `\"A\"` gzips to 19 551 bytes, ratio 1023), so the ~5 MB a `localStorage` origin\n * holds could ask for gigabytes on read. A legitimate value is far below this:\n * it had to pass through `JSON.stringify` and `gzipSync` in the same tab first.\n */\nconst MAX_DECOMPRESSED_BYTES = 64 * 1024 * 1024;\n\n/**\n * Decode a string produced by {@link compressToString}.\n *\n * A string without the marker is parsed as plain JSON, so values written before\n * compression was enabled — or by a write that fell back after `gzipSync`\n * failed — still read back.\n *\n * The output size is bounded before anything is inflated. fflate sizes its\n * output buffer from the gzip trailer (`ISIZE`) and never grows it — measured: a\n * 1 MB payload whose trailer claims 10 bytes inflates to exactly 10 — so\n * refusing a trailer above {@link MAX_DECOMPRESSED_BYTES} bounds the allocation,\n * whatever the trailer says about the real data. A corrupted or hostile value\n * fails fast instead of taking the tab's memory.\n *\n * @typeParam T - The expected value shape.\n * @param raw - The stored string.\n * @returns The decoded value.\n * @throws If the payload is neither valid compressed data nor valid JSON, or\n * declares a decompressed size above 64 MiB.\n */\nexport function decompressFromString<T>(raw: string): T {\n if (!raw.startsWith(MARKER)) return JSON.parse(raw) as T;\n const compressed = base64ToBytes(raw.slice(MARKER.length));\n if (compressed.byteLength >= 4) {\n const trailer = new DataView(\n compressed.buffer,\n compressed.byteOffset,\n compressed.byteLength,\n );\n const size = trailer.getUint32(compressed.byteLength - 4, true);\n if (size > MAX_DECOMPRESSED_BYTES) {\n throw new RangeError(\n `Compressed value declares ${size} bytes once inflated, above the ` +\n `${MAX_DECOMPRESSED_BYTES}-byte limit.`,\n );\n }\n }\n const bytes = gunzipSync(compressed);\n return JSON.parse(new TextDecoder().decode(bytes)) as T;\n}\n\n/**\n * Codec pair for {@link useLocalStorage}, so a compressed key gets the hook's\n * cross-tab sync and SSR guard for free.\n *\n * @example\n * const [save, setSave] = useLocalStorage(\"save\", EMPTY_SAVE, compressedStorageCodec);\n */\nexport const compressedStorageCodec: StorageCodec = {\n serialize: compressToString,\n deserialize: decompressFromString,\n};\n\n/**\n * Typed `localStorage` wrapper that gzips what it writes.\n *\n * {@link createJsonStorage} with {@link compressedStorageCodec}, so it is the\n * same implementation as {@link storage} and genuinely interchangeable with it —\n * `get`, `set` and `remove`, differing only in how the value is encoded. It used\n * to be a hand-written copy that had no `remove`, which made that promise false\n * for anybody who took the docstring at its word.\n *\n * When compression itself fails the value is written as plain JSON rather than\n * dropped: a slightly larger record still loads, an absent one does not. Only a\n * storage-level failure — quota, blocked storage — loses the write.\n */\nexport const compressedStorage: JsonStorage = createJsonStorage(compressedStorageCodec);\n"],"mappings":"A,mFA4BA,IAAMQ,EAAS,SAiBf,SAAgBC,EAAoB,EAAkB,CAClD,IAAMC,EAAO,KAAK,UAAUC,CAAK,EAC3BC,GAAA,EAAQZ,EAAAA,SAAAA,CAAS,IAAI,YAAY,CAAC,CAAC,OAAOU,CAAI,CAAC,EACrD,MAAO,GAAGF,IAASP,EAAAA,cAAcW,CAAK,GAC1C,CAUA,IAAMC,EAAyB,SAsB/B,SAAgBC,EAAwB,EAAgB,CACpD,GAAI,CAACC,EAAI,WAAWP,CAAM,EAAG,OAAO,KAAK,MAAMO,CAAG,EAClD,IAAMC,EAAad,EAAAA,cAAca,EAAI,MAAM,CAAa,CAAC,EACzD,GAAIC,EAAW,YAAc,EAAG,CAM5B,IAAMC,EAAO,IALO,SAChBD,EAAW,OACXA,EAAW,WACXA,EAAW,UAEF,CAAA,CAAQ,UAAUA,EAAW,WAAa,EAAG,EAAI,EAC9D,GAAIC,EAAOJ,EACP,MAAU,WACN,6BAA6BI,EAAK,kCAC3BJ,EAAuB,aAClC,CAER,CACA,IAAMD,GAAA,EAAQT,EAAAA,WAAAA,CAAWa,CAAU,EACnC,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAOJ,CAAK,CAAC,CACrD,CASA,IAAaM,EAAuC,CAChD,UAAWT,EACX,YAAaK,CACjB,EAeaK,EAAiCf,EAAAA,kBAAkBc,CAAsB,E"}
@@ -7,16 +7,22 @@ function o(e) {
7
7
  let t = JSON.stringify(e), r = i(new TextEncoder().encode(t));
8
8
  return `${a}${n(r)}`;
9
9
  }
10
- function s(e) {
10
+ var s = 67108864;
11
+ function c(e) {
11
12
  if (!e.startsWith(a)) return JSON.parse(e);
12
- let n = r(t(e.slice(6)));
13
- return JSON.parse(new TextDecoder().decode(n));
13
+ let n = t(e.slice(6));
14
+ if (n.byteLength >= 4) {
15
+ let e = new DataView(n.buffer, n.byteOffset, n.byteLength).getUint32(n.byteLength - 4, !0);
16
+ if (e > s) throw RangeError(`Compressed value declares ${e} bytes once inflated, above the ${s}-byte limit.`);
17
+ }
18
+ let i = r(n);
19
+ return JSON.parse(new TextDecoder().decode(i));
14
20
  }
15
- var c = {
21
+ var l = {
16
22
  serialize: o,
17
- deserialize: s
18
- }, l = e(c);
23
+ deserialize: c
24
+ }, u = e(l);
19
25
  //#endregion
20
- export { o as compressToString, l as compressedStorage, c as compressedStorageCodec, s as decompressFromString };
26
+ export { o as compressToString, u as compressedStorage, l as compressedStorageCodec, c as decompressFromString };
21
27
 
22
28
  //# sourceMappingURL=compressed-storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compressed-storage.js","names":["MARKER","compressToString","json","value","bytes","gzipSync","bytesToBase64","decompressFromString","raw","gunzipSync","base64ToBytes","compressedStorageCodec","compressedStorage","createJsonStorage"],"sources":["../../src/utils/compressed-storage.ts"],"sourcesContent":["import { gunzipSync, gzipSync } from \"fflate\";\n\nimport { base64ToBytes, bytesToBase64 } from \"./base64\";\n\nimport { createJsonStorage, type JsonStorage, type StorageCodec } from \"./storage\";\n\n/**\n * Gzip-backed `localStorage`, for payloads that would otherwise eat the quota.\n *\n * Kept in its own module rather than folded into {@link storage} so that\n * importing the plain typed wrapper never drags `fflate` into the bundle.\n *\n * @tempest-limits empty-catch — every operation here is best-effort by\n * contract, exactly like {@link storage}. `localStorage` throws on quota\n * exhaustion, in Safari private mode, and when a cross-origin frame has storage\n * blocked; a caller persisting state has no recovery to run and no message to\n * show, so the write is dropped and the value stays in memory for the session.\n */\n\n/**\n * Prefix stamped on every value this module writes.\n *\n * It makes the format self-describing, which is what lets a read fall back to\n * plain JSON: a key that predates compression — or that a degraded write stored\n * uncompressed — is still readable, so turning compression on for an existing\n * key does not orphan the data already there. The character also cannot open a\n * JSON document, so the two cases can never be confused.\n */\nconst MARKER = \"~tgz1:\";\n\n/**\n * Serialize a value to a gzipped, base64 string carrying the format marker.\n *\n * Base64 costs a third more characters than the raw compressed bytes, and\n * `localStorage` bills two bytes per character on top of that. Packing the\n * bytes into UTF-16 code units directly would be denser, but lone surrogates\n * survive neither every storage implementation nor a JSON round-trip, and a\n * save that decodes to garbage is far worse than one that is bigger. Even with\n * that overhead a typical JSON document lands well under a third of its\n * uncompressed size.\n *\n * @typeParam T - The value being stored.\n * @param value - Any JSON-serializable value.\n * @returns The encoded string, ready for `localStorage`.\n */\nexport function compressToString<T>(value: T): string {\n const json = JSON.stringify(value);\n const bytes = gzipSync(new TextEncoder().encode(json));\n return `${MARKER}${bytesToBase64(bytes)}`;\n}\n\n/**\n * Decode a string produced by {@link compressToString}.\n *\n * A string without the marker is parsed as plain JSON, so values written before\n * compression was enabled — or by a write that fell back after `gzipSync`\n * failed — still read back.\n *\n * @typeParam T - The expected value shape.\n * @param raw - The stored string.\n * @returns The decoded value.\n * @throws If the payload is neither valid compressed data nor valid JSON.\n */\nexport function decompressFromString<T>(raw: string): T {\n if (!raw.startsWith(MARKER)) return JSON.parse(raw) as T;\n const bytes = gunzipSync(base64ToBytes(raw.slice(MARKER.length)));\n return JSON.parse(new TextDecoder().decode(bytes)) as T;\n}\n\n/**\n * Codec pair for {@link useLocalStorage}, so a compressed key gets the hook's\n * cross-tab sync and SSR guard for free.\n *\n * @example\n * const [save, setSave] = useLocalStorage(\"save\", EMPTY_SAVE, compressedStorageCodec);\n */\nexport const compressedStorageCodec: StorageCodec = {\n serialize: compressToString,\n deserialize: decompressFromString,\n};\n\n/**\n * Typed `localStorage` wrapper that gzips what it writes.\n *\n * {@link createJsonStorage} with {@link compressedStorageCodec}, so it is the\n * same implementation as {@link storage} and genuinely interchangeable with it —\n * `get`, `set` and `remove`, differing only in how the value is encoded. It used\n * to be a hand-written copy that had no `remove`, which made that promise false\n * for anybody who took the docstring at its word.\n *\n * When compression itself fails the value is written as plain JSON rather than\n * dropped: a slightly larger record still loads, an absent one does not. Only a\n * storage-level failure — quota, blocked storage — loses the write.\n */\nexport const compressedStorage: JsonStorage = createJsonStorage(compressedStorageCodec);\n"],"mappings":";;;;AA4BA,IAAMA,IAAS;AAiBf,SAAgBC,EAAoB,GAAkB;CAClD,IAAMC,IAAO,KAAK,UAAUC,CAAK,GAC3BC,IAAQC,EAAS,IAAI,YAAY,CAAC,CAAC,OAAOH,CAAI,CAAC;CACrD,OAAO,GAAGF,IAASM,EAAcF,CAAK;AAC1C;AAcA,SAAgBG,EAAwB,GAAgB;CACpD,IAAI,CAACC,EAAI,WAAWR,CAAM,GAAG,OAAO,KAAK,MAAMQ,CAAG;CAClD,IAAMJ,IAAQK,EAAWC,EAAcF,EAAI,MAAM,CAAa,CAAC,CAAC;CAChE,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAOJ,CAAK,CAAC;AACrD;AASA,IAAaO,IAAuC;CAChD,WAAWV;CACX,aAAaM;AACjB,GAeaK,IAAiCC,EAAkBF,CAAsB"}
1
+ {"version":3,"file":"compressed-storage.js","names":["MARKER","compressToString","json","value","bytes","gzipSync","bytesToBase64","MAX_DECOMPRESSED_BYTES","decompressFromString","raw","compressed","base64ToBytes","size","gunzipSync","compressedStorageCodec","compressedStorage","createJsonStorage"],"sources":["../../src/utils/compressed-storage.ts"],"sourcesContent":["import { gunzipSync, gzipSync } from \"fflate\";\n\nimport { base64ToBytes, bytesToBase64 } from \"./base64\";\n\nimport { createJsonStorage, type JsonStorage, type StorageCodec } from \"./storage\";\n\n/**\n * Gzip-backed `localStorage`, for payloads that would otherwise eat the quota.\n *\n * Kept in its own module rather than folded into {@link storage} so that\n * importing the plain typed wrapper never drags `fflate` into the bundle.\n *\n * @tempest-limits empty-catch — every operation here is best-effort by\n * contract, exactly like {@link storage}. `localStorage` throws on quota\n * exhaustion, in Safari private mode, and when a cross-origin frame has storage\n * blocked; a caller persisting state has no recovery to run and no message to\n * show, so the write is dropped and the value stays in memory for the session.\n */\n\n/**\n * Prefix stamped on every value this module writes.\n *\n * It makes the format self-describing, which is what lets a read fall back to\n * plain JSON: a key that predates compression — or that a degraded write stored\n * uncompressed — is still readable, so turning compression on for an existing\n * key does not orphan the data already there. The character also cannot open a\n * JSON document, so the two cases can never be confused.\n */\nconst MARKER = \"~tgz1:\";\n\n/**\n * Serialize a value to a gzipped, base64 string carrying the format marker.\n *\n * Base64 costs a third more characters than the raw compressed bytes, and\n * `localStorage` bills two bytes per character on top of that. Packing the\n * bytes into UTF-16 code units directly would be denser, but lone surrogates\n * survive neither every storage implementation nor a JSON round-trip, and a\n * save that decodes to garbage is far worse than one that is bigger. Even with\n * that overhead a typical JSON document lands well under a third of its\n * uncompressed size.\n *\n * @typeParam T - The value being stored.\n * @param value - Any JSON-serializable value.\n * @returns The encoded string, ready for `localStorage`.\n */\nexport function compressToString<T>(value: T): string {\n const json = JSON.stringify(value);\n const bytes = gzipSync(new TextEncoder().encode(json));\n return `${MARKER}${bytesToBase64(bytes)}`;\n}\n\n/**\n * Largest decompressed size {@link decompressFromString} will allocate: 64 MiB.\n *\n * Deflate reaches ~1000:1 on repetitive input (measured with fflate: 20 MB of\n * `\"A\"` gzips to 19 551 bytes, ratio 1023), so the ~5 MB a `localStorage` origin\n * holds could ask for gigabytes on read. A legitimate value is far below this:\n * it had to pass through `JSON.stringify` and `gzipSync` in the same tab first.\n */\nconst MAX_DECOMPRESSED_BYTES = 64 * 1024 * 1024;\n\n/**\n * Decode a string produced by {@link compressToString}.\n *\n * A string without the marker is parsed as plain JSON, so values written before\n * compression was enabled — or by a write that fell back after `gzipSync`\n * failed — still read back.\n *\n * The output size is bounded before anything is inflated. fflate sizes its\n * output buffer from the gzip trailer (`ISIZE`) and never grows it — measured: a\n * 1 MB payload whose trailer claims 10 bytes inflates to exactly 10 — so\n * refusing a trailer above {@link MAX_DECOMPRESSED_BYTES} bounds the allocation,\n * whatever the trailer says about the real data. A corrupted or hostile value\n * fails fast instead of taking the tab's memory.\n *\n * @typeParam T - The expected value shape.\n * @param raw - The stored string.\n * @returns The decoded value.\n * @throws If the payload is neither valid compressed data nor valid JSON, or\n * declares a decompressed size above 64 MiB.\n */\nexport function decompressFromString<T>(raw: string): T {\n if (!raw.startsWith(MARKER)) return JSON.parse(raw) as T;\n const compressed = base64ToBytes(raw.slice(MARKER.length));\n if (compressed.byteLength >= 4) {\n const trailer = new DataView(\n compressed.buffer,\n compressed.byteOffset,\n compressed.byteLength,\n );\n const size = trailer.getUint32(compressed.byteLength - 4, true);\n if (size > MAX_DECOMPRESSED_BYTES) {\n throw new RangeError(\n `Compressed value declares ${size} bytes once inflated, above the ` +\n `${MAX_DECOMPRESSED_BYTES}-byte limit.`,\n );\n }\n }\n const bytes = gunzipSync(compressed);\n return JSON.parse(new TextDecoder().decode(bytes)) as T;\n}\n\n/**\n * Codec pair for {@link useLocalStorage}, so a compressed key gets the hook's\n * cross-tab sync and SSR guard for free.\n *\n * @example\n * const [save, setSave] = useLocalStorage(\"save\", EMPTY_SAVE, compressedStorageCodec);\n */\nexport const compressedStorageCodec: StorageCodec = {\n serialize: compressToString,\n deserialize: decompressFromString,\n};\n\n/**\n * Typed `localStorage` wrapper that gzips what it writes.\n *\n * {@link createJsonStorage} with {@link compressedStorageCodec}, so it is the\n * same implementation as {@link storage} and genuinely interchangeable with it —\n * `get`, `set` and `remove`, differing only in how the value is encoded. It used\n * to be a hand-written copy that had no `remove`, which made that promise false\n * for anybody who took the docstring at its word.\n *\n * When compression itself fails the value is written as plain JSON rather than\n * dropped: a slightly larger record still loads, an absent one does not. Only a\n * storage-level failure — quota, blocked storage — loses the write.\n */\nexport const compressedStorage: JsonStorage = createJsonStorage(compressedStorageCodec);\n"],"mappings":";;;;AA4BA,IAAMA,IAAS;AAiBf,SAAgBC,EAAoB,GAAkB;CAClD,IAAMC,IAAO,KAAK,UAAUC,CAAK,GAC3BC,IAAQC,EAAS,IAAI,YAAY,CAAC,CAAC,OAAOH,CAAI,CAAC;CACrD,OAAO,GAAGF,IAASM,EAAcF,CAAK;AAC1C;AAUA,IAAMG,IAAyB;AAsB/B,SAAgBC,EAAwB,GAAgB;CACpD,IAAI,CAACC,EAAI,WAAWT,CAAM,GAAG,OAAO,KAAK,MAAMS,CAAG;CAClD,IAAMC,IAAaC,EAAcF,EAAI,MAAM,CAAa,CAAC;CACzD,IAAIC,EAAW,cAAc,GAAG;EAM5B,IAAME,IAAO,IALO,SAChBF,EAAW,QACXA,EAAW,YACXA,EAAW,UAEF,CAAA,CAAQ,UAAUA,EAAW,aAAa,GAAG,EAAI;EAC9D,IAAIE,IAAOL,GACP,MAAU,WACN,6BAA6BK,EAAK,kCAC3BL,EAAuB,aAClC;CAER;CACA,IAAMH,IAAQS,EAAWH,CAAU;CACnC,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC,OAAON,CAAK,CAAC;AACrD;AASA,IAAaU,IAAuC;CAChD,WAAWb;CACX,aAAaO;AACjB,GAeaO,IAAiCC,EAAkBF,CAAsB"}
@@ -1,2 +1,2 @@
1
- const e=require("./absent.cjs"),t=require("./intl-cache.cjs");function n(n,r){return e.isAbsent(n)||!Number.isFinite(n)?e.fallbackText(r):t.numberFormat(`pt-BR`,{style:`currency`,currency:`BRL`}).format(n)}function r(n,r){if(e.isAbsent(n))return e.fallbackText(r);let i=typeof n==`string`?new Date(n):n;return Number.isNaN(i.getTime())?``:t.dateTimeFormat(`pt-BR`).format(i)}function i(t,n){if(e.isAbsent(t))return e.fallbackText(n,``);if(typeof t==`string`&&/^\d{4}-\d{2}-\d{2}$/.test(t))return t;let r=typeof t==`string`?new Date(t):t;if(Number.isNaN(r.getTime()))return``;let i=`${r.getMonth()+1}`.padStart(2,`0`),a=`${r.getDate()}`.padStart(2,`0`);return`${r.getFullYear()}-${i}-${a}`}function a(t,n){if(e.isAbsent(t))return e.fallbackText(n,``);if(typeof t==`string`&&/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(t))return t;let r=typeof t==`string`?new Date(t):t;if(Number.isNaN(r.getTime()))return``;let i=`${r.getMonth()+1}`.padStart(2,`0`),a=`${r.getDate()}`.padStart(2,`0`),o=`${r.getHours()}`.padStart(2,`0`),s=`${r.getMinutes()}`.padStart(2,`0`);return`${r.getFullYear()}-${i}-${a}T${o}:${s}`}function o(n,r){if(e.isAbsent(n))return e.fallbackText(r);let i=typeof n==`string`?new Date(n):n;return Number.isNaN(i.getTime())?``:t.dateTimeFormat(`pt-BR`,{dateStyle:`short`,timeStyle:`short`}).format(i)}function s(e,t={}){let n=e.replace(/\D/g,``).slice(0,11);if(!t.mobile)return n.length<=10?n.replace(/(\d{2})(\d)/,`($1) $2`).replace(/(\d{4})(\d)/,`$1-$2`):n.replace(/(\d{2})(\d)/,`($1) $2`).replace(/(\d{5})(\d)/,`$1-$2`);if(n.length<=2)return n;let r=n.slice(0,2),i=n.slice(2);i[0]!==`9`&&(i=`9${i}`),i=i.slice(0,9);let a=i.slice(0,5),o=i.slice(5);return o?`(${r}) ${a}-${o}`:`(${r}) ${a}`}function c(e){return e.replace(/\D/g,``).slice(0,11).replace(/(\d{3})(\d)/,`$1.$2`).replace(/(\d{3})(\d)/,`$1.$2`).replace(/(\d{3})(\d{1,2})$/,`$1-$2`)}function l(n,r={}){if(e.isAbsent(n)||!Number.isFinite(n))return e.fallbackText(r);let i=r.decimals??1;return t.numberFormat(`pt-BR`,{style:`percent`,minimumFractionDigits:i,maximumFractionDigits:i}).format(n)}exports.formatCPF=c,exports.formatCurrency=n,exports.formatDate=r,exports.formatDateForInput=i,exports.formatDateTime=o,exports.formatDateTimeForInput=a,exports.formatPercent=l,exports.formatPhone=s;
1
+ const e=require("./intl-cache.cjs"),t=require("./absent.cjs");function n(n,r){return t.isAbsent(n)||!Number.isFinite(n)?t.fallbackText(r):e.numberFormat(`pt-BR`,{style:`currency`,currency:`BRL`}).format(n)}function r(n,r){if(t.isAbsent(n))return t.fallbackText(r);let i=typeof n==`string`?new Date(n):n;return Number.isNaN(i.getTime())?``:e.dateTimeFormat(`pt-BR`).format(i)}function i(e,n){if(t.isAbsent(e))return t.fallbackText(n,``);if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}$/.test(e))return e;let r=typeof e==`string`?new Date(e):e;if(Number.isNaN(r.getTime()))return``;let i=`${r.getMonth()+1}`.padStart(2,`0`),a=`${r.getDate()}`.padStart(2,`0`);return`${r.getFullYear()}-${i}-${a}`}function a(e,n){if(t.isAbsent(e))return t.fallbackText(n,``);if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(e))return e;let r=typeof e==`string`?new Date(e):e;if(Number.isNaN(r.getTime()))return``;let i=`${r.getMonth()+1}`.padStart(2,`0`),a=`${r.getDate()}`.padStart(2,`0`),o=`${r.getHours()}`.padStart(2,`0`),s=`${r.getMinutes()}`.padStart(2,`0`);return`${r.getFullYear()}-${i}-${a}T${o}:${s}`}function o(n,r){if(t.isAbsent(n))return t.fallbackText(r);let i=typeof n==`string`?new Date(n):n;return Number.isNaN(i.getTime())?``:e.dateTimeFormat(`pt-BR`,{dateStyle:`short`,timeStyle:`short`}).format(i)}function s(e,t={}){let n=e.replace(/\D/g,``).slice(0,11);if(!t.mobile)return n.length<=10?n.replace(/(\d{2})(\d)/,`($1) $2`).replace(/(\d{4})(\d)/,`$1-$2`):n.replace(/(\d{2})(\d)/,`($1) $2`).replace(/(\d{5})(\d)/,`$1-$2`);if(n.length<=2)return n;let r=n.slice(0,2),i=n.slice(2);i[0]!==`9`&&(i=`9${i}`),i=i.slice(0,9);let a=i.slice(0,5),o=i.slice(5);return o?`(${r}) ${a}-${o}`:`(${r}) ${a}`}function c(e){return e.replace(/\D/g,``).slice(0,11).replace(/(\d{3})(\d)/,`$1.$2`).replace(/(\d{3})(\d)/,`$1.$2`).replace(/(\d{3})(\d{1,2})$/,`$1-$2`)}function l(n,r={}){if(t.isAbsent(n)||!Number.isFinite(n))return t.fallbackText(r);let i=r.decimals??1;return e.numberFormat(`pt-BR`,{style:`percent`,minimumFractionDigits:i,maximumFractionDigits:i}).format(n)}exports.formatCPF=c,exports.formatCurrency=n,exports.formatDate=r,exports.formatDateForInput=i,exports.formatDateTime=o,exports.formatDateTimeForInput=a,exports.formatPercent=l,exports.formatPhone=s;
2
2
  //# sourceMappingURL=format.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.cjs","names":["isAbsent","fallbackText","numberFormat","dateTimeFormat","require_absent","require_intl_cache","formatCurrency","value","options","formatDate","date","formatDateForInput","month","day","formatDateTimeForInput","hours","minutes","formatDateTime","formatPhone","digits","area","subscriber","prefix","suffix","formatCPF","formatPercent","decimals"],"sources":["../../src/utils/format.ts"],"sourcesContent":["import { fallbackText, isAbsent, type FormatFallbackOptions } from \"@/utils/absent\";\nimport { dateTimeFormat, numberFormat } from \"@/utils/intl-cache\";\n\n/**\n * Format a number as Brazilian Real currency.\n *\n * An absent or unrepresentable amount renders as `\"—\"` rather than as a number.\n * That branch is a data-correctness fix, not cosmetics: `Intl` coerces `null` to\n * zero, so an amount the backend left out used to reach the screen as\n * **`\"R$ 0,00\"`** — a plausible figure a reader has no way to question — and\n * `undefined` as `\"R$ NaN\"`.\n *\n * @example\n * ```typescript\n * formatCurrency(1234.56); // \"R$ 1.234,56\"\n * formatCurrency(null); // \"—\"\n * formatCurrency(null, { fallback: \"sem valor\" }); // \"sem valor\"\n * ```\n *\n * @param value - The amount in BRL, or `null`/`undefined` when there is none.\n * @param options - Text to render when there is nothing to format.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(\n value: number | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value) || !Number.isFinite(value)) return fallbackText(options);\n return numberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(value);\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy`.\n *\n * Absent input answers `\"—\"`. It used to **throw**: `new Date(null)` is epoch and\n * `null.getTime` is a `TypeError`, so a nullable column — which is every\n * `deleted_at`, `expires_at` and half the `updated_at` a FastAPI backend sends —\n * took the screen down instead of rendering a gap.\n *\n * A value that is present and unparseable still answers `\"\"`, deliberately: on a\n * screen that reads a log, \"nobody filled this in\" and \"what they filled in is\n * broken\" are different findings, and the second is the one worth chasing.\n *\n * @example\n * ```typescript\n * formatDate(\"2026-05-16T12:00:00Z\"); // \"16/05/2026\"\n * formatDate(null); // \"—\"\n * formatDate(\"banana\"); // \"\"\n * ```\n *\n * @param value - ISO string, Date, or `null`/`undefined`.\n * @param options - Text to render when the value is absent.\n * @returns Formatted date string, the fallback when absent, or empty string when\n * the value is present and invalid.\n */\nexport function formatDate(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options);\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return dateTimeFormat(\"pt-BR\").format(date);\n}\n\n/**\n * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an\n * `<input type=\"date\">` accepts.\n *\n * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,\n * which is the reflex and which is wrong: `toISOString` converts to UTC first, so\n * anything after 21:00 in UTC-3 reports the next day and the form opens on the\n * wrong date. `formatDate` cannot fill this role because a date input rejects\n * `dd/MM/yyyy` outright.\n *\n * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut\n * is load-bearing rather than an optimisation: `new Date(\"2026-05-16\")` is parsed\n * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the\n * exact value a backend sent would move it back a day.\n *\n * @example\n * <input type=\"date\" defaultValue={formatDateForInput(order.createdAt)} />\n *\n * Absent input answers `\"\"` here — **not** the em dash the reading formatters\n * use — because that is what an input reads as \"no value\"; an em dash would\n * arrive as content the user has to delete before typing a date.\n *\n * @param value - ISO string, Date, or `null`/`undefined`.\n * @param options - Text for an absent value; defaults to `\"\"`.\n * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —\n * which is what a date input reads as \"no value\", unlike `\"Invalid Date\"`.\n */\nexport function formatDateForInput(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options, \"\");\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\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 * Format an ISO date or Date instance as `yyyy-MM-ddTHH:mm`, the value an\n * `<input type=\"datetime-local\">` accepts.\n *\n * The sibling of {@link formatDateForInput}, and it exists for the same reason:\n * `toISOString().slice(0, 16)` is the reflex and it is wrong. `toISOString`\n * converts to UTC first, so a 22:00 appointment in UTC-3 opens the form on the\n * next day at 01:00 — here the trap costs the hour as well as the date.\n *\n * A value already in `yyyy-MM-ddTHH:mm` is returned untouched. A value carrying\n * a zone (`...Z`, `...-03:00`) deliberately does **not** take that shortcut: it\n * is a different instant from the naive string that looks like it, so it is\n * converted to the local calendar parts the input has to show.\n *\n * Seconds are dropped. A `datetime-local` steps by the minute unless the app\n * sets `step`, so a `:ss` the field cannot represent would be silently discarded\n * on the first edit anyway — truncating here keeps the rendered value and the\n * submitted value the same.\n *\n * @example\n * <input\n * type=\"datetime-local\"\n * defaultValue={formatDateTimeForInput(appointment.startsAt)}\n * />\n *\n * @param value - ISO string or Date.\n * @returns The `yyyy-MM-ddTHH:mm` value, or an empty string when the input is\n * invalid — which is what a datetime input reads as \"no value\", unlike\n * `\"Invalid Date\"`.\n */\nexport function formatDateTimeForInput(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options, \"\");\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n const hours = `${date.getHours()}`.padStart(2, \"0\");\n const minutes = `${date.getMinutes()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}T${hours}:${minutes}`;\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.\n *\n * Same contract as {@link formatDate}: absent answers the fallback (`\"—\"` by\n * default) instead of throwing, and present-but-invalid answers `\"\"`.\n *\n * @param value - ISO string, Date, or `null`/`undefined`.\n * @param options - Text to render when the value is absent.\n * @returns Formatted datetime string, the fallback when absent, or empty string\n * when the value is present and invalid.\n */\nexport function formatDateTime(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options);\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return dateTimeFormat(\"pt-BR\", { dateStyle: \"short\", timeStyle: \"short\" }).format(date);\n}\n\nexport interface FormatPhoneOptions {\n /**\n * Treat the number as a mobile line: insert the mandatory `9` after the area\n * code when it is missing, and group the subscriber part `5+4` from the\n * first digit typed instead of waiting for the eleventh.\n *\n * Default `false`, which keeps the length-based behaviour: `4+4` up to ten\n * digits, `5+4` at eleven.\n */\n mobile?: boolean;\n}\n\n/**\n * Apply the Brazilian phone mask `(XX) XXXXX-XXXX` or `(XX) XXXX-XXXX`.\n *\n * By default the grouping is decided by **length**, which is what a field\n * accepting both landlines and mobiles needs.\n *\n * `mobile: true` is for a field that only accepts mobile numbers, and it exists\n * because the default is wrong as an as-you-type mask there. Reading anything up\n * to ten digits as a landline puts the hyphen after the fourth subscriber digit,\n * so a half-typed mobile renders `(11) 9123-4`; it only becomes `(11) 91234-5`\n * once the eleventh digit lands. The separator visibly jumps backwards while the\n * user is still typing. With `mobile`, the same input reads `(11) 91234` and the\n * hyphen never moves. It also inserts the leading `9` every Brazilian mobile\n * carries, so a ten-digit number gets corrected rather than masked as a landline.\n *\n * @param value - Raw digits or partially masked string.\n * @param options - Masking options.\n * @returns Masked phone string.\n *\n * @example\n * formatPhone(\"1191234\"); // \"(11) 9123-4\"\n * formatPhone(\"1191234\", { mobile: true }); // \"(11) 91234\"\n * formatPhone(\"1112345678\", { mobile: true }); // \"(11) 91234-5678\" — 9 inserted\n */\nexport function formatPhone(value: string, options: FormatPhoneOptions = {}): string {\n const digits = value.replace(/\\D/g, \"\").slice(0, 11);\n\n if (!options.mobile) {\n if (digits.length <= 10) {\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{4})(\\d)/, \"$1-$2\");\n }\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{5})(\\d)/, \"$1-$2\");\n }\n\n if (digits.length <= 2) return digits;\n\n const area = digits.slice(0, 2);\n let subscriber = digits.slice(2);\n if (subscriber[0] !== \"9\") subscriber = `9${subscriber}`;\n subscriber = subscriber.slice(0, 9);\n\n const prefix = subscriber.slice(0, 5);\n const suffix = subscriber.slice(5);\n return suffix ? `(${area}) ${prefix}-${suffix}` : `(${area}) ${prefix}`;\n}\n\n/**\n * Apply the Brazilian CPF mask `XXX.XXX.XXX-XX`.\n *\n * @param value - Raw digits or partially masked string.\n * @returns Masked CPF string.\n */\nexport function formatCPF(value: string): string {\n return value\n .replace(/\\D/g, \"\")\n .slice(0, 11)\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d{1,2})$/, \"$1-$2\");\n}\n\n/** How {@link formatPercent} renders a fraction. */\nexport interface FormatPercentOptions extends FormatFallbackOptions {\n /**\n * Decimal places to show, fixed (padded as well as truncated).\n *\n * Defaults to `1`, which is what the function always did. Two is the case\n * that produced this option: a confidence of 98,74% and one of 98,7% are the\n * same number on a summary card and two different readings on a detail\n * screen, so the precision belongs to the call site.\n */\n decimals?: number;\n}\n\n/**\n * Format a fraction (0-1) as a percentage.\n *\n * A non-finite input renders as `\"—\"` rather than `\"NaN%\"` or `\"∞%\"`, which is\n * what `Intl.NumberFormat` produces and what a division by zero upstream puts on\n * the screen. It matches `formatDurationMs`, which already answers that way.\n *\n * @example\n * ```typescript\n * formatPercent(0.9874); // \"98,7%\"\n * formatPercent(0.9874, { decimals: 2 }); // \"98,74%\"\n * formatPercent(0 / 0); // \"—\"\n * ```\n *\n * @param value - Fraction between 0 and 1.\n * @param options - Rendering options; `decimals` defaults to 1.\n * @returns Formatted percent string, e.g. \"12,5%\", or `\"—\"` for a non-finite input.\n */\nexport function formatPercent(\n value: number | null | undefined,\n options: FormatPercentOptions = {},\n): string {\n if (isAbsent(value) || !Number.isFinite(value)) return fallbackText(options);\n const decimals = options.decimals ?? 1;\n return numberFormat(\"pt-BR\", {\n style: \"percent\",\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(value);\n}\n"],"mappings":"A,8DAuBA,SAAgBM,EACZ,EACA,EACM,CAEN,OADIN,EAAAA,SAASO,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAUN,EAAAA,aAAaO,CAAO,EACpEN,EAAAA,aAAa,QAAS,CAAE,MAAO,WAAY,SAAU,KAAM,CAAC,CAAC,CAAC,OAAOK,CAAK,CACrF,CA0BA,SAAgBE,EACZ,EACA,EACM,CACN,GAAIT,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,CAAO,EAChD,IAAME,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAE3D,OADI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAU,GAClCP,EAAAA,eAAe,OAAO,CAAC,CAAC,OAAOO,CAAI,CAC9C,CA6BA,SAAgBC,EACZ,EACA,EACM,CACN,GAAIX,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,EAAS,EAAE,EACpD,GAAI,OAAOD,GAAU,UAAY,sBAAsB,KAAKA,CAAK,EAAG,OAAOA,EAC3E,IAAMG,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAC3D,GAAI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAG,MAAO,GACzC,IAAME,EAAQ,GAAGF,EAAK,SAAS,EAAI,IAAI,SAAS,EAAG,GAAG,EAChDG,EAAM,GAAGH,EAAK,QAAQ,IAAI,SAAS,EAAG,GAAG,EAC/C,MAAO,GAAGA,EAAK,YAAY,EAAE,GAAGE,EAAM,GAAGC,GAC7C,CAgCA,SAAgBC,EACZ,EACA,EACM,CACN,GAAId,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,EAAS,EAAE,EACpD,GAAI,OAAOD,GAAU,UAAY,kCAAkC,KAAKA,CAAK,EAAG,OAAOA,EACvF,IAAMG,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAC3D,GAAI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAG,MAAO,GACzC,IAAME,EAAQ,GAAGF,EAAK,SAAS,EAAI,IAAI,SAAS,EAAG,GAAG,EAChDG,EAAM,GAAGH,EAAK,QAAQ,IAAI,SAAS,EAAG,GAAG,EACzCK,EAAQ,GAAGL,EAAK,SAAS,IAAI,SAAS,EAAG,GAAG,EAC5CM,EAAU,GAAGN,EAAK,WAAW,IAAI,SAAS,EAAG,GAAG,EACtD,MAAO,GAAGA,EAAK,YAAY,EAAE,GAAGE,EAAM,GAAGC,EAAI,GAAGE,EAAM,GAAGC,GAC7D,CAaA,SAAgBC,EACZ,EACA,EACM,CACN,GAAIjB,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,CAAO,EAChD,IAAME,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAE3D,OADI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAU,GAClCP,EAAAA,eAAe,QAAS,CAAE,UAAW,QAAS,UAAW,OAAQ,CAAC,CAAC,CAAC,OAAOO,CAAI,CAC1F,CAsCA,SAAgBQ,EAAY,EAAe,EAA8B,CAAC,EAAW,CACjF,IAAMC,EAASZ,EAAM,QAAQ,MAAO,EAAE,CAAC,CAAC,MAAM,EAAG,EAAE,EAEnD,GAAI,CAACC,EAAQ,OAIT,OAHIW,EAAO,QAAU,GACVA,EAAO,QAAQ,cAAe,SAAS,CAAC,CAAC,QAAQ,cAAe,OAAO,EAE3EA,EAAO,QAAQ,cAAe,SAAS,CAAC,CAAC,QAAQ,cAAe,OAAO,EAGlF,GAAIA,EAAO,QAAU,EAAG,OAAOA,EAE/B,IAAMC,EAAOD,EAAO,MAAM,EAAG,CAAC,EAC1BE,EAAaF,EAAO,MAAM,CAAC,EAC3BE,EAAW,KAAO,MAAK,EAAa,IAAIA,KAC5C,EAAaA,EAAW,MAAM,EAAG,CAAC,EAElC,IAAMC,EAASD,EAAW,MAAM,EAAG,CAAC,EAC9BE,EAASF,EAAW,MAAM,CAAC,EACjC,OAAOE,EAAS,IAAIH,EAAK,IAAIE,EAAO,GAAGC,IAAW,IAAIH,EAAK,IAAIE,GACnE,CAQA,SAAgBE,EAAU,EAAuB,CAC7C,OAAOjB,EACF,QAAQ,MAAO,EAAE,CAAC,CAClB,MAAM,EAAG,EAAE,CAAC,CACZ,QAAQ,cAAe,OAAO,CAAC,CAC/B,QAAQ,cAAe,OAAO,CAAC,CAC/B,QAAQ,oBAAqB,OAAO,CAC7C,CAiCA,SAAgBkB,EACZ,EACA,EAAgC,CAAC,EAC3B,CACN,GAAIzB,EAAAA,SAASO,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAG,OAAON,EAAAA,aAAaO,CAAO,EAC3E,IAAMkB,EAAWlB,EAAQ,UAAY,EACrC,OAAON,EAAAA,aAAa,QAAS,CACzB,MAAO,UACP,sBAAuBwB,EACvB,sBAAuBA,CAC3B,CAAC,CAAC,CAAC,OAAOnB,CAAK,CACnB,C"}
1
+ {"version":3,"file":"format.cjs","names":["isAbsent","fallbackText","numberFormat","dateTimeFormat","require_intl_cache","require_absent","formatCurrency","value","options","formatDate","date","formatDateForInput","month","day","formatDateTimeForInput","hours","minutes","formatDateTime","formatPhone","digits","area","subscriber","prefix","suffix","formatCPF","formatPercent","decimals"],"sources":["../../src/utils/format.ts"],"sourcesContent":["import { fallbackText, isAbsent, type FormatFallbackOptions } from \"@/utils/absent\";\nimport { dateTimeFormat, numberFormat } from \"@/utils/intl-cache\";\n\n/**\n * Format a number as Brazilian Real currency.\n *\n * An absent or unrepresentable amount renders as `\"—\"` rather than as a number.\n * That branch is a data-correctness fix, not cosmetics: `Intl` coerces `null` to\n * zero, so an amount the backend left out used to reach the screen as\n * **`\"R$ 0,00\"`** — a plausible figure a reader has no way to question — and\n * `undefined` as `\"R$ NaN\"`.\n *\n * @example\n * ```typescript\n * formatCurrency(1234.56); // \"R$ 1.234,56\"\n * formatCurrency(null); // \"—\"\n * formatCurrency(null, { fallback: \"sem valor\" }); // \"sem valor\"\n * ```\n *\n * @param value - The amount in BRL, or `null`/`undefined` when there is none.\n * @param options - Text to render when there is nothing to format.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(\n value: number | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value) || !Number.isFinite(value)) return fallbackText(options);\n return numberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(value);\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy`.\n *\n * Absent input answers `\"—\"`. It used to **throw**: `new Date(null)` is epoch and\n * `null.getTime` is a `TypeError`, so a nullable column — which is every\n * `deleted_at`, `expires_at` and half the `updated_at` a FastAPI backend sends —\n * took the screen down instead of rendering a gap.\n *\n * A value that is present and unparseable still answers `\"\"`, deliberately: on a\n * screen that reads a log, \"nobody filled this in\" and \"what they filled in is\n * broken\" are different findings, and the second is the one worth chasing.\n *\n * @example\n * ```typescript\n * formatDate(\"2026-05-16T12:00:00Z\"); // \"16/05/2026\"\n * formatDate(null); // \"—\"\n * formatDate(\"banana\"); // \"\"\n * ```\n *\n * @param value - ISO string, Date, or `null`/`undefined`.\n * @param options - Text to render when the value is absent.\n * @returns Formatted date string, the fallback when absent, or empty string when\n * the value is present and invalid.\n */\nexport function formatDate(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options);\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return dateTimeFormat(\"pt-BR\").format(date);\n}\n\n/**\n * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an\n * `<input type=\"date\">` accepts.\n *\n * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,\n * which is the reflex and which is wrong: `toISOString` converts to UTC first, so\n * anything after 21:00 in UTC-3 reports the next day and the form opens on the\n * wrong date. `formatDate` cannot fill this role because a date input rejects\n * `dd/MM/yyyy` outright.\n *\n * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut\n * is load-bearing rather than an optimisation: `new Date(\"2026-05-16\")` is parsed\n * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the\n * exact value a backend sent would move it back a day.\n *\n * @example\n * <input type=\"date\" defaultValue={formatDateForInput(order.createdAt)} />\n *\n * Absent input answers `\"\"` here — **not** the em dash the reading formatters\n * use — because that is what an input reads as \"no value\"; an em dash would\n * arrive as content the user has to delete before typing a date.\n *\n * @param value - ISO string, Date, or `null`/`undefined`.\n * @param options - Text for an absent value; defaults to `\"\"`.\n * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —\n * which is what a date input reads as \"no value\", unlike `\"Invalid Date\"`.\n */\nexport function formatDateForInput(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options, \"\");\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\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 * Format an ISO date or Date instance as `yyyy-MM-ddTHH:mm`, the value an\n * `<input type=\"datetime-local\">` accepts.\n *\n * The sibling of {@link formatDateForInput}, and it exists for the same reason:\n * `toISOString().slice(0, 16)` is the reflex and it is wrong. `toISOString`\n * converts to UTC first, so a 22:00 appointment in UTC-3 opens the form on the\n * next day at 01:00 — here the trap costs the hour as well as the date.\n *\n * A value already in `yyyy-MM-ddTHH:mm` is returned untouched. A value carrying\n * a zone (`...Z`, `...-03:00`) deliberately does **not** take that shortcut: it\n * is a different instant from the naive string that looks like it, so it is\n * converted to the local calendar parts the input has to show.\n *\n * Seconds are dropped. A `datetime-local` steps by the minute unless the app\n * sets `step`, so a `:ss` the field cannot represent would be silently discarded\n * on the first edit anyway — truncating here keeps the rendered value and the\n * submitted value the same.\n *\n * @example\n * <input\n * type=\"datetime-local\"\n * defaultValue={formatDateTimeForInput(appointment.startsAt)}\n * />\n *\n * @param value - ISO string or Date.\n * @returns The `yyyy-MM-ddTHH:mm` value, or an empty string when the input is\n * invalid — which is what a datetime input reads as \"no value\", unlike\n * `\"Invalid Date\"`.\n */\nexport function formatDateTimeForInput(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options, \"\");\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n const hours = `${date.getHours()}`.padStart(2, \"0\");\n const minutes = `${date.getMinutes()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}T${hours}:${minutes}`;\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.\n *\n * Same contract as {@link formatDate}: absent answers the fallback (`\"—\"` by\n * default) instead of throwing, and present-but-invalid answers `\"\"`.\n *\n * @param value - ISO string, Date, or `null`/`undefined`.\n * @param options - Text to render when the value is absent.\n * @returns Formatted datetime string, the fallback when absent, or empty string\n * when the value is present and invalid.\n */\nexport function formatDateTime(\n value: string | Date | null | undefined,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value)) return fallbackText(options);\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return dateTimeFormat(\"pt-BR\", { dateStyle: \"short\", timeStyle: \"short\" }).format(date);\n}\n\nexport interface FormatPhoneOptions {\n /**\n * Treat the number as a mobile line: insert the mandatory `9` after the area\n * code when it is missing, and group the subscriber part `5+4` from the\n * first digit typed instead of waiting for the eleventh.\n *\n * Default `false`, which keeps the length-based behaviour: `4+4` up to ten\n * digits, `5+4` at eleven.\n */\n mobile?: boolean;\n}\n\n/**\n * Apply the Brazilian phone mask `(XX) XXXXX-XXXX` or `(XX) XXXX-XXXX`.\n *\n * By default the grouping is decided by **length**, which is what a field\n * accepting both landlines and mobiles needs.\n *\n * `mobile: true` is for a field that only accepts mobile numbers, and it exists\n * because the default is wrong as an as-you-type mask there. Reading anything up\n * to ten digits as a landline puts the hyphen after the fourth subscriber digit,\n * so a half-typed mobile renders `(11) 9123-4`; it only becomes `(11) 91234-5`\n * once the eleventh digit lands. The separator visibly jumps backwards while the\n * user is still typing. With `mobile`, the same input reads `(11) 91234` and the\n * hyphen never moves. It also inserts the leading `9` every Brazilian mobile\n * carries, so a ten-digit number gets corrected rather than masked as a landline.\n *\n * @param value - Raw digits or partially masked string.\n * @param options - Masking options.\n * @returns Masked phone string.\n *\n * @example\n * formatPhone(\"1191234\"); // \"(11) 9123-4\"\n * formatPhone(\"1191234\", { mobile: true }); // \"(11) 91234\"\n * formatPhone(\"1112345678\", { mobile: true }); // \"(11) 91234-5678\" — 9 inserted\n */\nexport function formatPhone(value: string, options: FormatPhoneOptions = {}): string {\n const digits = value.replace(/\\D/g, \"\").slice(0, 11);\n\n if (!options.mobile) {\n if (digits.length <= 10) {\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{4})(\\d)/, \"$1-$2\");\n }\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{5})(\\d)/, \"$1-$2\");\n }\n\n if (digits.length <= 2) return digits;\n\n const area = digits.slice(0, 2);\n let subscriber = digits.slice(2);\n if (subscriber[0] !== \"9\") subscriber = `9${subscriber}`;\n subscriber = subscriber.slice(0, 9);\n\n const prefix = subscriber.slice(0, 5);\n const suffix = subscriber.slice(5);\n return suffix ? `(${area}) ${prefix}-${suffix}` : `(${area}) ${prefix}`;\n}\n\n/**\n * Apply the Brazilian CPF mask `XXX.XXX.XXX-XX`.\n *\n * @param value - Raw digits or partially masked string.\n * @returns Masked CPF string.\n */\nexport function formatCPF(value: string): string {\n return value\n .replace(/\\D/g, \"\")\n .slice(0, 11)\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d{1,2})$/, \"$1-$2\");\n}\n\n/** How {@link formatPercent} renders a fraction. */\nexport interface FormatPercentOptions extends FormatFallbackOptions {\n /**\n * Decimal places to show, fixed (padded as well as truncated).\n *\n * Defaults to `1`, which is what the function always did. Two is the case\n * that produced this option: a confidence of 98,74% and one of 98,7% are the\n * same number on a summary card and two different readings on a detail\n * screen, so the precision belongs to the call site.\n */\n decimals?: number;\n}\n\n/**\n * Format a fraction (0-1) as a percentage.\n *\n * A non-finite input renders as `\"—\"` rather than `\"NaN%\"` or `\"∞%\"`, which is\n * what `Intl.NumberFormat` produces and what a division by zero upstream puts on\n * the screen. It matches `formatDurationMs`, which already answers that way.\n *\n * @example\n * ```typescript\n * formatPercent(0.9874); // \"98,7%\"\n * formatPercent(0.9874, { decimals: 2 }); // \"98,74%\"\n * formatPercent(0 / 0); // \"—\"\n * ```\n *\n * @param value - Fraction between 0 and 1.\n * @param options - Rendering options; `decimals` defaults to 1.\n * @returns Formatted percent string, e.g. \"12,5%\", or `\"—\"` for a non-finite input.\n */\nexport function formatPercent(\n value: number | null | undefined,\n options: FormatPercentOptions = {},\n): string {\n if (isAbsent(value) || !Number.isFinite(value)) return fallbackText(options);\n const decimals = options.decimals ?? 1;\n return numberFormat(\"pt-BR\", {\n style: \"percent\",\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(value);\n}\n"],"mappings":"A,8DAuBA,SAAgBM,EACZ,EACA,EACM,CAEN,OADIN,EAAAA,SAASO,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAUN,EAAAA,aAAaO,CAAO,EACpEN,EAAAA,aAAa,QAAS,CAAE,MAAO,WAAY,SAAU,KAAM,CAAC,CAAC,CAAC,OAAOK,CAAK,CACrF,CA0BA,SAAgBE,EACZ,EACA,EACM,CACN,GAAIT,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,CAAO,EAChD,IAAME,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAE3D,OADI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAU,GAClCP,EAAAA,eAAe,OAAO,CAAC,CAAC,OAAOO,CAAI,CAC9C,CA6BA,SAAgBC,EACZ,EACA,EACM,CACN,GAAIX,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,EAAS,EAAE,EACpD,GAAI,OAAOD,GAAU,UAAY,sBAAsB,KAAKA,CAAK,EAAG,OAAOA,EAC3E,IAAMG,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAC3D,GAAI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAG,MAAO,GACzC,IAAME,EAAQ,GAAGF,EAAK,SAAS,EAAI,IAAI,SAAS,EAAG,GAAG,EAChDG,EAAM,GAAGH,EAAK,QAAQ,IAAI,SAAS,EAAG,GAAG,EAC/C,MAAO,GAAGA,EAAK,YAAY,EAAE,GAAGE,EAAM,GAAGC,GAC7C,CAgCA,SAAgBC,EACZ,EACA,EACM,CACN,GAAId,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,EAAS,EAAE,EACpD,GAAI,OAAOD,GAAU,UAAY,kCAAkC,KAAKA,CAAK,EAAG,OAAOA,EACvF,IAAMG,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAC3D,GAAI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAG,MAAO,GACzC,IAAME,EAAQ,GAAGF,EAAK,SAAS,EAAI,IAAI,SAAS,EAAG,GAAG,EAChDG,EAAM,GAAGH,EAAK,QAAQ,IAAI,SAAS,EAAG,GAAG,EACzCK,EAAQ,GAAGL,EAAK,SAAS,IAAI,SAAS,EAAG,GAAG,EAC5CM,EAAU,GAAGN,EAAK,WAAW,IAAI,SAAS,EAAG,GAAG,EACtD,MAAO,GAAGA,EAAK,YAAY,EAAE,GAAGE,EAAM,GAAGC,EAAI,GAAGE,EAAM,GAAGC,GAC7D,CAaA,SAAgBC,EACZ,EACA,EACM,CACN,GAAIjB,EAAAA,SAASO,CAAK,EAAG,OAAON,EAAAA,aAAaO,CAAO,EAChD,IAAME,EAAO,OAAOH,GAAU,SAAW,IAAI,KAAKA,CAAK,EAAIA,EAE3D,OADI,OAAO,MAAMG,EAAK,QAAQ,CAAC,EAAU,GAClCP,EAAAA,eAAe,QAAS,CAAE,UAAW,QAAS,UAAW,OAAQ,CAAC,CAAC,CAAC,OAAOO,CAAI,CAC1F,CAsCA,SAAgBQ,EAAY,EAAe,EAA8B,CAAC,EAAW,CACjF,IAAMC,EAASZ,EAAM,QAAQ,MAAO,EAAE,CAAC,CAAC,MAAM,EAAG,EAAE,EAEnD,GAAI,CAACC,EAAQ,OAIT,OAHIW,EAAO,QAAU,GACVA,EAAO,QAAQ,cAAe,SAAS,CAAC,CAAC,QAAQ,cAAe,OAAO,EAE3EA,EAAO,QAAQ,cAAe,SAAS,CAAC,CAAC,QAAQ,cAAe,OAAO,EAGlF,GAAIA,EAAO,QAAU,EAAG,OAAOA,EAE/B,IAAMC,EAAOD,EAAO,MAAM,EAAG,CAAC,EAC1BE,EAAaF,EAAO,MAAM,CAAC,EAC3BE,EAAW,KAAO,MAAK,EAAa,IAAIA,KAC5C,EAAaA,EAAW,MAAM,EAAG,CAAC,EAElC,IAAMC,EAASD,EAAW,MAAM,EAAG,CAAC,EAC9BE,EAASF,EAAW,MAAM,CAAC,EACjC,OAAOE,EAAS,IAAIH,EAAK,IAAIE,EAAO,GAAGC,IAAW,IAAIH,EAAK,IAAIE,GACnE,CAQA,SAAgBE,EAAU,EAAuB,CAC7C,OAAOjB,EACF,QAAQ,MAAO,EAAE,CAAC,CAClB,MAAM,EAAG,EAAE,CAAC,CACZ,QAAQ,cAAe,OAAO,CAAC,CAC/B,QAAQ,cAAe,OAAO,CAAC,CAC/B,QAAQ,oBAAqB,OAAO,CAC7C,CAiCA,SAAgBkB,EACZ,EACA,EAAgC,CAAC,EAC3B,CACN,GAAIzB,EAAAA,SAASO,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAG,OAAON,EAAAA,aAAaO,CAAO,EAC3E,IAAMkB,EAAWlB,EAAQ,UAAY,EACrC,OAAON,EAAAA,aAAa,QAAS,CACzB,MAAO,UACP,sBAAuBwB,EACvB,sBAAuBA,CAC3B,CAAC,CAAC,CAAC,OAAOnB,CAAK,CACnB,C"}
@@ -1,37 +1,37 @@
1
- import { fallbackText as e, isAbsent as t } from "./absent.js";
2
- import { dateTimeFormat as n, numberFormat as r } from "./intl-cache.js";
1
+ import { dateTimeFormat as e, numberFormat as t } from "./intl-cache.js";
2
+ import { fallbackText as n, isAbsent as r } from "./absent.js";
3
3
  //#region src/utils/format.ts
4
- function i(n, i) {
5
- return t(n) || !Number.isFinite(n) ? e(i) : r("pt-BR", {
4
+ function i(e, i) {
5
+ return r(e) || !Number.isFinite(e) ? n(i) : t("pt-BR", {
6
6
  style: "currency",
7
7
  currency: "BRL"
8
- }).format(n);
8
+ }).format(e);
9
9
  }
10
- function a(r, i) {
11
- if (t(r)) return e(i);
12
- let a = typeof r == "string" ? new Date(r) : r;
13
- return Number.isNaN(a.getTime()) ? "" : n("pt-BR").format(a);
10
+ function a(t, i) {
11
+ if (r(t)) return n(i);
12
+ let a = typeof t == "string" ? new Date(t) : t;
13
+ return Number.isNaN(a.getTime()) ? "" : e("pt-BR").format(a);
14
14
  }
15
- function o(n, r) {
16
- if (t(n)) return e(r, "");
17
- if (typeof n == "string" && /^\d{4}-\d{2}-\d{2}$/.test(n)) return n;
18
- let i = typeof n == "string" ? new Date(n) : n;
15
+ function o(e, t) {
16
+ if (r(e)) return n(t, "");
17
+ if (typeof e == "string" && /^\d{4}-\d{2}-\d{2}$/.test(e)) return e;
18
+ let i = typeof e == "string" ? new Date(e) : e;
19
19
  if (Number.isNaN(i.getTime())) return "";
20
20
  let a = `${i.getMonth() + 1}`.padStart(2, "0"), o = `${i.getDate()}`.padStart(2, "0");
21
21
  return `${i.getFullYear()}-${a}-${o}`;
22
22
  }
23
- function s(n, r) {
24
- if (t(n)) return e(r, "");
25
- if (typeof n == "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(n)) return n;
26
- let i = typeof n == "string" ? new Date(n) : n;
23
+ function s(e, t) {
24
+ if (r(e)) return n(t, "");
25
+ if (typeof e == "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(e)) return e;
26
+ let i = typeof e == "string" ? new Date(e) : e;
27
27
  if (Number.isNaN(i.getTime())) return "";
28
28
  let a = `${i.getMonth() + 1}`.padStart(2, "0"), o = `${i.getDate()}`.padStart(2, "0"), s = `${i.getHours()}`.padStart(2, "0"), c = `${i.getMinutes()}`.padStart(2, "0");
29
29
  return `${i.getFullYear()}-${a}-${o}T${s}:${c}`;
30
30
  }
31
- function c(r, i) {
32
- if (t(r)) return e(i);
33
- let a = typeof r == "string" ? new Date(r) : r;
34
- return Number.isNaN(a.getTime()) ? "" : n("pt-BR", {
31
+ function c(t, i) {
32
+ if (r(t)) return n(i);
33
+ let a = typeof t == "string" ? new Date(t) : t;
34
+ return Number.isNaN(a.getTime()) ? "" : e("pt-BR", {
35
35
  dateStyle: "short",
36
36
  timeStyle: "short"
37
37
  }).format(a);
@@ -48,14 +48,14 @@ function l(e, t = {}) {
48
48
  function u(e) {
49
49
  return e.replace(/\D/g, "").slice(0, 11).replace(/(\d{3})(\d)/, "$1.$2").replace(/(\d{3})(\d)/, "$1.$2").replace(/(\d{3})(\d{1,2})$/, "$1-$2");
50
50
  }
51
- function d(n, i = {}) {
52
- if (t(n) || !Number.isFinite(n)) return e(i);
51
+ function d(e, i = {}) {
52
+ if (r(e) || !Number.isFinite(e)) return n(i);
53
53
  let a = i.decimals ?? 1;
54
- return r("pt-BR", {
54
+ return t("pt-BR", {
55
55
  style: "percent",
56
56
  minimumFractionDigits: a,
57
57
  maximumFractionDigits: a
58
- }).format(n);
58
+ }).format(e);
59
59
  }
60
60
  //#endregion
61
61
  export { u as formatCPF, i as formatCurrency, a as formatDate, o as formatDateForInput, c as formatDateTime, s as formatDateTimeForInput, d as formatPercent, l as formatPhone };
@@ -1,2 +1,2 @@
1
- const e=require("./absent.cjs"),t=require("./intl-cache.cjs");function n(e,t,n){if(Number.isNaN(e))return e;if(t>n){let e=t;t=n,n=e}return e<t?t:e>n?n:e}function r(t,n=1,r){if(e.isAbsent(t)||!Number.isFinite(t))return e.fallbackText(r);if(t===0)return`0 B`;let i=[`B`,`KB`,`MB`,`GB`,`TB`],a=1024,o=Math.min(Math.floor(Math.log(Math.abs(t))/Math.log(a)),i.length-1),s=t/a**o,c=o===0?0:Math.max(0,n);return`${parseFloat(s.toFixed(c)).toString()} ${i[o]}`}function i(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||t===0?0:e/t*100}function a(n,r=`en-US`,i){return e.isAbsent(n)||!Number.isFinite(n)?e.fallbackText(i):t.numberFormat(r,{notation:`compact`,maximumFractionDigits:1}).format(n)}exports.clamp=n,exports.formatBytes=r,exports.formatCompactNumber=a,exports.percentOf=i;
1
+ const e=require("./intl-cache.cjs"),t=require("./absent.cjs");function n(e,t,n){if(Number.isNaN(e))return e;if(t>n){let e=t;t=n,n=e}return e<t?t:e>n?n:e}function r(e,n=1,r){if(t.isAbsent(e)||!Number.isFinite(e))return t.fallbackText(r);if(e===0)return`0 B`;let i=[`B`,`KB`,`MB`,`GB`,`TB`],a=1024,o=Math.min(Math.floor(Math.log(Math.abs(e))/Math.log(a)),i.length-1),s=e/a**o,c=o===0?0:Math.max(0,n);return`${parseFloat(s.toFixed(c)).toString()} ${i[o]}`}function i(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||t===0?0:e/t*100}function a(n,r=`en-US`,i){return t.isAbsent(n)||!Number.isFinite(n)?t.fallbackText(i):e.numberFormat(r,{notation:`compact`,maximumFractionDigits:1}).format(n)}exports.clamp=n,exports.formatBytes=r,exports.formatCompactNumber=a,exports.percentOf=i;
2
2
  //# sourceMappingURL=numbers.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"numbers.cjs","names":["isAbsent","fallbackText","numberFormat","require_absent","require_intl_cache","clamp","value","min","max","swap","formatBytes","bytes","options","units","base","exponent","digits","decimals","percentOf","part","total","formatCompactNumber","locale"],"sources":["../../src/utils/numbers.ts"],"sourcesContent":["import { fallbackText, isAbsent, type FormatFallbackOptions } from \"@/utils/absent\";\nimport { numberFormat } from \"@/utils/intl-cache\";\n/**\n * Clamp `value` between `min` and `max` (inclusive).\n *\n * @example\n * clamp(120, 0, 100); // 100\n * clamp(-5, 0, 100); // 0\n * clamp(42, 0, 100); // 42\n */\nexport function clamp(value: number, min: number, max: number): number {\n if (Number.isNaN(value)) return value;\n if (min > max) {\n const swap = min;\n min = max;\n max = swap;\n }\n if (value < min) return min;\n if (value > max) return max;\n return value;\n}\n\n/**\n * Format a byte count as a human-readable size string.\n *\n * Scales through B, KB, MB, GB and TB using 1024 as the base. `decimals`\n * controls the fractional digits for scaled units (default 1); `0` bytes\n * always renders as `\"0 B\"`.\n *\n * An absent or unrepresentable count renders as `\"—\"`. It used to render as\n * **`\"NaN undefined\"`**: `Math.log(NaN)` walks the exponent off the end of the\n * unit table, so the unit itself came out `undefined` — the size was not merely\n * unreadable, it claimed a unit that does not exist.\n *\n * @example\n * formatBytes(0); // \"0 B\"\n * formatBytes(1536); // \"1.5 KB\"\n * formatBytes(1048576); // \"1 MB\"\n * formatBytes(1536, 2); // \"1.50 KB\"\n * formatBytes(null); // \"—\"\n */\nexport function formatBytes(\n bytes: number | null | undefined,\n decimals: number = 1,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(bytes) || !Number.isFinite(bytes)) return fallbackText(options);\n if (bytes === 0) return \"0 B\";\n\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const base = 1024;\n const exponent = Math.min(\n Math.floor(Math.log(Math.abs(bytes)) / Math.log(base)),\n units.length - 1,\n );\n const value = bytes / base ** exponent;\n const digits = exponent === 0 ? 0 : Math.max(0, decimals);\n const formatted = parseFloat(value.toFixed(digits)).toString();\n\n return `${formatted} ${units[exponent]}`;\n}\n\n/**\n * What percentage of `total` is `part`, with an empty base yielding `0`.\n *\n * The guard is the whole point: `(part / total) * 100` produces `NaN` when the\n * base is zero and `Infinity` when only the base is missing, and both reach the\n * screen as a broken label — `NaN%` on an empty dashboard is the single most\n * common way a panel announces that it has no data yet.\n *\n * Returns a **0–100** number, which is what a bar width and a label want.\n * `formatPercent` takes a 0–1 fraction instead, so pair them as\n * `formatPercent(percentOf(a, b) / 100)` — or skip `percentOf` and pass the\n * fraction straight in.\n *\n * The result is not clamped: 120% of a target is a real number somebody wants to\n * see, and capping it would hide the interesting case.\n *\n * @example\n * percentOf(3, 4); // 75\n * percentOf(5, 0); // 0\n * percentOf(12, 8); // 150\n *\n * @param part - The measured amount.\n * @param total - The base it is measured against.\n * @returns The percentage, or `0` when the base is zero or either input is not finite.\n */\nexport function percentOf(part: number, total: number): number {\n if (!Number.isFinite(part) || !Number.isFinite(total) || total === 0) return 0;\n return (part / total) * 100;\n}\n\n/**\n * Format a number using compact notation (e.g. `1.2K`, `3.4M`).\n *\n * Wraps `Intl.NumberFormat` with `notation: \"compact\"`. The `locale` defaults\n * to `\"en-US\"`.\n *\n * An absent or unrepresentable value renders as `\"—\"`. The `null` case is the\n * one that matters: `Intl` coerces it to zero, so a count the backend left out\n * used to read as **`\"0\"`** — a number the reader has no reason to doubt.\n *\n * @example\n * formatCompactNumber(1234); // \"1.2K\"\n * formatCompactNumber(5600000); // \"5.6M\"\n * formatCompactNumber(1234, \"pt-BR\"); // \"1,2 mil\"\n * formatCompactNumber(null); // \"—\"\n */\nexport function formatCompactNumber(\n value: number | null | undefined,\n locale: string = \"en-US\",\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value) || !Number.isFinite(value)) return fallbackText(options);\n return numberFormat(locale, { notation: \"compact\", maximumFractionDigits: 1 }).format(value);\n}\n"],"mappings":"A,8DAUA,SAAgBK,EAAM,EAAe,EAAa,EAAqB,CACnE,GAAI,OAAO,MAAMC,CAAK,EAAG,OAAOA,EAChC,GAAIC,EAAMC,EAAK,CACX,IAAMC,EAAOF,EACb,EAAMC,EACN,EAAMC,CACV,CAGA,OAFIH,EAAQC,EAAYA,EACpBD,EAAQE,EAAYA,EACjBF,CACX,CAqBA,SAAgBI,EACZ,EACA,EAAmB,EACnB,EACM,CACN,GAAIV,EAAAA,SAASW,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAG,OAAOV,EAAAA,aAAaW,CAAO,EAC3E,GAAID,IAAU,EAAG,MAAO,MAExB,IAAME,EAAQ,CAAC,IAAK,KAAM,KAAM,KAAM,IAAI,EACpCC,EAAO,KACPC,EAAW,KAAK,IAClB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAIJ,CAAK,CAAC,EAAI,KAAK,IAAIG,CAAI,CAAC,EACrDD,EAAM,OAAS,CACnB,EACMP,EAAQK,EAAQG,GAAQC,EACxBC,EAASD,IAAa,EAAI,EAAI,KAAK,IAAI,EAAGE,CAAQ,EAGxD,MAAO,GAFW,WAAWX,EAAM,QAAQU,CAAM,CAAC,CAAC,CAAC,SAE1C,EAAU,GAAGH,EAAME,IACjC,CA2BA,SAAgBG,EAAU,EAAc,EAAuB,CAE3D,MADI,CAAC,OAAO,SAASC,CAAI,GAAK,CAAC,OAAO,SAASC,CAAK,GAAKA,IAAU,EAAU,EACrED,EAAOC,EAAS,GAC5B,CAkBA,SAAgBC,EACZ,EACA,EAAiB,QACjB,EACM,CAEN,OADIrB,EAAAA,SAASM,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAUL,EAAAA,aAAaW,CAAO,EACpEV,EAAAA,aAAaoB,EAAQ,CAAE,SAAU,UAAW,sBAAuB,CAAE,CAAC,CAAC,CAAC,OAAOhB,CAAK,CAC/F,C"}
1
+ {"version":3,"file":"numbers.cjs","names":["isAbsent","fallbackText","numberFormat","require_intl_cache","require_absent","clamp","value","min","max","swap","formatBytes","bytes","options","units","base","exponent","digits","decimals","percentOf","part","total","formatCompactNumber","locale"],"sources":["../../src/utils/numbers.ts"],"sourcesContent":["import { fallbackText, isAbsent, type FormatFallbackOptions } from \"@/utils/absent\";\nimport { numberFormat } from \"@/utils/intl-cache\";\n/**\n * Clamp `value` between `min` and `max` (inclusive).\n *\n * @example\n * clamp(120, 0, 100); // 100\n * clamp(-5, 0, 100); // 0\n * clamp(42, 0, 100); // 42\n */\nexport function clamp(value: number, min: number, max: number): number {\n if (Number.isNaN(value)) return value;\n if (min > max) {\n const swap = min;\n min = max;\n max = swap;\n }\n if (value < min) return min;\n if (value > max) return max;\n return value;\n}\n\n/**\n * Format a byte count as a human-readable size string.\n *\n * Scales through B, KB, MB, GB and TB using 1024 as the base. `decimals`\n * controls the fractional digits for scaled units (default 1); `0` bytes\n * always renders as `\"0 B\"`.\n *\n * An absent or unrepresentable count renders as `\"—\"`. It used to render as\n * **`\"NaN undefined\"`**: `Math.log(NaN)` walks the exponent off the end of the\n * unit table, so the unit itself came out `undefined` — the size was not merely\n * unreadable, it claimed a unit that does not exist.\n *\n * @example\n * formatBytes(0); // \"0 B\"\n * formatBytes(1536); // \"1.5 KB\"\n * formatBytes(1048576); // \"1 MB\"\n * formatBytes(1536, 2); // \"1.50 KB\"\n * formatBytes(null); // \"—\"\n */\nexport function formatBytes(\n bytes: number | null | undefined,\n decimals: number = 1,\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(bytes) || !Number.isFinite(bytes)) return fallbackText(options);\n if (bytes === 0) return \"0 B\";\n\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const base = 1024;\n const exponent = Math.min(\n Math.floor(Math.log(Math.abs(bytes)) / Math.log(base)),\n units.length - 1,\n );\n const value = bytes / base ** exponent;\n const digits = exponent === 0 ? 0 : Math.max(0, decimals);\n const formatted = parseFloat(value.toFixed(digits)).toString();\n\n return `${formatted} ${units[exponent]}`;\n}\n\n/**\n * What percentage of `total` is `part`, with an empty base yielding `0`.\n *\n * The guard is the whole point: `(part / total) * 100` produces `NaN` when the\n * base is zero and `Infinity` when only the base is missing, and both reach the\n * screen as a broken label — `NaN%` on an empty dashboard is the single most\n * common way a panel announces that it has no data yet.\n *\n * Returns a **0–100** number, which is what a bar width and a label want.\n * `formatPercent` takes a 0–1 fraction instead, so pair them as\n * `formatPercent(percentOf(a, b) / 100)` — or skip `percentOf` and pass the\n * fraction straight in.\n *\n * The result is not clamped: 120% of a target is a real number somebody wants to\n * see, and capping it would hide the interesting case.\n *\n * @example\n * percentOf(3, 4); // 75\n * percentOf(5, 0); // 0\n * percentOf(12, 8); // 150\n *\n * @param part - The measured amount.\n * @param total - The base it is measured against.\n * @returns The percentage, or `0` when the base is zero or either input is not finite.\n */\nexport function percentOf(part: number, total: number): number {\n if (!Number.isFinite(part) || !Number.isFinite(total) || total === 0) return 0;\n return (part / total) * 100;\n}\n\n/**\n * Format a number using compact notation (e.g. `1.2K`, `3.4M`).\n *\n * Wraps `Intl.NumberFormat` with `notation: \"compact\"`. The `locale` defaults\n * to `\"en-US\"`.\n *\n * An absent or unrepresentable value renders as `\"—\"`. The `null` case is the\n * one that matters: `Intl` coerces it to zero, so a count the backend left out\n * used to read as **`\"0\"`** — a number the reader has no reason to doubt.\n *\n * @example\n * formatCompactNumber(1234); // \"1.2K\"\n * formatCompactNumber(5600000); // \"5.6M\"\n * formatCompactNumber(1234, \"pt-BR\"); // \"1,2 mil\"\n * formatCompactNumber(null); // \"—\"\n */\nexport function formatCompactNumber(\n value: number | null | undefined,\n locale: string = \"en-US\",\n options?: FormatFallbackOptions,\n): string {\n if (isAbsent(value) || !Number.isFinite(value)) return fallbackText(options);\n return numberFormat(locale, { notation: \"compact\", maximumFractionDigits: 1 }).format(value);\n}\n"],"mappings":"A,8DAUA,SAAgBK,EAAM,EAAe,EAAa,EAAqB,CACnE,GAAI,OAAO,MAAMC,CAAK,EAAG,OAAOA,EAChC,GAAIC,EAAMC,EAAK,CACX,IAAMC,EAAOF,EACb,EAAMC,EACN,EAAMC,CACV,CAGA,OAFIH,EAAQC,EAAYA,EACpBD,EAAQE,EAAYA,EACjBF,CACX,CAqBA,SAAgBI,EACZ,EACA,EAAmB,EACnB,EACM,CACN,GAAIV,EAAAA,SAASW,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAG,OAAOV,EAAAA,aAAaW,CAAO,EAC3E,GAAID,IAAU,EAAG,MAAO,MAExB,IAAME,EAAQ,CAAC,IAAK,KAAM,KAAM,KAAM,IAAI,EACpCC,EAAO,KACPC,EAAW,KAAK,IAClB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAIJ,CAAK,CAAC,EAAI,KAAK,IAAIG,CAAI,CAAC,EACrDD,EAAM,OAAS,CACnB,EACMP,EAAQK,EAAQG,GAAQC,EACxBC,EAASD,IAAa,EAAI,EAAI,KAAK,IAAI,EAAGE,CAAQ,EAGxD,MAAO,GAFW,WAAWX,EAAM,QAAQU,CAAM,CAAC,CAAC,CAAC,SAE1C,EAAU,GAAGH,EAAME,IACjC,CA2BA,SAAgBG,EAAU,EAAc,EAAuB,CAE3D,MADI,CAAC,OAAO,SAASC,CAAI,GAAK,CAAC,OAAO,SAASC,CAAK,GAAKA,IAAU,EAAU,EACrED,EAAOC,EAAS,GAC5B,CAkBA,SAAgBC,EACZ,EACA,EAAiB,QACjB,EACM,CAEN,OADIrB,EAAAA,SAASM,CAAK,GAAK,CAAC,OAAO,SAASA,CAAK,EAAUL,EAAAA,aAAaW,CAAO,EACpEV,EAAAA,aAAaoB,EAAQ,CAAE,SAAU,UAAW,sBAAuB,CAAE,CAAC,CAAC,CAAC,OAAOhB,CAAK,CAC/F,C"}
@@ -1,5 +1,5 @@
1
- import { fallbackText as e, isAbsent as t } from "./absent.js";
2
- import { numberFormat as n } from "./intl-cache.js";
1
+ import { numberFormat as e } from "./intl-cache.js";
2
+ import { fallbackText as t, isAbsent as n } from "./absent.js";
3
3
  //#region src/utils/numbers.ts
4
4
  function r(e, t, n) {
5
5
  if (Number.isNaN(e)) return e;
@@ -9,23 +9,23 @@ function r(e, t, n) {
9
9
  }
10
10
  return e < t ? t : e > n ? n : e;
11
11
  }
12
- function i(n, r = 1, i) {
13
- if (t(n) || !Number.isFinite(n)) return e(i);
14
- if (n === 0) return "0 B";
12
+ function i(e, r = 1, i) {
13
+ if (n(e) || !Number.isFinite(e)) return t(i);
14
+ if (e === 0) return "0 B";
15
15
  let a = [
16
16
  "B",
17
17
  "KB",
18
18
  "MB",
19
19
  "GB",
20
20
  "TB"
21
- ], o = 1024, s = Math.min(Math.floor(Math.log(Math.abs(n)) / Math.log(o)), a.length - 1), c = n / o ** s, l = s === 0 ? 0 : Math.max(0, r);
21
+ ], o = 1024, s = Math.min(Math.floor(Math.log(Math.abs(e)) / Math.log(o)), a.length - 1), c = e / o ** s, l = s === 0 ? 0 : Math.max(0, r);
22
22
  return `${parseFloat(c.toFixed(l)).toString()} ${a[s]}`;
23
23
  }
24
24
  function a(e, t) {
25
25
  return !Number.isFinite(e) || !Number.isFinite(t) || t === 0 ? 0 : e / t * 100;
26
26
  }
27
27
  function o(r, i = "en-US", a) {
28
- return t(r) || !Number.isFinite(r) ? e(a) : n(i, {
28
+ return n(r) || !Number.isFinite(r) ? t(a) : e(i, {
29
29
  notation: "compact",
30
30
  maximumFractionDigits: 1
31
31
  }).format(r);
@@ -1,2 +1,2 @@
1
- function e(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function t(e,t){let n={};for(let r of t)Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function n(e,t){let n=new Set(t),r={};for(let t of Object.keys(e))n.has(t)||(r[t]=e[t]);return r}function r(t,n){if(!e(t)||!e(n))return n===void 0?t:n;let i={...t};for(let t of Object.keys(n)){let a=n[t],o=i[t];i[t]=e(o)&&e(a)?r(o,a):a}return i}function i(t){return t==null?!0:typeof t==`string`||Array.isArray(t)?t.length===0:t instanceof Map||t instanceof Set?t.size===0:e(t)?Object.keys(t).length===0:!1}exports.deepMerge=r,exports.isEmpty=i,exports.omit=n,exports.pick=t;
1
+ function e(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function t(e,t){let n={};for(let r of t)Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function n(e,t){let n=new Set(t),r={};for(let t of Object.keys(e))n.has(t)||(r[t]=e[t]);return r}var r=new Set([`__proto__`,`constructor`,`prototype`]);function i(t,n){if(!e(t)||!e(n))return n===void 0?t:n;let a={...t};for(let t of Object.keys(n)){if(r.has(t))continue;let o=n[t],s=a[t];a[t]=e(s)&&e(o)?i(s,o):o}return a}function a(t){return t==null?!0:typeof t==`string`||Array.isArray(t)?t.length===0:t instanceof Map||t instanceof Set?t.size===0:e(t)?Object.keys(t).length===0:!1}exports.deepMerge=i,exports.isEmpty=a,exports.omit=n,exports.pick=t;
2
2
  //# sourceMappingURL=objects.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"objects.cjs","names":["isPlainRecord","value","proto","pick","result","key","keys","obj","omit","exclude","deepMerge","target","source","sourceValue","targetValue","isEmpty"],"sources":["../../src/utils/objects.ts"],"sourcesContent":["/**\n * Determine whether a value is a plain object literal.\n *\n * Returns `false` for arrays, `null`, dates, maps, sets, and class instances.\n *\n * @internal\n */\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Create a new object containing only the given `keys` from `obj`.\n *\n * Missing keys are skipped. The input is never mutated.\n *\n * @example\n * pick({ id: 1, name: \"Ana\", age: 30 }, [\"id\", \"name\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Create a new object with the given `keys` removed from `obj`.\n *\n * The input is never mutated.\n *\n * @example\n * omit({ id: 1, name: \"Ana\", age: 30 }, [\"age\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\n const exclude = new Set<PropertyKey>(keys);\n const result = {} as Record<PropertyKey, unknown>;\n for (const key of Object.keys(obj) as (keyof T)[]) {\n if (!exclude.has(key)) {\n result[key] = obj[key];\n }\n }\n return result as Omit<T, K>;\n}\n\n/**\n * Recursively merge `source` into `target`, returning a new object.\n *\n * - Plain objects are merged key by key, recursing into nested plain objects.\n * - Arrays and non-plain values (dates, class instances, primitives) replace\n * the target value entirely — they are not merged element by element.\n * - Neither `target` nor `source` is mutated.\n *\n * @example\n * deepMerge(\n * { a: 1, nested: { x: 1, y: 2 } },\n * { nested: { y: 20, z: 30 } },\n * );\n * // { a: 1, nested: { x: 1, y: 20, z: 30 } }\n *\n * @example\n * deepMerge({ tags: [\"a\", \"b\"] }, { tags: [\"c\"] });\n * // { tags: [\"c\"] } (arrays replaced, not merged)\n */\nexport function deepMerge<T>(target: T, source: Partial<T>): T {\n if (!isPlainRecord(target) || !isPlainRecord(source)) {\n return (source === undefined ? target : (source as unknown as T)) as T;\n }\n const result: Record<string, unknown> = { ...target };\n for (const key of Object.keys(source)) {\n const sourceValue = (source as Record<string, unknown>)[key];\n const targetValue = result[key];\n if (isPlainRecord(targetValue) && isPlainRecord(sourceValue)) {\n result[key] = deepMerge(targetValue, sourceValue);\n } else {\n result[key] = sourceValue;\n }\n }\n return result as T;\n}\n\n/**\n * Check whether a value is considered \"empty\".\n *\n * Returns `true` for `null`, `undefined`, empty strings, empty arrays, empty\n * plain objects, and empty `Map`/`Set` instances. Returns `false` for numbers\n * (including `0` and `NaN`), booleans, and dates, which are never empty.\n *\n * @example\n * isEmpty(null); // true\n * isEmpty(\"\"); // true\n * isEmpty([]); // true\n * isEmpty({}); // true\n * isEmpty(new Map()); // true\n * isEmpty(0); // false\n * isEmpty(false); // false\n * isEmpty(new Date()); // false\n */\nexport function isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.length === 0;\n if (Array.isArray(value)) return value.length === 0;\n if (value instanceof Map || value instanceof Set) return value.size === 0;\n if (isPlainRecord(value)) return Object.keys(value).length === 0;\n return false;\n}\n"],"mappings":"AAOA,SAASA,EAAc,EAAkD,CACrE,GAAI,OAAOC,GAAU,WAAYA,EAAgB,MAAO,GACxD,IAAMC,EAAQ,OAAO,eAAeD,CAAK,EACzC,OAAOC,IAAU,OAAO,WAAaA,IAAU,IACnD,CAWA,SAAgBC,EAA0C,EAAQ,EAAuB,CACrF,IAAMC,EAAS,CAAC,EAChB,IAAK,IAAMC,KAAOC,EACV,OAAO,UAAU,eAAe,KAAKC,EAAKF,CAAG,IAC7C,EAAOA,GAAOE,EAAIF,IAG1B,OAAOD,CACX,CAWA,SAAgBI,EAA0C,EAAQ,EAAuB,CACrF,IAAMC,EAAU,IAAI,IAAiBH,CAAI,EACnCF,EAAS,CAAC,EAChB,IAAK,IAAMC,KAAO,OAAO,KAAKE,CAAG,EACxBE,EAAQ,IAAIJ,CAAG,IAChB,EAAOA,GAAOE,EAAIF,IAG1B,OAAOD,CACX,CAqBA,SAAgBM,EAAa,EAAW,EAAuB,CAC3D,GAAI,CAACV,EAAcW,CAAM,GAAK,CAACX,EAAcY,CAAM,EAC/C,OAAQA,IAAW,IAAA,GAAYD,EAAUC,EAE7C,IAAMR,EAAkC,CAAE,GAAGO,CAAO,EACpD,IAAK,IAAMN,KAAO,OAAO,KAAKO,CAAM,EAAG,CACnC,IAAMC,EAAeD,EAAmCP,GAClDS,EAAcV,EAAOC,GAC3B,AAGID,EAAOC,GAHPL,EAAcc,CAAW,GAAKd,EAAca,CAAW,EACzCH,EAAUI,EAAaD,CAAW,EAElCA,CAEtB,CACA,OAAOT,CACX,CAmBA,SAAgBW,EAAQ,EAAyB,CAM7C,OALId,GAAU,KAAoC,GAC9C,OAAOA,GAAU,UACjB,MAAM,QAAQA,CAAK,EAAUA,EAAM,SAAW,EAC9CA,aAAiB,KAAOA,aAAiB,IAAYA,EAAM,OAAS,EACpED,EAAcC,CAAK,EAAU,OAAO,KAAKA,CAAK,CAAC,CAAC,SAAW,EACxD,EACX,C"}
1
+ {"version":3,"file":"objects.cjs","names":["isPlainRecord","value","proto","pick","result","key","keys","obj","omit","exclude","UNSAFE_MERGE_KEYS","deepMerge","target","source","sourceValue","targetValue","isEmpty"],"sources":["../../src/utils/objects.ts"],"sourcesContent":["/**\n * Determine whether a value is a plain object literal.\n *\n * Returns `false` for arrays, `null`, dates, maps, sets, and class instances.\n *\n * @internal\n */\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Create a new object containing only the given `keys` from `obj`.\n *\n * Missing keys are skipped. The input is never mutated.\n *\n * @example\n * pick({ id: 1, name: \"Ana\", age: 30 }, [\"id\", \"name\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Create a new object with the given `keys` removed from `obj`.\n *\n * The input is never mutated.\n *\n * @example\n * omit({ id: 1, name: \"Ana\", age: 30 }, [\"age\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\n const exclude = new Set<PropertyKey>(keys);\n const result = {} as Record<PropertyKey, unknown>;\n for (const key of Object.keys(obj) as (keyof T)[]) {\n if (!exclude.has(key)) {\n result[key] = obj[key];\n }\n }\n return result as Omit<T, K>;\n}\n\n/** Keys {@link deepMerge} never copies from `source`. */\nconst UNSAFE_MERGE_KEYS: ReadonlySet<string> = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * Recursively merge `source` into `target`, returning a new object.\n *\n * - Plain objects are merged key by key, recursing into nested plain objects.\n * - Arrays and non-plain values (dates, class instances, primitives) replace\n * the target value entirely — they are not merged element by element.\n * - Neither `target` nor `source` is mutated.\n * - `__proto__`, `constructor` and `prototype` keys in `source` are skipped.\n * Assigning `result[\"__proto__\"]` does not create a key: it **replaces the\n * result's prototype**, so a `JSON.parse`d settings blob carrying\n * `{\"__proto__\": {\"isAdmin\": true}}` produced an object whose `isAdmin` read\n * `true` through inheritance while `Object.keys` showed nothing. No global\n * prototype is touched either way; the skip removes the surprise, and the\n * other two names go with it because nothing legitimate merges into them.\n *\n * @example\n * deepMerge(\n * { a: 1, nested: { x: 1, y: 2 } },\n * { nested: { y: 20, z: 30 } },\n * );\n * // { a: 1, nested: { x: 1, y: 20, z: 30 } }\n *\n * @example\n * deepMerge({ tags: [\"a\", \"b\"] }, { tags: [\"c\"] });\n * // { tags: [\"c\"] } (arrays replaced, not merged)\n */\nexport function deepMerge<T>(target: T, source: Partial<T>): T {\n if (!isPlainRecord(target) || !isPlainRecord(source)) {\n return (source === undefined ? target : (source as unknown as T)) as T;\n }\n const result: Record<string, unknown> = { ...target };\n for (const key of Object.keys(source)) {\n if (UNSAFE_MERGE_KEYS.has(key)) continue;\n const sourceValue = (source as Record<string, unknown>)[key];\n const targetValue = result[key];\n if (isPlainRecord(targetValue) && isPlainRecord(sourceValue)) {\n result[key] = deepMerge(targetValue, sourceValue);\n } else {\n result[key] = sourceValue;\n }\n }\n return result as T;\n}\n\n/**\n * Check whether a value is considered \"empty\".\n *\n * Returns `true` for `null`, `undefined`, empty strings, empty arrays, empty\n * plain objects, and empty `Map`/`Set` instances. Returns `false` for numbers\n * (including `0` and `NaN`), booleans, and dates, which are never empty.\n *\n * @example\n * isEmpty(null); // true\n * isEmpty(\"\"); // true\n * isEmpty([]); // true\n * isEmpty({}); // true\n * isEmpty(new Map()); // true\n * isEmpty(0); // false\n * isEmpty(false); // false\n * isEmpty(new Date()); // false\n */\nexport function isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.length === 0;\n if (Array.isArray(value)) return value.length === 0;\n if (value instanceof Map || value instanceof Set) return value.size === 0;\n if (isPlainRecord(value)) return Object.keys(value).length === 0;\n return false;\n}\n"],"mappings":"AAOA,SAASA,EAAc,EAAkD,CACrE,GAAI,OAAOC,GAAU,WAAYA,EAAgB,MAAO,GACxD,IAAMC,EAAQ,OAAO,eAAeD,CAAK,EACzC,OAAOC,IAAU,OAAO,WAAaA,IAAU,IACnD,CAWA,SAAgBC,EAA0C,EAAQ,EAAuB,CACrF,IAAMC,EAAS,CAAC,EAChB,IAAK,IAAMC,KAAOC,EACV,OAAO,UAAU,eAAe,KAAKC,EAAKF,CAAG,IAC7C,EAAOA,GAAOE,EAAIF,IAG1B,OAAOD,CACX,CAWA,SAAgBI,EAA0C,EAAQ,EAAuB,CACrF,IAAMC,EAAU,IAAI,IAAiBH,CAAI,EACnCF,EAAS,CAAC,EAChB,IAAK,IAAMC,KAAO,OAAO,KAAKE,CAAG,EACxBE,EAAQ,IAAIJ,CAAG,IAChB,EAAOA,GAAOE,EAAIF,IAG1B,OAAOD,CACX,CAGA,IAAMM,EAAyC,IAAI,IAAI,CAAC,YAAa,cAAe,WAAW,CAAC,EA4BhG,SAAgBC,EAAa,EAAW,EAAuB,CAC3D,GAAI,CAACX,EAAcY,CAAM,GAAK,CAACZ,EAAca,CAAM,EAC/C,OAAQA,IAAW,IAAA,GAAYD,EAAUC,EAE7C,IAAMT,EAAkC,CAAE,GAAGQ,CAAO,EACpD,IAAK,IAAMP,KAAO,OAAO,KAAKQ,CAAM,EAAG,CACnC,GAAIH,EAAkB,IAAIL,CAAG,EAAG,SAChC,IAAMS,EAAeD,EAAmCR,GAClDU,EAAcX,EAAOC,GAC3B,AAGID,EAAOC,GAHPL,EAAce,CAAW,GAAKf,EAAcc,CAAW,EACzCH,EAAUI,EAAaD,CAAW,EAElCA,CAEtB,CACA,OAAOV,CACX,CAmBA,SAAgBY,EAAQ,EAAyB,CAM7C,OALIf,GAAU,KAAoC,GAC9C,OAAOA,GAAU,UACjB,MAAM,QAAQA,CAAK,EAAUA,EAAM,SAAW,EAC9CA,aAAiB,KAAOA,aAAiB,IAAYA,EAAM,OAAS,EACpED,EAAcC,CAAK,EAAU,OAAO,KAAKA,CAAK,CAAC,CAAC,SAAW,EACxD,EACX,C"}
@@ -14,19 +14,25 @@ function n(e, t) {
14
14
  for (let t of Object.keys(e)) n.has(t) || (r[t] = e[t]);
15
15
  return r;
16
16
  }
17
- function r(t, n) {
17
+ var r = /* @__PURE__ */ new Set([
18
+ "__proto__",
19
+ "constructor",
20
+ "prototype"
21
+ ]);
22
+ function i(t, n) {
18
23
  if (!e(t) || !e(n)) return n === void 0 ? t : n;
19
- let i = { ...t };
24
+ let a = { ...t };
20
25
  for (let t of Object.keys(n)) {
21
- let a = n[t], o = i[t];
22
- i[t] = e(o) && e(a) ? r(o, a) : a;
26
+ if (r.has(t)) continue;
27
+ let o = n[t], s = a[t];
28
+ a[t] = e(s) && e(o) ? i(s, o) : o;
23
29
  }
24
- return i;
30
+ return a;
25
31
  }
26
- function i(t) {
32
+ function a(t) {
27
33
  return t == null ? !0 : typeof t == "string" || Array.isArray(t) ? t.length === 0 : t instanceof Map || t instanceof Set ? t.size === 0 : e(t) ? Object.keys(t).length === 0 : !1;
28
34
  }
29
35
  //#endregion
30
- export { r as deepMerge, i as isEmpty, n as omit, t as pick };
36
+ export { i as deepMerge, a as isEmpty, n as omit, t as pick };
31
37
 
32
38
  //# sourceMappingURL=objects.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"objects.js","names":["isPlainRecord","value","proto","pick","result","key","keys","obj","omit","exclude","deepMerge","target","source","sourceValue","targetValue","isEmpty"],"sources":["../../src/utils/objects.ts"],"sourcesContent":["/**\n * Determine whether a value is a plain object literal.\n *\n * Returns `false` for arrays, `null`, dates, maps, sets, and class instances.\n *\n * @internal\n */\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Create a new object containing only the given `keys` from `obj`.\n *\n * Missing keys are skipped. The input is never mutated.\n *\n * @example\n * pick({ id: 1, name: \"Ana\", age: 30 }, [\"id\", \"name\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Create a new object with the given `keys` removed from `obj`.\n *\n * The input is never mutated.\n *\n * @example\n * omit({ id: 1, name: \"Ana\", age: 30 }, [\"age\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\n const exclude = new Set<PropertyKey>(keys);\n const result = {} as Record<PropertyKey, unknown>;\n for (const key of Object.keys(obj) as (keyof T)[]) {\n if (!exclude.has(key)) {\n result[key] = obj[key];\n }\n }\n return result as Omit<T, K>;\n}\n\n/**\n * Recursively merge `source` into `target`, returning a new object.\n *\n * - Plain objects are merged key by key, recursing into nested plain objects.\n * - Arrays and non-plain values (dates, class instances, primitives) replace\n * the target value entirely — they are not merged element by element.\n * - Neither `target` nor `source` is mutated.\n *\n * @example\n * deepMerge(\n * { a: 1, nested: { x: 1, y: 2 } },\n * { nested: { y: 20, z: 30 } },\n * );\n * // { a: 1, nested: { x: 1, y: 20, z: 30 } }\n *\n * @example\n * deepMerge({ tags: [\"a\", \"b\"] }, { tags: [\"c\"] });\n * // { tags: [\"c\"] } (arrays replaced, not merged)\n */\nexport function deepMerge<T>(target: T, source: Partial<T>): T {\n if (!isPlainRecord(target) || !isPlainRecord(source)) {\n return (source === undefined ? target : (source as unknown as T)) as T;\n }\n const result: Record<string, unknown> = { ...target };\n for (const key of Object.keys(source)) {\n const sourceValue = (source as Record<string, unknown>)[key];\n const targetValue = result[key];\n if (isPlainRecord(targetValue) && isPlainRecord(sourceValue)) {\n result[key] = deepMerge(targetValue, sourceValue);\n } else {\n result[key] = sourceValue;\n }\n }\n return result as T;\n}\n\n/**\n * Check whether a value is considered \"empty\".\n *\n * Returns `true` for `null`, `undefined`, empty strings, empty arrays, empty\n * plain objects, and empty `Map`/`Set` instances. Returns `false` for numbers\n * (including `0` and `NaN`), booleans, and dates, which are never empty.\n *\n * @example\n * isEmpty(null); // true\n * isEmpty(\"\"); // true\n * isEmpty([]); // true\n * isEmpty({}); // true\n * isEmpty(new Map()); // true\n * isEmpty(0); // false\n * isEmpty(false); // false\n * isEmpty(new Date()); // false\n */\nexport function isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.length === 0;\n if (Array.isArray(value)) return value.length === 0;\n if (value instanceof Map || value instanceof Set) return value.size === 0;\n if (isPlainRecord(value)) return Object.keys(value).length === 0;\n return false;\n}\n"],"mappings":";AAOA,SAASA,EAAc,GAAkD;CACrE,IAAI,OAAOC,KAAU,aAAYA,GAAgB,OAAO;CACxD,IAAMC,IAAQ,OAAO,eAAeD,CAAK;CACzC,OAAOC,MAAU,OAAO,aAAaA,MAAU;AACnD;AAWA,SAAgBC,EAA0C,GAAQ,GAAuB;CACrF,IAAMC,IAAS,CAAC;CAChB,KAAK,IAAMC,KAAOC,GACd,AAAI,OAAO,UAAU,eAAe,KAAKC,GAAKF,CAAG,MAC7C,EAAOA,KAAOE,EAAIF;CAG1B,OAAOD;AACX;AAWA,SAAgBI,EAA0C,GAAQ,GAAuB;CACrF,IAAMC,IAAU,IAAI,IAAiBH,CAAI,GACnCF,IAAS,CAAC;CAChB,KAAK,IAAMC,KAAO,OAAO,KAAKE,CAAG,GAC7B,AAAKE,EAAQ,IAAIJ,CAAG,MAChB,EAAOA,KAAOE,EAAIF;CAG1B,OAAOD;AACX;AAqBA,SAAgBM,EAAa,GAAW,GAAuB;CAC3D,IAAI,CAACV,EAAcW,CAAM,KAAK,CAACX,EAAcY,CAAM,GAC/C,OAAQA,MAAW,KAAA,IAAYD,IAAUC;CAE7C,IAAMR,IAAkC,EAAE,GAAGO,EAAO;CACpD,KAAK,IAAMN,KAAO,OAAO,KAAKO,CAAM,GAAG;EACnC,IAAMC,IAAeD,EAAmCP,IAClDS,IAAcV,EAAOC;EAC3B,AAGID,EAAOC,KAHPL,EAAcc,CAAW,KAAKd,EAAca,CAAW,IACzCH,EAAUI,GAAaD,CAAW,IAElCA;CAEtB;CACA,OAAOT;AACX;AAmBA,SAAgBW,EAAQ,GAAyB;CAM7C,OALId,KAAU,OAAoC,KAC9C,OAAOA,KAAU,YACjB,MAAM,QAAQA,CAAK,IAAUA,EAAM,WAAW,IAC9CA,aAAiB,OAAOA,aAAiB,MAAYA,EAAM,SAAS,IACpED,EAAcC,CAAK,IAAU,OAAO,KAAKA,CAAK,CAAC,CAAC,WAAW,IACxD;AACX"}
1
+ {"version":3,"file":"objects.js","names":["isPlainRecord","value","proto","pick","result","key","keys","obj","omit","exclude","UNSAFE_MERGE_KEYS","deepMerge","target","source","sourceValue","targetValue","isEmpty"],"sources":["../../src/utils/objects.ts"],"sourcesContent":["/**\n * Determine whether a value is a plain object literal.\n *\n * Returns `false` for arrays, `null`, dates, maps, sets, and class instances.\n *\n * @internal\n */\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Create a new object containing only the given `keys` from `obj`.\n *\n * Missing keys are skipped. The input is never mutated.\n *\n * @example\n * pick({ id: 1, name: \"Ana\", age: 30 }, [\"id\", \"name\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/**\n * Create a new object with the given `keys` removed from `obj`.\n *\n * The input is never mutated.\n *\n * @example\n * omit({ id: 1, name: \"Ana\", age: 30 }, [\"age\"]);\n * // { id: 1, name: \"Ana\" }\n */\nexport function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {\n const exclude = new Set<PropertyKey>(keys);\n const result = {} as Record<PropertyKey, unknown>;\n for (const key of Object.keys(obj) as (keyof T)[]) {\n if (!exclude.has(key)) {\n result[key] = obj[key];\n }\n }\n return result as Omit<T, K>;\n}\n\n/** Keys {@link deepMerge} never copies from `source`. */\nconst UNSAFE_MERGE_KEYS: ReadonlySet<string> = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * Recursively merge `source` into `target`, returning a new object.\n *\n * - Plain objects are merged key by key, recursing into nested plain objects.\n * - Arrays and non-plain values (dates, class instances, primitives) replace\n * the target value entirely — they are not merged element by element.\n * - Neither `target` nor `source` is mutated.\n * - `__proto__`, `constructor` and `prototype` keys in `source` are skipped.\n * Assigning `result[\"__proto__\"]` does not create a key: it **replaces the\n * result's prototype**, so a `JSON.parse`d settings blob carrying\n * `{\"__proto__\": {\"isAdmin\": true}}` produced an object whose `isAdmin` read\n * `true` through inheritance while `Object.keys` showed nothing. No global\n * prototype is touched either way; the skip removes the surprise, and the\n * other two names go with it because nothing legitimate merges into them.\n *\n * @example\n * deepMerge(\n * { a: 1, nested: { x: 1, y: 2 } },\n * { nested: { y: 20, z: 30 } },\n * );\n * // { a: 1, nested: { x: 1, y: 20, z: 30 } }\n *\n * @example\n * deepMerge({ tags: [\"a\", \"b\"] }, { tags: [\"c\"] });\n * // { tags: [\"c\"] } (arrays replaced, not merged)\n */\nexport function deepMerge<T>(target: T, source: Partial<T>): T {\n if (!isPlainRecord(target) || !isPlainRecord(source)) {\n return (source === undefined ? target : (source as unknown as T)) as T;\n }\n const result: Record<string, unknown> = { ...target };\n for (const key of Object.keys(source)) {\n if (UNSAFE_MERGE_KEYS.has(key)) continue;\n const sourceValue = (source as Record<string, unknown>)[key];\n const targetValue = result[key];\n if (isPlainRecord(targetValue) && isPlainRecord(sourceValue)) {\n result[key] = deepMerge(targetValue, sourceValue);\n } else {\n result[key] = sourceValue;\n }\n }\n return result as T;\n}\n\n/**\n * Check whether a value is considered \"empty\".\n *\n * Returns `true` for `null`, `undefined`, empty strings, empty arrays, empty\n * plain objects, and empty `Map`/`Set` instances. Returns `false` for numbers\n * (including `0` and `NaN`), booleans, and dates, which are never empty.\n *\n * @example\n * isEmpty(null); // true\n * isEmpty(\"\"); // true\n * isEmpty([]); // true\n * isEmpty({}); // true\n * isEmpty(new Map()); // true\n * isEmpty(0); // false\n * isEmpty(false); // false\n * isEmpty(new Date()); // false\n */\nexport function isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.length === 0;\n if (Array.isArray(value)) return value.length === 0;\n if (value instanceof Map || value instanceof Set) return value.size === 0;\n if (isPlainRecord(value)) return Object.keys(value).length === 0;\n return false;\n}\n"],"mappings":";AAOA,SAASA,EAAc,GAAkD;CACrE,IAAI,OAAOC,KAAU,aAAYA,GAAgB,OAAO;CACxD,IAAMC,IAAQ,OAAO,eAAeD,CAAK;CACzC,OAAOC,MAAU,OAAO,aAAaA,MAAU;AACnD;AAWA,SAAgBC,EAA0C,GAAQ,GAAuB;CACrF,IAAMC,IAAS,CAAC;CAChB,KAAK,IAAMC,KAAOC,GACd,AAAI,OAAO,UAAU,eAAe,KAAKC,GAAKF,CAAG,MAC7C,EAAOA,KAAOE,EAAIF;CAG1B,OAAOD;AACX;AAWA,SAAgBI,EAA0C,GAAQ,GAAuB;CACrF,IAAMC,IAAU,IAAI,IAAiBH,CAAI,GACnCF,IAAS,CAAC;CAChB,KAAK,IAAMC,KAAO,OAAO,KAAKE,CAAG,GAC7B,AAAKE,EAAQ,IAAIJ,CAAG,MAChB,EAAOA,KAAOE,EAAIF;CAG1B,OAAOD;AACX;AAGA,IAAMM,oBAAyC,IAAI,IAAI;CAAC;CAAa;CAAe;AAAW,CAAC;AA4BhG,SAAgBC,EAAa,GAAW,GAAuB;CAC3D,IAAI,CAACX,EAAcY,CAAM,KAAK,CAACZ,EAAca,CAAM,GAC/C,OAAQA,MAAW,KAAA,IAAYD,IAAUC;CAE7C,IAAMT,IAAkC,EAAE,GAAGQ,EAAO;CACpD,KAAK,IAAMP,KAAO,OAAO,KAAKQ,CAAM,GAAG;EACnC,IAAIH,EAAkB,IAAIL,CAAG,GAAG;EAChC,IAAMS,IAAeD,EAAmCR,IAClDU,IAAcX,EAAOC;EAC3B,AAGID,EAAOC,KAHPL,EAAce,CAAW,KAAKf,EAAcc,CAAW,IACzCH,EAAUI,GAAaD,CAAW,IAElCA;CAEtB;CACA,OAAOV;AACX;AAmBA,SAAgBY,EAAQ,GAAyB;CAM7C,OALIf,KAAU,OAAoC,KAC9C,OAAOA,KAAU,YACjB,MAAM,QAAQA,CAAK,IAAUA,EAAM,WAAW,IAC9CA,aAAiB,OAAOA,aAAiB,MAAYA,EAAM,SAAS,IACpED,EAAcC,CAAK,IAAU,OAAO,KAAKA,CAAK,CAAC,CAAC,WAAW,IACxD;AACX"}
@@ -1,2 +1,2 @@
1
- var e=new Set([`http:`,`https:`,`mailto:`,`tel:`,`sms:`]),t=new Set([`http:`,`https:`,`blob:`]),n=/^data:image\/(png|jpe?g|gif|webp|avif|bmp);base64,[a-z0-9+/=\s]+$/i;function r(e){return e.replace(/[\u0000-\u0020\u007F]/g,``).trim()}function i(e){return/^[a-z][a-z0-9+.-]*:/i.test(e)?e.slice(0,e.indexOf(`:`)+1).toLowerCase():null}function a(t){let n=r(t);if(!n)return null;let a=i(n);return a===null||e.has(a)?n:null}function o(e){let t=r(e);return t?n.test(t)?t:/^data:/i.test(t)?null:a(t):null}function s(e){let a=r(e);if(!a)return null;if(n.test(a))return e;let o=i(a);return o===null||t.has(o)?e:null}exports.safeAttachmentUrl=s,exports.safeImageUrl=o,exports.safeLinkUrl=a;
1
+ var e=new Set([`http:`,`https:`,`mailto:`,`tel:`,`sms:`]),t=new Set([`http:`,`https:`,`blob:`]),n=/^data:image\/(png|jpe?g|gif|webp|avif|bmp);base64,[a-z0-9+/=\s]+$/i;function r(e){return e.replace(/[\u0000-\u0020\u007F]/g,``).trim()}function i(e){return/^[a-z][a-z0-9+.-]*:/i.test(e)?e.slice(0,e.indexOf(`:`)+1).toLowerCase():null}function a(t){let n=r(t);if(!n)return null;let a=i(n);return a===null||e.has(a)?n:null}function o(e){let t=r(e);return t?n.test(t)?t:/^data:/i.test(t)?null:a(t):null}function s(e){let a=r(e);if(!a)return null;if(n.test(a))return e;let o=i(a);return o===null||t.has(o)?e:null}function c(e){if(e!==void 0&&a(e)!==null)return e}exports.safeAttachmentUrl=s,exports.safeImageUrl=o,exports.safeLinkUrl=a,exports.vettedHref=c;
2
2
  //# sourceMappingURL=safe-url.cjs.map