measurable 2.0.0 → 3.0.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/CHANGELOG.md +49 -0
- package/README.md +96 -22
- package/dist/dimensions/angle.js +12 -5
- package/dist/dimensions/area.js +37 -16
- package/dist/dimensions/data.js +11 -8
- package/dist/dimensions/energy.js +18 -11
- package/dist/dimensions/force.js +5 -5
- package/dist/dimensions/frequency.js +2 -2
- package/dist/dimensions/illuminance.js +7 -4
- package/dist/dimensions/length.js +6 -6
- package/dist/dimensions/luminance.js +5 -8
- package/dist/dimensions/luminousIntensity.js +7 -7
- package/dist/dimensions/mass.js +12 -8
- package/dist/dimensions/power.js +4 -4
- package/dist/dimensions/pressure.js +12 -9
- package/dist/dimensions/temperature.js +3 -3
- package/dist/dimensions/time.js +18 -6
- package/dist/dimensions/volume.js +56 -23
- package/dist/lib/Dimension.d.ts +14 -5
- package/dist/lib/Dimension.js +18 -16
- package/dist/lib/Quantity.d.ts +70 -0
- package/dist/lib/Quantity.js +53 -0
- package/dist/lib/Rational.d.ts +1 -1
- package/dist/lib/Rational.js +1 -1
- package/dist/lib/Unit.d.ts +18 -11
- package/dist/lib/Unit.js +7 -3
- package/dist/utils/definePrefixed.d.ts +8 -15
- package/dist/utils/definePrefixed.js +21 -10
- package/package.json +8 -3
package/dist/dimensions/time.js
CHANGED
|
@@ -5,14 +5,26 @@ const Dimension_1 = require("../lib/Dimension");
|
|
|
5
5
|
const definePrefixed_1 = require("../utils/definePrefixed");
|
|
6
6
|
/** Time / duration. Base unit: second. */
|
|
7
7
|
exports.time = new Dimension_1.Dimension("time");
|
|
8
|
-
exports.second = exports.time.base("second",
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
exports.second = exports.time.base("second", {
|
|
9
|
+
symbol: "s",
|
|
10
|
+
plural: "seconds",
|
|
11
|
+
aliases: ["sec", "secs"],
|
|
12
|
+
});
|
|
13
|
+
exports.minute = exports.time.unit("minute", 60, {
|
|
14
|
+
symbol: "min",
|
|
15
|
+
plural: "minutes",
|
|
16
|
+
aliases: ["mins"],
|
|
17
|
+
});
|
|
18
|
+
exports.hour = exports.time.unit("hour", 3600, {
|
|
19
|
+
symbol: "h",
|
|
20
|
+
plural: "hours",
|
|
21
|
+
aliases: ["hr", "hrs"],
|
|
22
|
+
});
|
|
23
|
+
exports.day = exports.time.unit("day", 86400, { symbol: "d", plural: "days" });
|
|
24
|
+
exports.week = exports.time.unit("week", 604800, { symbol: "wk", plural: "weeks" });
|
|
13
25
|
/**
|
|
14
26
|
* SI-submultiple seconds (millisecond, microsecond, nanosecond, …). Only
|
|
15
27
|
* fractions are generated; larger spans use minute/hour/day/week above.
|
|
16
28
|
*/
|
|
17
|
-
exports.metricTime = (0, definePrefixed_1.definePrefixed)(exports.time,
|
|
29
|
+
exports.metricTime = (0, definePrefixed_1.definePrefixed)(exports.time, exports.second, definePrefixed_1.SI_SUBMULTIPLE_PREFIXES);
|
|
18
30
|
exports.millisecond = exports.metricTime.millisecond, exports.microsecond = exports.metricTime.microsecond, exports.nanosecond = exports.metricTime.nanosecond, exports.picosecond = exports.metricTime.picosecond;
|
|
@@ -5,33 +5,66 @@ const Dimension_1 = require("../lib/Dimension");
|
|
|
5
5
|
const definePrefixed_1 = require("../utils/definePrefixed");
|
|
6
6
|
/** Volume / capacity. Base unit: liter. */
|
|
7
7
|
exports.volume = new Dimension_1.Dimension("volume");
|
|
8
|
-
exports.liter = exports.volume.base("liter",
|
|
8
|
+
exports.liter = exports.volume.base("liter", { symbol: "L", plural: "liters" });
|
|
9
9
|
// US and Imperial liquid measures share names ("gallon", "pint", …) but differ
|
|
10
10
|
// in size, so each is a distinct unit carrying the shared aliases; parsing
|
|
11
11
|
// disambiguates via a preferred measurement system. Imperial has 160 fluid
|
|
12
12
|
// ounces per gallon, US customary has 128.
|
|
13
|
-
exports.usGallon = exports.volume.unit("usGallon", 3.785411784,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
exports.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
exports.usGallon = exports.volume.unit("usGallon", 3.785411784, {
|
|
14
|
+
symbol: "gal",
|
|
15
|
+
plural: "gallons",
|
|
16
|
+
aliases: ["gallon"],
|
|
17
|
+
});
|
|
18
|
+
exports.usQuart = exports.volume.unit("usQuart", 0.946352946, {
|
|
19
|
+
symbol: "qt",
|
|
20
|
+
plural: "quarts",
|
|
21
|
+
aliases: ["quart"],
|
|
22
|
+
});
|
|
23
|
+
exports.usPint = exports.volume.unit("usPint", 0.473176473, {
|
|
24
|
+
symbol: "pt",
|
|
25
|
+
plural: "pints",
|
|
26
|
+
aliases: ["pint"],
|
|
27
|
+
});
|
|
28
|
+
exports.usGill = exports.volume.unit("usGill", 0.11829411825, { plural: "gills", aliases: ["gill"] });
|
|
29
|
+
exports.usFluidOunce = exports.volume.unit("usFluidOunce", 0.0295735295625, {
|
|
30
|
+
symbol: "floz",
|
|
31
|
+
plural: "fluidOunces",
|
|
32
|
+
aliases: ["fluidOunce"],
|
|
33
|
+
});
|
|
34
|
+
exports.imperialGallon = exports.volume.unit("imperialGallon", 4.54609, {
|
|
35
|
+
symbol: "gal",
|
|
36
|
+
plural: "gallons",
|
|
37
|
+
aliases: ["gallon"],
|
|
38
|
+
});
|
|
39
|
+
exports.imperialQuart = exports.volume.unit("imperialQuart", 1.1365225, {
|
|
40
|
+
symbol: "qt",
|
|
41
|
+
plural: "quarts",
|
|
42
|
+
aliases: ["quart"],
|
|
43
|
+
});
|
|
44
|
+
exports.imperialPint = exports.volume.unit("imperialPint", 0.56826125, {
|
|
45
|
+
symbol: "pt",
|
|
46
|
+
plural: "pints",
|
|
47
|
+
aliases: ["pint"],
|
|
48
|
+
});
|
|
49
|
+
exports.imperialGill = exports.volume.unit("imperialGill", 0.1420653125, {
|
|
50
|
+
plural: "gills",
|
|
51
|
+
aliases: ["gill"],
|
|
52
|
+
});
|
|
53
|
+
exports.imperialFluidOunce = exports.volume.unit("imperialFluidOunce", 0.0284130625, {
|
|
54
|
+
symbol: "floz",
|
|
55
|
+
plural: "fluidOunces",
|
|
56
|
+
aliases: ["fluidOunce"],
|
|
57
|
+
});
|
|
31
58
|
// US-only cooking measures (no competing imperial unit, so left unprefixed).
|
|
32
|
-
exports.cup = exports.volume.unit("cup", 0.2365882365,
|
|
33
|
-
exports.tablespoon = exports.volume.unit("tablespoon", 0.01478676478125,
|
|
34
|
-
|
|
59
|
+
exports.cup = exports.volume.unit("cup", 0.2365882365, { plural: "cups" });
|
|
60
|
+
exports.tablespoon = exports.volume.unit("tablespoon", 0.01478676478125, {
|
|
61
|
+
symbol: "tbsp",
|
|
62
|
+
plural: "tablespoons",
|
|
63
|
+
});
|
|
64
|
+
exports.teaspoon = exports.volume.unit("teaspoon", 0.00492892159375, {
|
|
65
|
+
symbol: "tsp",
|
|
66
|
+
plural: "teaspoons",
|
|
67
|
+
});
|
|
35
68
|
/** Every SI-prefixed liter (milliliter, centiliter, kiloliter, …), keyed by name. */
|
|
36
|
-
exports.metricVolume = (0, definePrefixed_1.definePrefixed)(exports.volume,
|
|
69
|
+
exports.metricVolume = (0, definePrefixed_1.definePrefixed)(exports.volume, exports.liter);
|
|
37
70
|
exports.kiloliter = exports.metricVolume.kiloliter, exports.hectoliter = exports.metricVolume.hectoliter, exports.decaliter = exports.metricVolume.decaliter, exports.deciliter = exports.metricVolume.deciliter, exports.centiliter = exports.metricVolume.centiliter, exports.milliliter = exports.metricVolume.milliliter;
|
package/dist/lib/Dimension.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Rational } from "
|
|
1
|
+
import { Rational } from "./Rational";
|
|
2
2
|
import { Unit } from "./Unit";
|
|
3
3
|
/** A linear unit with an additive offset (e.g. temperature scales). */
|
|
4
4
|
export interface AffineSpec {
|
|
@@ -16,6 +16,15 @@ export interface CustomSpec {
|
|
|
16
16
|
toBase: (value: number) => number;
|
|
17
17
|
fromBase: (value: number) => number;
|
|
18
18
|
}
|
|
19
|
+
/** Optional descriptors for a unit: its symbol, plural, and extra parse aliases. */
|
|
20
|
+
export interface UnitDef {
|
|
21
|
+
/** Canonical symbol, e.g. `"g"`, `"km"`, `"°C"`. Also registered for parsing. */
|
|
22
|
+
symbol?: string;
|
|
23
|
+
/** Plural name, e.g. `"grams"`. Also registered for parsing. */
|
|
24
|
+
plural?: string;
|
|
25
|
+
/** Additional names this unit parses from (beyond name, symbol, and plural). */
|
|
26
|
+
aliases?: string[];
|
|
27
|
+
}
|
|
19
28
|
/**
|
|
20
29
|
* A dimension is a single *kind* of measurable quantity (length, volume, mass,
|
|
21
30
|
* temperature, …). It owns one canonical **base unit** that every other unit in
|
|
@@ -48,15 +57,15 @@ export declare class Dimension {
|
|
|
48
57
|
baseUnit?: Unit;
|
|
49
58
|
constructor(name: string);
|
|
50
59
|
/** Define the canonical base unit (identity transform). */
|
|
51
|
-
base(name: string,
|
|
60
|
+
base(name: string, def?: UnitDef): Unit;
|
|
52
61
|
/**
|
|
53
62
|
* Define a linear unit. `scale` is how many base units make up one of this
|
|
54
63
|
* unit (e.g. a kilometer is `1000` meters). Pass a {@link Rational} for a
|
|
55
64
|
* scale a decimal cannot represent exactly.
|
|
56
65
|
*/
|
|
57
|
-
unit(name: string, scale: number | Rational,
|
|
66
|
+
unit(name: string, scale: number | Rational, def?: UnitDef): Unit;
|
|
58
67
|
/** Define an affine unit (scale plus additive offset, e.g. °C against K). */
|
|
59
|
-
affine(name: string, { scale, offset }: AffineSpec,
|
|
68
|
+
affine(name: string, { scale, offset }: AffineSpec, def?: UnitDef): Unit;
|
|
60
69
|
/**
|
|
61
70
|
* Define a non-linear unit from an arbitrary, hand-written inverse transform
|
|
62
71
|
* pair. Reserve this for units that genuinely cannot be expressed as `value *
|
|
@@ -64,7 +73,7 @@ export declare class Dimension {
|
|
|
64
73
|
* affine units should use {@link unit} / {@link affine} so conversions stay
|
|
65
74
|
* exact.
|
|
66
75
|
*/
|
|
67
|
-
custom(name: string, { toBase, fromBase }: CustomSpec,
|
|
76
|
+
custom(name: string, { toBase, fromBase }: CustomSpec, def?: UnitDef): Unit;
|
|
68
77
|
/** Convert a `number` value between two units of this dimension. */
|
|
69
78
|
convert(value: number, from: Unit, to: Unit): number;
|
|
70
79
|
/**
|
package/dist/lib/Dimension.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Dimension = void 0;
|
|
4
4
|
const InvalidConversionError_1 = require("../errors/InvalidConversionError");
|
|
5
|
-
const Rational_1 = require("
|
|
5
|
+
const Rational_1 = require("./Rational");
|
|
6
6
|
const Unit_1 = require("./Unit");
|
|
7
7
|
/** The rational `0`. */
|
|
8
8
|
const zero = new Rational_1.Rational(0n);
|
|
@@ -39,8 +39,8 @@ class Dimension {
|
|
|
39
39
|
this.index = new Map();
|
|
40
40
|
}
|
|
41
41
|
/** Define the canonical base unit (identity transform). */
|
|
42
|
-
base(name,
|
|
43
|
-
const unit = this.defineLinear(name, { scale: one, offset: zero },
|
|
42
|
+
base(name, def = {}) {
|
|
43
|
+
const unit = this.defineLinear(name, { scale: one, offset: zero }, def);
|
|
44
44
|
this.baseUnit = unit;
|
|
45
45
|
return unit;
|
|
46
46
|
}
|
|
@@ -49,12 +49,12 @@ class Dimension {
|
|
|
49
49
|
* unit (e.g. a kilometer is `1000` meters). Pass a {@link Rational} for a
|
|
50
50
|
* scale a decimal cannot represent exactly.
|
|
51
51
|
*/
|
|
52
|
-
unit(name, scale,
|
|
53
|
-
return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: zero },
|
|
52
|
+
unit(name, scale, def = {}) {
|
|
53
|
+
return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: zero }, def);
|
|
54
54
|
}
|
|
55
55
|
/** Define an affine unit (scale plus additive offset, e.g. °C against K). */
|
|
56
|
-
affine(name, { scale, offset },
|
|
57
|
-
return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: Rational_1.Rational.from(offset) },
|
|
56
|
+
affine(name, { scale, offset }, def = {}) {
|
|
57
|
+
return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: Rational_1.Rational.from(offset) }, def);
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* Define a non-linear unit from an arbitrary, hand-written inverse transform
|
|
@@ -63,8 +63,8 @@ class Dimension {
|
|
|
63
63
|
* affine units should use {@link unit} / {@link affine} so conversions stay
|
|
64
64
|
* exact.
|
|
65
65
|
*/
|
|
66
|
-
custom(name, { toBase, fromBase },
|
|
67
|
-
return this.define(name,
|
|
66
|
+
custom(name, { toBase, fromBase }, def = {}) {
|
|
67
|
+
return this.define(name, def, { toBase, fromBase });
|
|
68
68
|
}
|
|
69
69
|
/** Convert a `number` value between two units of this dimension. */
|
|
70
70
|
convert(value, from, to) {
|
|
@@ -99,20 +99,22 @@ class Dimension {
|
|
|
99
99
|
return this.units.has(unit);
|
|
100
100
|
}
|
|
101
101
|
/** Define a linear / affine unit from its exact rational transform. */
|
|
102
|
-
defineLinear(name, linear,
|
|
103
|
-
return this.define(name,
|
|
102
|
+
defineLinear(name, linear, def) {
|
|
103
|
+
return this.define(name, def, { linear });
|
|
104
104
|
}
|
|
105
|
-
define(name, aliases, transform) {
|
|
105
|
+
define(name, { symbol, plural, aliases = [] }, transform) {
|
|
106
106
|
for (const existing of this.units) {
|
|
107
107
|
if (existing.name === name) {
|
|
108
108
|
throw new Error(`Duplicate unit name "${name}" in dimension "${this.name}"`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
const unit = new Unit_1.Unit({ name, dimension: this, ...transform });
|
|
111
|
+
const unit = new Unit_1.Unit({ name, dimension: this, symbol, plural, ...transform });
|
|
112
112
|
this.units.add(unit);
|
|
113
|
-
this
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
// Register every label this unit can be parsed from, de-duplicated so a unit
|
|
114
|
+
// never appears twice among a token's candidates (e.g. if symbol === name).
|
|
115
|
+
const tokens = new Set([name, symbol, plural, ...aliases].filter((t) => !!t));
|
|
116
|
+
for (const token of tokens) {
|
|
117
|
+
this.register(token, unit);
|
|
116
118
|
}
|
|
117
119
|
return unit;
|
|
118
120
|
}
|
package/dist/lib/Quantity.d.ts
CHANGED
|
@@ -7,6 +7,45 @@ export interface ParseOptions {
|
|
|
7
7
|
/** Preferred measurement system, used only to break ties on shared aliases. */
|
|
8
8
|
prefer?: MeasurementSystem;
|
|
9
9
|
}
|
|
10
|
+
/** Options for {@link Quantity.format}. */
|
|
11
|
+
export interface FormatOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Which label to render after the magnitude:
|
|
14
|
+
* - `"auto"` (default) — singular `name` when the magnitude is exactly ±1,
|
|
15
|
+
* otherwise the `plural`.
|
|
16
|
+
* - `"name"` — always the singular name.
|
|
17
|
+
* - `"plural"` — always the plural.
|
|
18
|
+
* - `"symbol"` — the unit's symbol.
|
|
19
|
+
*
|
|
20
|
+
* `plural`/`symbol` fall back to the unit's `name` when that field is unset.
|
|
21
|
+
* Labels are English/canonical; localize the *magnitude* via {@link locale} /
|
|
22
|
+
* {@link numberFormat}.
|
|
23
|
+
*/
|
|
24
|
+
unit?: "auto" | "name" | "plural" | "symbol";
|
|
25
|
+
/**
|
|
26
|
+
* BCP 47 locale(s) used to render the magnitude (e.g. `"de-DE"`, `["fr", "en"]`).
|
|
27
|
+
* Passed straight to `Number.prototype.toLocaleString`. When omitted, the
|
|
28
|
+
* runtime's default locale is used.
|
|
29
|
+
*/
|
|
30
|
+
locale?: string | string[];
|
|
31
|
+
/**
|
|
32
|
+
* `Intl.NumberFormat` options for the magnitude — precision
|
|
33
|
+
* (`minimumFractionDigits` / `maximumFractionDigits`), `style: "currency"`,
|
|
34
|
+
* grouping, and so on. Passed straight to `Number.prototype.toLocaleString`.
|
|
35
|
+
*/
|
|
36
|
+
numberFormat?: Intl.NumberFormatOptions;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The rendered pieces of a formatted quantity, as returned by
|
|
40
|
+
* {@link Quantity.formatParts}. Each is already a finished string; the caller
|
|
41
|
+
* decides how to assemble them (a plain join, JSX, a template, …).
|
|
42
|
+
*/
|
|
43
|
+
export interface FormattedParts {
|
|
44
|
+
/** The locale-formatted magnitude, e.g. `"1.234,5"`. */
|
|
45
|
+
magnitude: string;
|
|
46
|
+
/** The chosen unit label, e.g. `"kilometers"`, `"km"`. */
|
|
47
|
+
unit: string;
|
|
48
|
+
}
|
|
10
49
|
/** A magnitude paired with a unit (e.g. `5` `kilometer`). */
|
|
11
50
|
export declare class Quantity {
|
|
12
51
|
readonly unit: Unit;
|
|
@@ -30,6 +69,37 @@ export declare class Quantity {
|
|
|
30
69
|
private inRational;
|
|
31
70
|
/** Render as `"<magnitude> <unit name>"`, e.g. `"5 kilometer"`. */
|
|
32
71
|
toString(): string;
|
|
72
|
+
/**
|
|
73
|
+
* Render as `"<magnitude> <label>"`, choosing the label per `options.unit`
|
|
74
|
+
* (default `"auto"`: magnitude-aware singular/plural). Unlike {@link toString},
|
|
75
|
+
* this can use the unit's symbol or plural — e.g. `"5 grams"`, `"5 g"`,
|
|
76
|
+
* `"1 gram"`. Pass `locale` / `numberFormat` to localize the magnitude via
|
|
77
|
+
* `toLocaleString` — e.g. `format({ locale: "de-DE" })` → `"1.234,5 meters"`,
|
|
78
|
+
* or `format({ numberFormat: { maximumFractionDigits: 2 } })` for precision.
|
|
79
|
+
*
|
|
80
|
+
* For non-string output (e.g. JSX), use {@link formatParts} and assemble the
|
|
81
|
+
* pieces yourself.
|
|
82
|
+
*/
|
|
83
|
+
format(options?: FormatOptions): string;
|
|
84
|
+
/**
|
|
85
|
+
* Like {@link format}, but returns the rendered magnitude and label as
|
|
86
|
+
* separate strings instead of joining them, so the caller controls the
|
|
87
|
+
* assembly. Useful when a single string won't do — e.g. styling the magnitude
|
|
88
|
+
* in a React component:
|
|
89
|
+
*
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const { magnitude, unit } = q.formatParts({ locale: "de-DE" });
|
|
92
|
+
* return <><b>{magnitude}</b> {unit}</>;
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
formatParts(options?: FormatOptions): FormattedParts;
|
|
96
|
+
/**
|
|
97
|
+
* Render the magnitude via `toLocaleString`. With no `locale`/`numberFormat`
|
|
98
|
+
* it uses the runtime's default locale; supply either for locale- and
|
|
99
|
+
* precision-aware formatting.
|
|
100
|
+
*/
|
|
101
|
+
private formatMagnitude;
|
|
102
|
+
private formatLabel;
|
|
33
103
|
/**
|
|
34
104
|
* Add another quantity, returned in *this* quantity's unit. The other operand
|
|
35
105
|
* is converted into this unit first, so the two may use different units of the
|
package/dist/lib/Quantity.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Quantity = void 0;
|
|
4
|
+
const assert_never_1 = require("assert-never");
|
|
4
5
|
const AmbiguousUnitError_1 = require("../errors/AmbiguousUnitError");
|
|
5
6
|
const UnknownUnitError_1 = require("../errors/UnknownUnitError");
|
|
6
7
|
const scaleOf_1 = require("../utils/scaleOf");
|
|
@@ -31,6 +32,58 @@ class Quantity {
|
|
|
31
32
|
toString() {
|
|
32
33
|
return `${this.magnitude} ${this.unit.name}`;
|
|
33
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Render as `"<magnitude> <label>"`, choosing the label per `options.unit`
|
|
37
|
+
* (default `"auto"`: magnitude-aware singular/plural). Unlike {@link toString},
|
|
38
|
+
* this can use the unit's symbol or plural — e.g. `"5 grams"`, `"5 g"`,
|
|
39
|
+
* `"1 gram"`. Pass `locale` / `numberFormat` to localize the magnitude via
|
|
40
|
+
* `toLocaleString` — e.g. `format({ locale: "de-DE" })` → `"1.234,5 meters"`,
|
|
41
|
+
* or `format({ numberFormat: { maximumFractionDigits: 2 } })` for precision.
|
|
42
|
+
*
|
|
43
|
+
* For non-string output (e.g. JSX), use {@link formatParts} and assemble the
|
|
44
|
+
* pieces yourself.
|
|
45
|
+
*/
|
|
46
|
+
format(options = {}) {
|
|
47
|
+
const { magnitude, unit } = this.formatParts(options);
|
|
48
|
+
return `${magnitude} ${unit}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Like {@link format}, but returns the rendered magnitude and label as
|
|
52
|
+
* separate strings instead of joining them, so the caller controls the
|
|
53
|
+
* assembly. Useful when a single string won't do — e.g. styling the magnitude
|
|
54
|
+
* in a React component:
|
|
55
|
+
*
|
|
56
|
+
* ```tsx
|
|
57
|
+
* const { magnitude, unit } = q.formatParts({ locale: "de-DE" });
|
|
58
|
+
* return <><b>{magnitude}</b> {unit}</>;
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
formatParts(options = {}) {
|
|
62
|
+
return { magnitude: this.formatMagnitude(options), unit: this.formatLabel(options) };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render the magnitude via `toLocaleString`. With no `locale`/`numberFormat`
|
|
66
|
+
* it uses the runtime's default locale; supply either for locale- and
|
|
67
|
+
* precision-aware formatting.
|
|
68
|
+
*/
|
|
69
|
+
formatMagnitude({ locale, numberFormat }) {
|
|
70
|
+
return this.magnitude.toLocaleString(locale, numberFormat);
|
|
71
|
+
}
|
|
72
|
+
formatLabel({ unit = "auto" }) {
|
|
73
|
+
const { name, symbol, plural } = this.unit;
|
|
74
|
+
switch (unit) {
|
|
75
|
+
case "symbol":
|
|
76
|
+
return symbol ?? name;
|
|
77
|
+
case "name":
|
|
78
|
+
return name;
|
|
79
|
+
case "plural":
|
|
80
|
+
return plural ?? name;
|
|
81
|
+
case "auto":
|
|
82
|
+
return Math.abs(this.magnitude) === 1 ? name : (plural ?? name);
|
|
83
|
+
default:
|
|
84
|
+
return (0, assert_never_1.assertNever)(unit);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
34
87
|
/**
|
|
35
88
|
* Add another quantity, returned in *this* quantity's unit. The other operand
|
|
36
89
|
* is converted into this unit first, so the two may use different units of the
|
package/dist/lib/Rational.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare class Rational {
|
|
|
16
16
|
/**
|
|
17
17
|
* Build a rational from integer numerator and denominator. Pass exact ratios
|
|
18
18
|
* the literal way — `new Rational(5, 9)` — using `bigint` or integer `number`.
|
|
19
|
-
* For a decimal value, use {@link Rational.
|
|
19
|
+
* For a decimal value, use {@link Rational.from} instead.
|
|
20
20
|
*/
|
|
21
21
|
constructor(numerator: bigint | number, denominator?: bigint | number);
|
|
22
22
|
/** Coerce a `number | Rational` to a `Rational`, parsing numbers as decimals. */
|
package/dist/lib/Rational.js
CHANGED
|
@@ -17,7 +17,7 @@ class Rational {
|
|
|
17
17
|
/**
|
|
18
18
|
* Build a rational from integer numerator and denominator. Pass exact ratios
|
|
19
19
|
* the literal way — `new Rational(5, 9)` — using `bigint` or integer `number`.
|
|
20
|
-
* For a decimal value, use {@link Rational.
|
|
20
|
+
* For a decimal value, use {@link Rational.from} instead.
|
|
21
21
|
*/
|
|
22
22
|
constructor(numerator, denominator = 1n) {
|
|
23
23
|
let n = toBigInt(numerator);
|
package/dist/lib/Unit.d.ts
CHANGED
|
@@ -9,21 +9,23 @@ export interface LinearTransform {
|
|
|
9
9
|
scale: Rational;
|
|
10
10
|
offset: Rational;
|
|
11
11
|
}
|
|
12
|
-
interface BaseUnitOptions {
|
|
12
|
+
export interface BaseUnitOptions {
|
|
13
13
|
name: string;
|
|
14
14
|
dimension: Dimension;
|
|
15
|
+
/** Canonical symbol, e.g. `"g"`, `"km"`, `"°C"` (optional). */
|
|
16
|
+
symbol?: string;
|
|
17
|
+
/** Plural name, e.g. `"grams"`, `"kilometers"` (optional). */
|
|
18
|
+
plural?: string;
|
|
15
19
|
}
|
|
16
|
-
|
|
20
|
+
export type UnitConversionOptions = {
|
|
17
21
|
/** Exact transform for linear / affine units (the common case). */
|
|
18
22
|
linear: LinearTransform;
|
|
19
|
-
}
|
|
20
|
-
interface CustomConversionOptions {
|
|
23
|
+
} | {
|
|
21
24
|
/** Hand-written transform for non-linear units; mutually exclusive with `linear`. */
|
|
22
25
|
toBase: (value: number) => number;
|
|
23
26
|
fromBase: (value: number) => number;
|
|
24
|
-
}
|
|
25
|
-
export type
|
|
26
|
-
type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
27
|
+
};
|
|
28
|
+
export type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
27
29
|
/**
|
|
28
30
|
* A single unit of measurement (e.g. "meter", "celsius").
|
|
29
31
|
*
|
|
@@ -44,8 +46,10 @@ type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
|
44
46
|
* {@link MeasurementSystem}s (metric/imperial/…); that membership lives on the
|
|
45
47
|
* measurement systems, not here, so a `Unit` stays a lean descriptor.
|
|
46
48
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
+
* Parsing aliases live in the dimension's lookup index. A unit additionally
|
|
50
|
+
* carries its canonical {@link symbol} and {@link plural} as first-class data
|
|
51
|
+
* so callers can choose how to render it (see {@link Quantity.format}); both are
|
|
52
|
+
* optional and English-centric — real localization is left to the consumer.
|
|
49
53
|
*
|
|
50
54
|
* Units are normally created through a {@link Dimension}'s builder methods
|
|
51
55
|
* (`base`, `unit`, `affine`, `custom`) rather than constructed directly.
|
|
@@ -53,8 +57,12 @@ type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
|
53
57
|
export declare class Unit {
|
|
54
58
|
readonly name: string;
|
|
55
59
|
readonly dimension: Dimension;
|
|
60
|
+
/** Canonical symbol, e.g. `"g"`, `"km"`, `"°C"` (optional). */
|
|
61
|
+
readonly symbol?: string;
|
|
62
|
+
/** Plural name, e.g. `"grams"`, `"kilometers"` (optional). */
|
|
63
|
+
readonly plural?: string;
|
|
56
64
|
private readonly conversion;
|
|
57
|
-
constructor({ name, dimension, ...conversionOptions }: UnitOptions);
|
|
65
|
+
constructor({ name, dimension, symbol, plural, ...conversionOptions }: UnitOptions);
|
|
58
66
|
/**
|
|
59
67
|
* Exact affine transform to the base unit, present for all but non-linear
|
|
60
68
|
* units. When both ends of a conversion have one, {@link Dimension.convert}
|
|
@@ -66,4 +74,3 @@ export declare class Unit {
|
|
|
66
74
|
/** Convert a value in the dimension's base unit to this unit. */
|
|
67
75
|
fromBase(value: number): number;
|
|
68
76
|
}
|
|
69
|
-
export {};
|
package/dist/lib/Unit.js
CHANGED
|
@@ -21,16 +21,20 @@ exports.Unit = void 0;
|
|
|
21
21
|
* {@link MeasurementSystem}s (metric/imperial/…); that membership lives on the
|
|
22
22
|
* measurement systems, not here, so a `Unit` stays a lean descriptor.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Parsing aliases live in the dimension's lookup index. A unit additionally
|
|
25
|
+
* carries its canonical {@link symbol} and {@link plural} as first-class data
|
|
26
|
+
* so callers can choose how to render it (see {@link Quantity.format}); both are
|
|
27
|
+
* optional and English-centric — real localization is left to the consumer.
|
|
26
28
|
*
|
|
27
29
|
* Units are normally created through a {@link Dimension}'s builder methods
|
|
28
30
|
* (`base`, `unit`, `affine`, `custom`) rather than constructed directly.
|
|
29
31
|
*/
|
|
30
32
|
class Unit {
|
|
31
|
-
constructor({ name, dimension, ...conversionOptions }) {
|
|
33
|
+
constructor({ name, dimension, symbol, plural, ...conversionOptions }) {
|
|
32
34
|
this.name = name;
|
|
33
35
|
this.dimension = dimension;
|
|
36
|
+
this.symbol = symbol;
|
|
37
|
+
this.plural = plural;
|
|
34
38
|
this.conversion = conversionOptions;
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Dimension } from "../lib/Dimension";
|
|
2
|
-
import { Rational } from "../lib/Rational";
|
|
3
2
|
import type { Unit } from "../lib/Unit";
|
|
4
3
|
/** A metric (SI) prefix: a name, symbol, and power-of-ten factor. */
|
|
5
4
|
export interface SiPrefix {
|
|
@@ -11,20 +10,14 @@ export interface SiPrefix {
|
|
|
11
10
|
export declare const SI_PREFIXES: readonly SiPrefix[];
|
|
12
11
|
/** SI prefixes for fractions only (deci and smaller) — for units like seconds. */
|
|
13
12
|
export declare const SI_SUBMULTIPLE_PREFIXES: readonly SiPrefix[];
|
|
14
|
-
/** The unit a set of metric prefixes is generated relative to. */
|
|
15
|
-
export interface PrefixReference {
|
|
16
|
-
/** Singular unit name, e.g. "meter" → "kilometer", "millimeter", … */
|
|
17
|
-
name: string;
|
|
18
|
-
/** Primary symbol, e.g. "m" → "km", "mm", … */
|
|
19
|
-
symbol: string;
|
|
20
|
-
/** Scale of the reference relative to its dimension's base (meter → 1, gram → 0.001) (default 1). */
|
|
21
|
-
scale?: number | Rational;
|
|
22
|
-
}
|
|
23
13
|
/**
|
|
24
|
-
* Define metric-prefixed variants of a reference unit on
|
|
25
|
-
* variant is named `${prefix}${reference.name}` (e.g. "kilometer")
|
|
26
|
-
* `reference
|
|
27
|
-
*
|
|
14
|
+
* Define metric-prefixed variants of a `reference` unit on its dimension. Each
|
|
15
|
+
* variant is named `${prefix}${reference.name}` (e.g. "kilometer"), scaled by
|
|
16
|
+
* `prefix.factor` relative to the reference (its own scale is read from the unit
|
|
17
|
+
* via `scaleOf`, so prefixing a non-base unit like the watt-hour works
|
|
18
|
+
* automatically). Each variant carries a generated `symbol`
|
|
19
|
+
* (`${prefix.symbol}${reference.symbol}`, when the reference has a symbol) and a
|
|
20
|
+
* `plural` (`${name}s`), plus an ASCII "u" alias for micro.
|
|
28
21
|
*
|
|
29
22
|
* Prefixes whose generated name already exists on the dimension are skipped, so
|
|
30
23
|
* a base like "kilogram" is left intact when prefixing "gram".
|
|
@@ -32,4 +25,4 @@ export interface PrefixReference {
|
|
|
32
25
|
* Returns the created units keyed by name, for spreading into a
|
|
33
26
|
* {@link MeasurementSystem} or destructuring into named exports.
|
|
34
27
|
*/
|
|
35
|
-
export declare function definePrefixed(dimension: Dimension, reference:
|
|
28
|
+
export declare function definePrefixed(dimension: Dimension, reference: Unit, prefixes?: readonly SiPrefix[]): Record<string, Unit>;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SI_SUBMULTIPLE_PREFIXES = exports.SI_PREFIXES = void 0;
|
|
4
4
|
exports.definePrefixed = definePrefixed;
|
|
5
5
|
const Rational_1 = require("../lib/Rational");
|
|
6
|
+
const scaleOf_1 = require("./scaleOf");
|
|
6
7
|
/** The full set of SI prefixes, yotta (1e24) down to yocto (1e-24). */
|
|
7
8
|
exports.SI_PREFIXES = [
|
|
8
9
|
{ name: "yotta", symbol: "Y", factor: 1e24 },
|
|
@@ -29,10 +30,13 @@ exports.SI_PREFIXES = [
|
|
|
29
30
|
/** SI prefixes for fractions only (deci and smaller) — for units like seconds. */
|
|
30
31
|
exports.SI_SUBMULTIPLE_PREFIXES = exports.SI_PREFIXES.filter((prefix) => prefix.factor < 1);
|
|
31
32
|
/**
|
|
32
|
-
* Define metric-prefixed variants of a reference unit on
|
|
33
|
-
* variant is named `${prefix}${reference.name}` (e.g. "kilometer")
|
|
34
|
-
* `reference
|
|
35
|
-
*
|
|
33
|
+
* Define metric-prefixed variants of a `reference` unit on its dimension. Each
|
|
34
|
+
* variant is named `${prefix}${reference.name}` (e.g. "kilometer"), scaled by
|
|
35
|
+
* `prefix.factor` relative to the reference (its own scale is read from the unit
|
|
36
|
+
* via `scaleOf`, so prefixing a non-base unit like the watt-hour works
|
|
37
|
+
* automatically). Each variant carries a generated `symbol`
|
|
38
|
+
* (`${prefix.symbol}${reference.symbol}`, when the reference has a symbol) and a
|
|
39
|
+
* `plural` (`${name}s`), plus an ASCII "u" alias for micro.
|
|
36
40
|
*
|
|
37
41
|
* Prefixes whose generated name already exists on the dimension are skipped, so
|
|
38
42
|
* a base like "kilogram" is left intact when prefixing "gram".
|
|
@@ -41,21 +45,28 @@ exports.SI_SUBMULTIPLE_PREFIXES = exports.SI_PREFIXES.filter((prefix) => prefix.
|
|
|
41
45
|
* {@link MeasurementSystem} or destructuring into named exports.
|
|
42
46
|
*/
|
|
43
47
|
function definePrefixed(dimension, reference, prefixes = exports.SI_PREFIXES) {
|
|
48
|
+
// Prefer the reference's exact rational scale so the prefixed scale stays
|
|
49
|
+
// exact; fall back to its float scale only for non-linear references.
|
|
50
|
+
const referenceScale = reference.linear
|
|
51
|
+
? reference.linear.scale
|
|
52
|
+
: Rational_1.Rational.from((0, scaleOf_1.scaleOf)(reference));
|
|
44
53
|
const units = {};
|
|
45
54
|
for (const prefix of prefixes) {
|
|
46
55
|
const name = `${prefix.name}${reference.name}`;
|
|
47
56
|
if (dimension.get(name)) {
|
|
48
57
|
continue;
|
|
49
58
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
aliases.push(`u${reference.symbol}`);
|
|
53
|
-
}
|
|
59
|
+
const symbol = reference.symbol ? `${prefix.symbol}${reference.symbol}` : undefined;
|
|
60
|
+
const aliases = prefix.name === "micro" && reference.symbol ? [`u${reference.symbol}`] : [];
|
|
54
61
|
// Multiply as rationals: each factor (a power of ten, or an exact reference
|
|
55
62
|
// scale) is lossless on its own, but multiplying them as floats can drift
|
|
56
63
|
// (e.g. 3600 * 1e-9). Rational multiplication keeps the prefixed scale exact.
|
|
57
|
-
const scale =
|
|
58
|
-
units[name] = dimension.unit(name, scale,
|
|
64
|
+
const scale = referenceScale.times(Rational_1.Rational.from(prefix.factor));
|
|
65
|
+
units[name] = dimension.unit(name, scale, {
|
|
66
|
+
symbol,
|
|
67
|
+
plural: `${name}s`,
|
|
68
|
+
aliases,
|
|
69
|
+
});
|
|
59
70
|
}
|
|
60
71
|
return units;
|
|
61
72
|
}
|