@eifi1/ui-kit 0.55.0 → 0.57.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 +9 -6
- package/dist/billing/subscription-standing.d.ts +1 -1
- package/dist/billing/subscription-standing.js.map +1 -1
- package/dist/components/button.js +1 -0
- package/dist/components/button.js.map +1 -1
- package/dist/components/data-table.d.ts +16 -0
- package/dist/components/data-table.js +17 -0
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/icon-button.d.ts +3 -3
- package/dist/components/icon-button.js +1 -0
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/legended-series-chart.d.ts +5 -2
- package/dist/components/legended-series-chart.js +9 -2
- package/dist/components/legended-series-chart.js.map +1 -1
- package/dist/components/quantity-field.d.ts +61 -0
- package/dist/components/quantity-field.js +95 -0
- package/dist/components/quantity-field.js.map +1 -0
- package/dist/components/quantity-input.d.ts +139 -0
- package/dist/components/quantity-input.js +188 -0
- package/dist/components/quantity-input.js.map +1 -0
- package/dist/components/quantity-labels.d.ts +2 -0
- package/dist/components/quantity-labels.js +11 -0
- package/dist/components/quantity-labels.js.map +1 -0
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-inbox-bulk.d.ts +88 -0
- package/dist/feedback/feedback-inbox-bulk.js +316 -0
- package/dist/feedback/feedback-inbox-bulk.js.map +1 -0
- package/dist/feedback/feedback-inbox-page.d.ts +47 -3
- package/dist/feedback/feedback-inbox-page.js +19 -4
- package/dist/feedback/feedback-inbox-page.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +5 -1
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-table-labels.js +10 -1
- package/dist/feedback/feedback-table-labels.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +26 -0
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback.d.ts +1 -1
- package/dist/feedback.js.map +1 -1
- package/dist/i18n/defaults.js +3 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +20 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +4 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +26 -3
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +20 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +25 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +22 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +21 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +20 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +12 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/quantity.d.ts +107 -0
- package/dist/lib/quantity.js +166 -0
- package/dist/lib/quantity.js.map +1 -0
- package/dist/rhf/fields.d.ts +28 -0
- package/dist/rhf/fields.js +82 -0
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf.js.map +1 -1
- package/dist/theme/contrast.d.ts +1 -1
- package/dist/theme/palette-store.d.ts +1 -1
- package/dist/theme/text-size.d.ts +1 -1
- package/dist/theme/theme-store.d.ts +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { formatNumber } from "./format.js";
|
|
3
|
+
import { decimalMark, normalizeTypedMarks } from "./decimal-marks.js";
|
|
4
|
+
import { roundHalfAway } from "./round.js";
|
|
5
|
+
const QUANTITY_UNITS = ["g", "kg", "ml", "cl", "dl", "l", "pcs"];
|
|
6
|
+
const QUANTITY_DIGITS = {
|
|
7
|
+
g: 0,
|
|
8
|
+
kg: 3,
|
|
9
|
+
ml: 0,
|
|
10
|
+
cl: 1,
|
|
11
|
+
dl: 2,
|
|
12
|
+
l: 3,
|
|
13
|
+
pcs: 0
|
|
14
|
+
};
|
|
15
|
+
const MILLI = { g: 1, kg: 1e3, ml: 1, cl: 10, dl: 100, l: 1e3, pcs: 1e3 };
|
|
16
|
+
const FAMILY = {
|
|
17
|
+
g: "mass",
|
|
18
|
+
kg: "mass",
|
|
19
|
+
ml: "volume",
|
|
20
|
+
cl: "volume",
|
|
21
|
+
dl: "volume",
|
|
22
|
+
l: "volume",
|
|
23
|
+
pcs: "count"
|
|
24
|
+
};
|
|
25
|
+
const BASE = { mass: "kg", volume: "l", count: "pcs" };
|
|
26
|
+
function quantityFamily(unit) {
|
|
27
|
+
return FAMILY[unit];
|
|
28
|
+
}
|
|
29
|
+
function isUnit(unit) {
|
|
30
|
+
return typeof unit === "string" && Object.hasOwn(MILLI, unit);
|
|
31
|
+
}
|
|
32
|
+
function quantityMaxDigits(unit) {
|
|
33
|
+
return Math.round(Math.log10(MILLI[unit]));
|
|
34
|
+
}
|
|
35
|
+
function toMilli(amount, unit) {
|
|
36
|
+
if (amount === null || !Number.isFinite(amount)) return null;
|
|
37
|
+
return Math.round(roundHalfAway(amount, quantityMaxDigits(unit)) * MILLI[unit]) || 0;
|
|
38
|
+
}
|
|
39
|
+
function fromMilli(milli, unit) {
|
|
40
|
+
return milli / MILLI[unit] || 0;
|
|
41
|
+
}
|
|
42
|
+
function toBaseQuantity(q) {
|
|
43
|
+
if (!isUnit(q.unit)) return null;
|
|
44
|
+
const milli = toMilli(q.amount, q.unit);
|
|
45
|
+
if (milli === null) return null;
|
|
46
|
+
const unit = BASE[FAMILY[q.unit]];
|
|
47
|
+
return { amount: fromMilli(milli, unit), unit };
|
|
48
|
+
}
|
|
49
|
+
function convertQuantity(q, toUnit) {
|
|
50
|
+
if (!isUnit(q.unit) || !isUnit(toUnit) || FAMILY[q.unit] !== FAMILY[toUnit]) return null;
|
|
51
|
+
const milli = toMilli(q.amount, q.unit);
|
|
52
|
+
return { amount: milli === null ? null : fromMilli(milli, toUnit), unit: toUnit };
|
|
53
|
+
}
|
|
54
|
+
function sumQuantities(qs, unit) {
|
|
55
|
+
const target = unit ?? qs[0]?.unit;
|
|
56
|
+
if (target === void 0 || !isUnit(target)) return null;
|
|
57
|
+
const family = FAMILY[target];
|
|
58
|
+
let milli = qs.length === 0 ? 0 : null;
|
|
59
|
+
for (const q of qs) {
|
|
60
|
+
if (!isUnit(q.unit) || FAMILY[q.unit] !== family) return null;
|
|
61
|
+
const m = toMilli(q.amount, q.unit);
|
|
62
|
+
if (m !== null) milli = (milli ?? 0) + m;
|
|
63
|
+
}
|
|
64
|
+
return { amount: milli === null ? null : fromMilli(milli, target), unit: target };
|
|
65
|
+
}
|
|
66
|
+
function bestUnitFor(milli, family, unit) {
|
|
67
|
+
if (family === "count") return unit;
|
|
68
|
+
const large = Math.abs(milli) >= 1e3;
|
|
69
|
+
if (family === "mass") return large ? "kg" : "g";
|
|
70
|
+
return large ? "l" : "ml";
|
|
71
|
+
}
|
|
72
|
+
function unitSymbol(unit, words) {
|
|
73
|
+
return unit === "pcs" ? words?.pcs ?? "pcs" : unit;
|
|
74
|
+
}
|
|
75
|
+
function formatQuantity(q, locale, options = {}) {
|
|
76
|
+
let { amount, unit } = q;
|
|
77
|
+
if (options.bestUnit && isUnit(unit) && amount !== null && Number.isFinite(amount)) {
|
|
78
|
+
const milli = toMilli(amount, unit);
|
|
79
|
+
unit = bestUnitFor(milli, FAMILY[unit], unit);
|
|
80
|
+
amount = fromMilli(milli, unit);
|
|
81
|
+
}
|
|
82
|
+
const digits = isUnit(unit) ? quantityMaxDigits(unit) : 3;
|
|
83
|
+
return formatNumber(amount, { locale, digits: { min: 0, max: digits }, unit: unitSymbol(unit, options.labels) });
|
|
84
|
+
}
|
|
85
|
+
const UNIT_WORDS = {
|
|
86
|
+
g: "g",
|
|
87
|
+
kg: "kg",
|
|
88
|
+
ml: "ml",
|
|
89
|
+
cl: "cl",
|
|
90
|
+
dl: "dl",
|
|
91
|
+
l: "l",
|
|
92
|
+
// en
|
|
93
|
+
pcs: "pcs",
|
|
94
|
+
pc: "pcs",
|
|
95
|
+
piece: "pcs",
|
|
96
|
+
pieces: "pcs",
|
|
97
|
+
x: "pcs",
|
|
98
|
+
"\xD7": "pcs",
|
|
99
|
+
// de
|
|
100
|
+
stk: "pcs",
|
|
101
|
+
st\u00FCck: "pcs",
|
|
102
|
+
st: "pcs",
|
|
103
|
+
// fr
|
|
104
|
+
pce: "pcs",
|
|
105
|
+
pces: "pcs",
|
|
106
|
+
pi\u00E8ce: "pcs",
|
|
107
|
+
pi\u00E8ces: "pcs",
|
|
108
|
+
// it
|
|
109
|
+
pz: "pcs",
|
|
110
|
+
pezzo: "pcs",
|
|
111
|
+
pezzi: "pcs",
|
|
112
|
+
// es
|
|
113
|
+
ud: "pcs",
|
|
114
|
+
uds: "pcs",
|
|
115
|
+
unidad: "pcs",
|
|
116
|
+
unidades: "pcs",
|
|
117
|
+
// hu
|
|
118
|
+
db: "pcs",
|
|
119
|
+
darab: "pcs",
|
|
120
|
+
// zh
|
|
121
|
+
\u4E2A: "pcs",
|
|
122
|
+
\u4EF6: "pcs"
|
|
123
|
+
};
|
|
124
|
+
function unitKey(word) {
|
|
125
|
+
return word.trim().toLowerCase().replace(/\.$/, "");
|
|
126
|
+
}
|
|
127
|
+
const MULTIPACK = /\d\s*[x×*]\s*\d/i;
|
|
128
|
+
const QUANTITY_TEXT = /^(\d(?:[\d.,'’ ]*\d)?)\s*(.*)$/u;
|
|
129
|
+
const ONE_MARK = /^\d+(?:[.,]\d+)?$/;
|
|
130
|
+
const GROUPED = /^(\d{1,3}(?:([.,'’ ])\d{3})(?:\2\d{3})*)(?:([.,])(\d+))?$/;
|
|
131
|
+
function parseQuantity(text, locale, options = {}) {
|
|
132
|
+
const s = text.trim().replace(/\s+/g, " ");
|
|
133
|
+
if (s === "" || MULTIPACK.test(s)) return null;
|
|
134
|
+
const match = QUANTITY_TEXT.exec(s);
|
|
135
|
+
if (!match) return null;
|
|
136
|
+
const [, figure, word] = match;
|
|
137
|
+
const own = options.labels?.pcs;
|
|
138
|
+
const key = unitKey(word);
|
|
139
|
+
const unit = own !== void 0 && key !== "" && unitKey(own) === key ? "pcs" : UNIT_WORDS[key];
|
|
140
|
+
if (unit === void 0) return null;
|
|
141
|
+
let plain;
|
|
142
|
+
const grouped = GROUPED.exec(figure);
|
|
143
|
+
if (ONE_MARK.test(figure)) {
|
|
144
|
+
plain = normalizeTypedMarks(figure, decimalMark(locale)).replace(/,/g, ".");
|
|
145
|
+
} else if (grouped && grouped[2] !== grouped[3]) {
|
|
146
|
+
const [, whole, group, , fraction] = grouped;
|
|
147
|
+
plain = whole.split(group).join("") + (fraction === void 0 ? "" : `.${fraction}`);
|
|
148
|
+
} else {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const decimals = plain.includes(".") ? plain.split(".")[1].replace(/0+$/, "").length : 0;
|
|
152
|
+
if (decimals > quantityMaxDigits(unit)) return null;
|
|
153
|
+
return { amount: Number(plain), unit };
|
|
154
|
+
}
|
|
155
|
+
export {
|
|
156
|
+
QUANTITY_DIGITS,
|
|
157
|
+
QUANTITY_UNITS,
|
|
158
|
+
convertQuantity,
|
|
159
|
+
formatQuantity,
|
|
160
|
+
parseQuantity,
|
|
161
|
+
quantityFamily,
|
|
162
|
+
quantityMaxDigits,
|
|
163
|
+
sumQuantities,
|
|
164
|
+
toBaseQuantity
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=quantity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/quantity.ts"],"sourcesContent":["/**\n * Quantities — an amount and its unit (\"500 g\", \"1.5 l\", \"6 pcs\") — as plain data, with\n * the arithmetic, the formatting and the parsing an app needs around them: a shopping\n * list's totals, a comparison table's \"per kg\", a checklist's \"2 × 500 g\".\n *\n * Pure TypeScript, no React: {@link QuantityInput} and {@link QuantityField} are built on\n * it, and so is whatever an app prints outside them.\n *\n * WHY MILLI-UNITS. Every unit here is a whole number of thousandths of its family's base\n * — a gram is 1 milli-kilogram, a centilitre 10 millilitres, a piece 1000 milli-pieces —\n * so the arithmetic runs on integers and comes back exact: 0.1 kg + 0.2 kg is 0.3 kg, not\n * the 0.30000000000000004 kg a float sum gives. An amount is read onto that grid on the\n * way in (half away from zero, as `lib/round` rounds: a gram has no decimals, a litre\n * three), and an integer divided by a power of ten is the double the decimal literal\n * would be, so 300 / 1000 IS 0.3.\n *\n * Prices stay the app's: a quantity has no currency, and \"per kg\" is an app's division.\n */\nimport { formatNumber } from \"./format\";\nimport { decimalMark, normalizeTypedMarks } from \"./decimal-marks\";\nimport { roundHalfAway } from \"./round\";\n\n/** A unit a quantity is measured in: mass `g` `kg`, volume `ml` `cl` `dl` `l`, count `pcs`. */\nexport type QuantityUnit = \"g\" | \"kg\" | \"ml\" | \"cl\" | \"dl\" | \"l\" | \"pcs\";\n\n/** Which units convert into each other: mass (g, kg), volume (ml, cl, dl, l), count (pcs). */\nexport type QuantityFamily = \"mass\" | \"volume\" | \"count\";\n\n/** A family's base unit, what {@link toBaseQuantity} answers in. */\nexport type QuantityBaseUnit = \"kg\" | \"l\" | \"pcs\";\n\n/** An amount and its unit. `amount: null` is \"no amount yet\" — the unit is still chosen. */\nexport interface Quantity {\n amount: number | null;\n unit: QuantityUnit;\n}\n\n/** A quantity in its family's base unit, with an amount — {@link toBaseQuantity}'s answer. */\nexport interface BaseQuantity {\n amount: number;\n unit: QuantityBaseUnit;\n}\n\n/** Every unit, family by family, small to large — the order the unit select lists them in. */\nexport const QUANTITY_UNITS: readonly QuantityUnit[] = [\"g\", \"kg\", \"ml\", \"cl\", \"dl\", \"l\", \"pcs\"];\n\n/**\n * The decimals each unit is shown and entered with by default: g 0, kg 3, ml 0, cl 1,\n * dl 2, l 3, pcs 0. Each is a whole number of milli-units (a kilogram's third decimal\n * is a gram), so a value at these digits sums exactly. A count is whole by default; a\n * caller may allow up to three decimals (half a cabbage), never more.\n */\nexport const QUANTITY_DIGITS: Readonly<Record<QuantityUnit, number>> = {\n g: 0,\n kg: 3,\n ml: 0,\n cl: 1,\n dl: 2,\n l: 3,\n pcs: 0,\n};\n\n// Milli-units of the family's base in one unit. Every entry is a power of ten, and its\n// exponent is the most decimals the unit can carry and stay on the grid.\nconst MILLI: Readonly<Record<QuantityUnit, number>> = { g: 1, kg: 1000, ml: 1, cl: 10, dl: 100, l: 1000, pcs: 1000 };\n\nconst FAMILY: Readonly<Record<QuantityUnit, QuantityFamily>> = {\n g: \"mass\",\n kg: \"mass\",\n ml: \"volume\",\n cl: \"volume\",\n dl: \"volume\",\n l: \"volume\",\n pcs: \"count\",\n};\n\nconst BASE: Readonly<Record<QuantityFamily, QuantityBaseUnit>> = { mass: \"kg\", volume: \"l\", count: \"pcs\" };\n\n/** The family a unit belongs to: `\"mass\"`, `\"volume\"` or `\"count\"`. */\nexport function quantityFamily(unit: QuantityUnit): QuantityFamily {\n return FAMILY[unit];\n}\n\n/** Whether `unit` is one of the kit's units — for a value that arrives as JSON. */\nfunction isUnit(unit: unknown): unit is QuantityUnit {\n return typeof unit === \"string\" && Object.hasOwn(MILLI, unit);\n}\n\n/**\n * The most decimals `unit` can carry and stay on the milli grid: g 0, kg 3, ml 0, cl 1,\n * dl 2, l 3, pcs 3. A `digits` override is cut to this — a gram has no decimals to give.\n */\nexport function quantityMaxDigits(unit: QuantityUnit): number {\n return Math.round(Math.log10(MILLI[unit]));\n}\n\n/** `amount` in milli-units of its family's base, rounded onto the grid; `null` for no\n * (or no finite) amount. */\nfunction toMilli(amount: number | null, unit: QuantityUnit): number | null {\n if (amount === null || !Number.isFinite(amount)) return null;\n // Rounded to the unit's grid as WRITTEN first (1.005 kg is 1.00499… in binary, and\n // `1.005 * 1000` is 1004.9999999999999), then scaled: what is left of the binary error\n // is far below the half `Math.round` decides on.\n return Math.round(roundHalfAway(amount, quantityMaxDigits(unit)) * MILLI[unit]) || 0;\n}\n\n/** Milli-units back in `unit`: an integer over a power of ten, the double the decimal is. */\nfunction fromMilli(milli: number, unit: QuantityUnit): number {\n return milli / MILLI[unit] || 0;\n}\n\n/**\n * `q` in its family's base unit — kg, l or pcs — or `null` when it has no amount.\n * `{ amount: 500, unit: \"g\" }` → `{ amount: 0.5, unit: \"kg\" }`.\n */\nexport function toBaseQuantity(q: Quantity): BaseQuantity | null {\n if (!isUnit(q.unit)) return null;\n const milli = toMilli(q.amount, q.unit);\n if (milli === null) return null;\n const unit = BASE[FAMILY[q.unit]];\n return { amount: fromMilli(milli, unit), unit };\n}\n\n/**\n * `q` in `toUnit`, exactly — `{ amount: 500, unit: \"g\" }` in kg is 0.5 kg. Within a family\n * only: mass to volume, or pieces to kilograms, is `null` (a piece's weight is the app's\n * to know). A quantity with no amount converts to no amount in `toUnit`.\n */\nexport function convertQuantity(q: Quantity, toUnit: QuantityUnit): Quantity | null {\n if (!isUnit(q.unit) || !isUnit(toUnit) || FAMILY[q.unit] !== FAMILY[toUnit]) return null;\n const milli = toMilli(q.amount, q.unit);\n return { amount: milli === null ? null : fromMilli(milli, toUnit), unit: toUnit };\n}\n\n/**\n * The sum of `qs`, exactly, or `null` when they are not all of one family (grams and\n * pieces do not add up) — or `unit` is of another one.\n *\n * The result is in `unit` when one is given, else in the FIRST quantity's unit:\n * 500 g + 1 kg is 1500 g, 1 kg + 500 g is 1.5 kg; {@link formatQuantity}'s `bestUnit`\n * picks the readable one for display. A quantity with no amount adds nothing (a \"some\n * flour\" line in a list) but its unit still has to be of the family; if NONE has an\n * amount, the sum has none either. No quantities at all: `null`, or 0 in `unit` when\n * one is given.\n */\nexport function sumQuantities(qs: readonly Quantity[], unit?: QuantityUnit): Quantity | null {\n const target = unit ?? qs[0]?.unit;\n if (target === undefined || !isUnit(target)) return null;\n const family = FAMILY[target];\n let milli: number | null = qs.length === 0 ? 0 : null;\n for (const q of qs) {\n if (!isUnit(q.unit) || FAMILY[q.unit] !== family) return null;\n const m = toMilli(q.amount, q.unit);\n if (m !== null) milli = (milli ?? 0) + m;\n }\n return { amount: milli === null ? null : fromMilli(milli, target), unit: target };\n}\n\n/** The words {@link formatQuantity} and {@link parseQuantity} take — the `quantity` labels'\n * `pcs`, so a component's provider override reaches them. */\nexport interface QuantityUnitWords {\n /** The pieces unit as printed after a count (\"pcs\", \"Stk.\"). Default \"pcs\". */\n pcs?: string;\n}\n\nexport interface FormatQuantityOptions {\n /**\n * Show the amount in the unit that reads best: grams and millilitres from 1000 up\n * become kilograms and litres, and kilograms and litres below 1 become grams and\n * millilitres — 1500 g is \"1.5 kg\", 0.25 l is \"250 ml\". Centilitres and decilitres\n * are normalised the same way (75 cl is \"750 ml\", 15 dl is \"1.5 l\"): a list that\n * mixes them reads in two units, not four. Pieces stay pieces. Default `false`: the\n * quantity's own unit.\n */\n bestUnit?: boolean;\n /** The pieces word — pass the `quantity` labels (`useKitLabels(\"quantity\",\n * DEFAULT_QUANTITY_LABELS)`) to print the reader's language; default \"pcs\". */\n labels?: QuantityUnitWords;\n}\n\n/** The unit {@link FormatQuantityOptions.bestUnit} shows `milli` milli-units of `family` in. */\nfunction bestUnitFor(milli: number, family: QuantityFamily, unit: QuantityUnit): QuantityUnit {\n if (family === \"count\") return unit;\n const large = Math.abs(milli) >= 1000;\n if (family === \"mass\") return large ? \"kg\" : \"g\";\n return large ? \"l\" : \"ml\";\n}\n\n/** The symbol a unit is printed with — SI's in every language, but for the pieces word. */\nfunction unitSymbol(unit: QuantityUnit, words: QuantityUnitWords | undefined): string {\n return unit === \"pcs\" ? (words?.pcs ?? \"pcs\") : unit;\n}\n\n/**\n * `q` for a reader: \"1.5 kg\", \"500 g\", \"6 pcs\" — through {@link formatNumber}'s `unit`, so\n * the digits and their separators are `locale`'s (de-CH \"1.5 kg\", de-DE and fr \"1,5 kg\";\n * \"1’500 g\" with grouping) and the unit sits after a narrow no-break space. As many\n * decimals as the unit's grid holds (g and ml none, cl 1, dl 2, kg, l and pcs 3), trailing\n * zeros trimmed: 0.5 kg is \"0.5 kg\", never \"0.500 kg\". No amount prints `formatNumber`'s\n * empty value, \"—\", without a unit.\n *\n * g, kg, ml, cl, dl and l are SI symbols in every language; only pieces have a word, which\n * the kit does not translate on its own — pass `labels` (see {@link FormatQuantityOptions}).\n */\nexport function formatQuantity(q: Quantity, locale?: string, options: FormatQuantityOptions = {}): string {\n let { amount, unit } = q;\n if (options.bestUnit && isUnit(unit) && amount !== null && Number.isFinite(amount)) {\n const milli = toMilli(amount, unit) as number;\n unit = bestUnitFor(milli, FAMILY[unit], unit);\n amount = fromMilli(milli, unit);\n }\n // The unit's whole grid, not its entry digits: a count entered as 0.5 (a caller's\n // `digits`) prints \"0.5 pcs\", not a rounded \"1 pcs\".\n const digits = isUnit(unit) ? quantityMaxDigits(unit) : 3;\n return formatNumber(amount, { locale, digits: { min: 0, max: digits }, unit: unitSymbol(unit, options.labels) });\n}\n\n/* ── parsing ─────────────────────────────────────────────────────────────── */\n\n// The words a unit is written with, lower-case and without a trailing dot. The SI\n// symbols, and every pieces word the kit's catalogues print (`quantity.pcs`) with their\n// usual neighbours — a parser that read its own catalogue's word and not the plural on\n// the shelf label beside it would be a trap. `parse.test` holds every catalogue's word\n// to this list. \"x\" is \"6x\", a count written the way a receipt writes it.\nconst UNIT_WORDS: Readonly<Record<string, QuantityUnit>> = {\n g: \"g\",\n kg: \"kg\",\n ml: \"ml\",\n cl: \"cl\",\n dl: \"dl\",\n l: \"l\",\n // en\n pcs: \"pcs\",\n pc: \"pcs\",\n piece: \"pcs\",\n pieces: \"pcs\",\n x: \"pcs\",\n \"×\": \"pcs\",\n // de\n stk: \"pcs\",\n stück: \"pcs\",\n st: \"pcs\",\n // fr\n pce: \"pcs\",\n pces: \"pcs\",\n pièce: \"pcs\",\n pièces: \"pcs\",\n // it\n pz: \"pcs\",\n pezzo: \"pcs\",\n pezzi: \"pcs\",\n // es\n ud: \"pcs\",\n uds: \"pcs\",\n unidad: \"pcs\",\n unidades: \"pcs\",\n // hu\n db: \"pcs\",\n darab: \"pcs\",\n // zh\n 个: \"pcs\",\n 件: \"pcs\",\n};\n\n/** A unit word as the table above holds it: lower-case, no trailing dot. */\nfunction unitKey(word: string): string {\n return word.trim().toLowerCase().replace(/\\.$/, \"\");\n}\n\n// A multipack — \"6 x 1.5 l\", \"6x500g\", \"2 × 250 ml\": a count AND a quantity, which one\n// Quantity cannot hold. v1 refuses it rather than return either half as the whole.\nconst MULTIPACK = /\\d\\s*[x×*]\\s*\\d/i;\n\n// A figure, then the rest. The figure starts and ends with a digit and may hold the\n// marks and grouping spaces between (\"1.234,5\", \"1 500\", \"1’500\"); what follows it is\n// the unit, with or without a space (\"500g\", \"500 g\").\nconst QUANTITY_TEXT = /^(\\d(?:[\\d.,'’ ]*\\d)?)\\s*(.*)$/u;\n\n// A figure with one mark at most: which one is the decimal is the locale's question,\n// answered as the fields answer it (`normalizeTypedMarks`).\nconst ONE_MARK = /^\\d+(?:[.,]\\d+)?$/;\n// A figure grouped in thousands — one grouping character throughout, then perhaps a\n// decimal mark that is not it: \"1.234,5\", \"1,234.5\", \"1 500\", \"1’500.5\". Grouping in\n// anything but threes is no figure: \"1.2.3 kg\" is refused, where the fields' lenient\n// reading of typing would make it 123.\nconst GROUPED = /^(\\d{1,3}(?:([.,'’ ])\\d{3})(?:\\2\\d{3})*)(?:([.,])(\\d+))?$/;\n\n/**\n * The quantity `text` says — \"500g\", \"500 g\", \"1,5 kg\", \"1.5l\", \"2.5 dl\", \"75 cl\",\n * \"6 Stk\", \"6 pcs\", \"6x\" — or `null` when it says none.\n *\n * - The figure is read in `locale`'s decimal mark the way the kit's number fields read\n * typing (`lib/decimal-marks`): de-DE \"1,5 kg\" and de-CH \"1.5 kg\" are both 1.5 kg; a\n * de-DE \"1.500 g\" is grouping, so 1500 g. A lone comma in a point locale stays the\n * decimal, as Swiss typists write \"1,5\" (so an English \"1,500 g\" is 1.5 g — and\n * refused, below). Grouping is read in threes only (\"1.234,5\", \"1’500\", \"1 500\").\n * - The unit is an SI symbol in any case (\"1.5L\"), or a pieces word from any of the\n * kit's catalogues — \"pcs\", \"Stk.\", \"pce\", \"pz.\", \"ud.\", \"db\", \"个\" — or \"x\"; the\n * words in `options.labels` too, so what {@link formatQuantity} prints reads back.\n * - `null` for no figure, no unit (\"500\"), an unknown unit, a multipack (\"6 x 1.5 l\",\n * \"6x500g\" — v1), a sign, and an amount finer than the unit's grid (\"0.5 g\",\n * \"1.0005 kg\"): a misreading is likelier than half a gram, and the exact arithmetic\n * has no place for it.\n */\nexport function parseQuantity(\n text: string,\n locale?: string,\n options: { labels?: QuantityUnitWords } = {},\n): Quantity | null {\n // `\\s` is Unicode's: the narrow no-break space formatQuantity writes, and fr's\n // no-break grouping space, are spaces here.\n const s = text.trim().replace(/\\s+/g, \" \");\n if (s === \"\" || MULTIPACK.test(s)) return null;\n const match = QUANTITY_TEXT.exec(s);\n if (!match) return null;\n const [, figure, word] = match as unknown as [string, string, string];\n const own = options.labels?.pcs;\n const key = unitKey(word);\n const unit = own !== undefined && key !== \"\" && unitKey(own) === key ? \"pcs\" : UNIT_WORDS[key];\n if (unit === undefined) return null;\n\n let plain: string;\n const grouped = GROUPED.exec(figure);\n if (ONE_MARK.test(figure)) {\n plain = normalizeTypedMarks(figure, decimalMark(locale)).replace(/,/g, \".\");\n } else if (grouped && grouped[2] !== grouped[3]) {\n const [, whole, group, , fraction] = grouped as unknown as [string, string, string, string?, string?];\n plain = whole.split(group).join(\"\") + (fraction === undefined ? \"\" : `.${fraction}`);\n } else {\n return null;\n }\n // Decimals as written, trailing zeros aside (\"1.500 kg\" is 1.5): more than the unit's\n // grid holds is refused, not rounded.\n const decimals = plain.includes(\".\") ? plain.split(\".\")[1]!.replace(/0+$/, \"\").length : 0;\n if (decimals > quantityMaxDigits(unit)) return null;\n return { amount: Number(plain), unit };\n}\n"],"mappings":"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","names":[]}
|
package/dist/rhf/fields.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* <RhfToggleGroup name="interval" label="Interval" options={INTERVALS} />
|
|
17
17
|
* <RhfCountrySelect name="country" label="Country" preferred={["CH", "DE"]} />
|
|
18
18
|
* <RhfMonthPicker name="fiscalYear" label="Fiscal year" mode="year" valueAsNumber />
|
|
19
|
+
* <RhfQuantityField name="quantity" label="Quantity" family="mass" />
|
|
19
20
|
* </Form>
|
|
20
21
|
* ```
|
|
21
22
|
*
|
|
@@ -54,6 +55,8 @@ import { type InputProps } from "../components/input.js";
|
|
|
54
55
|
import { type SelectProps } from "../components/select.js";
|
|
55
56
|
import { type TextareaProps } from "../components/textarea.js";
|
|
56
57
|
import { type NumberFieldProps } from "../components/number-field.js";
|
|
58
|
+
import { type QuantityFieldProps } from "../components/quantity-field.js";
|
|
59
|
+
import { type QuantityUnit } from "../lib/quantity.js";
|
|
57
60
|
import type { MoneyFieldProps } from "../components/money-field.js";
|
|
58
61
|
import { type DatePickerProps, type DateRangePickerProps } from "../components/date-picker.js";
|
|
59
62
|
import { type TimeInputProps } from "../components/time-input.js";
|
|
@@ -202,6 +205,31 @@ export type RhfIntegerFieldProps<TFieldValues extends FieldValues = FieldValues,
|
|
|
202
205
|
* nullable column). Everything else is {@link RhfNumberField}'s.
|
|
203
206
|
*/
|
|
204
207
|
export declare function RhfIntegerField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, TTransformed = TFieldValues>(props: RhfIntegerFieldProps<TFieldValues, TName, TTransformed>): import("react").JSX.Element;
|
|
208
|
+
export type RhfQuantityFieldProps<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, TTransformed = TFieldValues> = RhfFieldBaseProps<TFieldValues, TName, TTransformed> & Omit<QuantityFieldProps, BoundByForm | "help" | "nullable" | "id"> & {
|
|
209
|
+
/** The unit a value with no unit starts in (a form whose default is `null`). Default
|
|
210
|
+
* the first unit the select offers. */
|
|
211
|
+
defaultUnit?: QuantityUnit;
|
|
212
|
+
/** `false`: an emptied amount snaps back instead of storing `{ amount: null, unit }`.
|
|
213
|
+
* Default `true`, as {@link RhfNumberField}'s — whether it may be empty is `rules`'. */
|
|
214
|
+
nullable?: boolean;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* The kit's {@link QuantityField} bound to one field, which holds a `Quantity` —
|
|
218
|
+
* `{ amount, unit }`, the object itself. A typed amount stores on blur or Enter, a picked
|
|
219
|
+
* unit at once, and either marks the field touched. A stored value that is not a quantity
|
|
220
|
+
* (`null`, a default not filled in yet) shows as no amount in `defaultUnit`.
|
|
221
|
+
*
|
|
222
|
+
* Bound as {@link RhfNumberField} is: `field.ref` is the amount `<input>`, so focus on
|
|
223
|
+
* error lands there; the form's error paints both controls and is the shell's message;
|
|
224
|
+
* the hint is the shell's description. The `suggestion` line stays the field's own,
|
|
225
|
+
* between the control and the hint.
|
|
226
|
+
*
|
|
227
|
+
* ```tsx
|
|
228
|
+
* <RhfQuantityField name="quantity" label="Quantity" family="volume" units={["ml", "dl", "l"]}
|
|
229
|
+
* suggestion={usualPack} suggestionFrom="Usual pack" rules={{ validate: (q) => q?.amount != null }} />
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export declare function RhfQuantityField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, TTransformed = TFieldValues>({ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className, defaultUnit, nullable, ...quantityProps }: RhfQuantityFieldProps<TFieldValues, TName, TTransformed>): import("react").JSX.Element;
|
|
205
233
|
export type RhfMoneyFieldProps<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, TTransformed = TFieldValues> = RhfFieldBaseProps<TFieldValues, TName, TTransformed> & Omit<MoneyFieldProps, BoundByForm | "onCommit" | "id"> & {
|
|
206
234
|
/**
|
|
207
235
|
* What the form stores: `"number"` (default) — `12.5`, or `emptyValue` while the
|
package/dist/rhf/fields.js
CHANGED
|
@@ -9,6 +9,9 @@ import { Input } from "../components/input.js";
|
|
|
9
9
|
import { Select } from "../components/select.js";
|
|
10
10
|
import { Textarea } from "../components/textarea.js";
|
|
11
11
|
import { NumberField } from "../components/number-field.js";
|
|
12
|
+
import { QuantityFieldBody } from "../components/quantity-field.js";
|
|
13
|
+
import { firstOfferedUnit } from "../components/quantity-input.js";
|
|
14
|
+
import { QUANTITY_UNITS } from "../lib/quantity.js";
|
|
12
15
|
import { AmountInput } from "../components/amount-input.js";
|
|
13
16
|
import { useMoneyDraft } from "../components/money-draft.js";
|
|
14
17
|
import {
|
|
@@ -34,6 +37,7 @@ import { useKitLocale } from "../i18n/kit-labels.js";
|
|
|
34
37
|
import { cn } from "../lib/cn.js";
|
|
35
38
|
import { decimalMark, readTyped } from "../lib/decimal-marks.js";
|
|
36
39
|
import { hasContent } from "../lib/presence.js";
|
|
40
|
+
import { plainText } from "../lib/plain-text.js";
|
|
37
41
|
function RhfLabel({ required, children }) {
|
|
38
42
|
const { id } = useFormField();
|
|
39
43
|
return /* @__PURE__ */ jsx(FormLabel, { id: `${id}-label`, required, children });
|
|
@@ -263,6 +267,83 @@ function RhfIntegerField(props) {
|
|
|
263
267
|
}
|
|
264
268
|
);
|
|
265
269
|
}
|
|
270
|
+
function RhfQuantityField({
|
|
271
|
+
name,
|
|
272
|
+
control,
|
|
273
|
+
rules,
|
|
274
|
+
label,
|
|
275
|
+
hint,
|
|
276
|
+
help,
|
|
277
|
+
required,
|
|
278
|
+
disabled,
|
|
279
|
+
excludeWhenDisabled,
|
|
280
|
+
className,
|
|
281
|
+
defaultUnit,
|
|
282
|
+
nullable = true,
|
|
283
|
+
...quantityProps
|
|
284
|
+
}) {
|
|
285
|
+
return /* @__PURE__ */ jsx(
|
|
286
|
+
RhfField,
|
|
287
|
+
{
|
|
288
|
+
...{ name, control, rules, label, hint, help, required, disabled, excludeWhenDisabled, className },
|
|
289
|
+
render: ({ field, invalid, id }) => (
|
|
290
|
+
// FormControl clones this element with `id` / `aria-describedby` /
|
|
291
|
+
// `aria-invalid`, which QuantityControl hands on to the field.
|
|
292
|
+
/* @__PURE__ */ jsx(
|
|
293
|
+
QuantityControl,
|
|
294
|
+
{
|
|
295
|
+
field,
|
|
296
|
+
focusRef: field.ref,
|
|
297
|
+
invalid,
|
|
298
|
+
required,
|
|
299
|
+
id,
|
|
300
|
+
defaultUnit,
|
|
301
|
+
fieldName: plainText(label),
|
|
302
|
+
nullable,
|
|
303
|
+
quantityProps
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
function QuantityControl({
|
|
311
|
+
field,
|
|
312
|
+
focusRef,
|
|
313
|
+
invalid,
|
|
314
|
+
required,
|
|
315
|
+
id,
|
|
316
|
+
defaultUnit,
|
|
317
|
+
fieldName,
|
|
318
|
+
nullable,
|
|
319
|
+
quantityProps,
|
|
320
|
+
...aria
|
|
321
|
+
}) {
|
|
322
|
+
const stored = field.value;
|
|
323
|
+
const held = stored !== null && typeof stored === "object" ? stored : {};
|
|
324
|
+
const unit = typeof held.unit === "string" && QUANTITY_UNITS.includes(held.unit) ? held.unit : defaultUnit ?? firstOfferedUnit(quantityProps.units, quantityProps.family);
|
|
325
|
+
const amount = typeof held.amount === "number" && Number.isFinite(held.amount) ? held.amount : null;
|
|
326
|
+
return /* @__PURE__ */ jsx(
|
|
327
|
+
QuantityFieldBody,
|
|
328
|
+
{
|
|
329
|
+
...quantityProps,
|
|
330
|
+
ref: focusRef,
|
|
331
|
+
"aria-describedby": aria["aria-describedby"],
|
|
332
|
+
"aria-invalid": aria["aria-invalid"],
|
|
333
|
+
required,
|
|
334
|
+
id,
|
|
335
|
+
value: { amount, unit },
|
|
336
|
+
fieldName,
|
|
337
|
+
onChange: (next) => {
|
|
338
|
+
field.onChange(next);
|
|
339
|
+
field.onBlur();
|
|
340
|
+
},
|
|
341
|
+
nullable,
|
|
342
|
+
disabled: field.disabled,
|
|
343
|
+
invalid
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
}
|
|
266
347
|
function MoneyControl({
|
|
267
348
|
field,
|
|
268
349
|
invalid,
|
|
@@ -1144,6 +1225,7 @@ export {
|
|
|
1144
1225
|
RhfMonthPicker,
|
|
1145
1226
|
RhfNumberField,
|
|
1146
1227
|
RhfPhoneInput,
|
|
1228
|
+
RhfQuantityField,
|
|
1147
1229
|
RhfSelect,
|
|
1148
1230
|
RhfTextCombobox,
|
|
1149
1231
|
RhfTextField,
|