tempest-react-sdk 0.65.0 → 0.67.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.
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +31 -36
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js +31 -40
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js +94 -73
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Combobox/Combobox.module.cjs +1 -1
- package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.module.js +4 -3
- package/dist/components/Combobox/Combobox.module.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js +80 -83
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js +43 -46
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js +129 -105
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.cjs +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.js +12 -11
- package/dist/components/DropdownMenu/DropdownMenu.module.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.cjs +1 -1
- package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.js +54 -29
- package/dist/components/HoverCard/HoverCard.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.module.cjs +1 -1
- package/dist/components/HoverCard/HoverCard.module.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.module.js +4 -3
- package/dist/components/HoverCard/HoverCard.module.js.map +1 -1
- package/dist/components/Lightbox/Lightbox.cjs +1 -1
- package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
- package/dist/components/Lightbox/Lightbox.js +86 -89
- package/dist/components/Lightbox/Lightbox.js.map +1 -1
- package/dist/components/Menubar/Menubar.cjs +1 -1
- package/dist/components/Menubar/Menubar.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.js +86 -65
- package/dist/components/Menubar/Menubar.js.map +1 -1
- package/dist/components/Menubar/Menubar.module.cjs +1 -1
- package/dist/components/Menubar/Menubar.module.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.module.js +4 -3
- package/dist/components/Menubar/Menubar.module.js.map +1 -1
- package/dist/components/Modal/Modal.cjs +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js +44 -51
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js +113 -96
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.cjs +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.js +4 -3
- package/dist/components/MultiSelect/MultiSelect.module.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +83 -61
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.js +4 -3
- package/dist/components/NavigationMenu/NavigationMenu.module.js.map +1 -1
- package/dist/components/Popover/Popover.cjs +1 -1
- package/dist/components/Popover/Popover.cjs.map +1 -1
- package/dist/components/Popover/Popover.js +57 -36
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Popover/Popover.module.cjs +1 -1
- package/dist/components/Popover/Popover.module.cjs.map +1 -1
- package/dist/components/Popover/Popover.module.js +7 -6
- package/dist/components/Popover/Popover.module.js.map +1 -1
- package/dist/components/Portal/anchor-position.cjs +2 -0
- package/dist/components/Portal/anchor-position.cjs.map +1 -0
- package/dist/components/Portal/anchor-position.js +99 -0
- package/dist/components/Portal/anchor-position.js.map +1 -0
- package/dist/components/Portal/escape-layer.cjs +2 -0
- package/dist/components/Portal/escape-layer.cjs.map +1 -0
- package/dist/components/Portal/escape-layer.js +30 -0
- package/dist/components/Portal/escape-layer.js.map +1 -0
- package/dist/components/Portal/tab-bridge.cjs +2 -0
- package/dist/components/Portal/tab-bridge.cjs.map +1 -0
- package/dist/components/Portal/tab-bridge.js +39 -0
- package/dist/components/Portal/tab-bridge.js.map +1 -0
- package/dist/components/Tooltip/Tooltip.cjs +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +43 -29
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.module.cjs +1 -1
- package/dist/components/Tooltip/Tooltip.module.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.module.js +4 -3
- package/dist/components/Tooltip/Tooltip.module.js.map +1 -1
- package/dist/hooks/use-focus-trap.cjs +1 -1
- package/dist/hooks/use-focus-trap.cjs.map +1 -1
- package/dist/hooks/use-focus-trap.js +2 -2
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-sortable.cjs +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js +3 -3
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +87 -85
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +2 -0
- package/dist/http/credential-scope.cjs.map +1 -0
- package/dist/http/credential-scope.js +23 -0
- package/dist/http/credential-scope.js.map +1 -0
- package/dist/http/describe-api-error.cjs +1 -1
- package/dist/http/describe-api-error.cjs.map +1 -1
- package/dist/http/describe-api-error.js +4 -2
- package/dist/http/describe-api-error.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +121 -120
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +35 -34
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/styles/actions.css +3 -0
- package/dist/styles/advanced.css +1 -0
- package/dist/styles/component/Combobox.css +1 -0
- package/dist/styles/component/DropdownMenu.css +1 -0
- package/dist/styles/component/HoverCard.css +1 -0
- package/dist/styles/component/Menubar.css +1 -0
- package/dist/styles/component/MultiSelect.css +1 -0
- package/dist/styles/component/NavigationMenu.css +1 -0
- package/dist/styles/component/Popover.css +1 -0
- package/dist/styles/component/Tooltip.css +1 -0
- package/dist/styles/forms.css +2 -0
- package/dist/styles/navigation.css +2 -0
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +364 -29
- package/dist/tempest-react-sdk.js +335 -333
- package/dist/utils/absent.cjs +2 -0
- package/dist/utils/absent.cjs.map +1 -0
- package/dist/utils/absent.js +12 -0
- package/dist/utils/absent.js.map +1 -0
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +42 -37
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/numbers.cjs +1 -1
- package/dist/utils/numbers.cjs.map +1 -1
- package/dist/utils/numbers.js +14 -12
- package/dist/utils/numbers.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"absent.cjs","names":[],"sources":["../../src/utils/absent.ts"],"sourcesContent":["/**\n * What every formatter prints when it has nothing to print.\n *\n * An em dash rather than an empty string, because in a table those are two\n * different claims: a blank cell reads as \"this column does not apply here\",\n * while `—` reads as \"this value is missing\". `formatDurationMs` in `/perf` has\n * answered this way since it shipped, and `formatPercent` joined it in 0.65.0;\n * this constant is what stops the third answer from appearing.\n */\nexport const ABSENT_TEXT = \"—\";\n\n/** The text a formatter falls back to when the value cannot be rendered. */\nexport interface FormatFallbackOptions {\n /**\n * Text for a value that is absent (`null`/`undefined`) or unrepresentable\n * (`NaN`, `Infinity`). Defaults to {@link ABSENT_TEXT}.\n *\n * The formatters that feed an `<input>` default to `\"\"` instead, because an\n * input reads an empty string as \"no value\" and would render an em dash as\n * content the user has to delete.\n */\n fallback?: string;\n}\n\n/**\n * Whether a value is absent, i.e. has nothing to format.\n *\n * `null` and `undefined` only. A present-but-unparseable value is a different\n * state, and the formatters keep it distinguishable: an ISO string that is not a\n * date still answers `\"\"`, which is what tells \"nobody filled this in\" apart\n * from \"what they filled in is broken\" while reading a log.\n *\n * @param value - Anything a formatter was handed.\n * @returns Whether the formatter should answer with the fallback.\n */\nexport function isAbsent(value: unknown): value is null | undefined {\n return value === null || value === undefined;\n}\n\n/**\n * Resolve the fallback text for a formatter call.\n *\n * @param options - The caller's options, if any.\n * @param defaultText - What this formatter uses when the caller said nothing.\n * @returns The text to render in place of the value.\n */\nexport function fallbackText(\n options: FormatFallbackOptions | undefined,\n defaultText: string = ABSENT_TEXT,\n): string {\n return options?.fallback ?? defaultText;\n}\n"],"mappings":"AASA,IAAa,EAAc,IA0B3B,SAAgB,EAAS,EAA2C,CAChE,OAAO,GAAU,IACrB,CASA,SAAgB,EACZ,EACA,EAAA,IACM,CACN,OAAO,GAAS,UAAY,CAChC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/utils/absent.ts
|
|
2
|
+
var e = "—";
|
|
3
|
+
function t(e) {
|
|
4
|
+
return e == null;
|
|
5
|
+
}
|
|
6
|
+
function n(e, t = "—") {
|
|
7
|
+
return e?.fallback ?? t;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { e as ABSENT_TEXT, n as fallbackText, t as isAbsent };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=absent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"absent.js","names":[],"sources":["../../src/utils/absent.ts"],"sourcesContent":["/**\n * What every formatter prints when it has nothing to print.\n *\n * An em dash rather than an empty string, because in a table those are two\n * different claims: a blank cell reads as \"this column does not apply here\",\n * while `—` reads as \"this value is missing\". `formatDurationMs` in `/perf` has\n * answered this way since it shipped, and `formatPercent` joined it in 0.65.0;\n * this constant is what stops the third answer from appearing.\n */\nexport const ABSENT_TEXT = \"—\";\n\n/** The text a formatter falls back to when the value cannot be rendered. */\nexport interface FormatFallbackOptions {\n /**\n * Text for a value that is absent (`null`/`undefined`) or unrepresentable\n * (`NaN`, `Infinity`). Defaults to {@link ABSENT_TEXT}.\n *\n * The formatters that feed an `<input>` default to `\"\"` instead, because an\n * input reads an empty string as \"no value\" and would render an em dash as\n * content the user has to delete.\n */\n fallback?: string;\n}\n\n/**\n * Whether a value is absent, i.e. has nothing to format.\n *\n * `null` and `undefined` only. A present-but-unparseable value is a different\n * state, and the formatters keep it distinguishable: an ISO string that is not a\n * date still answers `\"\"`, which is what tells \"nobody filled this in\" apart\n * from \"what they filled in is broken\" while reading a log.\n *\n * @param value - Anything a formatter was handed.\n * @returns Whether the formatter should answer with the fallback.\n */\nexport function isAbsent(value: unknown): value is null | undefined {\n return value === null || value === undefined;\n}\n\n/**\n * Resolve the fallback text for a formatter call.\n *\n * @param options - The caller's options, if any.\n * @param defaultText - What this formatter uses when the caller said nothing.\n * @returns The text to render in place of the value.\n */\nexport function fallbackText(\n options: FormatFallbackOptions | undefined,\n defaultText: string = ABSENT_TEXT,\n): string {\n return options?.fallback ?? defaultText;\n}\n"],"mappings":";AASA,IAAa,IAAc;AA0B3B,SAAgB,EAAS,GAA2C;CAChE,OAAO,KAAU;AACrB;AASA,SAAgB,EACZ,GACA,IAAA,KACM;CACN,OAAO,GAAS,YAAY;AAChC"}
|
package/dist/utils/format.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./intl-cache.cjs");function
|
|
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;
|
|
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/** 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
|
+
{"version":3,"file":"format.cjs","names":[],"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":"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"}
|
package/dist/utils/format.js
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fallbackText as e, isAbsent as t } from "./absent.js";
|
|
2
|
+
import { dateTimeFormat as n, numberFormat as r } from "./intl-cache.js";
|
|
2
3
|
//#region src/utils/format.ts
|
|
3
|
-
function n
|
|
4
|
-
return t("pt-BR", {
|
|
4
|
+
function i(n, i) {
|
|
5
|
+
return t(n) || !Number.isFinite(n) ? e(i) : r("pt-BR", {
|
|
5
6
|
style: "currency",
|
|
6
7
|
currency: "BRL"
|
|
7
|
-
}).format(
|
|
8
|
-
}
|
|
9
|
-
function r
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
let
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
8
|
+
}).format(n);
|
|
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);
|
|
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;
|
|
19
|
+
if (Number.isNaN(i.getTime())) return "";
|
|
20
|
+
let a = `${i.getMonth() + 1}`.padStart(2, "0"), o = `${i.getDate()}`.padStart(2, "0");
|
|
21
|
+
return `${i.getFullYear()}-${a}-${o}`;
|
|
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;
|
|
27
|
+
if (Number.isNaN(i.getTime())) return "";
|
|
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
|
+
return `${i.getFullYear()}-${a}-${o}T${s}:${c}`;
|
|
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", {
|
|
30
35
|
dateStyle: "short",
|
|
31
36
|
timeStyle: "short"
|
|
32
|
-
}).format(
|
|
37
|
+
}).format(a);
|
|
33
38
|
}
|
|
34
|
-
function
|
|
39
|
+
function l(e, t = {}) {
|
|
35
40
|
let n = e.replace(/\D/g, "").slice(0, 11);
|
|
36
41
|
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");
|
|
37
42
|
if (n.length <= 2) return n;
|
|
@@ -40,19 +45,19 @@ function s(e, t = {}) {
|
|
|
40
45
|
let a = i.slice(0, 5), o = i.slice(5);
|
|
41
46
|
return o ? `(${r}) ${a}-${o}` : `(${r}) ${a}`;
|
|
42
47
|
}
|
|
43
|
-
function
|
|
48
|
+
function u(e) {
|
|
44
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");
|
|
45
50
|
}
|
|
46
|
-
function
|
|
47
|
-
if (!Number.isFinite(
|
|
48
|
-
let
|
|
49
|
-
return
|
|
51
|
+
function d(n, i = {}) {
|
|
52
|
+
if (t(n) || !Number.isFinite(n)) return e(i);
|
|
53
|
+
let a = i.decimals ?? 1;
|
|
54
|
+
return r("pt-BR", {
|
|
50
55
|
style: "percent",
|
|
51
|
-
minimumFractionDigits:
|
|
52
|
-
maximumFractionDigits:
|
|
53
|
-
}).format(
|
|
56
|
+
minimumFractionDigits: a,
|
|
57
|
+
maximumFractionDigits: a
|
|
58
|
+
}).format(n);
|
|
54
59
|
}
|
|
55
60
|
//#endregion
|
|
56
|
-
export {
|
|
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 };
|
|
57
62
|
|
|
58
63
|
//# sourceMappingURL=format.js.map
|
package/dist/utils/format.js.map
CHANGED
|
@@ -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/** 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
|
+
{"version":3,"file":"format.js","names":[],"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":"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"}
|
package/dist/utils/numbers.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./intl-cache.cjs");function
|
|
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;
|
|
2
2
|
//# sourceMappingURL=numbers.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"numbers.cjs","names":[],"sources":["../../src/utils/numbers.ts"],"sourcesContent":["import { 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 * @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 */\nexport function formatBytes(bytes: number
|
|
1
|
+
{"version":3,"file":"numbers.cjs","names":[],"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":"8DAUA,SAAgB,EAAM,EAAe,EAAa,EAAqB,CACnE,GAAI,OAAO,MAAM,CAAK,EAAG,OAAO,EAChC,GAAI,EAAM,EAAK,CACX,IAAM,EAAO,EACb,EAAM,EACN,EAAM,CACV,CAGA,OAFI,EAAQ,EAAY,EACpB,EAAQ,EAAY,EACjB,CACX,CAqBA,SAAgB,EACZ,EACA,EAAmB,EACnB,EACM,CACN,GAAI,EAAA,SAAS,CAAK,GAAK,CAAC,OAAO,SAAS,CAAK,EAAG,OAAO,EAAA,aAAa,CAAO,EAC3E,GAAI,IAAU,EAAG,MAAO,MAExB,IAAM,EAAQ,CAAC,IAAK,KAAM,KAAM,KAAM,IAAI,EACpC,EAAO,KACP,EAAW,KAAK,IAClB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,CAAK,CAAC,EAAI,KAAK,IAAI,CAAI,CAAC,EACrD,EAAM,OAAS,CACnB,EACM,EAAQ,EAAQ,GAAQ,EACxB,EAAS,IAAa,EAAI,EAAI,KAAK,IAAI,EAAG,CAAQ,EAGxD,MAAO,GAFW,WAAW,EAAM,QAAQ,CAAM,CAAC,CAAC,CAAC,SAE1C,EAAU,GAAG,EAAM,IACjC,CA2BA,SAAgB,EAAU,EAAc,EAAuB,CAE3D,MADI,CAAC,OAAO,SAAS,CAAI,GAAK,CAAC,OAAO,SAAS,CAAK,GAAK,IAAU,EAAU,EACrE,EAAO,EAAS,GAC5B,CAkBA,SAAgB,EACZ,EACA,EAAiB,QACjB,EACM,CAEN,OADI,EAAA,SAAS,CAAK,GAAK,CAAC,OAAO,SAAS,CAAK,EAAU,EAAA,aAAa,CAAO,EACpE,EAAA,aAAa,EAAQ,CAAE,SAAU,UAAW,sBAAuB,CAAE,CAAC,CAAC,CAAC,OAAO,CAAK,CAC/F"}
|
package/dist/utils/numbers.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fallbackText as e, isAbsent as t } from "./absent.js";
|
|
2
|
+
import { numberFormat as n } from "./intl-cache.js";
|
|
2
3
|
//#region src/utils/numbers.ts
|
|
3
|
-
function
|
|
4
|
+
function r(e, t, n) {
|
|
4
5
|
if (Number.isNaN(e)) return e;
|
|
5
6
|
if (t > n) {
|
|
6
7
|
let e = t;
|
|
@@ -8,27 +9,28 @@ function t(e, t, n) {
|
|
|
8
9
|
}
|
|
9
10
|
return e < t ? t : e > n ? n : e;
|
|
10
11
|
}
|
|
11
|
-
function n
|
|
12
|
-
if (
|
|
13
|
-
|
|
12
|
+
function i(n, r = 1, i) {
|
|
13
|
+
if (t(n) || !Number.isFinite(n)) return e(i);
|
|
14
|
+
if (n === 0) return "0 B";
|
|
15
|
+
let a = [
|
|
14
16
|
"B",
|
|
15
17
|
"KB",
|
|
16
18
|
"MB",
|
|
17
19
|
"GB",
|
|
18
20
|
"TB"
|
|
19
|
-
],
|
|
20
|
-
return `${parseFloat(
|
|
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);
|
|
22
|
+
return `${parseFloat(c.toFixed(l)).toString()} ${a[s]}`;
|
|
21
23
|
}
|
|
22
|
-
function
|
|
24
|
+
function a(e, t) {
|
|
23
25
|
return !Number.isFinite(e) || !Number.isFinite(t) || t === 0 ? 0 : e / t * 100;
|
|
24
26
|
}
|
|
25
|
-
function
|
|
26
|
-
return e(n, {
|
|
27
|
+
function o(r, i = "en-US", a) {
|
|
28
|
+
return t(r) || !Number.isFinite(r) ? e(a) : n(i, {
|
|
27
29
|
notation: "compact",
|
|
28
30
|
maximumFractionDigits: 1
|
|
29
|
-
}).format(
|
|
31
|
+
}).format(r);
|
|
30
32
|
}
|
|
31
33
|
//#endregion
|
|
32
|
-
export {
|
|
34
|
+
export { r as clamp, i as formatBytes, o as formatCompactNumber, a as percentOf };
|
|
33
35
|
|
|
34
36
|
//# sourceMappingURL=numbers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"numbers.js","names":[],"sources":["../../src/utils/numbers.ts"],"sourcesContent":["import { 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 * @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 */\nexport function formatBytes(bytes: number
|
|
1
|
+
{"version":3,"file":"numbers.js","names":[],"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":";;;AAUA,SAAgB,EAAM,GAAe,GAAa,GAAqB;CACnE,IAAI,OAAO,MAAM,CAAK,GAAG,OAAO;CAChC,IAAI,IAAM,GAAK;EACX,IAAM,IAAO;EAEb,AADA,IAAM,GACN,IAAM;CACV;CAGA,OAFI,IAAQ,IAAY,IACpB,IAAQ,IAAY,IACjB;AACX;AAqBA,SAAgB,EACZ,GACA,IAAmB,GACnB,GACM;CACN,IAAI,EAAS,CAAK,KAAK,CAAC,OAAO,SAAS,CAAK,GAAG,OAAO,EAAa,CAAO;CAC3E,IAAI,MAAU,GAAG,OAAO;CAExB,IAAM,IAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;CAAI,GACpC,IAAO,MACP,IAAW,KAAK,IAClB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,CAAK,CAAC,IAAI,KAAK,IAAI,CAAI,CAAC,GACrD,EAAM,SAAS,CACnB,GACM,IAAQ,IAAQ,KAAQ,GACxB,IAAS,MAAa,IAAI,IAAI,KAAK,IAAI,GAAG,CAAQ;CAGxD,OAAO,GAFW,WAAW,EAAM,QAAQ,CAAM,CAAC,CAAC,CAAC,SAE1C,EAAU,GAAG,EAAM;AACjC;AA2BA,SAAgB,EAAU,GAAc,GAAuB;CAE3D,OADI,CAAC,OAAO,SAAS,CAAI,KAAK,CAAC,OAAO,SAAS,CAAK,KAAK,MAAU,IAAU,IACrE,IAAO,IAAS;AAC5B;AAkBA,SAAgB,EACZ,GACA,IAAiB,SACjB,GACM;CAEN,OADI,EAAS,CAAK,KAAK,CAAC,OAAO,SAAS,CAAK,IAAU,EAAa,CAAO,IACpE,EAAa,GAAQ;EAAE,UAAU;EAAW,uBAAuB;CAAE,CAAC,CAAC,CAAC,OAAO,CAAK;AAC/F"}
|