shelving 1.291.1 → 1.292.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.291.1",
3
+ "version": "1.292.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
package/util/format.d.ts CHANGED
@@ -89,6 +89,7 @@ export interface UnitFormatOptions extends FormatOptions, Omit<Intl.NumberFormat
89
89
  * - Unfortunately the list of supported units changes in different browsers.
90
90
  * - Ideally we want to format units using the built-in formatting so things like translation and internationalisation are covered.
91
91
  * - But we want provide fallback formatting for unsupported units, and do something _good enough_ job in most cases.
92
+ * - Compound units like `kilometer-per-hour` use the built-in formatting when both parts are supported.
92
93
  *
93
94
  * @param num Quantity to format.
94
95
  * @param unit Unit reference to format the quantity as, e.g. `"minute"` or `"product"`.
@@ -97,6 +98,20 @@ export interface UnitFormatOptions extends FormatOptions, Omit<Intl.NumberFormat
97
98
  * @see https://shelving.cc/util/format/formatUnit
98
99
  */
99
100
  export declare function formatUnit(num: number, unit: string, options?: UnitFormatOptions): string;
101
+ /**
102
+ * Format the short name of a unit on its own, e.g. `"km"` or `"min"`, with no number.
103
+ *
104
+ * - Uses the `Intl.NumberFormat` short name when the browser supports the unit, so the name is translated (e.g. `"Std."` for `hour` in German).
105
+ * - Falls back to `options.abbr`, then to the unit reference itself, when the browser does not support the unit.
106
+ * - Gives the name for an amount of one, so some units in some locales differ from their plural form (e.g. `"day"` not `"days"`).
107
+ *
108
+ * @param unit Unit reference, e.g. `"minute"` or `"product"`.
109
+ * @returns The short name of the unit, e.g. `"min"`.
110
+ * @example formatUnitAbbr("kilometer") // "km"
111
+ * @example formatUnitAbbr("dog", { abbr: "🐶" }) // "🐶"
112
+ * @see https://shelving.cc/util/format/formatUnitAbbr
113
+ */
114
+ export declare function formatUnitAbbr(unit: string, options?: UnitFormatOptions): string;
100
115
  /**
101
116
  * Options we use for currency formatting.
102
117
  *
package/util/format.js CHANGED
@@ -45,6 +45,7 @@ export function formatRange(from, to, options) {
45
45
  * - Unfortunately the list of supported units changes in different browsers.
46
46
  * - Ideally we want to format units using the built-in formatting so things like translation and internationalisation are covered.
47
47
  * - But we want provide fallback formatting for unsupported units, and do something _good enough_ job in most cases.
48
+ * - Compound units like `kilometer-per-hour` use the built-in formatting when both parts are supported.
48
49
  *
49
50
  * @param num Quantity to format.
50
51
  * @param unit Unit reference to format the quantity as, e.g. `"minute"` or `"product"`.
@@ -54,7 +55,7 @@ export function formatRange(from, to, options) {
54
55
  */
55
56
  export function formatUnit(num, unit, options) {
56
57
  // Check if the unit is supported by the browser.
57
- if (Intl.supportedValuesOf("unit").includes(unit))
58
+ if (_isIntlUnit(unit))
58
59
  return Intl.NumberFormat(options?.locale, { ...options, style: "unit", unit }).format(num);
59
60
  // Otherwise, use the default number format.
60
61
  const str = Intl.NumberFormat(options?.locale, { ...options, style: "decimal" }).format(num);
@@ -63,6 +64,43 @@ export function formatUnit(num, unit, options) {
63
64
  return `${str} ${str === "1" ? one : many}`;
64
65
  return `${str}${unitDisplay === "narrow" ? "" : " "}${abbr}`; // "short" is the default.
65
66
  }
67
+ /**
68
+ * Format the short name of a unit on its own, e.g. `"km"` or `"min"`, with no number.
69
+ *
70
+ * - Uses the `Intl.NumberFormat` short name when the browser supports the unit, so the name is translated (e.g. `"Std."` for `hour` in German).
71
+ * - Falls back to `options.abbr`, then to the unit reference itself, when the browser does not support the unit.
72
+ * - Gives the name for an amount of one, so some units in some locales differ from their plural form (e.g. `"day"` not `"days"`).
73
+ *
74
+ * @param unit Unit reference, e.g. `"minute"` or `"product"`.
75
+ * @returns The short name of the unit, e.g. `"min"`.
76
+ * @example formatUnitAbbr("kilometer") // "km"
77
+ * @example formatUnitAbbr("dog", { abbr: "🐶" }) // "🐶"
78
+ * @see https://shelving.cc/util/format/formatUnitAbbr
79
+ */
80
+ export function formatUnitAbbr(unit, options) {
81
+ if (_isIntlUnit(unit)) {
82
+ const parts = Intl.NumberFormat(options?.locale, { style: "unit", unit, unitDisplay: "short" }).formatToParts(1);
83
+ const abbr = parts
84
+ .filter(p => p.type === "unit")
85
+ .map(p => p.value)
86
+ .join("");
87
+ if (abbr)
88
+ return abbr;
89
+ }
90
+ return options?.abbr ?? unit;
91
+ }
92
+ /** Units that `Intl.NumberFormat` supports in this environment (created on first use). */
93
+ let _INTL_UNITS;
94
+ /**
95
+ * Is a unit supported by `Intl.NumberFormat` in this environment?
96
+ * - Compound units like `kilometer-per-hour` are supported when both parts are supported.
97
+ * - Returns `false` where `Intl.supportedValuesOf()` does not exist.
98
+ */
99
+ function _isIntlUnit(unit) {
100
+ _INTL_UNITS ??= new Set(typeof Intl.supportedValuesOf === "function" ? Intl.supportedValuesOf("unit") : []);
101
+ const parts = unit.split("-per-");
102
+ return parts.length <= 2 && parts.every(part => _INTL_UNITS?.has(part));
103
+ }
66
104
  /**
67
105
  * Format a currency amount (based on the user's browser language settings).
68
106
  *
package/util/units.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type UnitFormatOptions } from "./format.js";
1
+ import { type FormatOptions, type UnitFormatOptions } from "./format.js";
2
2
  import { ImmutableMap, type MapKey } from "./map.js";
3
3
  import type { ImmutableObject } from "./object.js";
4
4
  /** Conversion from one unit to another (either an amount to multiple by, or a function to convert). */
@@ -80,6 +80,17 @@ export declare class Unit<K extends string> {
80
80
  * @see https://shelving.cc/util/units/Unit/format
81
81
  */
82
82
  format(amount: number, options?: UnitFormatOptions): string;
83
+ /**
84
+ * Format the short name of this unit on its own, e.g. `km` or `min`, with no number.
85
+ * - Uses `Intl.NumberFormat` if this is a supported unit, so the name is translated.
86
+ * - Falls back to this unit's `abbr` option, then to its key.
87
+ *
88
+ * @param options Formatting options, e.g. `locale`.
89
+ * @returns The short name of this unit.
90
+ * @example LENGTH_UNITS.require("kilometer").formatAbbr() // "km"
91
+ * @see https://shelving.cc/util/units/Unit/formatAbbr
92
+ */
93
+ formatAbbr(options?: FormatOptions): string;
83
94
  }
84
95
  /**
85
96
  * Represent a list of related units of measure (e.g. all length units).
@@ -153,7 +164,7 @@ export type AngleUnitKey = MapKey<typeof ANGLE_UNITS>;
153
164
  export declare const MASS_UNITS: UnitList<"gram" | "kilogram" | "milligram" | "ounce" | "pound" | "stone">;
154
165
  export type MassUnitKey = MapKey<typeof MASS_UNITS>;
155
166
  /** Length units. */
156
- export declare const LENGTH_UNITS: UnitList<"centimeter" | "foot" | "furlong" | "inch" | "kilometer" | "meter" | "mile" | "millimeter" | "yard">;
167
+ export declare const LENGTH_UNITS: UnitList<"centimeter" | "foot" | "inch" | "kilometer" | "meter" | "mile" | "millimeter" | "yard">;
157
168
  export type LengthUnitKey = MapKey<typeof LENGTH_UNITS>;
158
169
  /** Speed units. */
159
170
  export declare const SPEED_UNITS: UnitList<"kilometer-per-hour" | "meter-per-second" | "mile-per-hour">;
@@ -162,7 +173,7 @@ export type SpeedUnitKey = MapKey<typeof SPEED_UNITS>;
162
173
  export declare const AREA_UNITS: UnitList<"acre" | "hectare" | "square-centimeter" | "square-foot" | "square-inch" | "square-kilometer" | "square-meter" | "square-millimeter" | "square-yard">;
163
174
  export type AreaUnitKey = MapKey<typeof AREA_UNITS>;
164
175
  /** Volume units. */
165
- export declare const VOLUME_UNITS: UnitList<"cubic-centimeter" | "cubic-foot" | "cubic-inch" | "cubic-meter" | "cubic-yard" | "imperial-fluid-ounce" | "imperial-gallon" | "imperial-pint" | "imperial-quart" | "liter" | "milliliter" | "us-fluid-ounce" | "us-gallon" | "us-pint" | "us-quart">;
176
+ export declare const VOLUME_UNITS: UnitList<"cubic-centimeter" | "cubic-foot" | "cubic-inch" | "cubic-meter" | "cubic-yard" | "imperial-fluid-ounce" | "imperial-gallon" | "imperial-pint" | "imperial-quart" | "liter" | "milliliter" | "us-cup" | "us-fluid-ounce" | "us-gallon" | "us-pint" | "us-quart">;
166
177
  export type VolumeUnitKey = MapKey<typeof VOLUME_UNITS>;
167
178
  /** Temperature units. */
168
179
  export declare const TEMPERATURE_UNITS: UnitList<"celsius" | "fahrenheit" | "kelvin">;
package/util/units.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { RequiredError } from "../error/RequiredError.js";
2
2
  import { ValueError } from "../error/ValueError.js";
3
3
  import { HOUR, MILLION, NNBSP } from "./constants.js";
4
- import { formatUnit } from "./format.js";
4
+ import { formatUnit, formatUnitAbbr } from "./format.js";
5
5
  import { ImmutableMap } from "./map.js";
6
6
  import { getProps } from "./object.js";
7
7
  /** Convert an amount using a `Conversion. */
@@ -113,6 +113,19 @@ export class Unit {
113
113
  format(amount, options) {
114
114
  return formatUnit(amount, this.key, { ...this.options, ...options });
115
115
  }
116
+ /**
117
+ * Format the short name of this unit on its own, e.g. `km` or `min`, with no number.
118
+ * - Uses `Intl.NumberFormat` if this is a supported unit, so the name is translated.
119
+ * - Falls back to this unit's `abbr` option, then to its key.
120
+ *
121
+ * @param options Formatting options, e.g. `locale`.
122
+ * @returns The short name of this unit.
123
+ * @example LENGTH_UNITS.require("kilometer").formatAbbr() // "km"
124
+ * @see https://shelving.cc/util/units/Unit/formatAbbr
125
+ */
126
+ formatAbbr(options) {
127
+ return formatUnitAbbr(this.key, { ...this.options, ...options });
128
+ }
116
129
  }
117
130
  /**
118
131
  * Represent a list of related units of measure (e.g. all length units).
@@ -176,7 +189,6 @@ const IN_PER_MI = 63360;
176
189
  const FT_PER_YD = 3;
177
190
  const FT_PER_MI = 5280;
178
191
  const YD_PER_MI = 1760;
179
- const YD_PER_FUR = 220;
180
192
  const MM_PER_CM = 10;
181
193
  const MM_PER_M = 1000;
182
194
  const MM_PER_KM = MILLION;
@@ -244,7 +256,6 @@ export const LENGTH_UNITS = new UnitList({
244
256
  inch: { abbr: "in", many: "inches", to: { millimeter: MM_PER_IN } },
245
257
  foot: { abbr: "ft", many: "feet", to: { millimeter: IN_PER_FT * MM_PER_IN, inch: IN_PER_FT } },
246
258
  yard: { abbr: "yd", to: { millimeter: IN_PER_YD * MM_PER_IN, inch: IN_PER_YD, foot: FT_PER_YD } },
247
- furlong: { abbr: "fur", to: { millimeter: IN_PER_YD * MM_PER_IN * YD_PER_FUR, foot: YD_PER_FUR * FT_PER_YD, yard: YD_PER_FUR } },
248
259
  mile: { abbr: "mi", to: { millimeter: MM_PER_MI, yard: YD_PER_MI, foot: FT_PER_MI, inch: IN_PER_MI } },
249
260
  });
250
261
  /** Speed units. */
@@ -252,7 +263,7 @@ export const SPEED_UNITS = new UnitList({
252
263
  // Metric.
253
264
  "meter-per-second": { abbr: "m/s", one: "meter per second", many: "meters per second", to: { "kilometer-per-hour": 3.6 } },
254
265
  "kilometer-per-hour": {
255
- abbr: "kph",
266
+ abbr: "km/h",
256
267
  one: "kilometer per hour",
257
268
  many: "kilometers per hour",
258
269
  to: { "meter-per-second": MM_PER_KM / HOUR },
@@ -293,28 +304,43 @@ export const VOLUME_UNITS = new UnitList({
293
304
  "cubic-meter": { abbr: "m³", to: { milliliter: MILLION } },
294
305
  // US.
295
306
  "us-fluid-ounce": {
296
- abbr: `fl${NNBSP}oz`,
307
+ abbr: `US${NNBSP}fl${NNBSP}oz`,
297
308
  one: "US fluid ounce",
298
309
  many: "US fluid ounces",
299
310
  to: { milliliter: (US_IN3_PER_GAL * ML_PER_IN3) / 128 },
300
311
  },
301
- "us-pint": { abbr: "pt", one: "US pint", to: { milliliter: (US_IN3_PER_GAL * ML_PER_IN3) / 8, "us-fluid-ounce": 16 } },
312
+ "us-cup": { abbr: `US${NNBSP}cup`, one: "US cup", to: { milliliter: (US_IN3_PER_GAL * ML_PER_IN3) / 16, "us-fluid-ounce": 8 } },
313
+ "us-pint": {
314
+ abbr: `US${NNBSP}pt`,
315
+ one: "US pint",
316
+ to: { milliliter: (US_IN3_PER_GAL * ML_PER_IN3) / 8, "us-cup": 2, "us-fluid-ounce": 16 },
317
+ },
302
318
  "us-quart": {
303
- abbr: "qt",
319
+ abbr: `US${NNBSP}qt`,
304
320
  one: "US quart",
305
- to: { milliliter: (US_IN3_PER_GAL * ML_PER_IN3) / 4, "us-pint": 2, "us-fluid-ounce": 32 },
321
+ to: { milliliter: (US_IN3_PER_GAL * ML_PER_IN3) / 4, "us-pint": 2, "us-cup": 4, "us-fluid-ounce": 32 },
306
322
  },
307
323
  "us-gallon": {
308
- abbr: "gal",
324
+ abbr: `US${NNBSP}gal`,
309
325
  one: "US gallon",
310
- to: { milliliter: US_IN3_PER_GAL * ML_PER_IN3, "us-quart": 4, "us-pint": 8, "us-fluid-ounce": 128 },
326
+ to: { milliliter: US_IN3_PER_GAL * ML_PER_IN3, "us-quart": 4, "us-pint": 8, "us-cup": 16, "us-fluid-ounce": 128 },
311
327
  },
312
328
  // Imperial.
313
- "imperial-fluid-ounce": { abbr: `fl${NNBSP}oz`, to: { milliliter: IMP_ML_PER_GAL / 160 } },
314
- "imperial-pint": { abbr: "pt", to: { milliliter: IMP_ML_PER_GAL / 8, "imperial-fluid-ounce": 20 } },
315
- "imperial-quart": { abbr: "qt", to: { milliliter: IMP_ML_PER_GAL / 4, "imperial-pint": 2, "imperial-fluid-ounce": 40 } },
329
+ "imperial-fluid-ounce": {
330
+ abbr: `fl${NNBSP}oz${NNBSP}Imp.`,
331
+ one: "imperial fluid ounce",
332
+ many: "imperial fluid ounces",
333
+ to: { milliliter: IMP_ML_PER_GAL / 160 },
334
+ },
335
+ "imperial-pint": { abbr: `pt${NNBSP}Imp.`, one: "imperial pint", to: { milliliter: IMP_ML_PER_GAL / 8, "imperial-fluid-ounce": 20 } },
336
+ "imperial-quart": {
337
+ abbr: `qt${NNBSP}Imp.`,
338
+ one: "imperial quart",
339
+ to: { milliliter: IMP_ML_PER_GAL / 4, "imperial-pint": 2, "imperial-fluid-ounce": 40 },
340
+ },
316
341
  "imperial-gallon": {
317
- abbr: "gal",
342
+ abbr: `gal${NNBSP}Imp.`,
343
+ one: "imperial gallon",
318
344
  to: { milliliter: IMP_ML_PER_GAL, "imperial-quart": 4, "imperial-pint": 8, "imperial-fluid-ounce": 160 },
319
345
  },
320
346
  "cubic-inch": { abbr: "in³", many: "cubic inches", to: { milliliter: ML_PER_IN3 } },