@kanso-labs/kanso-ui 0.43.6 → 0.44.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/README.md +1 -0
- package/dist/calendar/index.js +34 -10
- package/dist/calendar/index.js.map +1 -1
- package/dist/calendar/styles.js +30 -0
- package/dist/calendar/styles.js.map +1 -1
- package/dist/chip/index.js +27 -18
- package/dist/chip/index.js.map +1 -1
- package/dist/chip/label.js +32 -0
- package/dist/chip/label.js.map +1 -0
- package/dist/chip/styles.js +32 -2
- package/dist/chip/styles.js.map +1 -1
- package/dist/collection/text.js +24 -3
- package/dist/collection/text.js.map +1 -1
- package/dist/components/button/index.js.map +1 -1
- package/dist/components/calendar/index.d.ts +8 -1
- package/dist/components/calendar/index.js +129 -73
- package/dist/components/calendar/index.js.map +1 -1
- package/dist/components/checkbox/index.js +29 -30
- package/dist/components/checkbox/index.js.map +1 -1
- package/dist/components/checkbox-group/index.js +4 -2
- package/dist/components/checkbox-group/index.js.map +1 -1
- package/dist/components/chip/index.js +1 -1
- package/dist/components/chip/index.js.map +1 -1
- package/dist/components/chip-group/index.d.ts +35 -5
- package/dist/components/chip-group/index.js +137 -97
- package/dist/components/chip-group/index.js.map +1 -1
- package/dist/components/copy-field/index.js +1 -1
- package/dist/components/copy-field/index.js.map +1 -1
- package/dist/components/currency/index.d.ts +64 -6
- package/dist/components/currency/index.js +117 -40
- package/dist/components/currency/index.js.map +1 -1
- package/dist/components/date-picker/index.js +1 -1
- package/dist/components/dialog/index.d.ts +3 -1
- package/dist/components/dialog/index.js +121 -81
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/disclosure-group/index.js +1 -1
- package/dist/components/disclosure-group/index.js.map +1 -1
- package/dist/components/icon-button/index.js +18 -1
- package/dist/components/icon-button/index.js.map +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/keycap/index.d.ts +3 -0
- package/dist/components/keycap/index.js +30 -44
- package/dist/components/keycap/index.js.map +1 -1
- package/dist/components/list/index.d.ts +16 -2
- package/dist/components/list/index.js +124 -69
- package/dist/components/list/index.js.map +1 -1
- package/dist/components/list-detail/index.d.ts +11 -3
- package/dist/components/list-detail/index.js +13 -6
- package/dist/components/list-detail/index.js.map +1 -1
- package/dist/components/list-item/index.d.ts +16 -1
- package/dist/components/list-item/index.js.map +1 -1
- package/dist/components/menu/index.d.ts +6 -2
- package/dist/components/menu/index.js +22 -15
- package/dist/components/menu/index.js.map +1 -1
- package/dist/components/meter/index.d.ts +4 -1
- package/dist/components/meter/index.js +6 -0
- package/dist/components/meter/index.js.map +1 -1
- package/dist/components/navigation-bar/index.d.ts +14 -4
- package/dist/components/navigation-bar/index.js +77 -50
- package/dist/components/navigation-bar/index.js.map +1 -1
- package/dist/components/navigation-rail/index.d.ts +3 -2
- package/dist/components/navigation-rail/index.js +59 -52
- package/dist/components/navigation-rail/index.js.map +1 -1
- package/dist/components/navigation-tree/index.js +4 -1
- package/dist/components/navigation-tree/index.js.map +1 -1
- package/dist/components/number-field/index.d.ts +3 -2
- package/dist/components/number-field/index.js +91 -115
- package/dist/components/number-field/index.js.map +1 -1
- package/dist/components/popover/index.d.ts +3 -1
- package/dist/components/popover/index.js +102 -100
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/progress-indicator/index.d.ts +4 -2
- package/dist/components/progress-indicator/index.js +4 -3
- package/dist/components/progress-indicator/index.js.map +1 -1
- package/dist/components/radio-group/index.js +34 -24
- package/dist/components/radio-group/index.js.map +1 -1
- package/dist/components/search-field/index.d.ts +11 -1
- package/dist/components/search-field/index.js +32 -28
- package/dist/components/search-field/index.js.map +1 -1
- package/dist/components/segmented-button/index.d.ts +9 -2
- package/dist/components/segmented-button/index.js +1 -1
- package/dist/components/segmented-button/index.js.map +1 -1
- package/dist/components/separator/index.d.ts +19 -7
- package/dist/components/separator/index.js +36 -13
- package/dist/components/separator/index.js.map +1 -1
- package/dist/components/sheet/index.js +72 -61
- package/dist/components/sheet/index.js.map +1 -1
- package/dist/components/snackbar/index.d.ts +4 -1
- package/dist/components/snackbar/index.js +140 -55
- package/dist/components/snackbar/index.js.map +1 -1
- package/dist/components/stack/index.d.ts +6 -3
- package/dist/components/stack/index.js +48 -31
- package/dist/components/stack/index.js.map +1 -1
- package/dist/components/switch/index.d.ts +16 -6
- package/dist/components/switch/index.js +115 -62
- package/dist/components/switch/index.js.map +1 -1
- package/dist/components/text-area/index.d.ts +10 -1
- package/dist/components/text-area/index.js +21 -19
- package/dist/components/text-area/index.js.map +1 -1
- package/dist/components/text-field/index.d.ts +16 -2
- package/dist/components/text-field/index.js +49 -43
- package/dist/components/text-field/index.js.map +1 -1
- package/dist/components/time-picker/index.js +42 -38
- package/dist/components/time-picker/index.js.map +1 -1
- package/dist/components/tooltip/index.d.ts +3 -1
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tree/index.d.ts +15 -3
- package/dist/components/tree/index.js +41 -28
- package/dist/components/tree/index.js.map +1 -1
- package/dist/control/index.js +26 -23
- package/dist/control/index.js.map +1 -1
- package/dist/control/styles.js +8 -1
- package/dist/control/styles.js.map +1 -1
- package/dist/field/index.js +149 -131
- package/dist/field/index.js.map +1 -1
- package/dist/field/styles.js +14 -1
- package/dist/field/styles.js.map +1 -1
- package/dist/glyphs/index.js +37 -1
- package/dist/glyphs/index.js.map +1 -1
- package/dist/hooks/useClippedTitle.js +44 -0
- package/dist/hooks/useClippedTitle.js.map +1 -0
- package/dist/hooks/useRipple.js +6 -5
- package/dist/hooks/useRipple.js.map +1 -1
- package/dist/hooks/useScrollStop.js +133 -0
- package/dist/hooks/useScrollStop.js.map +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/messages.js +34 -0
- package/dist/i18n/messages.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/indicator/index.js +18 -16
- package/dist/indicator/index.js.map +1 -1
- package/dist/indicator/styles.js +3 -0
- package/dist/indicator/styles.js.map +1 -1
- package/dist/keycap/context.js +7 -0
- package/dist/keycap/context.js.map +1 -0
- package/dist/navigation/index.d.ts +16 -2
- package/dist/navigation/index.js +145 -44
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/styles.js +79 -16
- package/dist/navigation/styles.js.map +1 -1
- package/dist/overlay/modal.js +7 -0
- package/dist/overlay/modal.js.map +1 -0
- package/dist/row/index.js +92 -89
- package/dist/row/index.js.map +1 -1
- package/dist/row/styles.js +24 -1
- package/dist/row/styles.js.map +1 -1
- package/dist/styles/overlay.d.ts +6 -2
- package/dist/styles/overlay.js +9 -1
- package/dist/styles/overlay.js.map +1 -1
- package/dist/styles.css +177 -20
- package/dist/theme-scope.d.ts +5 -1
- package/dist/theme-scope.js +100 -51
- package/dist/theme-scope.js.map +1 -1
- package/dist/tokens/design.tokens.stylex.js.map +1 -1
- package/package.json +21 -20
- package/dist/hooks/useScrollable.js +0 -69
- package/dist/hooks/useScrollable.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["c","_c","HTMLAttributes","RefAttributes","stylex","useCallback","useEffect","useId","useRef","useState","useMessages","mergeStyles","colors","radii","spacing","typography","Button","COPIED_RESET_MS","CopyFieldProps","copiedLabel","copyLabel","label","onCopied","value","onCopyFailed","error","selectedLabel","Omit","HTMLDivElement","CopyField","t0","$","copiedLabelProp","copyLabelProp","selectedLabelProp","props","messages","copied","copy","copySelected","setCopy","shown","text","kind","timer","undefined","code","generatedId","rootId","id","copyId","copiedId","named","role","buttonName","t1","t2","Symbol","for","clearTimeout","current","t3","settle","setTimeout","count","write","navigator","clipboard","writeText","t4","selectAndCopy","reportCopied","handleCopy","className","t5","t6","t7","0","1","t8","t9","t10","t11","t12","t13","t14","t15","t16","t17","t18","thrown","queueMicrotask","element","HTMLElement","selection","window","getSelection","range","document","createRange","selectNodeContents","removeAllRanges","addRange","execCommand","_c"],"sources":["../../../src/components/copy-field/index.tsx"],"sourcesContent":["'use client';\n\nimport type { HTMLAttributes, RefAttributes } from 'react';\nimport * as stylex from '@stylexjs/stylex';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { useMessages } from '../../i18n';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, spacing, typography } from '../../tokens/design.tokens.stylex';\nimport Button from '../button';\n\n// How long the confirmation stands before the control returns to offering the\n// copy again. Not a motion token: those are transition durations, measured in\n// hundreds of milliseconds, and this is a dwell — long enough to be read\n// without being long enough to leave the button lying about what it will do.\nconst COPIED_RESET_MS = 2000;\n\n// No Material Design page draws a read-only value with a copy control. It\n// follows the outlined text field: the 1dp rule in the outline role, the\n// surface behind it, and the control at its trailing end. The value is\n// body-small beside a 32dp text button, so the box is shorter than the\n// field's 56, which suits a value that is read rather than edited, and its\n// corner is the shape scale's small step for the same reason.\n\ntype CopyFieldProps = {\n /**\n * What the button says once the value has been copied, and what is\n * announced. It reverts to `copyLabel` on its own after a couple of\n * seconds. Left out, it is the word for it in the I18nProvider's locale —\n * \"Copied\" in English.\n */\n copiedLabel?: string;\n /**\n * What the button says at rest. Left out, it is the word for it in the\n * I18nProvider's locale — \"Copy\" in English.\n */\n copyLabel?: string;\n /**\n * What the value is — \"Repository URL\", \"Access token\" — for a page with\n * more than one field. It names the field as a group and joins the button's\n * name, so a screen reader hears \"Copy Repository URL\" rather than one\n * \"Copy\" after another; it is not shown. The value itself stays out of the\n * button's name, since a token would be read aloud. Left out, the field is\n * unnamed and its button is \"Copy\" alone, as it always was.\n */\n label?: string;\n /**\n * Called with the value after it reaches the clipboard, never before.\n *\n * Named `onCopied` rather than `onCopy` because the root is a `<div>`, which\n * already has an `onCopy` of its own — React's handler for the native copy\n * event, fired when the reader presses the copy shortcut inside the element.\n * The two are different moments, and a prop that quietly shadowed the\n * built-in would be a trap rather than a convenience.\n */\n onCopied?: (value: string) => void;\n /**\n * Called when the write is refused, with whatever was thrown, and the\n * field's own fallback has not copied the value either. By then the value\n * is selected and the field says so — see `selectedLabel` — so this is for\n * an app that wants to say more, in a snackbar say.\n *\n * Takes the error where `onCopied` takes the value, because the value is\n * the one thing the call site already has and the reason is the one thing\n * it does not. The two refusals throw differently: outside a secure\n * context `navigator.clipboard` is not there at all, and where the\n * permission is denied it is a `DOMException`.\n */\n onCopyFailed?: (error: unknown) => void;\n /**\n * What is announced when the clipboard refused the write and the value has\n * been selected for the reader to copy themselves. The button keeps saying\n * `copyLabel`, since a label slot wide enough for this would double the\n * button's width at rest; the selection itself is what shows. Left out, it\n * is the phrase for it in the I18nProvider's locale — \"Selected to copy\"\n * in English.\n */\n selectedLabel?: string;\n /** The text shown, and the text copied. */\n value: string;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'children'>;\n\n/**\n * A value to be read and taken away — a repository URL, a token, a command.\n * It shows the value in the mono face and copies it on request, confirming\n * on the button itself.\n *\n * A write refused — outside a secure context, or where the permission is\n * denied — does not leave the reader with nothing. The field selects the\n * value and tries the older copy command, which some of those places still\n * allow; failing that, the selection is left ready for the keyboard's own\n * copy, the field announces so, and `onCopyFailed` is called. One click on\n * the value selects exactly the value too.\n */\nfunction CopyField({\n copiedLabel: copiedLabelProp,\n copyLabel: copyLabelProp,\n label,\n onCopied,\n onCopyFailed,\n selectedLabel: selectedLabelProp,\n value,\n ...props\n}: CopyFieldProps & RefAttributes<HTMLDivElement>) {\n const messages = useMessages();\n const copiedLabel = copiedLabelProp ?? messages.copied;\n const copyLabel = copyLabelProp ?? messages.copy;\n const selectedLabel = selectedLabelProp ?? messages.copySelected;\n // What the last write put on the clipboard, and how many writes there have\n // been. The confirmation is that text still being the value shown, so a\n // value that changes inside the dwell — a token regenerated, a selection\n // swapped — takes the label back to Copy at once rather than confirming a\n // copy of something else. The count is what lets a second press be\n // announced, below. `kind` is whether the text reached the clipboard or\n // was only selected for the reader to copy, after a refusal.\n const [copy, setCopy] = useState<null | {\n count: number;\n kind: 'copied' | 'selected';\n text: string;\n }>(null);\n const shown: 'copied' | 'copy' | 'selected' = copy?.text === value ? copy.kind : 'copy';\n const copied = shown === 'copied';\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const code = useRef<HTMLElement>(null);\n const generatedId = useId();\n const rootId = props.id ?? generatedId;\n const copyId = useId();\n const copiedId = useId();\n\n // With a label, the field is a group it names, and the button is named by\n // the label slot on show and then by the group: \"Copy Repository URL\",\n // then \"Copied Repository URL\". The slot on show rather than both, since\n // the two swap `aria-hidden` and a name taking both would say both.\n const named = label === undefined ? undefined : {\n 'aria-label': label,\n id: rootId,\n role: 'group'\n };\n const buttonName = label === undefined ? undefined : `${copied ? copiedId : copyId} ${rootId}`;\n\n // Without this, a field unmounted inside the dwell leaves a timer holding a\n // setState for a tree that is gone.\n useEffect(() => () => {\n clearTimeout(timer.current);\n }, []);\n const handleCopy = useCallback(() => {\n // The component's own state first, the dwell included, and the call\n // site's handler after it: a handler that threw used to skip the timer,\n // leaving the button on Copied for good. Its error is still the call\n // site's to see, raised on its own rather than swallowed.\n const settle = (kind: 'copied' | 'selected') => {\n clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n setCopy(null);\n }, COPIED_RESET_MS);\n setCopy(current => ({\n count: (current?.count ?? 0) + 1,\n kind,\n text: value\n }));\n };\n const write = async () => {\n try {\n await navigator.clipboard.writeText(value);\n } catch (error) {\n // A clipboard write is refused outside a secure context and wherever\n // the permission is denied, neither of which the call site can fix,\n // and a button that did nothing there left the reader with nothing.\n // So the value is selected, which is what the keyboard's own copy\n // acts on, and the older copy command is tried on that selection,\n // since some of those places still allow it. Only where that fails\n // too does the field announce that the value is selected — not that\n // it is copied, which it is not — and report the refusal.\n //\n // Nothing is logged — the library writes to the console nowhere, and\n // a warning an app cannot turn off is not its to emit.\n if (selectAndCopy(code.current)) {\n settle('copied');\n reportCopied(onCopied, value);\n return;\n }\n settle('selected');\n onCopyFailed?.(error);\n return;\n }\n settle('copied');\n reportCopied(onCopied, value);\n };\n void write();\n }, [onCopied, onCopyFailed, value]);\n return <div {...props} {...named} {...mergeStyles({\n className: \"x6s0dn4 x14s9m32 x1fwvt7v x1xdvaa9 x1y0btm7 xmkeg23 x9f619 x78zum5 x126z3so xmqoko8 x18fpw6u\"\n }, props)}>\n <span className=\"x139gbkf x1iyjqo2 x1b35g6e xdb2kus x9d6959 xwth0xh xybisxh xeuugli x6mezaz xvrbanz x126k92a\">\n <code dir=\"ltr\" ref={code} className=\"x9f619 xbppi91 x1qlqyl8 xj0a0fe x4ej8vm\">\n {value}\n </code>\n </span>\n <Button aria-labelledby={buttonName} onClick={handleCopy} size=\"xs\" variant=\"text\">\n <span className=\"xrvj5dj x1o2pa38\">\n <span aria-hidden={copied || undefined} id={copyId} {...{\n 0: {\n className: \"x15r89dc\"\n },\n 1: {\n className: \"x15r89dc xlshs6z\"\n }\n }[!!copied << 0]}>\n {copyLabel}\n </span>\n <span aria-hidden={!copied || undefined} id={copiedId} {...{\n 0: {\n className: \"x15r89dc\"\n },\n 1: {\n className: \"x15r89dc xlshs6z\"\n }\n }[!!!copied << 0]}>\n {copiedLabel}\n </span>\n </span>\n </Button>\n {/* The button's own label changes, but a label changing under a screen\n reader is not reliably announced. This is. `<output>` carries an\n implicit role of status, so it needs no role attribute of its own.\n Each write puts a new node in it, keyed by the count, since a second\n press inside the dwell otherwise left the text as it was and the\n region had nothing to announce. */}\n <output className=\"xjm9jq1 x1hyvwdk x1i1rx1s xb3r6kr x10l6tqk xuxw1ft\">\n {copy !== null && shown !== 'copy' ? <span key={copy.count}>\n {shown === 'copied' ? copiedLabel : selectedLabel}\n </span> : null}\n </output>\n </div>;\n}\n\n/**\n * Hands the call site the value it copied. What its handler throws is raised\n * on its own, outside the copy: it still reaches the page's error reporting,\n * where a rejection of the copy's own promise reached nothing but an\n * unhandled-rejection warning. Outside the component, since the React\n * Compiler compiles neither optional chaining nor a captured catch binding\n * inside a `try`.\n */\nfunction reportCopied(onCopied: ((value: string) => void) | undefined, value: string) {\n if (onCopied === undefined) {\n return;\n }\n try {\n onCopied(value);\n } catch (thrown) {\n queueMicrotask(() => {\n throw thrown;\n });\n }\n}\n\n/**\n * Selects the value's text, which is what the keyboard's own copy acts on,\n * and tries the older copy command on that selection, saying whether it\n * copied. The command is deprecated, and still the one way to the clipboard\n * on an insecure origin, which is where the newer one is refused.\n */\nfunction selectAndCopy(element: HTMLElement | null) {\n const selection = window.getSelection();\n if (element === null || selection === null) {\n return false;\n }\n const range = document.createRange();\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n try {\n // oxlint-disable-next-line typescript/no-deprecated -- the one fallback an insecure origin still allows\n return document.execCommand('copy');\n } catch {\n return false;\n }\n}\nexport type { CopyFieldProps };\nexport default CopyField;"],"mappings":";;;;;;;;AAeA,MAAMiB,kBAAkB;;;;;;;;;;;;;AA+ExB,SAAAY,UAAAC,IAAA;CAAA,MAAAC,IAAA9B,EAAA,EAAA;CAAmB,MAAA,EAAAkB,aAAAa,iBAAAZ,WAAAa,eAAAZ,OAAAC,UAAAE,cAAAE,eAAAQ,mBAAAX,OAAA,GAAAY,UAAAL;CAUjB,MAAAM,WAAiB1B,YAAY;CAC7B,MAAAS,cAAoBa,mBAAmBI,SAAQC;CAC/C,MAAAjB,YAAkBa,iBAAiBG,SAAQE;CAC3C,MAAAZ,gBAAsBQ,qBAAqBE,SAAQG;CAQnD,MAAA,CAAAD,MAAAE,WAAwB/B,SAIrB,IAAI;CACP,MAAAgC,QAA8CH,MAAII,SAAWnB,QAAQe,KAAIK,OAA3B;CAC9C,MAAAN,SAAeI,UAAU;CACzB,MAAAG,QAAcpC,OAAkDqC,KAAAA,CAAS;CACzE,MAAAC,OAAatC,OAAoB,IAAI;CACrC,MAAAuC,cAAoBxC,MAAM;CAC1B,MAAAyC,SAAeb,MAAKc,MAALF;CACf,MAAAG,SAAe3C,MAAM;CACrB,MAAA4C,WAAiB5C,MAAM;CAMvB,MAAA6C,QAAc/B,UAAUwB,KAAAA,IAAVA,KAAAA,IAAA;EAAA,cACExB;EAAK4B,IACfD;EAAMK,MACJ;CACR;CACA,MAAAC,aAAmBjC,UAAUwB,KAAAA,IAAVA,KAAAA,IAAA,GAAqCR,SAAAc,WAAAD,OAA0B,GAAIF;CAAS,IAAAO;CAAA,IAAAC;CAAA,IAAAzB,EAAA,OAAA0B,OAAAC,IAAA,2BAAA,GAAA;EAIrFH,iBAAM;GACdI,aAAaf,MAAKgB,OAAQ;EAAC;EAC1BJ,KAAA,CAAA;EAAEzB,EAAA,KAAAwB;EAAAxB,EAAA,KAAAyB;CAAA,OAAA;EAAAD,KAAAxB,EAAA;EAAAyB,KAAAzB,EAAA;CAAA;CAFLzB,UAAUiD,IAEPC,EAAE;CAAC,IAAAK;CAAA,IAAA9B,EAAA,OAAAT,YAAAS,EAAA,OAAAP,gBAAAO,EAAA,OAAAR,OAAA;EACyBsC,WAAA;GAK7B,MAAAC,UAAenB,SAAA;IACbgB,aAAaf,MAAKgB,OAAQ;IAC1BhB,MAAKgB,UAAWG,iBAAW;KACzBvB,QAAQ,IAAI;IAAC,GACZvB,eAAe;IAClBuB,SAAQoB,aAAY;KAAAI,QACVJ,SAAOI,SAAP,KAAuB;KAACrB;KAAAD,MAE1BnB;IACR,EAAE;GAAC;GAEL,MAAA0C,QAAc,YAAA;IACZ,IAAA;KACE,MAAMC,UAASC,UAAUC,UAAW7C,KAAK;IAAC,SAAA8C,IAAA;KACnC5C,MAAAA,QAAAA;KAYP,IAAI6C,cAAcxB,KAAIc,OAAQ,GAAC;MAC7BE,OAAO,QAAQ;MACfS,aAAajD,UAAUC,KAAK;MAAC;KAAA;KAG/BuC,OAAO,UAAU;KACjBtC,eAAeC,KAAK;KAAC;IAAA;IAGvBqC,OAAO,QAAQ;IACfS,aAAajD,UAAUC,KAAK;GAAC;GAE1B0C,MAAM;EAAC;EACblC,EAAA,KAAAT;EAAAS,EAAA,KAAAP;EAAAO,EAAA,KAAAR;EAAAQ,EAAA,KAAA8B;CAAA,OAAAA,KAAA9B,EAAA;CA5CD,MAAAyC,aAAmBX;CA6CmB,MAAAQ,KAAA1D,YAAY,EAAA8D,WACrC,+FACb,GAAGtC,KAAK;CAAC,IAAAuC;CAAA,IAAA3C,EAAA,OAAAR,OAAA;EACLmD,KAAA,oBAAA,QAAA;GAAgB,WAAA;GACd,UAAA,oBAAA,QAAA;IAAU,KAAA;IAAW5B,KAAAA;IAAgB,WAAA;IAClCvB,UAAAA;GACI,CAAA;EACF,CAAA;EAAAQ,EAAA,KAAAR;EAAAQ,EAAA,KAAA2C;CAAA,OAAAA,KAAA3C,EAAA;CAGgB,MAAA4C,KAAAtC,UAAAQ,KAAAA;CAAmB,IAAA+B;CAAA,IAAA7C,EAAA,OAAA0B,OAAAC,IAAA,2BAAA,GAAA;EAAkBkB,KAAA;GAAAC,GACrD,EAAAJ,WACU,WACb;GAACK,GACE,EAAAL,WACU,mBACb;EACF;EAAC1C,EAAA,KAAA6C;CAAA,OAAAA,KAAA7C,EAAA;CAPyD,MAAAgD,KAAAH,GAOxD,CAAC,CAACvC,UAAU;CAAE,IAAA2C;CAAA,IAAAjD,EAAA,OAAAmB,UAAAnB,EAAA,QAAAX,aAAAW,EAAA,QAAA4C,MAAA5C,EAAA,QAAAgD,IAAA;EAPdC,KAAA,oBAAA,QAAA;GAAmB,eAAAL;GAAyBzB,IAAAA;GAAM,GAAM6B;GAQrD3D,UAAAA;EACI,CAAA;EAAAW,EAAA,KAAAmB;EAAAnB,EAAA,MAAAX;EAAAW,EAAA,MAAA4C;EAAA5C,EAAA,MAAAgD;EAAAhD,EAAA,MAAAiD;CAAA,OAAAA,KAAAjD,EAAA;CACY,MAAAkD,MAAA,CAAC5C,UAADQ,KAAAA;CAAoB,IAAAqC;CAAA,IAAAnD,EAAA,QAAA0B,OAAAC,IAAA,2BAAA,GAAA;EAAoBwB,MAAA;GAAAL,GACxD,EAAAJ,WACU,WACb;GAACK,GACE,EAAAL,WACU,mBACb;EACF;EAAC1C,EAAA,MAAAmD;CAAA,OAAAA,MAAAnD,EAAA;CAP4D,MAAAoD,MAAAD,IAO3D,CAAC,CAAC,CAAC7C,UAAU;CAAE,IAAA+C;CAAA,IAAArD,EAAA,QAAAoB,YAAApB,EAAA,QAAAZ,eAAAY,EAAA,QAAAkD,OAAAlD,EAAA,QAAAoD,KAAA;EAPfC,MAAA,oBAAA,QAAA;GAAmB,eAAAH;GAA0B9B,IAAAA;GAAQ,GAAMgC;GAQxDhE,UAAAA;EACI,CAAA;EAAAY,EAAA,MAAAoB;EAAApB,EAAA,MAAAZ;EAAAY,EAAA,MAAAkD;EAAAlD,EAAA,MAAAoD;EAAApD,EAAA,MAAAqD;CAAA,OAAAA,MAAArD,EAAA;CAAA,IAAAsD;CAAA,IAAAtD,EAAA,QAAAqD,OAAArD,EAAA,QAAAiD,IAAA;EApBTK,MAAA,qBAAA,QAAA;GAAgB,WAAA;GAAhB,UAAA,CACEL,IAUAI,GAUK;;EAAArD,EAAA,MAAAqD;EAAArD,EAAA,MAAAiD;EAAAjD,EAAA,MAAAsD;CAAA,OAAAA,MAAAtD,EAAA;CAAA,IAAAuD;CAAA,IAAAvD,EAAA,QAAAuB,cAAAvB,EAAA,QAAAyC,cAAAzC,EAAA,QAAAsD,KAAA;EAtBTC,MAAA,oBAAC,QAAD;GAAyBhC,mBAAAA;GAAqBkB,SAAAA;GAAiB,MAAA;GAAa,SAAA;GAC1Ea,UAAAA;EADK,CAAA;EAuBEtD,EAAA,MAAAuB;EAAAvB,EAAA,MAAAyC;EAAAzC,EAAA,MAAAsD;EAAAtD,EAAA,MAAAuD;CAAA,OAAAA,MAAAvD,EAAA;CAAA,IAAAwD;CAAA,IAAAxD,EAAA,QAAAZ,eAAAY,EAAA,QAAAO,QAAAP,EAAA,QAAAL,iBAAAK,EAAA,QAAAU,OAAA;EAQN8C,MAAAjD,SAAS,QAAQG,UAAU,SAAS,oBAAA,QAAA,EAAA,UAChCA,UAAU,WAAVtB,cAAAO,cACI,GAFuCY,KAAI0B,KAE3C,IAFR;EAEejC,EAAA,MAAAZ;EAAAY,EAAA,MAAAO;EAAAP,EAAA,MAAAL;EAAAK,EAAA,MAAAU;EAAAV,EAAA,MAAAwD;CAAA,OAAAA,MAAAxD,EAAA;CAAA,IAAAyD;CAAA,IAAAzD,EAAA,QAAAwD,KAAA;EAHlBC,MAAA,oBAAA,UAAA;GAAkB,WAAA;GACfD,UAAAA;EAGM,CAAA;EAAAxD,EAAA,MAAAwD;EAAAxD,EAAA,MAAAyD;CAAA,OAAAA,MAAAzD,EAAA;CAAA,IAAA0D;CAAA,IAAA1D,EAAA,QAAAqB,SAAArB,EAAA,QAAAI,SAAAJ,EAAA,QAAAuD,OAAAvD,EAAA,QAAAyD,OAAAzD,EAAA,QAAAsC,MAAAtC,EAAA,QAAA2C,IAAA;EA1CNe,MAAA,qBAAA,OAAA;GA2CC,GA3CQtD;GAAK,GAAMiB;GAAK,GAAMiB;GAA/B,UAAA;IAGHK;IAKAY;IA8BAE;GAKI;;EAAAzD,EAAA,MAAAqB;EAAArB,EAAA,MAAAI;EAAAJ,EAAA,MAAAuD;EAAAvD,EAAA,MAAAyD;EAAAzD,EAAA,MAAAsC;EAAAtC,EAAA,MAAA2C;EAAA3C,EAAA,MAAA0D;CAAA,OAAAA,MAAA1D,EAAA;CAAA,OA3CD0D;AA2CC;;;;;;;;;AAWV,SAASlB,aAAajD,UAAiDC,OAAe;CACpF,IAAID,aAAauB,KAAAA,GACf;CAEF,IAAI;EACFvB,SAASC,KAAK;CAChB,SAASmE,QAAQ;EACfC,qBAAqB;GACnB,MAAMD;EACR,CAAC;CACH;AACF;;;;;;;AAQA,SAASpB,cAAcsB,SAA6B;CAClD,MAAME,YAAYC,OAAOC,aAAa;CACtC,IAAIJ,YAAY,QAAQE,cAAc,MACpC,OAAO;CAET,MAAMG,QAAQC,SAASC,YAAY;CACnCF,MAAMG,mBAAmBR,OAAO;CAChCE,UAAUO,gBAAgB;CAC1BP,UAAUQ,SAASL,KAAK;CACxB,IAAI;EAEF,OAAOC,SAASK,YAAY,MAAM;CACpC,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["c","_c","HTMLAttributes","RefAttributes","stylex","useCallback","useEffect","useId","useRef","useState","useMessages","mergeStyles","colors","radii","spacing","typography","Button","COPIED_RESET_MS","CopyFieldProps","copiedLabel","copyLabel","label","onCopied","value","onCopyFailed","error","selectedLabel","Omit","HTMLDivElement","CopyField","t0","$","copiedLabelProp","copyLabelProp","selectedLabelProp","props","messages","copied","copy","copySelected","setCopy","shown","text","kind","timer","undefined","code","generatedId","rootId","id","copyId","copiedId","named","role","buttonName","t1","t2","Symbol","for","clearTimeout","current","t3","settle","setTimeout","count","write","navigator","clipboard","writeText","t4","selectAndCopy","reportCopied","handleCopy","className","t5","t6","t7","0","1","t8","t9","t10","t11","t12","t13","t14","t15","t16","t17","t18","thrown","queueMicrotask","element","HTMLElement","selection","window","getSelection","range","document","createRange","selectNodeContents","removeAllRanges","addRange","execCommand","_c"],"sources":["../../../src/components/copy-field/index.tsx"],"sourcesContent":["'use client';\n\nimport type { HTMLAttributes, RefAttributes } from 'react';\nimport * as stylex from '@stylexjs/stylex';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { useMessages } from '../../i18n';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, spacing, typography } from '../../tokens/design.tokens.stylex';\nimport Button from '../button';\n\n// How long the confirmation stands before the control returns to offering the\n// copy again. Not a motion token: those are transition durations, measured in\n// hundreds of milliseconds, and this is a dwell — long enough to be read\n// without being long enough to leave the button lying about what it will do.\nconst COPIED_RESET_MS = 2000;\n\n// No Material Design page draws a read-only value with a copy control. It\n// follows the outlined text field: the 1dp rule in the outline role, the\n// surface behind it, and the control at its trailing end. The value is\n// body-small beside a 32dp text button with 8dp above and below it, so the\n// box is 50dp against the field's 56, which suits a value that is read\n// rather than edited, and its corner is the shape scale's small step for the\n// same reason.\n//\n// The 8dp is what holds the button's focus ring, which reaches 4dp out from\n// it, clear of the rule, and the trailing end gives the button the same 8dp\n// rather than the 12dp the value starts in by. The button brings 12dp of its\n// own around its label, so the label ends 20dp in where the two paddings\n// added up to 24.\n\ntype CopyFieldProps = {\n /**\n * What the button says once the value has been copied, and what is\n * announced. It reverts to `copyLabel` on its own after a couple of\n * seconds. Left out, it is the word for it in the I18nProvider's locale —\n * \"Copied\" in English.\n */\n copiedLabel?: string;\n /**\n * What the button says at rest. Left out, it is the word for it in the\n * I18nProvider's locale — \"Copy\" in English.\n */\n copyLabel?: string;\n /**\n * What the value is — \"Repository URL\", \"Access token\" — for a page with\n * more than one field. It names the field as a group and joins the button's\n * name, so a screen reader hears \"Copy Repository URL\" rather than one\n * \"Copy\" after another; it is not shown. The value itself stays out of the\n * button's name, since a token would be read aloud. Left out, the field is\n * unnamed and its button is \"Copy\" alone, as it always was.\n */\n label?: string;\n /**\n * Called with the value after it reaches the clipboard, never before.\n *\n * Named `onCopied` rather than `onCopy` because the root is a `<div>`, which\n * already has an `onCopy` of its own — React's handler for the native copy\n * event, fired when the reader presses the copy shortcut inside the element.\n * The two are different moments, and a prop that quietly shadowed the\n * built-in would be a trap rather than a convenience.\n */\n onCopied?: (value: string) => void;\n /**\n * Called when the write is refused, with whatever was thrown, and the\n * field's own fallback has not copied the value either. By then the value\n * is selected and the field says so — see `selectedLabel` — so this is for\n * an app that wants to say more, in a snackbar say.\n *\n * Takes the error where `onCopied` takes the value, because the value is\n * the one thing the call site already has and the reason is the one thing\n * it does not. The two refusals throw differently: outside a secure\n * context `navigator.clipboard` is not there at all, and where the\n * permission is denied it is a `DOMException`.\n */\n onCopyFailed?: (error: unknown) => void;\n /**\n * What is announced when the clipboard refused the write and the value has\n * been selected for the reader to copy themselves. The button keeps saying\n * `copyLabel`, since a label slot wide enough for this would double the\n * button's width at rest; the selection itself is what shows. Left out, it\n * is the phrase for it in the I18nProvider's locale — \"Selected to copy\"\n * in English.\n */\n selectedLabel?: string;\n /** The text shown, and the text copied. */\n value: string;\n} & Omit<HTMLAttributes<HTMLDivElement>, 'children'>;\n\n/**\n * A value to be read and taken away — a repository URL, a token, a command.\n * It shows the value in the mono face and copies it on request, confirming\n * on the button itself.\n *\n * A write refused — outside a secure context, or where the permission is\n * denied — does not leave the reader with nothing. The field selects the\n * value and tries the older copy command, which some of those places still\n * allow; failing that, the selection is left ready for the keyboard's own\n * copy, the field announces so, and `onCopyFailed` is called. One click on\n * the value selects exactly the value too.\n */\nfunction CopyField({\n copiedLabel: copiedLabelProp,\n copyLabel: copyLabelProp,\n label,\n onCopied,\n onCopyFailed,\n selectedLabel: selectedLabelProp,\n value,\n ...props\n}: CopyFieldProps & RefAttributes<HTMLDivElement>) {\n const messages = useMessages();\n const copiedLabel = copiedLabelProp ?? messages.copied;\n const copyLabel = copyLabelProp ?? messages.copy;\n const selectedLabel = selectedLabelProp ?? messages.copySelected;\n // What the last write put on the clipboard, and how many writes there have\n // been. The confirmation is that text still being the value shown, so a\n // value that changes inside the dwell — a token regenerated, a selection\n // swapped — takes the label back to Copy at once rather than confirming a\n // copy of something else. The count is what lets a second press be\n // announced, below. `kind` is whether the text reached the clipboard or\n // was only selected for the reader to copy, after a refusal.\n const [copy, setCopy] = useState<null | {\n count: number;\n kind: 'copied' | 'selected';\n text: string;\n }>(null);\n const shown: 'copied' | 'copy' | 'selected' = copy?.text === value ? copy.kind : 'copy';\n const copied = shown === 'copied';\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const code = useRef<HTMLElement>(null);\n const generatedId = useId();\n const rootId = props.id ?? generatedId;\n const copyId = useId();\n const copiedId = useId();\n\n // With a label, the field is a group it names, and the button is named by\n // the label slot on show and then by the group: \"Copy Repository URL\",\n // then \"Copied Repository URL\". The slot on show rather than both, since\n // the two swap `aria-hidden` and a name taking both would say both.\n const named = label === undefined ? undefined : {\n 'aria-label': label,\n id: rootId,\n role: 'group'\n };\n const buttonName = label === undefined ? undefined : `${copied ? copiedId : copyId} ${rootId}`;\n\n // Without this, a field unmounted inside the dwell leaves a timer holding a\n // setState for a tree that is gone.\n useEffect(() => () => {\n clearTimeout(timer.current);\n }, []);\n const handleCopy = useCallback(() => {\n // The component's own state first, the dwell included, and the call\n // site's handler after it: a handler that threw used to skip the timer,\n // leaving the button on Copied for good. Its error is still the call\n // site's to see, raised on its own rather than swallowed.\n const settle = (kind: 'copied' | 'selected') => {\n clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n setCopy(null);\n }, COPIED_RESET_MS);\n setCopy(current => ({\n count: (current?.count ?? 0) + 1,\n kind,\n text: value\n }));\n };\n const write = async () => {\n try {\n await navigator.clipboard.writeText(value);\n } catch (error) {\n // A clipboard write is refused outside a secure context and wherever\n // the permission is denied, neither of which the call site can fix,\n // and a button that did nothing there left the reader with nothing.\n // So the value is selected, which is what the keyboard's own copy\n // acts on, and the older copy command is tried on that selection,\n // since some of those places still allow it. Only where that fails\n // too does the field announce that the value is selected — not that\n // it is copied, which it is not — and report the refusal.\n //\n // Nothing is logged — the library writes to the console nowhere, and\n // a warning an app cannot turn off is not its to emit.\n if (selectAndCopy(code.current)) {\n settle('copied');\n reportCopied(onCopied, value);\n return;\n }\n settle('selected');\n onCopyFailed?.(error);\n return;\n }\n settle('copied');\n reportCopied(onCopied, value);\n };\n void write();\n }, [onCopied, onCopyFailed, value]);\n return <div {...props} {...named} {...mergeStyles({\n className: \"x6s0dn4 x14s9m32 x1fwvt7v x1xdvaa9 x1y0btm7 xmkeg23 x9f619 x78zum5 x126z3so x1cmllll xq7elwu xwde665\"\n }, props)}>\n <span className=\"x139gbkf x1iyjqo2 x1b35g6e xdb2kus x9d6959 xwth0xh xybisxh xeuugli x6mezaz xvrbanz x126k92a\">\n <code dir=\"ltr\" ref={code} className=\"x9f619 xbppi91 x1qlqyl8 xj0a0fe x4ej8vm\">\n {value}\n </code>\n </span>\n <Button aria-labelledby={buttonName} onClick={handleCopy} size=\"xs\" variant=\"text\">\n <span className=\"xrvj5dj x1o2pa38\">\n <span aria-hidden={copied || undefined} id={copyId} {...{\n 0: {\n className: \"x15r89dc\"\n },\n 1: {\n className: \"x15r89dc xlshs6z\"\n }\n }[!!copied << 0]}>\n {copyLabel}\n </span>\n <span aria-hidden={!copied || undefined} id={copiedId} {...{\n 0: {\n className: \"x15r89dc\"\n },\n 1: {\n className: \"x15r89dc xlshs6z\"\n }\n }[!!!copied << 0]}>\n {copiedLabel}\n </span>\n </span>\n </Button>\n {/* The button's own label changes, but a label changing under a screen\n reader is not reliably announced. This is. `<output>` carries an\n implicit role of status, so it needs no role attribute of its own.\n Each write puts a new node in it, keyed by the count, since a second\n press inside the dwell otherwise left the text as it was and the\n region had nothing to announce. */}\n <output className=\"xjm9jq1 x1hyvwdk x1i1rx1s xb3r6kr x10l6tqk xuxw1ft\">\n {copy !== null && shown !== 'copy' ? <span key={copy.count}>\n {shown === 'copied' ? copiedLabel : selectedLabel}\n </span> : null}\n </output>\n </div>;\n}\n\n/**\n * Hands the call site the value it copied. What its handler throws is raised\n * on its own, outside the copy: it still reaches the page's error reporting,\n * where a rejection of the copy's own promise reached nothing but an\n * unhandled-rejection warning. Outside the component, since the React\n * Compiler compiles neither optional chaining nor a captured catch binding\n * inside a `try`.\n */\nfunction reportCopied(onCopied: ((value: string) => void) | undefined, value: string) {\n if (onCopied === undefined) {\n return;\n }\n try {\n onCopied(value);\n } catch (thrown) {\n queueMicrotask(() => {\n throw thrown;\n });\n }\n}\n\n/**\n * Selects the value's text, which is what the keyboard's own copy acts on,\n * and tries the older copy command on that selection, saying whether it\n * copied. The command is deprecated, and still the one way to the clipboard\n * on an insecure origin, which is where the newer one is refused.\n */\nfunction selectAndCopy(element: HTMLElement | null) {\n const selection = window.getSelection();\n if (element === null || selection === null) {\n return false;\n }\n const range = document.createRange();\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n try {\n // oxlint-disable-next-line typescript/no-deprecated -- the one fallback an insecure origin still allows\n return document.execCommand('copy');\n } catch {\n return false;\n }\n}\nexport type { CopyFieldProps };\nexport default CopyField;"],"mappings":"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"}
|
|
@@ -1,17 +1,64 @@
|
|
|
1
1
|
import { RenderComponentProps } from "../../render/useRender.js";
|
|
2
2
|
//#region src/components/currency/index.d.ts
|
|
3
|
+
type CurrencyDisplay = 'code' | 'name' | 'narrowSymbol' | 'symbol';
|
|
4
|
+
type CurrencyNotation = 'compact' | 'standard';
|
|
3
5
|
type CurrencyProps = Omit<RenderComponentProps<'span'>, 'children'> & {
|
|
4
6
|
/**
|
|
5
7
|
* ISO 4217 code for the currency to format in.
|
|
6
8
|
* @default 'USD'
|
|
7
9
|
*/
|
|
8
10
|
currency?: string;
|
|
11
|
+
/**
|
|
12
|
+
* How the currency is written. `symbol` is the locale's own symbol, which
|
|
13
|
+
* tells one dollar from another where the locale has to — `CA$` in
|
|
14
|
+
* American English. `narrowSymbol` is the bare sign, `$` for every dollar,
|
|
15
|
+
* for a page that states its currency some other way. `code` writes the
|
|
16
|
+
* ISO code and `name` the currency's name in the locale's words, `US
|
|
17
|
+
* dollars`.
|
|
18
|
+
*
|
|
19
|
+
* A right-to-left locale draws a symbol made of letters and a sign with
|
|
20
|
+
* its sign first, so Arabic's `US$` reads `$US`. `code` writes `USD`,
|
|
21
|
+
* which has no sign to turn around.
|
|
22
|
+
* @default 'symbol'
|
|
23
|
+
*/
|
|
24
|
+
currencyDisplay?: CurrencyDisplay;
|
|
9
25
|
/**
|
|
10
26
|
* BCP 47 locale to format for. Defaults to React Aria's `I18nProvider`, as
|
|
11
27
|
* the date and number fields do, so an amount reads in the locale the app
|
|
12
28
|
* set — and to the browser's language where there is no provider.
|
|
29
|
+
*
|
|
30
|
+
* The element carries the locale it was formatted in as its `lang`, and
|
|
31
|
+
* that locale's writing direction as its `dir`, so a screen reader can
|
|
32
|
+
* read it by the locale's own number rules and its characters fall in the
|
|
33
|
+
* locale's own order. A `lang` or `dir` the call site passes wins.
|
|
13
34
|
*/
|
|
14
35
|
locale?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The most digits shown after the decimal point; the amount is rounded to
|
|
38
|
+
* them. Left out, the currency's own minor units decide — two for a
|
|
39
|
+
* dollar, none for a yen — and under `compact` notation the figure keeps
|
|
40
|
+
* two significant digits instead.
|
|
41
|
+
*
|
|
42
|
+
* A count the runtime rejects — negative, over a hundred, or below
|
|
43
|
+
* `minimumFractionDigits` — is dropped rather than thrown, and both counts
|
|
44
|
+
* fall back to the currency's own.
|
|
45
|
+
*/
|
|
46
|
+
maximumFractionDigits?: number;
|
|
47
|
+
/**
|
|
48
|
+
* The fewest digits shown after the decimal point, padded with zeros.
|
|
49
|
+
* Left out, the currency's own minor units decide.
|
|
50
|
+
*/
|
|
51
|
+
minimumFractionDigits?: number;
|
|
52
|
+
/**
|
|
53
|
+
* `compact` shortens a large amount to a few digits and the locale's word
|
|
54
|
+
* or letter for the scale — `$12B`, `12 Mrd. €` — for a figure that would
|
|
55
|
+
* otherwise run past a narrow container. An amount has no break
|
|
56
|
+
* opportunity, so a long one in hero type overflows rather than wrapping.
|
|
57
|
+
* The amount is rounded, so it suits a figure read at a glance rather
|
|
58
|
+
* than one read to the cent.
|
|
59
|
+
* @default 'standard'
|
|
60
|
+
*/
|
|
61
|
+
notation?: CurrencyNotation;
|
|
15
62
|
/**
|
|
16
63
|
* When to show the sign. `auto` shows one only on negatives, `always` also
|
|
17
64
|
* prefixes positives with a plus, and `never` shows neither — which leaves
|
|
@@ -31,8 +78,10 @@ type CurrencyProps = Omit<RenderComponentProps<'span'>, 'children'> & {
|
|
|
31
78
|
* The positive and negative roles are guaranteed legible on the surface
|
|
32
79
|
* family, which is what the library's own tokens and every demo scheme are
|
|
33
80
|
* held to. Over a container of its own — a selected row, a filled card —
|
|
34
|
-
* the pair is one colour family on top of another and is not guaranteed
|
|
35
|
-
*
|
|
81
|
+
* the pair is one colour family on top of another and is not guaranteed.
|
|
82
|
+
* `inherit` is the way out there, as it is on Text: it sets no colour, so
|
|
83
|
+
* the amount takes the container's own on-colour, and its sign is what
|
|
84
|
+
* says which way it goes.
|
|
36
85
|
* @default 'auto'
|
|
37
86
|
*/
|
|
38
87
|
tone?: CurrencyTone;
|
|
@@ -40,7 +89,7 @@ type CurrencyProps = Omit<RenderComponentProps<'span'>, 'children'> & {
|
|
|
40
89
|
value: number;
|
|
41
90
|
};
|
|
42
91
|
type CurrencySignDisplay = 'always' | 'auto' | 'never';
|
|
43
|
-
type CurrencyTone = 'auto' | 'negative' | 'neutral' | 'positive';
|
|
92
|
+
type CurrencyTone = 'auto' | 'inherit' | 'negative' | 'neutral' | 'positive';
|
|
44
93
|
/**
|
|
45
94
|
* An amount of money, formatted for the reader's locale in the mono face with
|
|
46
95
|
* tabular figures, so a column of amounts lines up on the decimal point.
|
|
@@ -52,12 +101,21 @@ type CurrencyTone = 'auto' | 'negative' | 'neutral' | 'positive';
|
|
|
52
101
|
* ```
|
|
53
102
|
*
|
|
54
103
|
* It sets no size of its own, so it takes the type around it: wrap it in a
|
|
55
|
-
* Text, or put it in a heading, for a larger figure.
|
|
104
|
+
* Text, or put it in a heading, for a larger figure. A figure that has to
|
|
105
|
+
* fit a narrow space takes `notation="compact"`, which shortens it to a few
|
|
106
|
+
* digits and a scale.
|
|
107
|
+
*
|
|
108
|
+
* Over a container of its own, such as a selected row, give it
|
|
109
|
+
* `tone="inherit"` so it reads in the container's own on-colour.
|
|
110
|
+
*
|
|
111
|
+
* It carries the `lang` and `dir` of the locale it was formatted in, and is
|
|
112
|
+
* isolated from the text around it, so an amount in a locale other than the
|
|
113
|
+
* page's reads in its own order.
|
|
56
114
|
*
|
|
57
115
|
* The call site's `className` and `style` land on the element holding the
|
|
58
116
|
* amount.
|
|
59
117
|
*/
|
|
60
|
-
declare function Currency({ currency, locale, render, sign, tone, value, ...props }: CurrencyProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
118
|
+
declare function Currency({ currency, currencyDisplay, locale, maximumFractionDigits, minimumFractionDigits, notation, render, sign, tone, value, ...props }: CurrencyProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
61
119
|
//#endregion
|
|
62
|
-
export { type CurrencyProps, type CurrencySignDisplay, type CurrencyTone, Currency as default };
|
|
120
|
+
export { type CurrencyDisplay, type CurrencyNotation, type CurrencyProps, type CurrencySignDisplay, type CurrencyTone, Currency as default };
|
|
63
121
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,15 +4,22 @@ import { useRender } from "../../render/useRender.js";
|
|
|
4
4
|
import { mergeStyles } from "../../styles/merge.js";
|
|
5
5
|
import { c } from "react/compiler-runtime";
|
|
6
6
|
import { useLocale } from "react-aria-components";
|
|
7
|
+
import { isRTL } from "react-aria";
|
|
7
8
|
//#region src/components/currency/index.tsx
|
|
8
9
|
const styles = { base: {
|
|
9
10
|
kB7OPa: "x9f619",
|
|
10
11
|
kMv6JI: "xbppi91",
|
|
11
12
|
kcqcaj: "xss6m8b",
|
|
12
13
|
k63SB2: "x1bf3uqz",
|
|
14
|
+
k9WMMc: "x16pb8o4",
|
|
15
|
+
kQ0QjF: "x4ej8vm",
|
|
13
16
|
$$css: true
|
|
14
17
|
} };
|
|
15
18
|
const tones = {
|
|
19
|
+
inherit: {
|
|
20
|
+
kMwMTN: "x1heor9g",
|
|
21
|
+
$$css: true
|
|
22
|
+
},
|
|
16
23
|
negative: {
|
|
17
24
|
kMwMTN: "x12gp0t",
|
|
18
25
|
$$css: true
|
|
@@ -38,58 +45,79 @@ const MINUS_SIGN = "−";
|
|
|
38
45
|
* ```
|
|
39
46
|
*
|
|
40
47
|
* It sets no size of its own, so it takes the type around it: wrap it in a
|
|
41
|
-
* Text, or put it in a heading, for a larger figure.
|
|
48
|
+
* Text, or put it in a heading, for a larger figure. A figure that has to
|
|
49
|
+
* fit a narrow space takes `notation="compact"`, which shortens it to a few
|
|
50
|
+
* digits and a scale.
|
|
51
|
+
*
|
|
52
|
+
* Over a container of its own, such as a selected row, give it
|
|
53
|
+
* `tone="inherit"` so it reads in the container's own on-colour.
|
|
54
|
+
*
|
|
55
|
+
* It carries the `lang` and `dir` of the locale it was formatted in, and is
|
|
56
|
+
* isolated from the text around it, so an amount in a locale other than the
|
|
57
|
+
* page's reads in its own order.
|
|
42
58
|
*
|
|
43
59
|
* The call site's `className` and `style` land on the element holding the
|
|
44
60
|
* amount.
|
|
45
61
|
*/
|
|
46
62
|
function Currency(t0) {
|
|
47
|
-
const $ = c(
|
|
48
|
-
const { currency: t1, locale, render, sign: t2, tone: t3, value, ...props$1 } = t0;
|
|
63
|
+
const $ = c(9);
|
|
64
|
+
const { currency: t1, currencyDisplay, locale, maximumFractionDigits, minimumFractionDigits, notation, render, sign: t2, tone: t3, value, ...props$1 } = t0;
|
|
49
65
|
const currency = t1 === void 0 ? "USD" : t1;
|
|
50
66
|
const sign = t2 === void 0 ? "auto" : t2;
|
|
51
67
|
const tone = t3 === void 0 ? "auto" : t3;
|
|
52
68
|
const provided = useLocale().locale;
|
|
53
69
|
const amount = formatCurrency(value, {
|
|
54
70
|
currency,
|
|
71
|
+
currencyDisplay,
|
|
55
72
|
fallbackLocale: provided,
|
|
56
73
|
locale: locale === void 0 || locale === "" ? provided : locale,
|
|
74
|
+
maximumFractionDigits,
|
|
75
|
+
minimumFractionDigits,
|
|
76
|
+
notation,
|
|
57
77
|
sign
|
|
58
78
|
});
|
|
59
79
|
const t4 = "span";
|
|
60
80
|
const t5 = amount.text;
|
|
61
|
-
const t6 =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
const t6 = props$1.dir ?? amount.direction;
|
|
82
|
+
const t7 = props$1.lang ?? amount.locale;
|
|
83
|
+
const t8 = mergeStyles(props(styles.base, tones[resolveTone(value, tone, amount.shownAsZero)]), props$1);
|
|
84
|
+
let t9;
|
|
85
|
+
if ($[0] !== amount || $[1] !== props$1 || $[2] !== t6 || $[3] !== t7 || $[4] !== t8) {
|
|
86
|
+
t9 = {
|
|
65
87
|
...props$1,
|
|
66
88
|
children: t5,
|
|
67
|
-
|
|
89
|
+
dir: t6,
|
|
90
|
+
lang: t7,
|
|
91
|
+
...t8
|
|
68
92
|
};
|
|
69
93
|
$[0] = amount;
|
|
70
94
|
$[1] = props$1;
|
|
71
95
|
$[2] = t6;
|
|
72
96
|
$[3] = t7;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
$[4] = t8;
|
|
98
|
+
$[5] = t9;
|
|
99
|
+
} else t9 = $[5];
|
|
100
|
+
let t10;
|
|
101
|
+
if ($[6] !== render || $[7] !== t9) {
|
|
102
|
+
t10 = {
|
|
77
103
|
defaultTagName: t4,
|
|
78
|
-
props:
|
|
104
|
+
props: t9,
|
|
79
105
|
render
|
|
80
106
|
};
|
|
81
|
-
$[
|
|
82
|
-
$[
|
|
83
|
-
$[
|
|
84
|
-
} else
|
|
85
|
-
return useRender(
|
|
107
|
+
$[6] = render;
|
|
108
|
+
$[7] = t9;
|
|
109
|
+
$[8] = t10;
|
|
110
|
+
} else t10 = $[8];
|
|
111
|
+
return useRender(t10);
|
|
86
112
|
}
|
|
87
113
|
const formatters = /* @__PURE__ */ new Map();
|
|
88
114
|
function formatCurrency(value, options) {
|
|
89
|
-
const { code, formatter } = formatterFor(options
|
|
115
|
+
const { code, direction, formatter, locale } = formatterFor(options);
|
|
90
116
|
const parts = formatter.formatToParts(value);
|
|
91
117
|
const amount = parts.map((part) => part.type === "minusSign" ? MINUS_SIGN : part.value).join("");
|
|
92
118
|
return {
|
|
119
|
+
direction,
|
|
120
|
+
locale,
|
|
93
121
|
shownAsZero: parts.every((part) => part.type !== "integer" && part.type !== "fraction" || /^0+$/.test(part.value)),
|
|
94
122
|
text: code === null || code === "" ? amount : `${amount} ${code}`
|
|
95
123
|
};
|
|
@@ -99,34 +127,83 @@ const SIGN_DISPLAY = {
|
|
|
99
127
|
auto: "negative",
|
|
100
128
|
never: "never"
|
|
101
129
|
};
|
|
102
|
-
|
|
103
|
-
|
|
130
|
+
const TWO_PLACES = {
|
|
131
|
+
maximumFractionDigits: 2,
|
|
132
|
+
minimumFractionDigits: 2
|
|
133
|
+
};
|
|
134
|
+
function formatterFor(options) {
|
|
135
|
+
const { currency, currencyDisplay, fallbackLocale, locale, maximumFractionDigits, minimumFractionDigits, notation, sign } = options;
|
|
136
|
+
const key = JSON.stringify([
|
|
137
|
+
locale,
|
|
138
|
+
currency,
|
|
139
|
+
sign,
|
|
140
|
+
fallbackLocale,
|
|
141
|
+
currencyDisplay,
|
|
142
|
+
notation,
|
|
143
|
+
String(minimumFractionDigits),
|
|
144
|
+
String(maximumFractionDigits)
|
|
145
|
+
]);
|
|
104
146
|
const cached = formatters.get(key);
|
|
105
147
|
if (cached) return cached;
|
|
106
148
|
const usable = [locale, fallbackLocale].find(isLocale) ?? "en-US";
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
const written = {
|
|
150
|
+
direction: isRTL(usable) ? "rtl" : "ltr",
|
|
151
|
+
locale: usable
|
|
152
|
+
};
|
|
153
|
+
const signDisplay = SIGN_DISPLAY[sign];
|
|
154
|
+
const digits = {
|
|
155
|
+
maximumFractionDigits,
|
|
156
|
+
minimumFractionDigits
|
|
157
|
+
};
|
|
158
|
+
const asCurrency = {
|
|
159
|
+
currency,
|
|
160
|
+
currencyDisplay,
|
|
161
|
+
notation,
|
|
162
|
+
signDisplay,
|
|
163
|
+
style: "currency"
|
|
164
|
+
};
|
|
165
|
+
const attempts = [
|
|
166
|
+
[null, {
|
|
167
|
+
...asCurrency,
|
|
168
|
+
...digits
|
|
169
|
+
}],
|
|
170
|
+
[currency, {
|
|
171
|
+
notation,
|
|
172
|
+
signDisplay,
|
|
173
|
+
...maximumFractionDigits === void 0 && minimumFractionDigits === void 0 ? TWO_PLACES : digits
|
|
174
|
+
}],
|
|
175
|
+
[null, asCurrency]
|
|
176
|
+
];
|
|
177
|
+
for (const [code, attempt] of attempts) {
|
|
178
|
+
const formatter = formatterOrNull(usable, attempt);
|
|
179
|
+
if (formatter !== null) {
|
|
180
|
+
const resolved = {
|
|
181
|
+
code,
|
|
182
|
+
formatter,
|
|
183
|
+
...written
|
|
184
|
+
};
|
|
185
|
+
formatters.set(key, resolved);
|
|
186
|
+
return resolved;
|
|
187
|
+
}
|
|
126
188
|
}
|
|
189
|
+
const resolved = {
|
|
190
|
+
code: currency,
|
|
191
|
+
formatter: new Intl.NumberFormat(usable, {
|
|
192
|
+
...TWO_PLACES,
|
|
193
|
+
signDisplay
|
|
194
|
+
}),
|
|
195
|
+
...written
|
|
196
|
+
};
|
|
127
197
|
formatters.set(key, resolved);
|
|
128
198
|
return resolved;
|
|
129
199
|
}
|
|
200
|
+
function formatterOrNull(locale, options) {
|
|
201
|
+
try {
|
|
202
|
+
return new Intl.NumberFormat(locale, options);
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
130
207
|
function isLocale(tag) {
|
|
131
208
|
try {
|
|
132
209
|
return Intl.NumberFormat.supportedLocalesOf(tag).length >= 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["c","_c","stylex","useLocale","RenderComponentProps","useRender","mergeStyles","colors","typography","styles","base","kB7OPa","kMv6JI","kcqcaj","k63SB2","$$css","tones","negative","kMwMTN","neutral","positive","MINUS_SIGN","CurrencyProps","Omit","currency","locale","sign","CurrencySignDisplay","tone","CurrencyTone","value","Currency","t0","$","t1","render","t2","t3","props","undefined","provided","amount","formatCurrency","fallbackLocale","t4","t5","text","t6","resolveTone","shownAsZero","t7","children","t8","defaultTagName","formatters","Map","ResolvedFormat","code","formatter","Intl","NumberFormat","options","formatterFor","parts","formatToParts","map","part","type","join","every","test","SIGN_DISPLAY","always","auto","never","const","Record","NumberFormatOptions","key","cached","get","usable","find","isLocale","resolved","signDisplay","style","maximumFractionDigits","minimumFractionDigits","set","tag","supportedLocalesOf","length","_c","props","stylex.props"],"sources":["../../../src/components/currency/index.tsx"],"sourcesContent":["'use client';\n\nimport * as stylex from '@stylexjs/stylex';\nimport { useLocale } from 'react-aria-components';\nimport type { RenderComponentProps } from '../../render/useRender';\nimport { useRender } from '../../render/useRender';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, typography } from '../../tokens/design.tokens.stylex';\n\n// Deliberately sets no font-size. The same value turns up at several sizes —\n// inside a list row, as a heading-scale figure, as a total — differing in\n// nothing else, so the size is the container's decision. What must never vary\n// is here: the mono face, tabular figures so a column of them lines up on the\n// decimal point, and the medium weight that separates a figure from the prose\n// around it.\n//\n// No Material Design page draws an amount. It is type alone: the mono face\n// and medium weight from the type tokens, tabular figures, and the on surface\n// role for the neutral tone. Positive and negative are the library's own\n// roles, the pair Tag draws from as well.\nconst styles = {\n base: {\n kB7OPa: \"x9f619\",\n kMv6JI: \"xbppi91\",\n kcqcaj: \"xss6m8b\",\n k63SB2: \"x1bf3uqz\",\n $$css: true\n }\n};\nconst tones = {\n negative: {\n kMwMTN: \"x12gp0t\",\n $$css: true\n },\n neutral: {\n kMwMTN: \"x139gbkf\",\n $$css: true\n },\n positive: {\n kMwMTN: \"x4no3js\",\n $$css: true\n }\n};\n\n// U+2212 MINUS SIGN, not the U+002D hyphen Intl actually emits. The hyphen is\n// a typographic stand-in: it is narrower than the plus it pairs with and does\n// not sit on the same optical centre as the digits, which shows up as a\n// wobbling column the moment amounts are stacked.\nconst MINUS_SIGN = '−';\ntype CurrencyProps = Omit<RenderComponentProps<'span'>, 'children'> & {\n /**\n * ISO 4217 code for the currency to format in.\n * @default 'USD'\n */\n currency?: string;\n /**\n * BCP 47 locale to format for. Defaults to React Aria's `I18nProvider`, as\n * the date and number fields do, so an amount reads in the locale the app\n * set — and to the browser's language where there is no provider.\n */\n locale?: string;\n /**\n * When to show the sign. `auto` shows one only on negatives, `always` also\n * prefixes positives with a plus, and `never` shows neither — which leaves\n * colour as the only thing separating a credit from a debt, so reach for it\n * only where the direction is already stated some other way.\n *\n * An amount that shows as zero carries no sign under any of them, since\n * there is nothing owed or owing: `-0.004` reads `$0.00`, not `−$0.00`.\n * @default 'auto'\n */\n sign?: CurrencySignDisplay;\n /**\n * Which colour role to render in. `auto` follows the sign of the amount as\n * it is shown, so one that rounds to zero — float residue, or a fraction\n * of a cent — reads as neutral rather than as a gain or a debt.\n *\n * The positive and negative roles are guaranteed legible on the surface\n * family, which is what the library's own tokens and every demo scheme are\n * held to. Over a container of its own — a selected row, a filled card —\n * the pair is one colour family on top of another and is not guaranteed, so\n * a custom theme placing one there has to check that pair itself.\n * @default 'auto'\n */\n tone?: CurrencyTone;\n /** The amount, in the currency's major units. */\n value: number;\n};\ntype CurrencySignDisplay = 'always' | 'auto' | 'never';\ntype CurrencyTone = 'auto' | 'negative' | 'neutral' | 'positive';\n\n/**\n * An amount of money, formatted for the reader's locale in the mono face with\n * tabular figures, so a column of amounts lines up on the decimal point.\n * Negatives carry a true minus sign, and the tone follows the sign unless\n * `tone` names one.\n *\n * ```tsx\n * <Currency value={-42.5} currency=\"EUR\" />\n * ```\n *\n * It sets no size of its own, so it takes the type around it: wrap it in a\n * Text, or put it in a heading, for a larger figure.\n *\n * The call site's `className` and `style` land on the element holding the\n * amount.\n */\nfunction Currency({\n currency = 'USD',\n locale,\n render,\n sign = 'auto',\n tone = 'auto',\n value,\n ...props\n}: CurrencyProps) {\n // React Aria holds the locale steady through hydration, where the runtime's\n // default differs between the server and the browser and the amount's text\n // would not match.\n const provided = useLocale().locale;\n const amount = formatCurrency(value, {\n currency,\n fallbackLocale: provided,\n // An empty string asks for the provider's locale as `undefined` does —\n // it is what clearing a locale control leaves — rather than reaching the\n // formatter as a tag it rejects.\n locale: locale === undefined || locale === '' ? provided : locale,\n sign\n });\n return useRender({\n defaultTagName: 'span',\n props: {\n ...props,\n children: amount.text,\n ...mergeStyles(stylex.props(styles.base, tones[resolveTone(value, tone, amount.shownAsZero)]), props)\n },\n render\n });\n}\n\n// Constructing an Intl.NumberFormat resolves the locale, loads the currency's\n// data and builds the pattern; formatToParts on one already built skips all\n// three, which measures about ten times cheaper per value. An instance is\n// stateless once constructed, so one can be shared across every component\n// asking for the same three options.\n//\n// What this saves is a mount and a changing amount, not an idle re-render:\n// the React Compiler already memoises the call below on the four things it\n// reads, so a component re-rendered with the same props never reaches here.\n// That cache belongs to one component instance and counts `value` among its\n// keys, which leaves two cases it cannot cover — a column of rows, where every\n// instance has a cache of its own and each builds a formatter, and a single\n// amount that ticks, where each new value misses.\n//\n// Unbounded on purpose. The keys are the option sets an app actually uses —\n// its locales times its currencies times the three sign settings — so the map\n// settles at that size rather than growing with renders or with values.\n//\n// A request the runtime rejects is cached too, under its own key, as the\n// fallback it resolved to, so a bad pair costs one failed construction rather\n// than one a render.\nconst formatters = new Map<string, ResolvedFormat>();\n\n/**\n * A formatter, and the currency code written beside its number when it is\n * the decimal fallback for a code the runtime rejected.\n */\ntype ResolvedFormat = {\n code: null | string;\n formatter: Intl.NumberFormat;\n};\n\n// formatToParts rather than a replace over the formatted string, so only the\n// sign is substituted. A blind replace would also hit a hyphen inside a\n// locale's currency name or grouping, and locales that wrap negatives in\n// parentheses have no minus to swap at all — both of which this leaves alone.\nfunction formatCurrency(value: number, options: {\n currency: string;\n fallbackLocale: string;\n locale: string;\n sign: CurrencySignDisplay;\n}) {\n const {\n code,\n formatter\n } = formatterFor(options.locale, options.currency, options.sign, options.fallbackLocale);\n const parts = formatter.formatToParts(value);\n const amount = parts.map(part => part.type === 'minusSign' ? MINUS_SIGN : part.value).join('');\n return {\n // Whether every digit shown is a zero, which is what the tone reads\n // rather than the raw value: the two never saw the same number before.\n shownAsZero: parts.every(part => part.type !== 'integer' && part.type !== 'fraction' || /^0+$/.test(part.value)),\n text: code === null || code === '' ? amount : `${amount} ${code}`\n };\n}\n\n// The sign each setting asks Intl for. `negative` and `exceptZero` are the\n// forms that leave a zero bare, which `auto` and `always` do not: they put a\n// minus on any negative input, and a plus on any positive one, including\n// those that round to a displayed zero.\nconst SIGN_DISPLAY = {\n always: 'exceptZero',\n auto: 'negative',\n never: 'never'\n} as const satisfies Record<CurrencySignDisplay, Intl.NumberFormatOptions['signDisplay']>;\n\n// A component that only shows an amount must not take the page down with it,\n// and `new Intl.NumberFormat` throws for a malformed locale (`xx_YY`, an\n// empty string) and for a currency code that is not one (`xx`, `US`). Valid\n// input takes exactly the path it always did; the rest falls back. A locale\n// the runtime rejects gives way to the provider's, and a currency code it\n// rejects to the plain number in that locale with the code written after it,\n// so the amount still reads and what was wrong with it stays visible.\n//\n// Nothing is logged: the library writes to the console nowhere, and a\n// warning an app cannot turn off is not its to emit.\nfunction formatterFor(locale: string, currency: string, sign: CurrencySignDisplay, fallbackLocale: string) {\n // Neither a BCP 47 locale nor an ISO 4217 code can contain a vertical bar\n // and `sign` is one of three known words, so joining on one cannot make two\n // different option sets share a key.\n const key = `${locale}|${currency}|${sign}|${fallbackLocale}`;\n const cached = formatters.get(key);\n if (cached) {\n return cached;\n }\n const usable = [locale, fallbackLocale].find(isLocale) ?? 'en-US';\n let resolved: ResolvedFormat;\n try {\n resolved = {\n code: null,\n formatter: new Intl.NumberFormat(usable, {\n currency,\n signDisplay: SIGN_DISPLAY[sign],\n style: 'currency'\n })\n };\n } catch {\n resolved = {\n code: currency,\n formatter: new Intl.NumberFormat(usable, {\n maximumFractionDigits: 2,\n minimumFractionDigits: 2,\n signDisplay: SIGN_DISPLAY[sign]\n })\n };\n }\n formatters.set(key, resolved);\n return resolved;\n}\n\n// Whether the runtime takes `tag` as a locale at all — one it parses, whether\n// or not it has data for it, which it then fills in from the nearest it has.\nfunction isLocale(tag: string) {\n try {\n return Intl.NumberFormat.supportedLocalesOf(tag).length >= 0;\n } catch {\n return false;\n }\n}\n\n// An amount shown as zero is neither owed nor owing, so it takes the neutral\n// role whatever the raw value's sign: negative zero, a rounded-away debt and\n// float residue all read as settled. Otherwise the sign decides.\nfunction resolveTone(value: number, tone: CurrencyTone, shownAsZero: boolean) {\n if (tone !== 'auto') {\n return tone;\n }\n if (shownAsZero) {\n return 'neutral';\n }\n if (value > 0) {\n return 'positive';\n }\n if (value < 0) {\n return 'negative';\n }\n return 'neutral';\n}\nexport type { CurrencyProps, CurrencySignDisplay, CurrencyTone };\nexport default Currency;"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["c","_c","stylex","isRTL","useLocale","RenderComponentProps","useRender","mergeStyles","colors","typography","styles","base","kB7OPa","kMv6JI","kcqcaj","k63SB2","k9WMMc","kQ0QjF","$$css","tones","inherit","kMwMTN","negative","neutral","positive","MINUS_SIGN","CurrencyDisplay","CurrencyNotation","CurrencyProps","Omit","currency","currencyDisplay","locale","maximumFractionDigits","minimumFractionDigits","notation","sign","CurrencySignDisplay","tone","CurrencyTone","value","Currency","t0","$","t1","render","t2","t3","props","undefined","provided","amount","formatCurrency","fallbackLocale","t4","t5","text","t6","dir","direction","t7","lang","t8","resolveTone","shownAsZero","t9","children","t10","defaultTagName","formatters","Map","ResolvedFormat","code","formatter","Intl","NumberFormat","options","FormatOptions","formatterFor","parts","formatToParts","map","part","type","join","every","test","SIGN_DISPLAY","always","auto","never","const","Record","NumberFormatOptions","TWO_PLACES","key","JSON","stringify","String","cached","get","usable","find","isLocale","written","signDisplay","digits","asCurrency","style","attempts","attempt","formatterOrNull","resolved","set","tag","supportedLocalesOf","length","_c","props","stylex.props"],"sources":["../../../src/components/currency/index.tsx"],"sourcesContent":["'use client';\n\nimport * as stylex from '@stylexjs/stylex';\nimport { isRTL } from 'react-aria';\nimport { useLocale } from 'react-aria-components';\nimport type { RenderComponentProps } from '../../render/useRender';\nimport { useRender } from '../../render/useRender';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, typography } from '../../tokens/design.tokens.stylex';\n\n// Deliberately sets no font-size. The same value turns up at several sizes —\n// inside a list row, as a heading-scale figure, as a total — differing in\n// nothing else, so the size is the container's decision. What must never vary\n// is here: the mono face, tabular figures so a column of them lines up on the\n// decimal point, and the medium weight that separates a figure from the prose\n// around it.\n//\n// No Material Design page draws an amount. It is type alone: the mono face\n// and medium weight from the type tokens, tabular figures, and the on surface\n// role for the neutral tone. Positive and negative are the library's own\n// roles, the pair Tag draws from as well.\n//\n// The amount is isolated from the text around it, so its characters are\n// ordered by its own direction, the one the element's `dir` names, rather\n// than by the paragraph's. Without that a minus sign or a currency code at\n// the edge of an amount is ordered with its neighbours: an American amount on\n// a right-to-left page draws its minus on the far side of the digits, and an\n// Arabic amount on a left-to-right page puts its code at the wrong end. A\n// browser's own stylesheet isolates an element carrying `dir` too; the rule\n// here keeps the amount isolated where a page's styles override that one.\n//\n// `dir` also turns `start` around, which an amount laid out as a block — a\n// flex item stretched across a column — would otherwise align to: an Arabic\n// amount in a left-to-right column would sit at its right edge.\n// `match-parent` reads `start` against the direction around the amount, so it\n// lines up with its neighbours, and inherits any alignment the container set.\n// Chromium knows it only as `-webkit-match-parent`, which is the fallback.\nconst styles = {\n base: {\n kB7OPa: \"x9f619\",\n kMv6JI: \"xbppi91\",\n kcqcaj: \"xss6m8b\",\n k63SB2: \"x1bf3uqz\",\n k9WMMc: \"x16pb8o4\",\n kQ0QjF: \"x4ej8vm\",\n $$css: true\n }\n};\nconst tones = {\n inherit: {\n kMwMTN: \"x1heor9g\",\n $$css: true\n },\n negative: {\n kMwMTN: \"x12gp0t\",\n $$css: true\n },\n neutral: {\n kMwMTN: \"x139gbkf\",\n $$css: true\n },\n positive: {\n kMwMTN: \"x4no3js\",\n $$css: true\n }\n};\n\n// U+2212 MINUS SIGN, not the U+002D hyphen Intl actually emits. The hyphen is\n// a typographic stand-in: it is narrower than the plus it pairs with and does\n// not sit on the same optical centre as the digits, which shows up as a\n// wobbling column the moment amounts are stacked.\nconst MINUS_SIGN = '−';\ntype CurrencyDisplay = 'code' | 'name' | 'narrowSymbol' | 'symbol';\ntype CurrencyNotation = 'compact' | 'standard';\ntype CurrencyProps = Omit<RenderComponentProps<'span'>, 'children'> & {\n /**\n * ISO 4217 code for the currency to format in.\n * @default 'USD'\n */\n currency?: string;\n /**\n * How the currency is written. `symbol` is the locale's own symbol, which\n * tells one dollar from another where the locale has to — `CA$` in\n * American English. `narrowSymbol` is the bare sign, `$` for every dollar,\n * for a page that states its currency some other way. `code` writes the\n * ISO code and `name` the currency's name in the locale's words, `US\n * dollars`.\n *\n * A right-to-left locale draws a symbol made of letters and a sign with\n * its sign first, so Arabic's `US$` reads `$US`. `code` writes `USD`,\n * which has no sign to turn around.\n * @default 'symbol'\n */\n currencyDisplay?: CurrencyDisplay;\n /**\n * BCP 47 locale to format for. Defaults to React Aria's `I18nProvider`, as\n * the date and number fields do, so an amount reads in the locale the app\n * set — and to the browser's language where there is no provider.\n *\n * The element carries the locale it was formatted in as its `lang`, and\n * that locale's writing direction as its `dir`, so a screen reader can\n * read it by the locale's own number rules and its characters fall in the\n * locale's own order. A `lang` or `dir` the call site passes wins.\n */\n locale?: string;\n /**\n * The most digits shown after the decimal point; the amount is rounded to\n * them. Left out, the currency's own minor units decide — two for a\n * dollar, none for a yen — and under `compact` notation the figure keeps\n * two significant digits instead.\n *\n * A count the runtime rejects — negative, over a hundred, or below\n * `minimumFractionDigits` — is dropped rather than thrown, and both counts\n * fall back to the currency's own.\n */\n maximumFractionDigits?: number;\n /**\n * The fewest digits shown after the decimal point, padded with zeros.\n * Left out, the currency's own minor units decide.\n */\n minimumFractionDigits?: number;\n /**\n * `compact` shortens a large amount to a few digits and the locale's word\n * or letter for the scale — `$12B`, `12 Mrd. €` — for a figure that would\n * otherwise run past a narrow container. An amount has no break\n * opportunity, so a long one in hero type overflows rather than wrapping.\n * The amount is rounded, so it suits a figure read at a glance rather\n * than one read to the cent.\n * @default 'standard'\n */\n notation?: CurrencyNotation;\n /**\n * When to show the sign. `auto` shows one only on negatives, `always` also\n * prefixes positives with a plus, and `never` shows neither — which leaves\n * colour as the only thing separating a credit from a debt, so reach for it\n * only where the direction is already stated some other way.\n *\n * An amount that shows as zero carries no sign under any of them, since\n * there is nothing owed or owing: `-0.004` reads `$0.00`, not `−$0.00`.\n * @default 'auto'\n */\n sign?: CurrencySignDisplay;\n /**\n * Which colour role to render in. `auto` follows the sign of the amount as\n * it is shown, so one that rounds to zero — float residue, or a fraction\n * of a cent — reads as neutral rather than as a gain or a debt.\n *\n * The positive and negative roles are guaranteed legible on the surface\n * family, which is what the library's own tokens and every demo scheme are\n * held to. Over a container of its own — a selected row, a filled card —\n * the pair is one colour family on top of another and is not guaranteed.\n * `inherit` is the way out there, as it is on Text: it sets no colour, so\n * the amount takes the container's own on-colour, and its sign is what\n * says which way it goes.\n * @default 'auto'\n */\n tone?: CurrencyTone;\n /** The amount, in the currency's major units. */\n value: number;\n};\ntype CurrencySignDisplay = 'always' | 'auto' | 'never';\ntype CurrencyTone = 'auto' | 'inherit' | 'negative' | 'neutral' | 'positive';\n\n/**\n * An amount of money, formatted for the reader's locale in the mono face with\n * tabular figures, so a column of amounts lines up on the decimal point.\n * Negatives carry a true minus sign, and the tone follows the sign unless\n * `tone` names one.\n *\n * ```tsx\n * <Currency value={-42.5} currency=\"EUR\" />\n * ```\n *\n * It sets no size of its own, so it takes the type around it: wrap it in a\n * Text, or put it in a heading, for a larger figure. A figure that has to\n * fit a narrow space takes `notation=\"compact\"`, which shortens it to a few\n * digits and a scale.\n *\n * Over a container of its own, such as a selected row, give it\n * `tone=\"inherit\"` so it reads in the container's own on-colour.\n *\n * It carries the `lang` and `dir` of the locale it was formatted in, and is\n * isolated from the text around it, so an amount in a locale other than the\n * page's reads in its own order.\n *\n * The call site's `className` and `style` land on the element holding the\n * amount.\n */\nfunction Currency({\n currency = 'USD',\n currencyDisplay,\n locale,\n maximumFractionDigits,\n minimumFractionDigits,\n notation,\n render,\n sign = 'auto',\n tone = 'auto',\n value,\n ...props\n}: CurrencyProps) {\n // React Aria holds the locale steady through hydration, where the runtime's\n // default differs between the server and the browser and the amount's text\n // would not match.\n const provided = useLocale().locale;\n const amount = formatCurrency(value, {\n currency,\n currencyDisplay,\n fallbackLocale: provided,\n // An empty string asks for the provider's locale as `undefined` does —\n // it is what clearing a locale control leaves — rather than reaching the\n // formatter as a tag it rejects.\n locale: locale === undefined || locale === '' ? provided : locale,\n maximumFractionDigits,\n minimumFractionDigits,\n notation,\n sign\n });\n return useRender({\n defaultTagName: 'span',\n props: {\n ...props,\n children: amount.text,\n // The call site's own win, for a page that states them some other way.\n dir: props.dir ?? amount.direction,\n lang: props.lang ?? amount.locale,\n ...mergeStyles(stylex.props(styles.base, tones[resolveTone(value, tone, amount.shownAsZero)]), props)\n },\n render\n });\n}\n\n// Constructing an Intl.NumberFormat resolves the locale, loads the currency's\n// data and builds the pattern; formatToParts on one already built skips all\n// three, which measures about ten times cheaper per value. An instance is\n// stateless once constructed, so one can be shared across every component\n// asking for the same options.\n//\n// What this saves is a mount and a changing amount, not an idle re-render:\n// the React Compiler already memoises the call below on the props it reads,\n// so a component re-rendered with the same props never reaches here.\n// That cache belongs to one component instance and counts `value` among its\n// keys, which leaves two cases it cannot cover — a column of rows, where every\n// instance has a cache of its own and each builds a formatter, and a single\n// amount that ticks, where each new value misses.\n//\n// Unbounded on purpose. The keys are the option sets an app actually uses —\n// its locales times its currencies times the handful of sign, display,\n// notation and digit settings it writes — so the map settles at that size\n// rather than growing with renders or with values.\n//\n// A request the runtime rejects is cached too, under its own key, as the\n// fallback it resolved to, so a bad pair costs one failed construction rather\n// than one a render.\nconst formatters = new Map<string, ResolvedFormat>();\n\n/**\n * A formatter, the locale it formats in and that locale's direction, and the\n * currency code written beside its number when it is the decimal fallback for\n * a code the runtime rejected.\n */\ntype ResolvedFormat = {\n code: null | string;\n direction: 'ltr' | 'rtl';\n formatter: Intl.NumberFormat;\n locale: string;\n};\n\n// formatToParts rather than a replace over the formatted string, so only the\n// sign is substituted. A blind replace would also hit a hyphen inside a\n// locale's currency name or grouping, and locales that wrap negatives in\n// parentheses have no minus to swap at all — both of which this leaves alone.\nfunction formatCurrency(value: number, options: FormatOptions) {\n const {\n code,\n direction,\n formatter,\n locale\n } = formatterFor(options);\n const parts = formatter.formatToParts(value);\n const amount = parts.map(part => part.type === 'minusSign' ? MINUS_SIGN : part.value).join('');\n return {\n direction,\n locale,\n // Whether every digit shown is a zero, which is what the tone reads\n // rather than the raw value: the two never saw the same number before.\n shownAsZero: parts.every(part => part.type !== 'integer' && part.type !== 'fraction' || /^0+$/.test(part.value)),\n text: code === null || code === '' ? amount : `${amount} ${code}`\n };\n}\n\n// The sign each setting asks Intl for. `negative` and `exceptZero` are the\n// forms that leave a zero bare, which `auto` and `always` do not: they put a\n// minus on any negative input, and a plus on any positive one, including\n// those that round to a displayed zero.\nconst SIGN_DISPLAY = {\n always: 'exceptZero',\n auto: 'negative',\n never: 'never'\n} as const satisfies Record<CurrencySignDisplay, Intl.NumberFormatOptions['signDisplay']>;\n\n// What the plain number shows when a currency code is rejected and the call\n// site gave no digit count: two places, as most currencies have.\nconst TWO_PLACES = {\n maximumFractionDigits: 2,\n minimumFractionDigits: 2\n};\n\n/** Everything that decides how an amount is written, value aside. */\ntype FormatOptions = {\n currency: string;\n currencyDisplay: CurrencyDisplay | undefined;\n fallbackLocale: string;\n locale: string;\n maximumFractionDigits: number | undefined;\n minimumFractionDigits: number | undefined;\n notation: CurrencyNotation | undefined;\n sign: CurrencySignDisplay;\n};\n\n// A component that only shows an amount must not take the page down with it,\n// and `new Intl.NumberFormat` throws for a malformed locale (`xx_YY`, an\n// empty string), for a currency code that is not one (`xx`, `US`), and for a\n// digit count out of range or below the minimum it is paired with. Valid\n// input takes exactly the path it always did; the rest falls back. A locale\n// the runtime rejects gives way to the provider's. A currency code it rejects\n// gives way to the plain number in that locale with the code written after\n// it, so the amount still reads and what was wrong with it stays visible.\n// Digit counts it rejects are dropped for the currency's own.\n//\n// Nothing is logged: the library writes to the console nowhere, and a\n// warning an app cannot turn off is not its to emit.\nfunction formatterFor(options: FormatOptions) {\n const {\n currency,\n currencyDisplay,\n fallbackLocale,\n locale,\n maximumFractionDigits,\n minimumFractionDigits,\n notation,\n sign\n } = options;\n // JSON over an array cannot make two option sets share a key, whatever a\n // rejected locale or code holds. The digit counts go in as strings, since\n // JSON writes a count left out and one that is not a number alike.\n const key = JSON.stringify([locale, currency, sign, fallbackLocale, currencyDisplay, notation, String(minimumFractionDigits), String(maximumFractionDigits)]);\n const cached = formatters.get(key);\n if (cached) {\n return cached;\n }\n\n // The locale the amount is written in, which is the one its `lang` and\n // `dir` name. React Aria's `isRTL` is what the provider's own direction\n // comes from, so an amount in the provider's locale and the page around it\n // agree.\n const usable = [locale, fallbackLocale].find(isLocale) ?? 'en-US';\n const written = {\n direction: isRTL(usable) ? 'rtl' : 'ltr',\n locale: usable\n } as const;\n const signDisplay = SIGN_DISPLAY[sign];\n const digits = {\n maximumFractionDigits,\n minimumFractionDigits\n };\n const asCurrency = {\n currency,\n currencyDisplay,\n notation,\n signDisplay,\n style: 'currency'\n } as const;\n // In order: everything asked for; the plain number with the code after it,\n // for a code the runtime rejects; and each again without the digit counts,\n // for counts it rejects. The plain number shows two places unless the\n // call site gave a count, as a currency with minor units does.\n const attempts: [null | string, Intl.NumberFormatOptions][] = [[null, {\n ...asCurrency,\n ...digits\n }], [currency, {\n notation,\n signDisplay,\n ...(maximumFractionDigits === undefined && minimumFractionDigits === undefined ? TWO_PLACES : digits)\n }], [null, asCurrency]];\n for (const [code, attempt] of attempts) {\n const formatter = formatterOrNull(usable, attempt);\n if (formatter !== null) {\n const resolved = {\n code,\n formatter,\n ...written\n };\n formatters.set(key, resolved);\n return resolved;\n }\n }\n const resolved = {\n code: currency,\n formatter: new Intl.NumberFormat(usable, {\n ...TWO_PLACES,\n signDisplay\n }),\n ...written\n };\n formatters.set(key, resolved);\n return resolved;\n}\n\n// A formatter for the options, or null where the runtime rejects them.\nfunction formatterOrNull(locale: string, options: Intl.NumberFormatOptions) {\n try {\n return new Intl.NumberFormat(locale, options);\n } catch {\n return null;\n }\n}\n\n// Whether the runtime takes `tag` as a locale at all — one it parses, whether\n// or not it has data for it, which it then fills in from the nearest it has.\nfunction isLocale(tag: string) {\n try {\n return Intl.NumberFormat.supportedLocalesOf(tag).length >= 0;\n } catch {\n return false;\n }\n}\n\n// An amount shown as zero is neither owed nor owing, so it takes the neutral\n// role whatever the raw value's sign: negative zero, a rounded-away debt and\n// float residue all read as settled. Otherwise the sign decides.\nfunction resolveTone(value: number, tone: CurrencyTone, shownAsZero: boolean) {\n if (tone !== 'auto') {\n return tone;\n }\n if (shownAsZero) {\n return 'neutral';\n }\n if (value > 0) {\n return 'positive';\n }\n if (value < 0) {\n return 'negative';\n }\n return 'neutral';\n}\nexport type { CurrencyDisplay, CurrencyNotation, CurrencyProps, CurrencySignDisplay, CurrencyTone };\nexport default Currency;"],"mappings":"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"}
|
|
@@ -3,8 +3,8 @@ import { props } from "../../node_modules/@stylexjs/stylex/lib/es/stylex.js";
|
|
|
3
3
|
import { mergeStatefulStyles } from "../../styles/merge.js";
|
|
4
4
|
import { CalendarGlyph } from "../../glyphs/index.js";
|
|
5
5
|
import { focus } from "../../styles/focus.js";
|
|
6
|
-
import Calendar$1 from "../calendar/index.js";
|
|
7
6
|
import { fieldStyles, invalidFrom, useFieldValidationBehavior } from "../../field/root.js";
|
|
7
|
+
import Calendar$1 from "../calendar/index.js";
|
|
8
8
|
import { FieldBox, FieldMessage, FieldSegments } from "../../field/index.js";
|
|
9
9
|
import { overlay } from "../../styles/overlay.js";
|
|
10
10
|
import { datePopoverClassName, picker, triggerClassName } from "../../styles/picker.js";
|
|
@@ -43,7 +43,9 @@ declare function DialogBody({ ref: callerRef, ...props }: HTMLAttributes<HTMLDiv
|
|
|
43
43
|
* something that has to be answered before the page carries on — a screen
|
|
44
44
|
* reader announces it rather than waiting to be asked. It also keeps the
|
|
45
45
|
* dialog the basic one below the medium breakpoint, centred and sized to its
|
|
46
|
-
* content, where a dialog of the default role fills the window.
|
|
46
|
+
* content, where a dialog of the default role fills the window. Its
|
|
47
|
+
* `Dialog.Body` is its description, so the message is read out with the
|
|
48
|
+
* title; an `aria-describedby` of the call site's own names another.
|
|
47
49
|
*
|
|
48
50
|
* A press on the scrim closes the dialog unless `isDismissable` says
|
|
49
51
|
* otherwise, which is the same default `Sheet` takes and the reverse of
|