@djangocfg/ui-core 2.1.542 → 2.1.544
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.544",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
|
-
"@djangocfg/i18n": "^2.1.
|
|
133
|
+
"@djangocfg/i18n": "^2.1.544",
|
|
134
134
|
"consola": "^3.4.2",
|
|
135
135
|
"lucide-react": "^0.545.0",
|
|
136
136
|
"moment": "^2.30.1",
|
|
@@ -206,9 +206,9 @@
|
|
|
206
206
|
"vaul": "1.1.2"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@djangocfg/eslint-config": "^2.1.
|
|
210
|
-
"@djangocfg/i18n": "^2.1.
|
|
211
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
209
|
+
"@djangocfg/eslint-config": "^2.1.544",
|
|
210
|
+
"@djangocfg/i18n": "^2.1.544",
|
|
211
|
+
"@djangocfg/typescript-config": "^2.1.544",
|
|
212
212
|
"@storybook/react-vite": "^10.5.0",
|
|
213
213
|
"@types/node": "^24.13.3",
|
|
214
214
|
"@types/react": "19.2.15",
|
|
@@ -59,11 +59,34 @@ the dependency direction and may not depend on it.
|
|
|
59
59
|
| `defaultValue` | `number` | Uncontrolled initial amount, minor units. |
|
|
60
60
|
| `onValueChange` | `(minorUnits: number) => void` | |
|
|
61
61
|
| `currency` | `string` | ISO 4217. Drives symbol, decimals, minor-unit factor. Default `USD`. |
|
|
62
|
+
| `fractionDigits` | `number` | Overrides the currency's decimals. `0` makes the field whole-unit — see below. |
|
|
62
63
|
| `locale` | `string` | BCP 47. Defaults to the runtime's. |
|
|
63
64
|
| `showSymbol` | `boolean` | Default `true`. Set false when the label carries the unit. |
|
|
64
65
|
| `max` | `number` | Minor units. Entry beyond it is ignored, not clamped after the fact. |
|
|
65
66
|
| `groupClassName` | `string` | Applied to the group, to size the whole control. |
|
|
66
67
|
|
|
68
|
+
## Whole amounts
|
|
69
|
+
|
|
70
|
+
The default suits money that is paid — a price, an invoice, a charge. It does
|
|
71
|
+
not suit money that is merely large. On a property asking price the cents are
|
|
72
|
+
two zeros the reader looks past and, because entry is right-anchored, two
|
|
73
|
+
keystrokes they have to type: 1,400,000 costs nine digits, not seven.
|
|
74
|
+
|
|
75
|
+
`fractionDigits={0}` makes the field whole-unit:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
const [asking, setAsking] = useState(1_400_000); // dollars, not cents
|
|
79
|
+
|
|
80
|
+
<MoneyField currency="USD" fractionDigits={0} value={asking} onValueChange={setAsking} />
|
|
81
|
+
// shows "1,400,000"; typing 1400000 reads as 1,400,000
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**The unit of `value` follows this prop.** Minor units at the default, major
|
|
85
|
+
units at `0` — the component divides by `10 ** fractionDigits`, so a caller
|
|
86
|
+
that changes the prop must change what it stores. This is deliberate: an
|
|
87
|
+
integer at any precision keeps the exactness the component exists for, and a
|
|
88
|
+
field that reported cents while displaying whole dollars would be a trap.
|
|
89
|
+
|
|
67
90
|
## Accessibility
|
|
68
91
|
|
|
69
92
|
The currency symbol is `aria-hidden` — a screen reader announcing "dollar sign"
|
|
@@ -59,14 +59,22 @@ function currencySymbol(currency: string, locale: string | undefined): string {
|
|
|
59
59
|
// Format / parse
|
|
60
60
|
// =============================================================================
|
|
61
61
|
|
|
62
|
+
/** Minor units per major unit for a given precision — 100 at two digits, 1 at none. */
|
|
63
|
+
function factorFor(digits: number): number {
|
|
64
|
+
return 10 ** digits;
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
/** Format minor units for display, grouped and with the locale's separators. */
|
|
63
|
-
function formatMinor(
|
|
64
|
-
|
|
68
|
+
function formatMinor(
|
|
69
|
+
minor: number,
|
|
70
|
+
digits: number,
|
|
71
|
+
locale: string | undefined,
|
|
72
|
+
): string {
|
|
65
73
|
return new Intl.NumberFormat(locale, {
|
|
66
74
|
minimumFractionDigits: digits,
|
|
67
75
|
maximumFractionDigits: digits,
|
|
68
76
|
useGrouping: true,
|
|
69
|
-
}).format(minor /
|
|
77
|
+
}).format(minor / factorFor(digits));
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
/**
|
|
@@ -77,7 +85,7 @@ function formatMinor(minor: number, currency: string, locale: string | undefined
|
|
|
77
85
|
* entry people expect from a money field, and the reason the caret never has
|
|
78
86
|
* to sit "before the decimal point".
|
|
79
87
|
*/
|
|
80
|
-
function parseToMinor(raw: string
|
|
88
|
+
function parseToMinor(raw: string): number {
|
|
81
89
|
const digits = raw.replace(/\D/g, '');
|
|
82
90
|
if (!digits) return 0;
|
|
83
91
|
// Number, not parseInt: 16 digits of cents is past MAX_SAFE_INTEGER, and
|
|
@@ -133,6 +141,21 @@ export interface MoneyFieldProps
|
|
|
133
141
|
onValueChange?: (minorUnits: number) => void;
|
|
134
142
|
/** ISO 4217 code — drives the symbol, the decimals and the minor-unit factor. */
|
|
135
143
|
currency?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Digits after the decimal separator, overriding the currency's own.
|
|
146
|
+
*
|
|
147
|
+
* The default is right for money that is paid: a price, an invoice, a
|
|
148
|
+
* charge. It is wrong for money that is merely large — a property asking
|
|
149
|
+
* price, a valuation, a budget — where the cents are always two zeros the
|
|
150
|
+
* reader has to look past and two keystrokes they have to type, since entry
|
|
151
|
+
* is right-anchored.
|
|
152
|
+
*
|
|
153
|
+
* `0` makes the field whole-unit: `value` is then dollars rather than cents,
|
|
154
|
+
* and typing "1400000" reads as 1,400,000. The unit of `value` always
|
|
155
|
+
* follows this — minor units at 2, major units at 0 — so a caller that
|
|
156
|
+
* changes it must change what it stores.
|
|
157
|
+
*/
|
|
158
|
+
fractionDigits?: number;
|
|
136
159
|
/** BCP 47 tag for separators and symbol placement. Defaults to the runtime's. */
|
|
137
160
|
locale?: string;
|
|
138
161
|
/** Show the currency symbol before the input. Set false when the label carries it. */
|
|
@@ -177,6 +200,7 @@ const MoneyField = React.forwardRef<HTMLInputElement, MoneyFieldProps>(
|
|
|
177
200
|
defaultValue,
|
|
178
201
|
onValueChange,
|
|
179
202
|
currency = 'USD',
|
|
203
|
+
fractionDigits,
|
|
180
204
|
locale,
|
|
181
205
|
showSymbol = true,
|
|
182
206
|
max,
|
|
@@ -195,9 +219,11 @@ const MoneyField = React.forwardRef<HTMLInputElement, MoneyFieldProps>(
|
|
|
195
219
|
const [internal, setInternal] = React.useState<number>(defaultValue ?? 0);
|
|
196
220
|
const minor = isControlled ? value : internal;
|
|
197
221
|
|
|
222
|
+
const digits = fractionDigits ?? currencyFractionDigits(currency);
|
|
223
|
+
|
|
198
224
|
const display = React.useMemo(
|
|
199
|
-
() => formatMinor(minor,
|
|
200
|
-
[minor,
|
|
225
|
+
() => formatMinor(minor, digits, locale),
|
|
226
|
+
[minor, digits, locale],
|
|
201
227
|
);
|
|
202
228
|
const symbol = React.useMemo(
|
|
203
229
|
() => currencySymbol(currency, locale),
|
|
@@ -230,7 +256,7 @@ const MoneyField = React.forwardRef<HTMLInputElement, MoneyFieldProps>(
|
|
|
230
256
|
const typed = el.value;
|
|
231
257
|
const caret = el.selectionStart ?? typed.length;
|
|
232
258
|
|
|
233
|
-
let next = parseToMinor(typed
|
|
259
|
+
let next = parseToMinor(typed);
|
|
234
260
|
if (max !== undefined && next > max) next = max;
|
|
235
261
|
|
|
236
262
|
// Anchor on digits to the LEFT of the caret, which regrouping cannot
|
|
@@ -242,12 +268,12 @@ const MoneyField = React.forwardRef<HTMLInputElement, MoneyFieldProps>(
|
|
|
242
268
|
typed.length < display.length && digitsLeft === digitsBefore(display, caret + 1);
|
|
243
269
|
if (deletedSeparator && digitsLeft > 0) digitsLeft -= 1;
|
|
244
270
|
|
|
245
|
-
const nextDisplay = formatMinor(next,
|
|
271
|
+
const nextDisplay = formatMinor(next, digits, locale);
|
|
246
272
|
pendingCaretRef.current = offsetAfterDigits(nextDisplay, digitsLeft);
|
|
247
273
|
|
|
248
274
|
commit(next);
|
|
249
275
|
},
|
|
250
|
-
[commit,
|
|
276
|
+
[commit, digits, display, locale, max],
|
|
251
277
|
);
|
|
252
278
|
|
|
253
279
|
return (
|