tempest-react-sdk 0.64.0 → 0.65.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 (97) hide show
  1. package/README.md +4 -4
  2. package/dist/components/Command/Command.module.cjs.map +1 -1
  3. package/dist/components/Command/Command.module.js.map +1 -1
  4. package/dist/components/ContextMenu/ContextMenu.module.cjs.map +1 -1
  5. package/dist/components/ContextMenu/ContextMenu.module.js.map +1 -1
  6. package/dist/components/DataTable/DataTable.cjs +1 -1
  7. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  8. package/dist/components/DataTable/DataTable.js +1 -0
  9. package/dist/components/DataTable/DataTable.js.map +1 -1
  10. package/dist/components/DataTable/use-dev-warnings.cjs +1 -1
  11. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
  12. package/dist/components/DataTable/use-dev-warnings.js +4 -3
  13. package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
  14. package/dist/components/DropdownMenu/DropdownMenu.module.cjs.map +1 -1
  15. package/dist/components/DropdownMenu/DropdownMenu.module.js.map +1 -1
  16. package/dist/components/InstallBanner/InstallBanner.cjs +1 -1
  17. package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
  18. package/dist/components/InstallBanner/InstallBanner.js +62 -37
  19. package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
  20. package/dist/components/InstallBanner/InstallBanner.module.cjs +1 -1
  21. package/dist/components/InstallBanner/InstallBanner.module.cjs.map +1 -1
  22. package/dist/components/InstallBanner/InstallBanner.module.js +4 -3
  23. package/dist/components/InstallBanner/InstallBanner.module.js.map +1 -1
  24. package/dist/components/InstallButton/InstallButton.cjs +1 -1
  25. package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
  26. package/dist/components/InstallButton/InstallButton.js +34 -10
  27. package/dist/components/InstallButton/InstallButton.js.map +1 -1
  28. package/dist/components/InstallButton/InstallButton.module.cjs +3 -0
  29. package/dist/components/InstallButton/InstallButton.module.cjs.map +1 -0
  30. package/dist/components/InstallButton/InstallButton.module.js +10 -0
  31. package/dist/components/InstallButton/InstallButton.module.js.map +1 -0
  32. package/dist/components/InstallButton/install-hints.cjs +2 -0
  33. package/dist/components/InstallButton/install-hints.cjs.map +1 -0
  34. package/dist/components/InstallButton/install-hints.js +32 -0
  35. package/dist/components/InstallButton/install-hints.js.map +1 -0
  36. package/dist/components/SegmentedControl/SegmentedControl.module.cjs.map +1 -1
  37. package/dist/components/SegmentedControl/SegmentedControl.module.js.map +1 -1
  38. package/dist/components/Select/Select.cjs +1 -1
  39. package/dist/components/Select/Select.cjs.map +1 -1
  40. package/dist/components/Select/Select.js +43 -40
  41. package/dist/components/Select/Select.js.map +1 -1
  42. package/dist/components/Select/Select.module.cjs +1 -1
  43. package/dist/components/Select/Select.module.cjs.map +1 -1
  44. package/dist/components/Select/Select.module.js +5 -3
  45. package/dist/components/Select/Select.module.js.map +1 -1
  46. package/dist/hooks/use-install-prompt.cjs +1 -1
  47. package/dist/hooks/use-install-prompt.cjs.map +1 -1
  48. package/dist/hooks/use-install-prompt.js +9 -2
  49. package/dist/hooks/use-install-prompt.js.map +1 -1
  50. package/dist/http/api-client.cjs +1 -1
  51. package/dist/http/api-client.cjs.map +1 -1
  52. package/dist/http/api-client.js +80 -74
  53. package/dist/http/api-client.js.map +1 -1
  54. package/dist/http/decode-response.cjs +2 -0
  55. package/dist/http/decode-response.cjs.map +1 -0
  56. package/dist/http/decode-response.js +28 -0
  57. package/dist/http/decode-response.js.map +1 -0
  58. package/dist/styles/InstallButton.css +2 -0
  59. package/dist/styles/actions.css +6 -1
  60. package/dist/styles/advanced.css +2 -2
  61. package/dist/styles/component/Command.css +1 -1
  62. package/dist/styles/component/ContextMenu.css +1 -1
  63. package/dist/styles/component/DropdownMenu.css +1 -1
  64. package/dist/styles/component/InstallBanner.css +2 -0
  65. package/dist/styles/component/InstallButton.css +4 -0
  66. package/dist/styles/component/SegmentedControl.css +1 -1
  67. package/dist/styles/component/Select.css +4 -0
  68. package/dist/styles/core.css +2 -2
  69. package/dist/styles/forms.css +4 -0
  70. package/dist/styles/manifest.json +2 -1
  71. package/dist/styles/navigation.css +1 -1
  72. package/dist/styles/tokens.css +2 -2
  73. package/dist/styles.css +1 -1
  74. package/dist/tempest-react-sdk.cjs +1 -1
  75. package/dist/tempest-react-sdk.d.ts +258 -26
  76. package/dist/tempest-react-sdk.js +319 -318
  77. package/dist/theme/ThemeProvider.cjs +1 -1
  78. package/dist/theme/ThemeProvider.cjs.map +1 -1
  79. package/dist/theme/ThemeProvider.js +40 -45
  80. package/dist/theme/ThemeProvider.js.map +1 -1
  81. package/dist/theme/create-theme.cjs +1 -1
  82. package/dist/theme/create-theme.cjs.map +1 -1
  83. package/dist/theme/create-theme.js +1 -1
  84. package/dist/theme/create-theme.js.map +1 -1
  85. package/dist/theme/initial-theme.cjs +2 -2
  86. package/dist/theme/initial-theme.cjs.map +1 -1
  87. package/dist/theme/initial-theme.js +7 -12
  88. package/dist/theme/initial-theme.js.map +1 -1
  89. package/dist/utils/format.cjs +1 -1
  90. package/dist/utils/format.cjs.map +1 -1
  91. package/dist/utils/format.js +5 -3
  92. package/dist/utils/format.js.map +1 -1
  93. package/dist/utils/storage.cjs +1 -1
  94. package/dist/utils/storage.cjs.map +1 -1
  95. package/dist/utils/storage.js +13 -0
  96. package/dist/utils/storage.js.map +1 -1
  97. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"initial-theme.cjs","names":[],"sources":["../../src/theme/initial-theme.ts"],"sourcesContent":["import 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 let stored: ThemeMode | null;\n try {\n stored = window.localStorage.getItem(storageKey) as ThemeMode | null;\n } catch {\n stored = null;\n }\n\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":"AAcA,SAAgB,EAAgB,EAAkC,CAAC,EAAkB,CACjF,GAAM,CAAE,aAAa,gBAAiB,eAAe,UAAa,EAElE,GAAI,OAAO,OAAW,IAClB,OAAO,IAAiB,OAAS,OAAS,QAG9C,IAAI,EACJ,GAAI,CACA,EAAS,OAAO,aAAa,QAAQ,CAAU,CACnD,MAAQ,CACJ,EAAS,IACb,CAEA,IAAM,EAAkB,GAAU,EAGlC,OAFI,IAAS,QAAU,IAAS,QAAgB,EAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,QAAU,OAAS,OAChF,CAUA,SAAgB,EAAgB,EAAkC,CAAC,EAAW,CAC1E,IAAM,EAAa,EAAQ,YAAc,gBACnC,EAAe,EAAQ,cAAgB,SAC7C,MAAO;;YAEC,KAAK,UAAU,CAAU,EAAE;YAC3B,KAAK,UAAU,CAAY,EAAE;;;;;;EAMvC,KAAK,CACP"}
1
+ {"version":3,"file":"initial-theme.cjs","names":[],"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":"wCAeA,SAAgB,EAAgB,EAAkC,CAAC,EAAkB,CACjF,GAAM,CAAE,aAAa,gBAAiB,eAAe,UAAa,EAElE,GAAI,OAAO,OAAW,IAClB,OAAO,IAAiB,OAAS,OAAS,QAI9C,IAAM,EADS,EAAA,QAAQ,OAAO,CACN,GAAU,EAGlC,OAFI,IAAS,QAAU,IAAS,QAAgB,EAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,QAAU,OAAS,OAChF,CAUA,SAAgB,EAAgB,EAAkC,CAAC,EAAW,CAC1E,IAAM,EAAa,EAAQ,YAAc,gBACnC,EAAe,EAAQ,cAAgB,SAC7C,MAAO;;YAEC,KAAK,UAAU,CAAU,EAAE;YAC3B,KAAK,UAAU,CAAY,EAAE;;;;;;EAMvC,KAAK,CACP"}
@@ -1,17 +1,12 @@
1
+ import { storage as e } from "../utils/storage.js";
1
2
  //#region src/theme/initial-theme.ts
2
- function e(e = {}) {
3
- let { storageKey: t = "tempest-theme", defaultTheme: n = "system" } = e;
4
- if (typeof window > "u") return n === "dark" ? "dark" : "light";
5
- let r;
6
- try {
7
- r = window.localStorage.getItem(t);
8
- } catch {
9
- r = null;
10
- }
11
- let i = r ?? n;
3
+ function t(t = {}) {
4
+ let { storageKey: n = "tempest-theme", defaultTheme: r = "system" } = t;
5
+ if (typeof window > "u") return r === "dark" ? "dark" : "light";
6
+ let i = e.getRaw(n) ?? r;
12
7
  return i === "dark" || i === "light" ? i : window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
13
8
  }
14
- function t(e = {}) {
9
+ function n(e = {}) {
15
10
  let t = e.storageKey ?? "tempest-theme", n = e.defaultTheme ?? "system";
16
11
  return `
17
12
  (function(){try{
@@ -25,6 +20,6 @@ function t(e = {}) {
25
20
  `.trim();
26
21
  }
27
22
  //#endregion
28
- export { e as getInitialTheme, t as themeInitScript };
23
+ export { t as getInitialTheme, n as themeInitScript };
29
24
 
30
25
  //# sourceMappingURL=initial-theme.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"initial-theme.js","names":[],"sources":["../../src/theme/initial-theme.ts"],"sourcesContent":["import 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 let stored: ThemeMode | null;\n try {\n stored = window.localStorage.getItem(storageKey) as ThemeMode | null;\n } catch {\n stored = null;\n }\n\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":";AAcA,SAAgB,EAAgB,IAAkC,CAAC,GAAkB;CACjF,IAAM,EAAE,gBAAa,iBAAiB,kBAAe,aAAa;CAElE,IAAI,OAAO,SAAW,KAClB,OAAO,MAAiB,SAAS,SAAS;CAG9C,IAAI;CACJ,IAAI;EACA,IAAS,OAAO,aAAa,QAAQ,CAAU;CACnD,QAAQ;EACJ,IAAS;CACb;CAEA,IAAM,IAAkB,KAAU;CAGlC,OAFI,MAAS,UAAU,MAAS,UAAgB,IAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,UAAU,SAAS;AAChF;AAUA,SAAgB,EAAgB,IAAkC,CAAC,GAAW;CAC1E,IAAM,IAAa,EAAQ,cAAc,iBACnC,IAAe,EAAQ,gBAAgB;CAC7C,OAAO;;YAEC,KAAK,UAAU,CAAU,EAAE;YAC3B,KAAK,UAAU,CAAY,EAAE;;;;;;EAMvC,KAAK;AACP"}
1
+ {"version":3,"file":"initial-theme.js","names":[],"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,SAAgB,EAAgB,IAAkC,CAAC,GAAkB;CACjF,IAAM,EAAE,gBAAa,iBAAiB,kBAAe,aAAa;CAElE,IAAI,OAAO,SAAW,KAClB,OAAO,MAAiB,SAAS,SAAS;CAI9C,IAAM,IADS,EAAQ,OAAO,CACN,KAAU;CAGlC,OAFI,MAAS,UAAU,MAAS,UAAgB,IAEzC,OAAO,WAAW,8BAA8B,CAAC,CAAC,UAAU,SAAS;AAChF;AAUA,SAAgB,EAAgB,IAAkC,CAAC,GAAW;CAC1E,IAAM,IAAa,EAAQ,cAAc,iBACnC,IAAe,EAAQ,gBAAgB;CAC7C,OAAO;;YAEC,KAAK,UAAU,CAAU,EAAE;YAC3B,KAAK,UAAU,CAAY,EAAE;;;;;;EAMvC,KAAK;AACP"}
@@ -1,2 +1,2 @@
1
- const e=require("./intl-cache.cjs");function t(t){return e.numberFormat(`pt-BR`,{style:`currency`,currency:`BRL`}).format(t)}function n(t){let n=typeof t==`string`?new Date(t):t;return Number.isNaN(n.getTime())?``:e.dateTimeFormat(`pt-BR`).format(n)}function r(e){if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}$/.test(e))return e;let t=typeof e==`string`?new Date(e):e;if(Number.isNaN(t.getTime()))return``;let n=`${t.getMonth()+1}`.padStart(2,`0`),r=`${t.getDate()}`.padStart(2,`0`);return`${t.getFullYear()}-${n}-${r}`}function i(e){if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(e))return e;let t=typeof e==`string`?new Date(e):e;if(Number.isNaN(t.getTime()))return``;let n=`${t.getMonth()+1}`.padStart(2,`0`),r=`${t.getDate()}`.padStart(2,`0`),i=`${t.getHours()}`.padStart(2,`0`),a=`${t.getMinutes()}`.padStart(2,`0`);return`${t.getFullYear()}-${n}-${r}T${i}:${a}`}function a(t){let n=typeof t==`string`?new Date(t):t;return Number.isNaN(n.getTime())?``:e.dateTimeFormat(`pt-BR`,{dateStyle:`short`,timeStyle:`short`}).format(n)}function o(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 s(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 c(t){return e.numberFormat(`pt-BR`,{style:`percent`,minimumFractionDigits:1,maximumFractionDigits:1}).format(t)}exports.formatCPF=s,exports.formatCurrency=t,exports.formatDate=n,exports.formatDateForInput=r,exports.formatDateTime=a,exports.formatDateTimeForInput=i,exports.formatPercent=c,exports.formatPhone=o;
1
+ const e=require("./intl-cache.cjs");function t(t){return e.numberFormat(`pt-BR`,{style:`currency`,currency:`BRL`}).format(t)}function n(t){let n=typeof t==`string`?new Date(t):t;return Number.isNaN(n.getTime())?``:e.dateTimeFormat(`pt-BR`).format(n)}function r(e){if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}$/.test(e))return e;let t=typeof e==`string`?new Date(e):e;if(Number.isNaN(t.getTime()))return``;let n=`${t.getMonth()+1}`.padStart(2,`0`),r=`${t.getDate()}`.padStart(2,`0`);return`${t.getFullYear()}-${n}-${r}`}function i(e){if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(e))return e;let t=typeof e==`string`?new Date(e):e;if(Number.isNaN(t.getTime()))return``;let n=`${t.getMonth()+1}`.padStart(2,`0`),r=`${t.getDate()}`.padStart(2,`0`),i=`${t.getHours()}`.padStart(2,`0`),a=`${t.getMinutes()}`.padStart(2,`0`);return`${t.getFullYear()}-${n}-${r}T${i}:${a}`}function a(t){let n=typeof t==`string`?new Date(t):t;return Number.isNaN(n.getTime())?``:e.dateTimeFormat(`pt-BR`,{dateStyle:`short`,timeStyle:`short`}).format(n)}function o(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 s(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 c(t,n={}){if(!Number.isFinite(t))return`—`;let r=n.decimals??1;return e.numberFormat(`pt-BR`,{style:`percent`,minimumFractionDigits:r,maximumFractionDigits:r}).format(t)}exports.formatCPF=s,exports.formatCurrency=t,exports.formatDate=n,exports.formatDateForInput=r,exports.formatDateTime=a,exports.formatDateTimeForInput=i,exports.formatPercent=c,exports.formatPhone=o;
2
2
  //# sourceMappingURL=format.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.cjs","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["import { dateTimeFormat, numberFormat } from \"@/utils/intl-cache\";\n\n/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\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 * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\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 * @param value - ISO string or Date.\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(value: string | Date): string {\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(value: string | Date): string {\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 * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\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/**\n * Format a fraction (0-1) as a percentage with one decimal.\n *\n * @param value - Fraction between 0 and 1.\n * @returns Formatted percent string, e.g. \"12,5%\".\n */\nexport function formatPercent(value: number): string {\n return numberFormat(\"pt-BR\", {\n style: \"percent\",\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n }).format(value);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"format.cjs","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["import { dateTimeFormat, numberFormat } from \"@/utils/intl-cache\";\n\n/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\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 * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\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 * @param value - ISO string or Date.\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(value: string | Date): string {\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(value: string | Date): string {\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 * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\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 {\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(value: number, options: FormatPercentOptions = {}): string {\n if (!Number.isFinite(value)) return \"—\";\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":"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"}
@@ -43,11 +43,13 @@ function s(e, t = {}) {
43
43
  function c(e) {
44
44
  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");
45
45
  }
46
- function l(e) {
46
+ function l(e, n = {}) {
47
+ if (!Number.isFinite(e)) return "—";
48
+ let r = n.decimals ?? 1;
47
49
  return t("pt-BR", {
48
50
  style: "percent",
49
- minimumFractionDigits: 1,
50
- maximumFractionDigits: 1
51
+ minimumFractionDigits: r,
52
+ maximumFractionDigits: r
51
53
  }).format(e);
52
54
  }
53
55
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["import { dateTimeFormat, numberFormat } from \"@/utils/intl-cache\";\n\n/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\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 * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\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 * @param value - ISO string or Date.\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(value: string | Date): string {\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(value: string | Date): string {\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 * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\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/**\n * Format a fraction (0-1) as a percentage with one decimal.\n *\n * @param value - Fraction between 0 and 1.\n * @returns Formatted percent string, e.g. \"12,5%\".\n */\nexport function formatPercent(value: number): string {\n return numberFormat(\"pt-BR\", {\n style: \"percent\",\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n }).format(value);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"format.js","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["import { dateTimeFormat, numberFormat } from \"@/utils/intl-cache\";\n\n/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\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 * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\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 * @param value - ISO string or Date.\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(value: string | Date): string {\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(value: string | Date): string {\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 * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\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 {\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(value: number, options: FormatPercentOptions = {}): string {\n if (!Number.isFinite(value)) return \"—\";\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":"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"}
@@ -1,2 +1,2 @@
1
- var e={serialize:e=>JSON.stringify(e),deserialize:e=>JSON.parse(e)};function t(e,t){try{return e.serialize(t)}catch{try{return JSON.stringify(t)}catch{return null}}}function n(n=e){return{get(e,t){if(typeof window>`u`)return t;try{let r=window.localStorage.getItem(e);return r===null?t:n.deserialize(r)}catch{return t}},set(e,r){if(typeof window>`u`)return;let i=t(n,r);if(i!==null)try{window.localStorage.setItem(e,i)}catch{}},remove(e){if(!(typeof window>`u`))try{window.localStorage.removeItem(e)}catch{}}}}var r=n();exports.createJsonStorage=n,exports.storage=r;
1
+ var e={serialize:e=>JSON.stringify(e),deserialize:e=>JSON.parse(e)};function t(e,t){try{return e.serialize(t)}catch{try{return JSON.stringify(t)}catch{return null}}}function n(n=e){return{get(e,t){if(typeof window>`u`)return t;try{let r=window.localStorage.getItem(e);return r===null?t:n.deserialize(r)}catch{return t}},set(e,r){if(typeof window>`u`)return;let i=t(n,r);if(i!==null)try{window.localStorage.setItem(e,i)}catch{}},getRaw(e){if(typeof window>`u`)return null;try{return window.localStorage.getItem(e)}catch{return null}},setRaw(e,t){if(!(typeof window>`u`))try{window.localStorage.setItem(e,t)}catch{}},remove(e){if(!(typeof window>`u`))try{window.localStorage.removeItem(e)}catch{}}}}var r=n();exports.createJsonStorage=n,exports.storage=r;
2
2
  //# sourceMappingURL=storage.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.cjs","names":[],"sources":["../../src/utils/storage.ts"],"sourcesContent":["/** How a {@link createJsonStorage} store turns values into text and back. */\nexport interface StorageCodec {\n /** Encode a value for storage. */\n serialize: <T>(value: T) => string;\n /** Decode a stored string. */\n deserialize: <T>(raw: string) => T;\n}\n\n/** The surface every store built by {@link createJsonStorage} exposes. */\nexport interface JsonStorage {\n /**\n * Read and decode a key.\n *\n * @typeParam T - The expected value shape.\n * @param key - Storage key.\n * @param fallback - Returned when the key is absent, unreadable, or corrupt.\n * @returns The stored value, or `fallback`.\n */\n get<T>(key: string, fallback: T): T;\n /**\n * Encode and write a key.\n *\n * @typeParam T - The value being stored.\n * @param key - Storage key.\n * @param value - Any value the codec can represent.\n */\n set<T>(key: string, value: T): void;\n /**\n * Delete a key.\n *\n * @param key - Storage key.\n */\n remove(key: string): void;\n}\n\nconst jsonCodec: StorageCodec = {\n serialize: (value) => JSON.stringify(value),\n deserialize: (raw) => JSON.parse(raw),\n};\n\n/**\n * Encode with the codec, degrading to plain JSON when the codec itself fails.\n *\n * A codec that throws — a compressor running out of room on a huge value —\n * should cost size, not the record: a slightly larger row still loads, an absent\n * one does not. `null` means not even JSON could represent the value, which is\n * the one case where dropping the write is the only option left.\n *\n * @param codec - The store's codec.\n * @param value - The value to encode.\n * @returns The encoded string, or `null` when the value cannot be represented.\n */\nfunction encode<T>(codec: StorageCodec, value: T): string | null {\n try {\n return codec.serialize(value);\n } catch {\n try {\n return JSON.stringify(value);\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Build a typed `localStorage` wrapper around a codec.\n *\n * One implementation behind every store the SDK ships, so they cannot drift in\n * surface. {@link storage} and {@link compressedStorage} are both this function —\n * before it, the compressed one was a hand-written copy of the same guards and\n * had no `remove`, while its own docstring promised the two were interchangeable\n * at the call site. They were not, and the promise failed exactly where somebody\n * followed it.\n *\n * @example\n * const session = createJsonStorage();\n * session.set(\"draft\", { title: \"Sem título\" });\n *\n * @example\n * const packed = createJsonStorage(compressedStorageCodec);\n *\n * @param codec - Encoder pair. Defaults to plain JSON.\n * @returns A store with `get` / `set` / `remove`.\n *\n * @tempest-limits empty-catch — every method is best-effort by contract:\n * `localStorage` throws on quota exhaustion, in Safari private mode, and when a\n * cross-origin frame has storage blocked. A caller persisting a preference has no\n * recovery to run and no user-facing message to show, so the write is dropped and\n * the app keeps the value in memory for the session.\n */\nexport function createJsonStorage(codec: StorageCodec = jsonCodec): JsonStorage {\n return {\n get<T>(key: string, fallback: T): T {\n if (typeof window === \"undefined\") return fallback;\n try {\n const raw = window.localStorage.getItem(key);\n return raw === null ? fallback : codec.deserialize<T>(raw);\n } catch {\n return fallback;\n }\n },\n\n set<T>(key: string, value: T): void {\n if (typeof window === \"undefined\") return;\n const encoded = encode(codec, value);\n if (encoded === null) return;\n try {\n window.localStorage.setItem(key, encoded);\n } catch {\n /* empty */\n }\n },\n\n remove(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n /* empty */\n }\n },\n };\n}\n\n/**\n * Typed wrapper around `localStorage` that JSON-encodes values and silently\n * handles environments where storage is unavailable (SSR, private mode).\n */\nexport const storage: JsonStorage = createJsonStorage();\n"],"mappings":"AAmCA,IAAM,EAA0B,CAC5B,UAAY,GAAU,KAAK,UAAU,CAAK,EAC1C,YAAc,GAAQ,KAAK,MAAM,CAAG,CACxC,EAcA,SAAS,EAAU,EAAqB,EAAyB,CAC7D,GAAI,CACA,OAAO,EAAM,UAAU,CAAK,CAChC,MAAQ,CACJ,GAAI,CACA,OAAO,KAAK,UAAU,CAAK,CAC/B,MAAQ,CACJ,OAAO,IACX,CACJ,CACJ,CA4BA,SAAgB,EAAkB,EAAsB,EAAwB,CAC5E,MAAO,CACH,IAAO,EAAa,EAAgB,CAChC,GAAI,OAAO,OAAW,IAAa,OAAO,EAC1C,GAAI,CACA,IAAM,EAAM,OAAO,aAAa,QAAQ,CAAG,EAC3C,OAAO,IAAQ,KAAO,EAAW,EAAM,YAAe,CAAG,CAC7D,MAAQ,CACJ,OAAO,CACX,CACJ,EAEA,IAAO,EAAa,EAAgB,CAChC,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,EAAU,EAAO,EAAO,CAAK,EAC/B,OAAY,KAChB,GAAI,CACA,OAAO,aAAa,QAAQ,EAAK,CAAO,CAC5C,MAAQ,CAER,CACJ,EAEA,OAAO,EAAmB,CAClB,YAAO,OAAW,KACtB,GAAI,CACA,OAAO,aAAa,WAAW,CAAG,CACtC,MAAQ,CAER,CACJ,CACJ,CACJ,CAMA,IAAa,EAAuB,EAAkB"}
1
+ {"version":3,"file":"storage.cjs","names":[],"sources":["../../src/utils/storage.ts"],"sourcesContent":["/** How a {@link createJsonStorage} store turns values into text and back. */\nexport interface StorageCodec {\n /** Encode a value for storage. */\n serialize: <T>(value: T) => string;\n /** Decode a stored string. */\n deserialize: <T>(raw: string) => T;\n}\n\n/** The surface every store built by {@link createJsonStorage} exposes. */\nexport interface JsonStorage {\n /**\n * Read and decode a key.\n *\n * The value is decoded by the store's codec — JSON for {@link storage} —\n * which is what makes a key written as a bare string unreadable here: it is\n * not the shape this method writes. {@link JsonStorage.getRaw} reads those.\n *\n * @typeParam T - The expected value shape.\n * @param key - Storage key.\n * @param fallback - Returned when the key is absent, unreadable, or corrupt.\n * @returns The stored value, or `fallback`.\n */\n get<T>(key: string, fallback: T): T;\n /**\n * Encode and write a key.\n *\n * The value is **encoded** — `set(\"theme\", \"dark\")` stores `\"dark\"` with the\n * quotes, not `dark`. That matters when an existing key is being adopted:\n * whatever read it before will no longer recognise what this writes, and the\n * old value will no longer parse here. {@link JsonStorage.setRaw} is the\n * passthrough.\n *\n * @typeParam T - The value being stored.\n * @param key - Storage key.\n * @param value - Any value the codec can represent.\n */\n set<T>(key: string, value: T): void;\n /**\n * Read a key exactly as `localStorage` holds it, with no decoding.\n *\n * The escape hatch {@link JsonStorage.get} cannot be: `get` runs the codec,\n * so a key holding a bare `dark` — written by another library, by the app\n * before it adopted this wrapper, or by the SDK's own `ThemeProvider` —\n * fails to parse and comes back as the fallback. Reading that key is a\n * different operation, not a variant of the same one.\n *\n * @param key - Storage key.\n * @returns The stored string, or `null` when the key is absent or storage is\n * unavailable.\n */\n getRaw(key: string): string | null;\n /**\n * Write a string exactly as given, with no encoding.\n *\n * The counterpart of {@link JsonStorage.getRaw}, and what keeps adoption from\n * being a silent migration: `set(\"theme\", \"dark\")` stores `\"dark\"` **with the\n * quotes**, which the app's previous reader no longer recognises.\n *\n * @param key - Storage key.\n * @param value - The exact string to store.\n */\n setRaw(key: string, value: string): void;\n /**\n * Delete a key.\n *\n * @param key - Storage key.\n */\n remove(key: string): void;\n}\n\nconst jsonCodec: StorageCodec = {\n serialize: (value) => JSON.stringify(value),\n deserialize: (raw) => JSON.parse(raw),\n};\n\n/**\n * Encode with the codec, degrading to plain JSON when the codec itself fails.\n *\n * A codec that throws — a compressor running out of room on a huge value —\n * should cost size, not the record: a slightly larger row still loads, an absent\n * one does not. `null` means not even JSON could represent the value, which is\n * the one case where dropping the write is the only option left.\n *\n * @param codec - The store's codec.\n * @param value - The value to encode.\n * @returns The encoded string, or `null` when the value cannot be represented.\n */\nfunction encode<T>(codec: StorageCodec, value: T): string | null {\n try {\n return codec.serialize(value);\n } catch {\n try {\n return JSON.stringify(value);\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Build a typed `localStorage` wrapper around a codec.\n *\n * One implementation behind every store the SDK ships, so they cannot drift in\n * surface. {@link storage} and {@link compressedStorage} are both this function —\n * before it, the compressed one was a hand-written copy of the same guards and\n * had no `remove`, while its own docstring promised the two were interchangeable\n * at the call site. They were not, and the promise failed exactly where somebody\n * followed it.\n *\n * @example\n * const session = createJsonStorage();\n * session.set(\"draft\", { title: \"Sem título\" });\n *\n * @example\n * const packed = createJsonStorage(compressedStorageCodec);\n *\n * @param codec - Encoder pair. Defaults to plain JSON.\n * @returns A store with `get` / `set` / `getRaw` / `setRaw` / `remove`.\n *\n * @tempest-limits empty-catch — every method is best-effort by contract:\n * `localStorage` throws on quota exhaustion, in Safari private mode, and when a\n * cross-origin frame has storage blocked. A caller persisting a preference has no\n * recovery to run and no user-facing message to show, so the write is dropped and\n * the app keeps the value in memory for the session.\n */\nexport function createJsonStorage(codec: StorageCodec = jsonCodec): JsonStorage {\n return {\n get<T>(key: string, fallback: T): T {\n if (typeof window === \"undefined\") return fallback;\n try {\n const raw = window.localStorage.getItem(key);\n return raw === null ? fallback : codec.deserialize<T>(raw);\n } catch {\n return fallback;\n }\n },\n\n set<T>(key: string, value: T): void {\n if (typeof window === \"undefined\") return;\n const encoded = encode(codec, value);\n if (encoded === null) return;\n try {\n window.localStorage.setItem(key, encoded);\n } catch {\n /* empty */\n }\n },\n\n getRaw(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n\n setRaw(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n /* empty */\n }\n },\n\n remove(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n /* empty */\n }\n },\n };\n}\n\n/**\n * Typed wrapper around `localStorage` that JSON-encodes values and silently\n * handles environments where storage is unavailable (SSR, private mode).\n *\n * `get`/`set` encode; `getRaw`/`setRaw` do not. Reach for the raw pair when the\n * key is shared with something that does not speak JSON — an existing key in an\n * app adopting this wrapper, a key another library owns, or the theme\n * preference the SDK's own `ThemeProvider` stores as a bare string.\n */\nexport const storage: JsonStorage = createJsonStorage();\n"],"mappings":"AAsEA,IAAM,EAA0B,CAC5B,UAAY,GAAU,KAAK,UAAU,CAAK,EAC1C,YAAc,GAAQ,KAAK,MAAM,CAAG,CACxC,EAcA,SAAS,EAAU,EAAqB,EAAyB,CAC7D,GAAI,CACA,OAAO,EAAM,UAAU,CAAK,CAChC,MAAQ,CACJ,GAAI,CACA,OAAO,KAAK,UAAU,CAAK,CAC/B,MAAQ,CACJ,OAAO,IACX,CACJ,CACJ,CA4BA,SAAgB,EAAkB,EAAsB,EAAwB,CAC5E,MAAO,CACH,IAAO,EAAa,EAAgB,CAChC,GAAI,OAAO,OAAW,IAAa,OAAO,EAC1C,GAAI,CACA,IAAM,EAAM,OAAO,aAAa,QAAQ,CAAG,EAC3C,OAAO,IAAQ,KAAO,EAAW,EAAM,YAAe,CAAG,CAC7D,MAAQ,CACJ,OAAO,CACX,CACJ,EAEA,IAAO,EAAa,EAAgB,CAChC,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,EAAU,EAAO,EAAO,CAAK,EAC/B,OAAY,KAChB,GAAI,CACA,OAAO,aAAa,QAAQ,EAAK,CAAO,CAC5C,MAAQ,CAER,CACJ,EAEA,OAAO,EAA4B,CAC/B,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,GAAI,CACA,OAAO,OAAO,aAAa,QAAQ,CAAG,CAC1C,MAAQ,CACJ,OAAO,IACX,CACJ,EAEA,OAAO,EAAa,EAAqB,CACjC,YAAO,OAAW,KACtB,GAAI,CACA,OAAO,aAAa,QAAQ,EAAK,CAAK,CAC1C,MAAQ,CAER,CACJ,EAEA,OAAO,EAAmB,CAClB,YAAO,OAAW,KACtB,GAAI,CACA,OAAO,aAAa,WAAW,CAAG,CACtC,MAAQ,CAER,CACJ,CACJ,CACJ,CAWA,IAAa,EAAuB,EAAkB"}
@@ -32,6 +32,19 @@ function n(n = e) {
32
32
  window.localStorage.setItem(e, i);
33
33
  } catch {}
34
34
  },
35
+ getRaw(e) {
36
+ if (typeof window > "u") return null;
37
+ try {
38
+ return window.localStorage.getItem(e);
39
+ } catch {
40
+ return null;
41
+ }
42
+ },
43
+ setRaw(e, t) {
44
+ if (!(typeof window > "u")) try {
45
+ window.localStorage.setItem(e, t);
46
+ } catch {}
47
+ },
35
48
  remove(e) {
36
49
  if (!(typeof window > "u")) try {
37
50
  window.localStorage.removeItem(e);
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","names":[],"sources":["../../src/utils/storage.ts"],"sourcesContent":["/** How a {@link createJsonStorage} store turns values into text and back. */\nexport interface StorageCodec {\n /** Encode a value for storage. */\n serialize: <T>(value: T) => string;\n /** Decode a stored string. */\n deserialize: <T>(raw: string) => T;\n}\n\n/** The surface every store built by {@link createJsonStorage} exposes. */\nexport interface JsonStorage {\n /**\n * Read and decode a key.\n *\n * @typeParam T - The expected value shape.\n * @param key - Storage key.\n * @param fallback - Returned when the key is absent, unreadable, or corrupt.\n * @returns The stored value, or `fallback`.\n */\n get<T>(key: string, fallback: T): T;\n /**\n * Encode and write a key.\n *\n * @typeParam T - The value being stored.\n * @param key - Storage key.\n * @param value - Any value the codec can represent.\n */\n set<T>(key: string, value: T): void;\n /**\n * Delete a key.\n *\n * @param key - Storage key.\n */\n remove(key: string): void;\n}\n\nconst jsonCodec: StorageCodec = {\n serialize: (value) => JSON.stringify(value),\n deserialize: (raw) => JSON.parse(raw),\n};\n\n/**\n * Encode with the codec, degrading to plain JSON when the codec itself fails.\n *\n * A codec that throws — a compressor running out of room on a huge value —\n * should cost size, not the record: a slightly larger row still loads, an absent\n * one does not. `null` means not even JSON could represent the value, which is\n * the one case where dropping the write is the only option left.\n *\n * @param codec - The store's codec.\n * @param value - The value to encode.\n * @returns The encoded string, or `null` when the value cannot be represented.\n */\nfunction encode<T>(codec: StorageCodec, value: T): string | null {\n try {\n return codec.serialize(value);\n } catch {\n try {\n return JSON.stringify(value);\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Build a typed `localStorage` wrapper around a codec.\n *\n * One implementation behind every store the SDK ships, so they cannot drift in\n * surface. {@link storage} and {@link compressedStorage} are both this function —\n * before it, the compressed one was a hand-written copy of the same guards and\n * had no `remove`, while its own docstring promised the two were interchangeable\n * at the call site. They were not, and the promise failed exactly where somebody\n * followed it.\n *\n * @example\n * const session = createJsonStorage();\n * session.set(\"draft\", { title: \"Sem título\" });\n *\n * @example\n * const packed = createJsonStorage(compressedStorageCodec);\n *\n * @param codec - Encoder pair. Defaults to plain JSON.\n * @returns A store with `get` / `set` / `remove`.\n *\n * @tempest-limits empty-catch — every method is best-effort by contract:\n * `localStorage` throws on quota exhaustion, in Safari private mode, and when a\n * cross-origin frame has storage blocked. A caller persisting a preference has no\n * recovery to run and no user-facing message to show, so the write is dropped and\n * the app keeps the value in memory for the session.\n */\nexport function createJsonStorage(codec: StorageCodec = jsonCodec): JsonStorage {\n return {\n get<T>(key: string, fallback: T): T {\n if (typeof window === \"undefined\") return fallback;\n try {\n const raw = window.localStorage.getItem(key);\n return raw === null ? fallback : codec.deserialize<T>(raw);\n } catch {\n return fallback;\n }\n },\n\n set<T>(key: string, value: T): void {\n if (typeof window === \"undefined\") return;\n const encoded = encode(codec, value);\n if (encoded === null) return;\n try {\n window.localStorage.setItem(key, encoded);\n } catch {\n /* empty */\n }\n },\n\n remove(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n /* empty */\n }\n },\n };\n}\n\n/**\n * Typed wrapper around `localStorage` that JSON-encodes values and silently\n * handles environments where storage is unavailable (SSR, private mode).\n */\nexport const storage: JsonStorage = createJsonStorage();\n"],"mappings":";AAmCA,IAAM,IAA0B;CAC5B,YAAY,MAAU,KAAK,UAAU,CAAK;CAC1C,cAAc,MAAQ,KAAK,MAAM,CAAG;AACxC;AAcA,SAAS,EAAU,GAAqB,GAAyB;CAC7D,IAAI;EACA,OAAO,EAAM,UAAU,CAAK;CAChC,QAAQ;EACJ,IAAI;GACA,OAAO,KAAK,UAAU,CAAK;EAC/B,QAAQ;GACJ,OAAO;EACX;CACJ;AACJ;AA4BA,SAAgB,EAAkB,IAAsB,GAAwB;CAC5E,OAAO;EACH,IAAO,GAAa,GAAgB;GAChC,IAAI,OAAO,SAAW,KAAa,OAAO;GAC1C,IAAI;IACA,IAAM,IAAM,OAAO,aAAa,QAAQ,CAAG;IAC3C,OAAO,MAAQ,OAAO,IAAW,EAAM,YAAe,CAAG;GAC7D,QAAQ;IACJ,OAAO;GACX;EACJ;EAEA,IAAO,GAAa,GAAgB;GAChC,IAAI,OAAO,SAAW,KAAa;GACnC,IAAM,IAAU,EAAO,GAAO,CAAK;GAC/B,UAAY,MAChB,IAAI;IACA,OAAO,aAAa,QAAQ,GAAK,CAAO;GAC5C,QAAQ,CAER;EACJ;EAEA,OAAO,GAAmB;GAClB,aAAO,SAAW,MACtB,IAAI;IACA,OAAO,aAAa,WAAW,CAAG;GACtC,QAAQ,CAER;EACJ;CACJ;AACJ;AAMA,IAAa,IAAuB,EAAkB"}
1
+ {"version":3,"file":"storage.js","names":[],"sources":["../../src/utils/storage.ts"],"sourcesContent":["/** How a {@link createJsonStorage} store turns values into text and back. */\nexport interface StorageCodec {\n /** Encode a value for storage. */\n serialize: <T>(value: T) => string;\n /** Decode a stored string. */\n deserialize: <T>(raw: string) => T;\n}\n\n/** The surface every store built by {@link createJsonStorage} exposes. */\nexport interface JsonStorage {\n /**\n * Read and decode a key.\n *\n * The value is decoded by the store's codec — JSON for {@link storage} —\n * which is what makes a key written as a bare string unreadable here: it is\n * not the shape this method writes. {@link JsonStorage.getRaw} reads those.\n *\n * @typeParam T - The expected value shape.\n * @param key - Storage key.\n * @param fallback - Returned when the key is absent, unreadable, or corrupt.\n * @returns The stored value, or `fallback`.\n */\n get<T>(key: string, fallback: T): T;\n /**\n * Encode and write a key.\n *\n * The value is **encoded** — `set(\"theme\", \"dark\")` stores `\"dark\"` with the\n * quotes, not `dark`. That matters when an existing key is being adopted:\n * whatever read it before will no longer recognise what this writes, and the\n * old value will no longer parse here. {@link JsonStorage.setRaw} is the\n * passthrough.\n *\n * @typeParam T - The value being stored.\n * @param key - Storage key.\n * @param value - Any value the codec can represent.\n */\n set<T>(key: string, value: T): void;\n /**\n * Read a key exactly as `localStorage` holds it, with no decoding.\n *\n * The escape hatch {@link JsonStorage.get} cannot be: `get` runs the codec,\n * so a key holding a bare `dark` — written by another library, by the app\n * before it adopted this wrapper, or by the SDK's own `ThemeProvider` —\n * fails to parse and comes back as the fallback. Reading that key is a\n * different operation, not a variant of the same one.\n *\n * @param key - Storage key.\n * @returns The stored string, or `null` when the key is absent or storage is\n * unavailable.\n */\n getRaw(key: string): string | null;\n /**\n * Write a string exactly as given, with no encoding.\n *\n * The counterpart of {@link JsonStorage.getRaw}, and what keeps adoption from\n * being a silent migration: `set(\"theme\", \"dark\")` stores `\"dark\"` **with the\n * quotes**, which the app's previous reader no longer recognises.\n *\n * @param key - Storage key.\n * @param value - The exact string to store.\n */\n setRaw(key: string, value: string): void;\n /**\n * Delete a key.\n *\n * @param key - Storage key.\n */\n remove(key: string): void;\n}\n\nconst jsonCodec: StorageCodec = {\n serialize: (value) => JSON.stringify(value),\n deserialize: (raw) => JSON.parse(raw),\n};\n\n/**\n * Encode with the codec, degrading to plain JSON when the codec itself fails.\n *\n * A codec that throws — a compressor running out of room on a huge value —\n * should cost size, not the record: a slightly larger row still loads, an absent\n * one does not. `null` means not even JSON could represent the value, which is\n * the one case where dropping the write is the only option left.\n *\n * @param codec - The store's codec.\n * @param value - The value to encode.\n * @returns The encoded string, or `null` when the value cannot be represented.\n */\nfunction encode<T>(codec: StorageCodec, value: T): string | null {\n try {\n return codec.serialize(value);\n } catch {\n try {\n return JSON.stringify(value);\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Build a typed `localStorage` wrapper around a codec.\n *\n * One implementation behind every store the SDK ships, so they cannot drift in\n * surface. {@link storage} and {@link compressedStorage} are both this function —\n * before it, the compressed one was a hand-written copy of the same guards and\n * had no `remove`, while its own docstring promised the two were interchangeable\n * at the call site. They were not, and the promise failed exactly where somebody\n * followed it.\n *\n * @example\n * const session = createJsonStorage();\n * session.set(\"draft\", { title: \"Sem título\" });\n *\n * @example\n * const packed = createJsonStorage(compressedStorageCodec);\n *\n * @param codec - Encoder pair. Defaults to plain JSON.\n * @returns A store with `get` / `set` / `getRaw` / `setRaw` / `remove`.\n *\n * @tempest-limits empty-catch — every method is best-effort by contract:\n * `localStorage` throws on quota exhaustion, in Safari private mode, and when a\n * cross-origin frame has storage blocked. A caller persisting a preference has no\n * recovery to run and no user-facing message to show, so the write is dropped and\n * the app keeps the value in memory for the session.\n */\nexport function createJsonStorage(codec: StorageCodec = jsonCodec): JsonStorage {\n return {\n get<T>(key: string, fallback: T): T {\n if (typeof window === \"undefined\") return fallback;\n try {\n const raw = window.localStorage.getItem(key);\n return raw === null ? fallback : codec.deserialize<T>(raw);\n } catch {\n return fallback;\n }\n },\n\n set<T>(key: string, value: T): void {\n if (typeof window === \"undefined\") return;\n const encoded = encode(codec, value);\n if (encoded === null) return;\n try {\n window.localStorage.setItem(key, encoded);\n } catch {\n /* empty */\n }\n },\n\n getRaw(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n\n setRaw(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n /* empty */\n }\n },\n\n remove(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n /* empty */\n }\n },\n };\n}\n\n/**\n * Typed wrapper around `localStorage` that JSON-encodes values and silently\n * handles environments where storage is unavailable (SSR, private mode).\n *\n * `get`/`set` encode; `getRaw`/`setRaw` do not. Reach for the raw pair when the\n * key is shared with something that does not speak JSON — an existing key in an\n * app adopting this wrapper, a key another library owns, or the theme\n * preference the SDK's own `ThemeProvider` stores as a bare string.\n */\nexport const storage: JsonStorage = createJsonStorage();\n"],"mappings":";AAsEA,IAAM,IAA0B;CAC5B,YAAY,MAAU,KAAK,UAAU,CAAK;CAC1C,cAAc,MAAQ,KAAK,MAAM,CAAG;AACxC;AAcA,SAAS,EAAU,GAAqB,GAAyB;CAC7D,IAAI;EACA,OAAO,EAAM,UAAU,CAAK;CAChC,QAAQ;EACJ,IAAI;GACA,OAAO,KAAK,UAAU,CAAK;EAC/B,QAAQ;GACJ,OAAO;EACX;CACJ;AACJ;AA4BA,SAAgB,EAAkB,IAAsB,GAAwB;CAC5E,OAAO;EACH,IAAO,GAAa,GAAgB;GAChC,IAAI,OAAO,SAAW,KAAa,OAAO;GAC1C,IAAI;IACA,IAAM,IAAM,OAAO,aAAa,QAAQ,CAAG;IAC3C,OAAO,MAAQ,OAAO,IAAW,EAAM,YAAe,CAAG;GAC7D,QAAQ;IACJ,OAAO;GACX;EACJ;EAEA,IAAO,GAAa,GAAgB;GAChC,IAAI,OAAO,SAAW,KAAa;GACnC,IAAM,IAAU,EAAO,GAAO,CAAK;GAC/B,UAAY,MAChB,IAAI;IACA,OAAO,aAAa,QAAQ,GAAK,CAAO;GAC5C,QAAQ,CAER;EACJ;EAEA,OAAO,GAA4B;GAC/B,IAAI,OAAO,SAAW,KAAa,OAAO;GAC1C,IAAI;IACA,OAAO,OAAO,aAAa,QAAQ,CAAG;GAC1C,QAAQ;IACJ,OAAO;GACX;EACJ;EAEA,OAAO,GAAa,GAAqB;GACjC,aAAO,SAAW,MACtB,IAAI;IACA,OAAO,aAAa,QAAQ,GAAK,CAAK;GAC1C,QAAQ,CAER;EACJ;EAEA,OAAO,GAAmB;GAClB,aAAO,SAAW,MACtB,IAAI;IACA,OAAO,aAAa,WAAW,CAAG;GACtC,QAAQ,CAER;EACJ;CACJ;AACJ;AAWA,IAAa,IAAuB,EAAkB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tempest-react-sdk",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "description": "SDK público da Tempest com componentes, hooks e integrações para projetos React.",
5
5
  "type": "module",
6
6
  "license": "MIT",