@weasel-js/quantity 1.9.3 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +140 -11
- package/dist/index.js +305 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* `UnitSystem` at the API boundary. Internals never see units.
|
|
9
9
|
*
|
|
10
10
|
* An entry is affine — `base = value * factor + offset` — so a scale that
|
|
11
|
-
* puts zero somewhere else (degC against K) is expressible.
|
|
12
|
-
* units
|
|
11
|
+
* puts zero somewhere else (degC against K) is expressible. Arithmetic
|
|
12
|
+
* across units is `resolveUnitExpr`; a plane whose axes measure different
|
|
13
|
+
* things resolves through `resolveAxes`.
|
|
13
14
|
*/
|
|
14
15
|
/** A unit name (e.g. `'in'`, `'ft'`, `'mm'`). Looked up in a `UnitSystem`. */
|
|
15
16
|
type Unit = string;
|
|
@@ -56,6 +57,25 @@ declare function formatUnit(baseValue: number, displayUnit: Unit, unitSystem: Un
|
|
|
56
57
|
precision?: number;
|
|
57
58
|
suffix?: boolean;
|
|
58
59
|
}): string;
|
|
60
|
+
/** One of something for each axis of a plane. */
|
|
61
|
+
interface PerAxis<T> {
|
|
62
|
+
x: T;
|
|
63
|
+
y: T;
|
|
64
|
+
}
|
|
65
|
+
/** A plane's units: one system for both axes, or one each — a chart whose x
|
|
66
|
+
* is time and whose y is a value. */
|
|
67
|
+
type PlaneUnits = UnitSystem | PerAxis<UnitSystem>;
|
|
68
|
+
/** A measure on a plane: one value for both axes, or one each. */
|
|
69
|
+
type PlaneValue = UnitValue | Quantity | PerAxis<UnitValue | Quantity>;
|
|
70
|
+
/** The unit system `units` gives one axis. */
|
|
71
|
+
declare function axisUnits(units: PlaneUnits | undefined, axis: 'x' | 'y'): UnitSystem | undefined;
|
|
72
|
+
/** `v` in base units on each axis, every axis resolved against its own system. */
|
|
73
|
+
declare function resolveAxes(v: PlaneValue, units?: PlaneUnits): PerAxis<number>;
|
|
74
|
+
/** A base-unit point as text, each axis in its own display unit. */
|
|
75
|
+
declare function formatAxes(base: PerAxis<number>, display: Unit | PerAxis<Unit>, units: PlaneUnits, opts?: {
|
|
76
|
+
precision?: number;
|
|
77
|
+
suffix?: boolean;
|
|
78
|
+
}): PerAxis<string>;
|
|
59
79
|
/** Imperial unit system with base 'in'. */
|
|
60
80
|
declare const IMPERIAL_INCHES: UnitSystem;
|
|
61
81
|
/** Metric unit system with base 'mm'. */
|
|
@@ -321,6 +341,7 @@ declare const CONSTANTS: {
|
|
|
321
341
|
readonly spoken: "e";
|
|
322
342
|
};
|
|
323
343
|
};
|
|
344
|
+
/** How a `fraction` display picks and writes its fraction. */
|
|
324
345
|
type FractionOptions = {
|
|
325
346
|
/** Default 64. */
|
|
326
347
|
maxDenominator?: number;
|
|
@@ -389,14 +410,10 @@ declare function zoom(): ZoomDisplay;
|
|
|
389
410
|
declare const zoomKind: DisplayKind<ZoomDisplay>;
|
|
390
411
|
|
|
391
412
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
* U+2212 is the same visual width as `+` and reads as a sign rather
|
|
398
|
-
* than a hyphen — columns of signed numbers align cleanly and the
|
|
399
|
-
* glyph doesn't get confused with a bullet or list dash.
|
|
413
|
+
* The real MINUS SIGN (U+2212), which display formatting uses instead of the
|
|
414
|
+
* ASCII HYPHEN-MINUS (U+002D) that `toLocaleString` and template literals
|
|
415
|
+
* produce. It is the same visual width as `+`, so columns of signed numbers
|
|
416
|
+
* align.
|
|
400
417
|
*/
|
|
401
418
|
declare const MINUS_SIGN = "\u2212";
|
|
402
419
|
/**
|
|
@@ -525,4 +542,116 @@ declare function ordinal(): OrdinalDisplay;
|
|
|
525
542
|
/** The built-in `ordinal` kind, to spread into a `registerDisplayKind` replacement. */
|
|
526
543
|
declare const ordinalKind: DisplayKind<OrdinalDisplay>;
|
|
527
544
|
|
|
528
|
-
|
|
545
|
+
/**
|
|
546
|
+
* Arithmetic over values in different units, as plain data: `50% + 2ft`
|
|
547
|
+
* parses once and resolves against whatever context it is handed, so a stored
|
|
548
|
+
* expression follows a parent that resizes. `value` nodes are the leaves.
|
|
549
|
+
*/
|
|
550
|
+
type UnitExpr = {
|
|
551
|
+
op: 'value';
|
|
552
|
+
value: number;
|
|
553
|
+
unit?: Unit;
|
|
554
|
+
} | {
|
|
555
|
+
op: 'neg';
|
|
556
|
+
of: UnitExpr;
|
|
557
|
+
} | {
|
|
558
|
+
op: 'add' | 'sub' | 'mul' | 'div';
|
|
559
|
+
left: UnitExpr;
|
|
560
|
+
right: UnitExpr;
|
|
561
|
+
};
|
|
562
|
+
/** What a {@link UnitExpr} resolves against. */
|
|
563
|
+
interface UnitExprContext {
|
|
564
|
+
unitSystem?: UnitSystem;
|
|
565
|
+
/** What `100%` is, in base units. A `%` term throws without it, unless the
|
|
566
|
+
* unit system carries a `%` unit of its own. */
|
|
567
|
+
percentOf?: number;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Parse arithmetic over unit-tagged numbers: `+ - * /` (also `− × ÷`),
|
|
571
|
+
* parentheses, and adjacent terms read as one compound value (`5ft 3in`),
|
|
572
|
+
* with a leading sign carried across all of them. Throws on text it cannot
|
|
573
|
+
* read. Unit names are not checked here — resolving does that.
|
|
574
|
+
*/
|
|
575
|
+
declare function parseUnitExpr(text: string): UnitExpr;
|
|
576
|
+
/**
|
|
577
|
+
* An expression's value in base units. A bare number beside `+` or `-` is
|
|
578
|
+
* read as base units, as it is everywhere a `UnitValue` goes, and beside `*`
|
|
579
|
+
* or `/` as a scalar. A measure over a measure is a scalar; a product of two
|
|
580
|
+
* measures, or a scalar over a measure, throws.
|
|
581
|
+
*/
|
|
582
|
+
declare function resolveUnitExpr(expr: UnitExpr | string, ctx?: UnitExprContext): number;
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* A unit system nested in another: a sub-assembly drawn in millimeters inside
|
|
586
|
+
* a scene measured in inches. Values inside are read against `system`, and
|
|
587
|
+
* `scale` converts at the boundary.
|
|
588
|
+
*/
|
|
589
|
+
interface UnitScope {
|
|
590
|
+
system: UnitSystem;
|
|
591
|
+
/** What one of this scope's base units is worth in the enclosing scope's
|
|
592
|
+
* base units. Absent is 1, which is also what the outermost scope has. */
|
|
593
|
+
scale?: UnitEntry;
|
|
594
|
+
}
|
|
595
|
+
/** A measure in the innermost scope's base units, in the outermost's. `scopes` runs outermost first. */
|
|
596
|
+
declare function toRoot(base: number, scopes: readonly UnitScope[]): number;
|
|
597
|
+
/** The inverse of {@link toRoot}: an outermost-scope measure in the innermost scope's base units. */
|
|
598
|
+
declare function fromRoot(rootBase: number, scopes: readonly UnitScope[]): number;
|
|
599
|
+
/**
|
|
600
|
+
* Resolve `v` where it was written — against the innermost scope's unit
|
|
601
|
+
* system, so `ft` inside a metric sub-assembly is an error rather than a
|
|
602
|
+
* silent inch measure — and return it in the outermost scope's base units.
|
|
603
|
+
*/
|
|
604
|
+
declare function resolveScoped(v: UnitValue | Quantity, scopes: readonly UnitScope[]): number;
|
|
605
|
+
/** A measure in the outermost scope's base units, as text in a unit of the innermost scope. */
|
|
606
|
+
declare function formatScoped(rootBase: number, displayUnit: Unit, scopes: readonly UnitScope[], opts?: {
|
|
607
|
+
precision?: number;
|
|
608
|
+
suffix?: boolean;
|
|
609
|
+
}): string;
|
|
610
|
+
/** How {@link scopesOf} reads a tree it knows nothing else about. */
|
|
611
|
+
interface ScopeTree<Id> {
|
|
612
|
+
parentOf(id: Id): Id | null | undefined;
|
|
613
|
+
/** The scope `id` declares for what it holds, when it declares one. */
|
|
614
|
+
scopeOf(id: Id): UnitScope | undefined;
|
|
615
|
+
/** Count `id`'s own scope too — for a value written inside it rather than
|
|
616
|
+
* one that places it in its parent. Default false. */
|
|
617
|
+
inclusive?: boolean;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* The scopes enclosing `id`, outermost first, under `root`: what
|
|
621
|
+
* {@link resolveScoped} takes for a value that positions or sizes `id`. A
|
|
622
|
+
* node's own scope covers its children, not itself, unless `inclusive`.
|
|
623
|
+
*/
|
|
624
|
+
declare function scopesOf<Id>(id: Id, root: UnitScope, tree: ScopeTree<Id>): UnitScope[];
|
|
625
|
+
|
|
626
|
+
/** Options for presenting a range. */
|
|
627
|
+
interface RangeOptions extends PresentOptions {
|
|
628
|
+
/** What goes between the ends. Default an en dash, spaced when an end is negative. */
|
|
629
|
+
separator?: string;
|
|
630
|
+
/** The separator read aloud. Default ` to `. */
|
|
631
|
+
spokenSeparator?: string;
|
|
632
|
+
}
|
|
633
|
+
/** Options for presenting a vector. */
|
|
634
|
+
interface VectorOptions extends PresentOptions {
|
|
635
|
+
/** What goes between members. Default `, `. */
|
|
636
|
+
separator?: string;
|
|
637
|
+
/** A name before each member, by position: `['x', 'y']`. */
|
|
638
|
+
labels?: readonly string[];
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* A range as one run of parts: both ends under their own display, else
|
|
642
|
+
* `fallback`, around a `separator` part. A unit or percent sign both ends
|
|
643
|
+
* share is written once, after the second: `2–5ft`.
|
|
644
|
+
*/
|
|
645
|
+
declare function rangeParts(from: Quantity, to: Quantity, fallback?: Display, options?: RangeOptions): Part[];
|
|
646
|
+
/** A range as text, spoken text, and HTML: a `data-composite="range"` span
|
|
647
|
+
* holding a `<data value>` for each end and a `separator` part between. */
|
|
648
|
+
declare function presentRange(from: Quantity, to: Quantity, fallback?: Display, options?: RangeOptions): Presentation;
|
|
649
|
+
/** A vector as one run of parts: every member under its own display, else
|
|
650
|
+
* `fallback`, with a `separator` part between and a `label` part before each
|
|
651
|
+
* member that has one. */
|
|
652
|
+
declare function vectorParts(members: readonly Quantity[], fallback?: Display, options?: VectorOptions): Part[];
|
|
653
|
+
/** A vector as text, spoken text, and HTML: a `data-composite="vector"` span
|
|
654
|
+
* holding a `<data value>` for each member. */
|
|
655
|
+
declare function presentVector(members: readonly Quantity[], fallback?: Display, options?: VectorOptions): Presentation;
|
|
656
|
+
|
|
657
|
+
export { ANGLE_RADIANS, type BytesDisplay, CONSTANTS, type CompactDisplay, type CurrencyDisplay, type DecimalDisplay, type Display, type DisplayKind, type DurationDisplay, type FormatContext, type FractionDisplay, type FractionOptions, IMPERIAL_INCHES, type InfinityText, type IntegerDisplay, METRIC_MM, MINUS_SIGN, type MultiplierDisplay, type NamedConstant, type OrdinalDisplay, PIXELS, type Part, type PerAxis, type PercentDisplay, type PlaneUnits, type PlaneValue, type PresentOptions, type Presentation, type Quantity, QuantityView, type RangeOptions, type RatioDisplay, type RomanDisplay, type ScopeTree, type Tagged, type Unit, type UnitDisplay, type UnitEntry, type UnitExpr, type UnitExprContext, type UnitScale, type UnitScope, type UnitSystem, type UnitTable, type UnitValue, type VectorOptions, type ZoomDisplay, amount, axisUnits, bytes, bytesKind, compact, compactKind, currency, currencyKind, decimal, decimalKind, displayKindOf, displayOf, duration, durationKind, endless, entryScale, formatAxes, formatCompact, formatNumber, formatScoped, formatUnit, fraction, fractionKind, fromRoman, fromRoot, integer, integerKind, intlParts, multiplier, multiplierKind, nearestFraction, numberPart, ordinal, ordinalKind, parseAs, parseNumber, parseSignedNumber, parseUnitExpr, partsOf, partsToHtml, percent, percentKind, plural, present, presentRange, presentVector, qty, rangeParts, ratio, ratioKind, registerDisplayKind, resolveAxes, resolveScoped, resolveUnit, resolveUnitExpr, retag, roman, romanKind, scopesOf, signed, spokenSign, tag, textOf, toRoman, toRoot, unit, unitKind, unitOf, unitScale, vectorParts, zoom, zoomKind };
|
package/dist/index.js
CHANGED
|
@@ -52,11 +52,30 @@ function formatUnit(baseValue, displayUnit, unitSystem, opts) {
|
|
|
52
52
|
const suffix = opts?.suffix ?? true;
|
|
53
53
|
const display = (baseValue - offset) / factor;
|
|
54
54
|
let s = display.toFixed(precision);
|
|
55
|
+
if (Number(s) === 0) s = s.replace(/^-/, "");
|
|
55
56
|
if (s.includes(".")) {
|
|
56
57
|
s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
57
58
|
}
|
|
58
59
|
return suffix ? `${s}${displayUnit}` : s;
|
|
59
60
|
}
|
|
61
|
+
function axisUnits(units, axis) {
|
|
62
|
+
return units === void 0 || "base" in units ? units : units[axis];
|
|
63
|
+
}
|
|
64
|
+
function resolveAxes(v, units) {
|
|
65
|
+
const perAxis = typeof v === "object" && "x" in v;
|
|
66
|
+
const on = (axis) => {
|
|
67
|
+
try {
|
|
68
|
+
return resolveUnit(perAxis ? v[axis] : v, axisUnits(units, axis));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
throw new Error(`${axis} axis: ${e instanceof Error ? e.message : String(e)}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return { x: on("x"), y: on("y") };
|
|
74
|
+
}
|
|
75
|
+
function formatAxes(base, display, units, opts) {
|
|
76
|
+
const on = (axis) => formatUnit(base[axis], typeof display === "string" ? display : display[axis], axisUnits(units, axis), opts);
|
|
77
|
+
return { x: on("x"), y: on("y") };
|
|
78
|
+
}
|
|
60
79
|
function scaleOrThrow(caller, unitSystem, unit2) {
|
|
61
80
|
try {
|
|
62
81
|
return unitScale(unitSystem, unit2);
|
|
@@ -84,18 +103,23 @@ var PIXELS = {
|
|
|
84
103
|
// src/number.ts
|
|
85
104
|
var MINUS_SIGN = "\u2212";
|
|
86
105
|
function formatNumber(value, options) {
|
|
87
|
-
const formatted = Number.isFinite(value) ? value.toLocaleString(void 0, options) : String(value);
|
|
106
|
+
const formatted = Number.isFinite(value) ? unsignedZero(value, (n) => n.toLocaleString(void 0, options)) : String(value);
|
|
88
107
|
return formatted.replace(/^-/, MINUS_SIGN);
|
|
89
108
|
}
|
|
109
|
+
function unsignedZero(value, format) {
|
|
110
|
+
const shown = format(value);
|
|
111
|
+
return value <= 0 && shown === format(-0) ? format(0) : shown;
|
|
112
|
+
}
|
|
90
113
|
function parseSignedNumber(text) {
|
|
91
114
|
return Number(text.replace(MINUS_SIGN, "-"));
|
|
92
115
|
}
|
|
93
116
|
var TERM_NUMBER = String.raw`\d+(?:\.\d+)?|\.\d+`;
|
|
117
|
+
var DECIMAL_NUMBER = /^[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?$/i;
|
|
94
118
|
var EXPONENTS = { k: 3, m: 6, b: 9, t: 12 };
|
|
95
119
|
function formatCompact(value, decimals = 0) {
|
|
96
120
|
if (!Number.isFinite(value)) return formatNumber(value);
|
|
97
|
-
const options = Math.abs(value) < 1e3 ? { useGrouping: false, minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { notation: "compact", minimumSignificantDigits: 3, maximumSignificantDigits: 3 };
|
|
98
|
-
return value.toLocaleString("en-US", options).replace(/^-/, MINUS_SIGN);
|
|
121
|
+
const options = Number(Math.abs(value).toFixed(decimals)) < 1e3 ? { useGrouping: false, minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { notation: "compact", minimumSignificantDigits: 3, maximumSignificantDigits: 3 };
|
|
122
|
+
return unsignedZero(value, (n) => n.toLocaleString("en-US", options)).replace(/^-/, MINUS_SIGN);
|
|
99
123
|
}
|
|
100
124
|
function parseNumber(text, units, locale = "en-US") {
|
|
101
125
|
const n = readNumber(delocalized(text, locale), units);
|
|
@@ -116,8 +140,9 @@ function readNumber(text, units) {
|
|
|
116
140
|
let t = text.trim().replace(MINUS_SIGN, "-");
|
|
117
141
|
const exponent = EXPONENTS[t.slice(-1).toLowerCase()];
|
|
118
142
|
if (exponent !== void 0) t = t.slice(0, -1).trimEnd();
|
|
119
|
-
if (
|
|
120
|
-
|
|
143
|
+
if (!DECIMAL_NUMBER.test(t)) return Number.NaN;
|
|
144
|
+
const n = Number(exponent === void 0 ? t : `${t}e${exponent}`);
|
|
145
|
+
return Number.isFinite(n) ? n : Number.NaN;
|
|
121
146
|
}
|
|
122
147
|
var shapes = /* @__PURE__ */ new Map();
|
|
123
148
|
function shapeOf(locale) {
|
|
@@ -158,6 +183,14 @@ function latinDigits(text, locale) {
|
|
|
158
183
|
return digits ? t.replace(digits.pattern, (c) => digits.latin.get(c)) : t;
|
|
159
184
|
}
|
|
160
185
|
var BIDI_MARKS = /[\u061c\u200e\u200f]/g;
|
|
186
|
+
function numberWithin(text, locale) {
|
|
187
|
+
const t = latinDigits(text, locale);
|
|
188
|
+
const m = new RegExp(shapeOf(locale).run.source).exec(t);
|
|
189
|
+
if (!m) return "";
|
|
190
|
+
const around = t.slice(0, m.index) + t.slice(m.index + m[0].length);
|
|
191
|
+
const suffix = /^[kmbt](?!\p{L})/iu.exec(t.slice(m.index + m[0].length))?.[0] ?? "";
|
|
192
|
+
return `${/[-\u2212]/.test(around) ? "-" : ""}${m[0]}${suffix}`;
|
|
193
|
+
}
|
|
161
194
|
var decimalOf = (locale) => shapeOf(locale).decimal;
|
|
162
195
|
var SPELLINGS = [
|
|
163
196
|
["in", '"', "\u2033", "\u201D"],
|
|
@@ -213,11 +246,15 @@ function unitSuffixOf(text, units) {
|
|
|
213
246
|
}
|
|
214
247
|
|
|
215
248
|
// src/kind.ts
|
|
249
|
+
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """ };
|
|
250
|
+
function escapeMarkup(s) {
|
|
251
|
+
return s.replace(/[&<>"]/g, (c) => ESCAPES[c]);
|
|
252
|
+
}
|
|
216
253
|
function textOf(parts) {
|
|
217
254
|
return parts.map((p) => p.value).join("");
|
|
218
255
|
}
|
|
219
256
|
function numberPart(value, locale, options) {
|
|
220
|
-
return { type: "number", value: signed(value.toLocaleString(locale, options)) };
|
|
257
|
+
return { type: "number", value: signed(unsignedZero(value, (n) => n.toLocaleString(locale, options))) };
|
|
221
258
|
}
|
|
222
259
|
function signed(text) {
|
|
223
260
|
return text.replace(/^-/, MINUS_SIGN);
|
|
@@ -230,7 +267,9 @@ function plural(value, locale, singular, pluralForm) {
|
|
|
230
267
|
}
|
|
231
268
|
function intlParts(value, locale, options) {
|
|
232
269
|
const out = [];
|
|
233
|
-
|
|
270
|
+
const nf = new Intl.NumberFormat(locale, options);
|
|
271
|
+
const shown = value <= 0 && nf.format(value) === nf.format(-0) ? 0 : value;
|
|
272
|
+
for (const p of nf.formatToParts(shown)) {
|
|
234
273
|
const type = DIGIT_RUNS.has(p.type) ? "number" : p.type;
|
|
235
274
|
const v = p.type === "minusSign" ? MINUS_SIGN : p.value;
|
|
236
275
|
const last = out[out.length - 1];
|
|
@@ -306,8 +345,12 @@ var percentKind = {
|
|
|
306
345
|
})
|
|
307
346
|
)
|
|
308
347
|
),
|
|
309
|
-
parse: (text, _d, ctx) => parseNumber(text.
|
|
348
|
+
parse: (text, _d, ctx) => parseNumber(withoutPercentSign(text, ctx.locale), void 0, ctx.locale) / 100
|
|
310
349
|
};
|
|
350
|
+
function withoutPercentSign(text, locale) {
|
|
351
|
+
const m = /^\s*([-+\u2212]?)\s*[%٪]?\s*(.*?)\s*[%٪]?\s*$/su.exec(latinDigits(text, locale));
|
|
352
|
+
return m ? m[1] + m[2] : text;
|
|
353
|
+
}
|
|
311
354
|
function nearestFraction(x, maxDenominator) {
|
|
312
355
|
if (!Number.isFinite(x)) return [x, 1];
|
|
313
356
|
const sign = x < 0 ? -1 : 1;
|
|
@@ -421,7 +464,7 @@ var fractionKind = {
|
|
|
421
464
|
const { negative, whole, num, den, constant } = fractionPieces(value, d);
|
|
422
465
|
const counted = (n) => {
|
|
423
466
|
if (!constant) return `<mn>${n}</mn>`;
|
|
424
|
-
const mi = `<mi>${constant.symbol}</mi>`;
|
|
467
|
+
const mi = `<mi>${escapeMarkup(constant.symbol)}</mi>`;
|
|
425
468
|
return n === 1 ? mi : `<mn>${n}</mn>${mi}`;
|
|
426
469
|
};
|
|
427
470
|
const body = [
|
|
@@ -466,14 +509,20 @@ var multiplierKind = {
|
|
|
466
509
|
{ type: "symbol", value: d.symbol ?? "\xD7" }
|
|
467
510
|
],
|
|
468
511
|
speak: (value, d, ctx) => spokenSign(`${numberPart(value, ctx.locale, { maximumFractionDigits: d.places ?? 2 }).value} times`),
|
|
469
|
-
parse: (text,
|
|
512
|
+
parse: (text, d, ctx) => {
|
|
513
|
+
const own = d.symbol !== void 0 && text.trimEnd().endsWith(d.symbol) ? text.trimEnd().slice(0, -d.symbol.length) : text.replace(/\s*[x×X]\s*$/, "");
|
|
514
|
+
return parseNumber(own, void 0, ctx.locale);
|
|
515
|
+
}
|
|
470
516
|
};
|
|
471
517
|
function zoom() {
|
|
472
518
|
return { kind: "zoom" };
|
|
473
519
|
}
|
|
474
520
|
function zoomParts(z) {
|
|
475
521
|
if (!Number.isFinite(z)) return [{ type: "number", value: String(z) }];
|
|
476
|
-
const n = (v, grouping = false) => ({
|
|
522
|
+
const n = (v, grouping = false) => ({
|
|
523
|
+
type: "number",
|
|
524
|
+
value: unsignedZero(v, (x) => x.toLocaleString("en-US", { useGrouping: grouping, maximumFractionDigits: 1 })).replace(/^-/, MINUS_SIGN)
|
|
525
|
+
});
|
|
477
526
|
if (z <= 2) return [n(Math.round(z * 100)), { type: "percentSign", value: "%" }];
|
|
478
527
|
if (z >= 100) return [n(Math.round(z), true), { type: "symbol", value: "x" }];
|
|
479
528
|
return [n(Math.round(z * 10) / 10), { type: "symbol", value: "x" }];
|
|
@@ -570,7 +619,7 @@ var currencyKind = {
|
|
|
570
619
|
kind: "currency",
|
|
571
620
|
format: (value, d, ctx) => intlParts(value, ctx.locale, currencyOptions(d)),
|
|
572
621
|
speak: (value, d, ctx) => spokenSign(textOf(intlParts(value, ctx.locale, { ...currencyOptions(d), currencyDisplay: "name" }))),
|
|
573
|
-
parse: (text, _d, ctx) => parseNumber(text
|
|
622
|
+
parse: (text, _d, ctx) => parseNumber(numberWithin(text, ctx.locale), void 0, ctx.locale)
|
|
574
623
|
};
|
|
575
624
|
function duration(options = {}) {
|
|
576
625
|
return { kind: "duration", ...options };
|
|
@@ -638,14 +687,21 @@ var BINARY_NAMES = ["byte", "kibibyte", "mebibyte", "gibibyte", "tebibyte", "peb
|
|
|
638
687
|
function bytes(options = {}) {
|
|
639
688
|
return { kind: "bytes", ...options };
|
|
640
689
|
}
|
|
641
|
-
function byteScale(
|
|
690
|
+
function byteScale(d, value) {
|
|
691
|
+
const base = d.base ?? 1e3;
|
|
692
|
+
const last = DECIMAL_BYTES.length - 1;
|
|
693
|
+
const placesAt = (step2) => step2 === 0 ? 0 : d.places ?? 1;
|
|
642
694
|
let step = 0;
|
|
643
695
|
let scaled2 = value;
|
|
644
|
-
while (Math.abs(scaled2) >= base && step <
|
|
696
|
+
while (Math.abs(scaled2) >= base && step < last) {
|
|
697
|
+
scaled2 /= base;
|
|
698
|
+
step++;
|
|
699
|
+
}
|
|
700
|
+
if (step < last && Number(Math.abs(scaled2).toFixed(placesAt(step))) >= base) {
|
|
645
701
|
scaled2 /= base;
|
|
646
702
|
step++;
|
|
647
703
|
}
|
|
648
|
-
return { scaled: scaled2, step };
|
|
704
|
+
return { scaled: scaled2, step, places: placesAt(step) };
|
|
649
705
|
}
|
|
650
706
|
var BYTE_UNITS = Object.fromEntries([
|
|
651
707
|
...DECIMAL_BYTES.map((u, i) => [u, 1e3 ** i]),
|
|
@@ -655,20 +711,18 @@ var BYTE_UNITS = Object.fromEntries([
|
|
|
655
711
|
var bytesKind = {
|
|
656
712
|
kind: "bytes",
|
|
657
713
|
format: (value, d, ctx) => {
|
|
658
|
-
const
|
|
659
|
-
const
|
|
660
|
-
const units = base === 1024 ? BINARY_BYTES : DECIMAL_BYTES;
|
|
714
|
+
const { scaled: scaled2, step, places } = byteScale(d, value);
|
|
715
|
+
const units = d.base === 1024 ? BINARY_BYTES : DECIMAL_BYTES;
|
|
661
716
|
return [
|
|
662
|
-
numberPart(scaled2, ctx.locale, { maximumFractionDigits:
|
|
717
|
+
numberPart(scaled2, ctx.locale, { maximumFractionDigits: places }),
|
|
663
718
|
{ type: "literal", value: " " },
|
|
664
719
|
{ type: "unit", value: units[step] }
|
|
665
720
|
];
|
|
666
721
|
},
|
|
667
722
|
speak: (value, d, ctx) => {
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
const number = numberPart(scaled2, ctx.locale, { maximumFractionDigits: step === 0 ? 0 : d.places ?? 1 }).value;
|
|
723
|
+
const { scaled: scaled2, step, places } = byteScale(d, value);
|
|
724
|
+
const name = (d.base === 1024 ? BINARY_NAMES : DECIMAL_NAMES)[step];
|
|
725
|
+
const number = numberPart(scaled2, ctx.locale, { maximumFractionDigits: places }).value;
|
|
672
726
|
return spokenSign(`${number} ${plural(Math.abs(parseNumber(number, void 0, ctx.locale)), ctx.locale, name, `${name}s`)}`);
|
|
673
727
|
},
|
|
674
728
|
parse: (text, _d, ctx) => parseNumber(text, BYTE_UNITS, ctx.locale)
|
|
@@ -809,13 +863,9 @@ function contextOf(q, options) {
|
|
|
809
863
|
const unit2 = unitOf(q);
|
|
810
864
|
return unit2 === void 0 ? { locale: options?.locale ?? "en-US" } : { locale: options?.locale ?? "en-US", unit: unit2 };
|
|
811
865
|
}
|
|
812
|
-
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """ };
|
|
813
|
-
function escape2(s) {
|
|
814
|
-
return s.replace(/[&<>"]/g, (c) => ESCAPES[c]);
|
|
815
|
-
}
|
|
816
866
|
function partsToHtml(value, parts) {
|
|
817
|
-
const body = parts.map((p) => p.type === "literal" ?
|
|
818
|
-
return `<data value="${
|
|
867
|
+
const body = parts.map((p) => p.type === "literal" ? escapeMarkup(p.value) : `<span data-part="${escapeMarkup(p.type)}">${escapeMarkup(p.value)}</span>`).join("");
|
|
868
|
+
return `<data value="${escapeMarkup(String(value))}">${body}</data>`;
|
|
819
869
|
}
|
|
820
870
|
function partsOf(q, fallback, options) {
|
|
821
871
|
const display = displayOf(q, fallback) ?? DEFAULT_DISPLAY;
|
|
@@ -915,6 +965,231 @@ function qty(q, fallback, options) {
|
|
|
915
965
|
return new QuantityView(q, fallback, options);
|
|
916
966
|
}
|
|
917
967
|
|
|
918
|
-
|
|
968
|
+
// src/unitExpr.ts
|
|
969
|
+
var TOKEN = /\s*(?:(\d+(?:\.\d+)?|\.\d+)\s*([^\s\d.+\-−*×/÷()]+)?|([+\-−*×/÷()]))/y;
|
|
970
|
+
var OPS = { [MINUS_SIGN]: "-", "\xD7": "*", "\xF7": "/" };
|
|
971
|
+
function tokenize(text) {
|
|
972
|
+
const tokens = [];
|
|
973
|
+
let at = 0;
|
|
974
|
+
while (text.slice(at).trim() !== "") {
|
|
975
|
+
TOKEN.lastIndex = at;
|
|
976
|
+
const m = TOKEN.exec(text);
|
|
977
|
+
if (!m) throw new Error(`unitExpr: cannot read '${text.slice(at).trim()}' in '${text}'`);
|
|
978
|
+
at = TOKEN.lastIndex;
|
|
979
|
+
if (m[1] === void 0) tokens.push({ kind: "op", op: OPS[m[3]] ?? m[3] });
|
|
980
|
+
else if (m[2] === void 0) tokens.push({ kind: "value", value: Number(m[1]) });
|
|
981
|
+
else tokens.push({ kind: "value", value: Number(m[1]), unit: m[2] });
|
|
982
|
+
}
|
|
983
|
+
return tokens;
|
|
984
|
+
}
|
|
985
|
+
function parseUnitExpr(text) {
|
|
986
|
+
const tokens = tokenize(text);
|
|
987
|
+
let i = 0;
|
|
988
|
+
const isOp = (op) => {
|
|
989
|
+
const t = tokens[i];
|
|
990
|
+
return t !== void 0 && t.kind === "op" && t.op === op;
|
|
991
|
+
};
|
|
992
|
+
const fail = () => {
|
|
993
|
+
throw new Error(`unitExpr: '${text}' is not a complete expression`);
|
|
994
|
+
};
|
|
995
|
+
const sum = () => {
|
|
996
|
+
let left = product();
|
|
997
|
+
while (isOp("+") || isOp("-")) {
|
|
998
|
+
const op = isOp("+") ? "add" : "sub";
|
|
999
|
+
i++;
|
|
1000
|
+
left = { op, left, right: product() };
|
|
1001
|
+
}
|
|
1002
|
+
return left;
|
|
1003
|
+
};
|
|
1004
|
+
const product = () => {
|
|
1005
|
+
let left = unary();
|
|
1006
|
+
while (isOp("*") || isOp("/")) {
|
|
1007
|
+
const op = isOp("*") ? "mul" : "div";
|
|
1008
|
+
i++;
|
|
1009
|
+
left = { op, left, right: unary() };
|
|
1010
|
+
}
|
|
1011
|
+
return left;
|
|
1012
|
+
};
|
|
1013
|
+
const unary = () => {
|
|
1014
|
+
if (isOp("+")) {
|
|
1015
|
+
i++;
|
|
1016
|
+
return unary();
|
|
1017
|
+
}
|
|
1018
|
+
if (!isOp("-")) return primary();
|
|
1019
|
+
i++;
|
|
1020
|
+
const of = unary();
|
|
1021
|
+
return of.op === "value" ? { ...of, value: -of.value } : { op: "neg", of };
|
|
1022
|
+
};
|
|
1023
|
+
const primary = () => {
|
|
1024
|
+
if (isOp("(")) {
|
|
1025
|
+
i++;
|
|
1026
|
+
const inner = sum();
|
|
1027
|
+
if (!isOp(")")) fail();
|
|
1028
|
+
i++;
|
|
1029
|
+
return inner;
|
|
1030
|
+
}
|
|
1031
|
+
let left = literal();
|
|
1032
|
+
while (tokens[i]?.kind === "value") left = { op: "add", left, right: literal() };
|
|
1033
|
+
return left;
|
|
1034
|
+
};
|
|
1035
|
+
const literal = () => {
|
|
1036
|
+
const t = tokens[i];
|
|
1037
|
+
if (t === void 0 || t.kind !== "value") return fail();
|
|
1038
|
+
i++;
|
|
1039
|
+
return t.unit === void 0 ? { op: "value", value: t.value } : { op: "value", value: t.value, unit: t.unit };
|
|
1040
|
+
};
|
|
1041
|
+
const expr = sum();
|
|
1042
|
+
if (i !== tokens.length) fail();
|
|
1043
|
+
return expr;
|
|
1044
|
+
}
|
|
1045
|
+
function resolveNode(expr, ctx) {
|
|
1046
|
+
if (expr.op === "value") return resolveValue(expr.value, expr.unit, ctx);
|
|
1047
|
+
if (expr.op === "neg") {
|
|
1048
|
+
const of = amountOf(resolveNode(expr.of, ctx));
|
|
1049
|
+
return { ...of, n: -of.n };
|
|
1050
|
+
}
|
|
1051
|
+
const l = amountOf(resolveNode(expr.left, ctx));
|
|
1052
|
+
const r = amountOf(resolveNode(expr.right, ctx));
|
|
1053
|
+
switch (expr.op) {
|
|
1054
|
+
case "add":
|
|
1055
|
+
return { n: l.n + r.n, measure: l.measure || r.measure, offset: false };
|
|
1056
|
+
case "sub":
|
|
1057
|
+
return { n: l.n - r.n, measure: l.measure || r.measure, offset: false };
|
|
1058
|
+
case "mul":
|
|
1059
|
+
if (l.measure && r.measure) throw new Error("unitExpr: cannot multiply two measures");
|
|
1060
|
+
return { n: l.n * r.n, measure: l.measure || r.measure, offset: false };
|
|
1061
|
+
case "div":
|
|
1062
|
+
if (r.measure && !l.measure) throw new Error("unitExpr: cannot divide by a measure");
|
|
1063
|
+
return { n: l.n / r.n, measure: l.measure && !r.measure, offset: false };
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function amountOf(r) {
|
|
1067
|
+
if (r.offset) {
|
|
1068
|
+
throw new Error("unitExpr: a unit with an offset names a point, and has no place in arithmetic");
|
|
1069
|
+
}
|
|
1070
|
+
return r;
|
|
1071
|
+
}
|
|
1072
|
+
function resolveValue(value, unit2, ctx) {
|
|
1073
|
+
if (unit2 === void 0) return { n: value, measure: false, offset: false };
|
|
1074
|
+
const units = ctx.unitSystem ? withSpellings(ctx.unitSystem.units) : void 0;
|
|
1075
|
+
if (unit2 === "%" && units?.["%"] === void 0) {
|
|
1076
|
+
if (ctx.percentOf === void 0) {
|
|
1077
|
+
throw new Error(`unitExpr: ${value}% needs a percentOf to resolve against`);
|
|
1078
|
+
}
|
|
1079
|
+
return { n: value / 100 * ctx.percentOf, measure: true, offset: false };
|
|
1080
|
+
}
|
|
1081
|
+
if (!ctx.unitSystem || !units) throw new Error(`unitExpr: ${value}${unit2} requires a UnitSystem`);
|
|
1082
|
+
let scale;
|
|
1083
|
+
try {
|
|
1084
|
+
scale = unitScale({ base: ctx.unitSystem.base, units }, unit2);
|
|
1085
|
+
} catch (e) {
|
|
1086
|
+
throw new Error(`unitExpr: ${e instanceof Error ? e.message : String(e)}`);
|
|
1087
|
+
}
|
|
1088
|
+
return { n: value * scale.factor + scale.offset, measure: true, offset: scale.offset !== 0 };
|
|
1089
|
+
}
|
|
1090
|
+
function resolveUnitExpr(expr, ctx = {}) {
|
|
1091
|
+
return resolveNode(typeof expr === "string" ? parseUnitExpr(expr) : expr, ctx).n;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/scope.ts
|
|
1095
|
+
function toRoot(base, scopes) {
|
|
1096
|
+
let n = base;
|
|
1097
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
1098
|
+
const { factor, offset } = entryScale(scopes[i].scale ?? 1);
|
|
1099
|
+
n = n * factor + offset;
|
|
1100
|
+
}
|
|
1101
|
+
return n;
|
|
1102
|
+
}
|
|
1103
|
+
function fromRoot(rootBase, scopes) {
|
|
1104
|
+
let n = rootBase;
|
|
1105
|
+
for (const scope of scopes) {
|
|
1106
|
+
const { factor, offset } = entryScale(scope.scale ?? 1);
|
|
1107
|
+
n = (n - offset) / factor;
|
|
1108
|
+
}
|
|
1109
|
+
return n;
|
|
1110
|
+
}
|
|
1111
|
+
function resolveScoped(v, scopes) {
|
|
1112
|
+
return toRoot(resolveUnit(v, scopes[scopes.length - 1]?.system), scopes);
|
|
1113
|
+
}
|
|
1114
|
+
function formatScoped(rootBase, displayUnit, scopes, opts) {
|
|
1115
|
+
return formatUnit(fromRoot(rootBase, scopes), displayUnit, scopes[scopes.length - 1].system, opts);
|
|
1116
|
+
}
|
|
1117
|
+
function scopesOf(id, root, tree) {
|
|
1118
|
+
const inner = [];
|
|
1119
|
+
let at = tree.inclusive ? id : tree.parentOf(id);
|
|
1120
|
+
while (at !== null && at !== void 0) {
|
|
1121
|
+
const scope = tree.scopeOf(at);
|
|
1122
|
+
if (scope) inner.push(scope);
|
|
1123
|
+
at = tree.parentOf(at);
|
|
1124
|
+
}
|
|
1125
|
+
return [root, ...inner.reverse()];
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// src/composite.ts
|
|
1129
|
+
var SEPARATOR = "separator";
|
|
1130
|
+
var MEASURE = /* @__PURE__ */ new Set(["unit", "percentSign"]);
|
|
1131
|
+
function measureTail(parts) {
|
|
1132
|
+
let at = parts.length;
|
|
1133
|
+
while (at > 0 && (MEASURE.has(parts[at - 1].type) || parts[at - 1].type === "literal")) at--;
|
|
1134
|
+
const tail = parts.slice(at);
|
|
1135
|
+
return tail.some((p) => MEASURE.has(p.type)) ? tail : [];
|
|
1136
|
+
}
|
|
1137
|
+
function rangeEnds(from, to, fallback, options) {
|
|
1138
|
+
const a = partsOf(from, fallback, options);
|
|
1139
|
+
const b = partsOf(to, fallback, options);
|
|
1140
|
+
const tail = measureTail(a);
|
|
1141
|
+
const shared = tail.length > 0 && textOf(tail) === textOf(measureTail(b));
|
|
1142
|
+
return [shared ? a.slice(0, a.length - tail.length) : a, b];
|
|
1143
|
+
}
|
|
1144
|
+
function rangeSeparator(from, to, options) {
|
|
1145
|
+
return options?.separator ?? (amount(from) < 0 || amount(to) < 0 ? " \u2013 " : "\u2013");
|
|
1146
|
+
}
|
|
1147
|
+
function rangeParts(from, to, fallback, options) {
|
|
1148
|
+
const [a, b] = rangeEnds(from, to, fallback, options);
|
|
1149
|
+
return [...a, { type: SEPARATOR, value: rangeSeparator(from, to, options) }, ...b];
|
|
1150
|
+
}
|
|
1151
|
+
function presentRange(from, to, fallback, options) {
|
|
1152
|
+
const [a, b] = rangeEnds(from, to, fallback, options);
|
|
1153
|
+
const separator = rangeSeparator(from, to, options);
|
|
1154
|
+
const spoken = [from, to].map((q) => present(q, fallback, options).spoken);
|
|
1155
|
+
return {
|
|
1156
|
+
text: `${textOf(a)}${separator}${textOf(b)}`,
|
|
1157
|
+
spoken: spoken.join(options?.spokenSeparator ?? " to "),
|
|
1158
|
+
html: compositeHtml("range", [partsToHtml(amount(from), a), partsToHtml(amount(to), b)], separator)
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function vectorParts(members, fallback, options) {
|
|
1162
|
+
const separator = options?.separator ?? ", ";
|
|
1163
|
+
return members.flatMap((q, i) => [
|
|
1164
|
+
...i > 0 ? [{ type: SEPARATOR, value: separator }] : [],
|
|
1165
|
+
...labelParts(options?.labels?.[i]),
|
|
1166
|
+
...partsOf(q, fallback, options)
|
|
1167
|
+
]);
|
|
1168
|
+
}
|
|
1169
|
+
function presentVector(members, fallback, options) {
|
|
1170
|
+
const separator = options?.separator ?? ", ";
|
|
1171
|
+
const label = (i) => textOf(labelParts(options?.labels?.[i]));
|
|
1172
|
+
return {
|
|
1173
|
+
text: textOf(vectorParts(members, fallback, options)),
|
|
1174
|
+
spoken: members.map((q, i) => `${label(i)}${present(q, fallback, options).spoken}`).join(separator),
|
|
1175
|
+
html: compositeHtml(
|
|
1176
|
+
"vector",
|
|
1177
|
+
members.map((q, i) => `${partsHtml(labelParts(options?.labels?.[i]))}${present(q, fallback, options).html}`),
|
|
1178
|
+
separator
|
|
1179
|
+
)
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function labelParts(label) {
|
|
1183
|
+
return label === void 0 ? [] : [{ type: "label", value: label }, { type: "literal", value: " " }];
|
|
1184
|
+
}
|
|
1185
|
+
function partsHtml(parts) {
|
|
1186
|
+
return parts.map((p) => p.type === "literal" ? escapeMarkup(p.value) : `<span data-part="${escapeMarkup(p.type)}">${escapeMarkup(p.value)}</span>`).join("");
|
|
1187
|
+
}
|
|
1188
|
+
function compositeHtml(kind, members, separator) {
|
|
1189
|
+
const between = partsHtml([{ type: SEPARATOR, value: separator }]);
|
|
1190
|
+
return `<span data-composite="${kind}">${members.join(between)}</span>`;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
export { ANGLE_RADIANS, CONSTANTS, IMPERIAL_INCHES, METRIC_MM, MINUS_SIGN, PIXELS, QuantityView, amount, axisUnits, bytes, bytesKind, compact, compactKind, currency, currencyKind, decimal, decimalKind, displayKindOf, displayOf, duration, durationKind, endless, entryScale, formatAxes, formatCompact, formatNumber, formatScoped, formatUnit, fraction, fractionKind, fromRoman, fromRoot, integer, integerKind, intlParts, multiplier, multiplierKind, nearestFraction, numberPart, ordinal, ordinalKind, parseAs, parseNumber, parseSignedNumber, parseUnitExpr, partsOf, partsToHtml, percent, percentKind, plural, present, presentRange, presentVector, qty, rangeParts, ratio, ratioKind, registerDisplayKind, resolveAxes, resolveScoped, resolveUnit, resolveUnitExpr, retag, roman, romanKind, scopesOf, signed, spokenSign, tag, textOf, toRoman, toRoot, unit, unitKind, unitOf, unitScale, vectorParts, zoom, zoomKind };
|
|
919
1194
|
//# sourceMappingURL=index.js.map
|
|
920
1195
|
//# sourceMappingURL=index.js.map
|