shelving 1.81.0 → 1.82.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.
@@ -10,7 +10,6 @@ import { ThroughGenerator } from "./ThroughGenerator.js";
10
10
  * console.log("RETURNED", watch.returned);
11
11
  */
12
12
  export declare class InspectGenerator<T, R, N> extends ThroughGenerator<T, R, N> {
13
- private static readonly NOVALUE;
14
13
  /** Get the number of results received by this iterator so far. */
15
14
  readonly count = 0;
16
15
  /** Is the iteration done? */
@@ -1,5 +1,7 @@
1
1
  import { ConditionError } from "../error/ConditionError.js";
2
2
  import { ThroughGenerator } from "./ThroughGenerator.js";
3
+ /** Used when the sequence hasn't inspected anything yet. */
4
+ const _NOVALUE = Symbol("shelving/InspectGenerator.NOVALUE");
3
5
  /**
4
6
  * Iterable that inspects a source iterable as it iterates.
5
7
  * - Stores: first/last yielded value, returned value, whether iteration is done, the number of items that were iterated.
@@ -17,25 +19,25 @@ export class InspectGenerator extends ThroughGenerator {
17
19
  this.count = 0;
18
20
  /** Is the iteration done? */
19
21
  this.done = false;
20
- this._first = InspectGenerator.NOVALUE;
21
- this._last = InspectGenerator.NOVALUE;
22
- this._returned = InspectGenerator.NOVALUE;
22
+ this._first = _NOVALUE;
23
+ this._last = _NOVALUE;
24
+ this._returned = _NOVALUE;
23
25
  }
24
26
  /** The first yielded value (throws if the iteration yielded no values, i.e. `this.count === 0`). */
25
27
  get first() {
26
- if (this._first === InspectGenerator.NOVALUE)
28
+ if (this._first === _NOVALUE)
27
29
  throw new ConditionError("Iteration not started");
28
30
  return this._first;
29
31
  }
30
32
  /** The last yielded value (throws if the iteration yielded no values, i.e. `this.count === 0`). */
31
33
  get last() {
32
- if (this._last === InspectGenerator.NOVALUE)
34
+ if (this._last === _NOVALUE)
33
35
  throw new ConditionError("Iteration not started");
34
36
  return this._last;
35
37
  }
36
38
  /** The returned value (throws if the iteration is not done, i.e. `this.done === false`). */
37
39
  get returned() {
38
- if (this._returned === InspectGenerator.NOVALUE)
40
+ if (this._returned === _NOVALUE)
39
41
  throw new ConditionError("Iteration not done");
40
42
  return this._returned;
41
43
  }
@@ -64,4 +66,3 @@ export class InspectGenerator extends ThroughGenerator {
64
66
  return result;
65
67
  }
66
68
  }
67
- InspectGenerator.NOVALUE = Symbol();
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.81.0",
14
+ "version": "1.82.0",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -71,6 +71,8 @@
71
71
  "@typescript-eslint/eslint-plugin": "^5.33.1",
72
72
  "@typescript-eslint/parser": "^5.33.1",
73
73
  "dpdm": "^3.9.0",
74
+ "esbuild": "^0.15.7",
75
+ "esbuild-jest": "^0.5.0",
74
76
  "eslint": "^8.22.0",
75
77
  "eslint-config-prettier": "^8.5.0",
76
78
  "eslint-plugin-import": "^2.26.0",
@@ -81,7 +83,6 @@
81
83
  "prettier": "^2.6.2",
82
84
  "react": "^18.1.0",
83
85
  "react-dom": "^18.1.0",
84
- "ts-jest": "^28.0.8",
85
86
  "typescript": "^4.6.4"
86
87
  },
87
88
  "peerDependencies": {
@@ -7,8 +7,8 @@ export declare type Allowed<T extends string | number> = ReadonlyArray<T> | {
7
7
  export declare function validateAllowed<T extends string | number>(value: unknown, allowed: Allowed<T>): T;
8
8
  /** Define a valid value from an allowed set of values. */
9
9
  export declare abstract class AllowSchema<T extends string | number> extends Schema<T> {
10
- readonly allow: Allowed<T>;
11
10
  readonly value: T | null;
11
+ readonly allow: Allowed<T>;
12
12
  constructor({ allow, value, ...options }: ConstructorParameters<typeof Schema>[0] & {
13
13
  allow: Allowed<T>;
14
14
  value?: T | null;
@@ -4,8 +4,8 @@ import { OptionalSchema } from "./OptionalSchema.js";
4
4
  import { Schema } from "./Schema.js";
5
5
  /** Validate a data object. */
6
6
  export declare class DataSchema<T extends Data> extends Schema<T> {
7
- readonly props: Validators<T>;
8
7
  readonly value: Partial<T>;
8
+ readonly props: Validators<T>;
9
9
  constructor({ value, props, ...options }: ConstructorParameters<typeof Schema>[0] & {
10
10
  readonly props: Validators<T>;
11
11
  readonly value?: Partial<T>;
@@ -3,8 +3,8 @@ import { Validator } from "../util/validate.js";
3
3
  import { Schema } from "./Schema.js";
4
4
  /** Validate a map-like object (whose props are all the same). */
5
5
  export declare class ObjectSchema<T> extends Schema<ImmutableObject<T>> {
6
- readonly items: Validator<T>;
7
6
  readonly value: ImmutableObject;
7
+ readonly items: Validator<T>;
8
8
  readonly min: number | null;
9
9
  readonly max: number | null;
10
10
  constructor({ value, items, min, max, ...rest }: ConstructorParameters<typeof Schema>[0] & {
@@ -11,6 +11,8 @@ export declare abstract class Schema<T extends unknown = unknown> implements Val
11
11
  readonly description: string;
12
12
  /** Placeholder, e.g. for showing in fields. */
13
13
  readonly placeholder: string;
14
+ /** Default value. */
15
+ readonly value: unknown;
14
16
  constructor({ title, description, placeholder, }: {
15
17
  /** Title of the schema, e.g. for using as the title of a corresponding field. */
16
18
  readonly title?: string;
@@ -2,7 +2,7 @@ import { Deferred } from "../util/async.js";
2
2
  import { runSequence } from "../util/sequence.js";
3
3
  import { AbstractSequence } from "./AbstractSequence.js";
4
4
  /** Used when the deferred sequence has no value or reason queued. */
5
- const NOVALUE = Symbol("shelving/DeferredSequence.NOVALUE");
5
+ const _NOVALUE = Symbol("shelving/DeferredSequence.NOVALUE");
6
6
  /**
7
7
  * Deferred sequence of values.
8
8
  * - Implements `AsyncIterable` so values can be iterated over using `for await...of`
@@ -14,27 +14,27 @@ export class DeferredSequence extends AbstractSequence {
14
14
  /** Resolve the current deferred in the sequence. */
15
15
  this.resolve = (value) => {
16
16
  this._nextValue = value;
17
- this._nextReason = NOVALUE;
17
+ this._nextReason = _NOVALUE;
18
18
  queueMicrotask(this._fulfill);
19
19
  };
20
- this._nextValue = NOVALUE;
20
+ this._nextValue = _NOVALUE;
21
21
  /** Reject the current deferred in the sequence. */
22
22
  this.reject = (reason) => {
23
- this._nextValue = NOVALUE;
23
+ this._nextValue = _NOVALUE;
24
24
  this._nextReason = reason;
25
25
  queueMicrotask(this._fulfill);
26
26
  };
27
- this._nextReason = NOVALUE;
27
+ this._nextReason = _NOVALUE;
28
28
  /** Fulfill the current deferred by resolving or rejecting it. */
29
29
  this._fulfill = () => {
30
30
  const { _deferred, _nextReason, _nextValue } = this;
31
31
  if (_deferred) {
32
32
  this._deferred = undefined;
33
- this._nextReason = NOVALUE;
34
- this._nextValue = NOVALUE;
35
- if (_nextReason !== NOVALUE)
33
+ this._nextReason = _NOVALUE;
34
+ this._nextValue = _NOVALUE;
35
+ if (_nextReason !== _NOVALUE)
36
36
  _deferred.reject(_nextReason);
37
- else if (_nextValue !== NOVALUE)
37
+ else if (_nextValue !== _NOVALUE)
38
38
  _deferred.resolve(_nextValue);
39
39
  }
40
40
  };
@@ -10,7 +10,6 @@ import { ThroughSequence } from "./ThroughSequence.js";
10
10
  * console.log("RETURNED", watch.returned);
11
11
  */
12
12
  export declare class InspectSequence<T, R> extends ThroughSequence<T, R> {
13
- private static readonly NOVALUE;
14
13
  /** Get the number of results received by this iterator so far. */
15
14
  readonly count = 0;
16
15
  /** Is the iteration done? */
@@ -1,5 +1,7 @@
1
1
  import { ConditionError } from "../error/ConditionError.js";
2
2
  import { ThroughSequence } from "./ThroughSequence.js";
3
+ /** Used when the sequence hasn't inspected anything yet. */
4
+ const _NOVALUE = Symbol("shelving/InspectSequence.NOVALUE");
3
5
  /**
4
6
  * Sequence of values that inspects a source sequence of values as it iterates.
5
7
  * - Stores: first/last yielded value, returned value, whether iteration is done, the number of items that were iterated.
@@ -17,25 +19,25 @@ export class InspectSequence extends ThroughSequence {
17
19
  this.count = 0;
18
20
  /** Is the iteration done? */
19
21
  this.done = false;
20
- this._first = InspectSequence.NOVALUE;
21
- this._last = InspectSequence.NOVALUE;
22
- this._returned = InspectSequence.NOVALUE;
22
+ this._first = _NOVALUE;
23
+ this._last = _NOVALUE;
24
+ this._returned = _NOVALUE;
23
25
  }
24
26
  /** The first yielded value (throws if the iteration yielded no values, i.e. `this.count === 0`). */
25
27
  get first() {
26
- if (this._first === InspectSequence.NOVALUE)
28
+ if (this._first === _NOVALUE)
27
29
  throw new ConditionError("Iteration not started");
28
30
  return this._first;
29
31
  }
30
32
  /** The last yielded value (throws if the iteration yielded no values, i.e. `this.count === 0`). */
31
33
  get last() {
32
- if (this._last === InspectSequence.NOVALUE)
34
+ if (this._last === _NOVALUE)
33
35
  throw new ConditionError("Iteration not started");
34
36
  return this._last;
35
37
  }
36
38
  /** The returned value (throws if the iteration is not done, i.e. `this.done === false`). */
37
39
  get returned() {
38
- if (this._returned === InspectSequence.NOVALUE)
40
+ if (this._returned === _NOVALUE)
39
41
  throw new ConditionError("Iteration not done");
40
42
  return this._returned;
41
43
  }
@@ -64,4 +66,3 @@ export class InspectSequence extends ThroughSequence {
64
66
  return result;
65
67
  }
66
68
  }
67
- InspectSequence.NOVALUE = Symbol();
package/util/async.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { SIGNAL } from "./constants.js";
1
2
  import type { Handler, Dispatch } from "./function.js";
2
3
  /** Is a value an asynchronous value implementing a `then()` function. */
3
4
  export declare const isAsync: <T>(v: T | PromiseLike<T>) => v is PromiseLike<T>;
@@ -39,8 +40,6 @@ export declare class Deferred<T = void> extends Promise<T> {
39
40
  export declare class Delay extends AbstractPromise<void> {
40
41
  constructor(ms: number);
41
42
  }
42
- /** The `SIGNAL` symbol indicates a signal. */
43
- export declare const SIGNAL: unique symbol;
44
43
  /** Resolve to `SIGNAL` on a specific signal. */
45
44
  export declare class Signal extends AbstractPromise<typeof Signal.SIGNAL> {
46
45
  /** The `SIGNAL` symbol indicates a signal. */
package/util/async.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AssertionError } from "../error/AssertionError.js";
2
+ import { SIGNAL } from "./constants.js";
2
3
  /** Is a value an asynchronous value implementing a `then()` function. */
3
4
  export const isAsync = (v) => typeof v === "object" && v !== null && typeof v.then === "function";
4
5
  /** Is a value a synchronous value. */
@@ -76,8 +77,6 @@ export class Delay extends AbstractPromise {
76
77
  setTimeout(this._resolve, ms);
77
78
  }
78
79
  }
79
- /** The `SIGNAL` symbol indicates a signal. */
80
- export const SIGNAL = Symbol("shelving/SIGNAL");
81
80
  /** Resolve to `SIGNAL` on a specific signal. */
82
81
  export class Signal extends AbstractPromise {
83
82
  constructor() {
@@ -0,0 +1,34 @@
1
+ /** One thousand. */
2
+ export declare const THOUSAND = 1000;
3
+ /** Ten thousand. */
4
+ export declare const TEN_THOUSAND: number;
5
+ /** Hundred thousand. */
6
+ export declare const HUNDRED_THOUSAND: number;
7
+ /** One million. */
8
+ export declare const MILLION: number;
9
+ /** One billion. */
10
+ export declare const BILLION: number;
11
+ /** One trillion. */
12
+ export declare const TRILLION: number;
13
+ /** One second in millseconds. */
14
+ export declare const SECOND = 1000;
15
+ /** One minute in millseconds. */
16
+ export declare const MINUTE: number;
17
+ /** One hour in millseconds. */
18
+ export declare const HOUR: number;
19
+ /** One day in millseconds. */
20
+ export declare const DAY: number;
21
+ /** One week in millseconds. */
22
+ export declare const WEEK: number;
23
+ /** One month in millseconds. */
24
+ export declare const MONTH: number;
25
+ /** One year in millseconds. */
26
+ export declare const YEAR: number;
27
+ /** Non-breaking space. */
28
+ export declare const NBSP = "\u00A0";
29
+ /** Thin space. */
30
+ export declare const THINSP = "\u2009";
31
+ /** Non-breaking narrow space (goes between numbers and their corresponding units). */
32
+ export declare const NNBSP = "\u202F";
33
+ /** The `SIGNAL` symbol indicates a signal. */
34
+ export declare const SIGNAL: unique symbol;
@@ -0,0 +1,34 @@
1
+ /** One thousand. */
2
+ export const THOUSAND = 1000;
3
+ /** Ten thousand. */
4
+ export const TEN_THOUSAND = 10 * THOUSAND;
5
+ /** Hundred thousand. */
6
+ export const HUNDRED_THOUSAND = 100 * THOUSAND;
7
+ /** One million. */
8
+ export const MILLION = 1000 * THOUSAND;
9
+ /** One billion. */
10
+ export const BILLION = 1000 * MILLION;
11
+ /** One trillion. */
12
+ export const TRILLION = 1000 * BILLION;
13
+ /** One second in millseconds. */
14
+ export const SECOND = 1000;
15
+ /** One minute in millseconds. */
16
+ export const MINUTE = 60 * SECOND;
17
+ /** One hour in millseconds. */
18
+ export const HOUR = 60 * MINUTE;
19
+ /** One day in millseconds. */
20
+ export const DAY = 24 * HOUR;
21
+ /** One week in millseconds. */
22
+ export const WEEK = 7 * DAY;
23
+ /** One month in millseconds. */
24
+ export const MONTH = 30 * DAY;
25
+ /** One year in millseconds. */
26
+ export const YEAR = 365 * DAY;
27
+ /** Non-breaking space. */
28
+ export const NBSP = "\xA0";
29
+ /** Thin space. */
30
+ export const THINSP = "\u2009";
31
+ /** Non-breaking narrow space (goes between numbers and their corresponding units). */
32
+ export const NNBSP = "\u202F";
33
+ /** The `SIGNAL` symbol indicates a signal. */
34
+ export const SIGNAL = Symbol("shelving/SIGNAL");
package/util/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./boolean.js";
6
6
  export * from "./class.js";
7
7
  export * from "./clone.js";
8
8
  export * from "./color.js";
9
+ export * from "./constants.js";
9
10
  export * from "./data.js";
10
11
  export * from "./date.js";
11
12
  export * from "./debug.js";
package/util/index.js CHANGED
@@ -6,6 +6,7 @@ export * from "./boolean.js";
6
6
  export * from "./class.js";
7
7
  export * from "./clone.js";
8
8
  export * from "./color.js";
9
+ export * from "./constants.js";
9
10
  export * from "./data.js";
10
11
  export * from "./date.js";
11
12
  export * from "./debug.js";
package/util/iterate.js CHANGED
@@ -99,6 +99,5 @@ export function* getChunks(input, size) {
99
99
  /** Merge two or more iterables into a single iterable set. */
100
100
  export function* mergeItems(...inputs) {
101
101
  for (const input of inputs)
102
- for (const item of input)
103
- yield item;
102
+ yield* input;
104
103
  }
package/util/map.d.ts CHANGED
@@ -2,6 +2,10 @@
2
2
  export declare type ImmutableMap<K = unknown, T = unknown> = ReadonlyMap<K, T>;
3
3
  /** `Map` that can be changed. */
4
4
  export declare type MutableMap<K = unknown, T = unknown> = Map<K, T>;
5
+ /** Extract the type for the value of an entry. */
6
+ export declare type MapKey<X> = X extends ReadonlyMap<infer Y, unknown> ? Y : never;
7
+ /** Extract the type for the value of an entry. */
8
+ export declare type MapValue<X> = X extends ReadonlyMap<unknown, infer Y> ? Y : never;
5
9
  /** Things that can be converted to maps. */
6
10
  export declare type PossibleMap<K, T> = ImmutableMap<K, T> | Iterable<readonly [K, T]>;
7
11
  /** Is an unknown value a map? */
@@ -14,3 +18,7 @@ export declare function getMap<K, T>(iterable: PossibleMap<K, T>): ImmutableMap<
14
18
  export declare function limitMap<T>(map: ImmutableMap<T>, limit: number): ImmutableMap<T>;
15
19
  /** Function that lets new items in a map be created and updated by calling a `reduce()` callback that receives the existing value. */
16
20
  export declare function setMapItem<K, T>(map: MutableMap<K, T>, key: K, value: T): T;
21
+ /** Map that changes `get()` to throw an error if the requested value doesn't exist. */
22
+ export declare class RequiredMap<K, T> extends Map<K, T> {
23
+ get(key: K): T;
24
+ }
package/util/map.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { AssertionError } from "../error/AssertionError.js";
2
+ import { RequiredError } from "../error/RequiredError.js";
2
3
  import { limitItems } from "./iterate.js";
4
+ import { getString } from "./string.js";
3
5
  /** Is an unknown value a map? */
4
6
  export const isMap = (v) => v instanceof Map;
5
7
  /** Assert that a value is a `Map` instance. */
@@ -20,3 +22,11 @@ export function setMapItem(map, key, value) {
20
22
  map.set(key, value);
21
23
  return value;
22
24
  }
25
+ /** Map that changes `get()` to throw an error if the requested value doesn't exist. */
26
+ export class RequiredMap extends Map {
27
+ get(key) {
28
+ if (!this.has(key))
29
+ throw new RequiredError(`Item "${getString(key)}" is required`);
30
+ return super.get(key);
31
+ }
32
+ }
package/util/number.d.ts CHANGED
@@ -1,6 +1,3 @@
1
- export declare const TRILLION = 1000000000000;
2
- export declare const BILLION = 1000000000;
3
- export declare const MILLION = 1000000;
4
1
  /** Is a value a number? */
5
2
  export declare const isNumber: (v: unknown) => v is number;
6
3
  /** Assert that a value is a number. */
@@ -67,8 +64,8 @@ export declare const truncateNumber: (num: number, precision?: number) => number
67
64
  * @returns The number formatted as a string in the browser's current locale.
68
65
  */
69
66
  export declare function formatNumber(num: number, maxPrecision?: number, minPrecision?: number): string;
70
- /** Format a number with a short suffix. */
71
- export declare const formatQuantity: (num: number, suffix: string, maxPrecision?: number, minPrecision?: number) => string;
67
+ /** Format a number with a short abbreviated suffix. */
68
+ export declare const formatQuantity: (num: number, abbr: string, maxPrecision?: number, minPrecision?: number) => string;
72
69
  /** Format a number with a longer full-word suffix. */
73
70
  export declare function formatFullQuantity(num: number, singular: string, plural: string, maxPrecision?: number, minPrecision?: number): string;
74
71
  /**
package/util/number.js CHANGED
@@ -1,8 +1,5 @@
1
1
  import { AssertionError } from "../error/AssertionError.js";
2
- // Constants.
3
- export const TRILLION = 1000000000000;
4
- export const BILLION = 1000000000;
5
- export const MILLION = 1000000;
2
+ import { BILLION, MILLION, NNBSP, TRILLION } from "./constants.js";
6
3
  /** Is a value a number? */
7
4
  export const isNumber = (v) => typeof v === "number";
8
5
  /** Assert that a value is a number. */
@@ -98,8 +95,8 @@ export function formatNumber(num, maxPrecision = 4, minPrecision = 0) {
98
95
  return Number.isNaN(num) ? "None" : "Infinity";
99
96
  return new Intl.NumberFormat(undefined, { maximumFractionDigits: maxPrecision, minimumFractionDigits: minPrecision }).format(num);
100
97
  }
101
- /** Format a number with a short suffix. */
102
- export const formatQuantity = (num, suffix, maxPrecision, minPrecision) => `${formatNumber(num, maxPrecision, minPrecision)}${suffix}`;
98
+ /** Format a number with a short abbreviated suffix. */
99
+ export const formatQuantity = (num, abbr, maxPrecision, minPrecision) => `${formatNumber(num, maxPrecision, minPrecision)}${NNBSP}${abbr}`;
103
100
  /** Format a number with a longer full-word suffix. */
104
101
  export function formatFullQuantity(num, singular, plural, maxPrecision, minPrecision) {
105
102
  const qty = formatNumber(num, maxPrecision, minPrecision);
@@ -1,4 +1,4 @@
1
- import { SIGNAL } from "./async.js";
1
+ import { SIGNAL } from "./constants.js";
2
2
  import { Handler, Stop, AsyncDispatch, Dispatch } from "./function.js";
3
3
  /** Slightly modify the definition of `AsyncIterable` to default `R` and `N` to `void` */
4
4
  export declare interface AsyncIterable<T, R = void, N = void> {
package/util/sequence.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { RequiredError } from "../error/RequiredError.js";
2
- import { Delay, Signal, SIGNAL } from "./async.js";
2
+ import { Delay, Signal } from "./async.js";
3
+ import { SIGNAL } from "./constants.js";
3
4
  import { logError } from "./error.js";
4
5
  import { dispatch } from "./function.js";
5
6
  /**
package/util/string.d.ts CHANGED
@@ -9,12 +9,6 @@ export declare type NotString = {
9
9
  toUpperCase?: never;
10
10
  toLowerCase?: never;
11
11
  };
12
- /** Non-breaking space. */
13
- export declare const NBSP = "\u00A0";
14
- /** Thin space. */
15
- export declare const THINSP = "\u2009";
16
- /** Non-breaking narrow space (goes between numbers and their corresponding units). */
17
- export declare const NNBSP = "\u202F";
18
12
  /** Is a value a string? */
19
13
  export declare const isString: (v: unknown) => v is string;
20
14
  /** Assert that a value is a string. */
package/util/string.js CHANGED
@@ -4,12 +4,6 @@ import { formatDate, isDate } from "./date.js";
4
4
  import { formatData, isData } from "./data.js";
5
5
  import { getArray, isArray } from "./array.js";
6
6
  import { formatNumber, isBetween } from "./number.js";
7
- /** Non-breaking space. */
8
- export const NBSP = "\xA0";
9
- /** Thin space. */
10
- export const THINSP = "\u2009";
11
- /** Non-breaking narrow space (goes between numbers and their corresponding units). */
12
- export const NNBSP = "\u202F";
13
7
  /** Is a value a string? */
14
8
  export const isString = (v) => typeof v === "string";
15
9
  /** Assert that a value is a string. */
package/util/units.d.ts CHANGED
@@ -1,100 +1,113 @@
1
1
  import { PossibleDate } from "./date.js";
2
- /** One second in millseconds. */
3
- export declare const SECOND = 1000;
4
- /** One minute in millseconds. */
5
- export declare const MINUTE: number;
6
- /** One hour in millseconds. */
7
- export declare const HOUR: number;
8
- /** One day in millseconds. */
9
- export declare const DAY: number;
10
- /** One week in millseconds. */
11
- export declare const WEEK: number;
12
- /** One month in millseconds. */
13
- export declare const MONTH: number;
14
- /** One year in millseconds. */
15
- export declare const YEAR: number;
16
- /** Valid information about a unit of measure. */
17
- export declare type UnitData = {
18
- /** Type of a unit. */
19
- readonly type: UnitType;
20
- /** Singular name for a unit, e.g. `foot` (only needed if different from reference). */
2
+ import { MapKey, RequiredMap } from "./map.js";
3
+ /** Conversion from one unit to another (either a number to multiple by, or a function to convert). */
4
+ declare type Conversion = number | ((num: number) => number);
5
+ /** Set of possible conversions for a set of items. */
6
+ declare type Conversions<T extends string> = {
7
+ readonly [K in T]?: Conversion;
8
+ };
9
+ declare type UnitProps<T extends string> = {
10
+ /** Short abbreviation for this unit, e.g. `km` (defaults to first letter of `ref`). */
11
+ readonly abbr?: string;
12
+ /** Singular name for this unit, e.g. `kilometer` (defaults to `ref` + "s"). */
21
13
  readonly singular?: string;
22
- /** Plural name for a unit, e.g. `feet` */
14
+ /** Plural name for this unit, e.g. `kilometers` (defaults to `ref`). */
23
15
  readonly plural?: string;
24
- /** Short suffix for this unit, e.g. `km` */
25
- readonly suffix: string;
26
- /** All units must specify their 'base' unit, e.g. `meter` for for distance units and `liter` for volume units. */
27
- readonly base: number;
28
- } & {
29
- [K in UnitReference]?: number;
30
- };
31
- /** Valid system of measurement reference. */
32
- export declare type UnitType = "percentage" | "angle" | "temperature" | "length" | "speed" | "pace" | "mass" | "time" | "volume";
33
- /** Valid unit of measurement reference (correspond to units allowed in `Intl.NumberFormat`, but not all). */
34
- export declare type UnitReference = "percent" | "permille" | "permyriad" | "ppm" | "percentage-point" | "basis-point" | "degree" | "millimeter" | "centimeter" | "meter" | "kilometer" | "mile" | "yard" | "foot" | "inch" | "liter" | "milliliter" | "gallon" | "fluid-ounce" | "milligram" | "gram" | "kilogram" | "pound" | "stone" | "ounce" | "millisecond" | "second" | "minute" | "day" | "hour" | "week" | "month" | "year";
35
- /** List of units. */
36
- export declare const UNITS: {
37
- [K in UnitReference]: UnitData;
16
+ /** Conversions to other units (typically needs at least the base conversion, unless it's already the base unit). */
17
+ readonly to?: Conversions<T>;
38
18
  };
39
- /** Convert between two units of the same type. */
40
- export declare function convertUnits(num: number, from: UnitReference, to: UnitReference): number;
41
- /**
42
- * Format a number with a given unit of measure, e.g. `12 kg` or `29.5 l`
43
- *
44
- * @param num The number to format.
45
- * @param unit String reference for a unit of measure e.g. `kilometer`
46
- * @param maxPrecision Number of decimal places to round the number to e.g. `2`
47
- */
48
- export declare const formatUnits: (num: number, unit: UnitReference, maxPrecision?: number, minPrecision?: number) => string;
49
- /**
50
- * Format a number with a given unit of measure, e.g. `12 kilograms` or `29.5 liters` or `1 degree`
51
- *
52
- * @param num The number to format.
53
- * @param unit String reference for a unit of measure e.g. `kilometer`
54
- * @param maxPrecision Number of decimal places to round the number to e.g. `2`
55
- */
56
- export declare const formatFullUnits: (num: number, unit: UnitReference, maxPrecision?: number, minPrecision?: number) => string;
57
- /**
58
- * Format a percentage.
59
- * - Combines `getPercent()` and `formatUnits()` for convenience.
60
- */
19
+ /** Represent a unit. */
20
+ export declare class Unit<T extends string> {
21
+ /** `UnitList` this unit belongs to. */
22
+ readonly list: UnitList<T>;
23
+ private readonly _to;
24
+ /** Reference for this unit, e.g. `kilometer` */
25
+ readonly ref: T;
26
+ /** Short abbreviation for this unit, e.g. `km` (defaults to first letter of `ref`). */
27
+ readonly abbr: string;
28
+ /** Singular name for this unit, e.g. `kilometer` (defaults to `ref`). */
29
+ readonly singular: string;
30
+ /** Plural name for this unit, e.g. `kilometers` (defaults to `singular` + "s"). */
31
+ readonly plural: string;
32
+ constructor(
33
+ /** `UnitList` this unit belongs to. */
34
+ list: UnitList<T>,
35
+ /** Reference for this unit. */
36
+ ref: T,
37
+ /** Props to configure this unit. */
38
+ { abbr, singular, plural, to }: UnitProps<T>);
39
+ /** Convert an amount from this unit to another unit. */
40
+ to(amount: number, ref?: T | Unit<T>): number;
41
+ /** Convert an amount from another unit to this unit. */
42
+ from(amount: number, ref?: T | Unit<T>): number;
43
+ /** Get a unit from a unit or unit reference. */
44
+ private _unit;
45
+ /** Convert an amount from this unit to another unit (must specify another `Unit` instance). */
46
+ private _toUnit;
47
+ /** Format a number with a given unit of measure, e.g. `12 kg` or `29.5 l` */
48
+ format(amount: number, maxPrecision?: number, minPrecision?: number): string;
49
+ /** Format a number with a given unit of measure, e.g. `12 kilograms` or `29.5 liters` or `1 degree` */
50
+ formatFull(amount: number, maxPrecision?: number, minPrecision?: number): string;
51
+ }
52
+ /** Represent a list of units. */
53
+ export declare class UnitList<T extends string> extends RequiredMap<T, Unit<T>> {
54
+ readonly base: Unit<T>;
55
+ constructor(units: {
56
+ [K in T]: UnitProps<T>;
57
+ });
58
+ }
59
+ export interface UnitList<T extends string> {
60
+ (units: {
61
+ [K in T]: UnitProps<T>;
62
+ }): UnitList<T>;
63
+ set: never;
64
+ delete: never;
65
+ }
66
+ /** Percentage units. */
67
+ export declare const PERCENTAGE_UNITS: UnitList<"percent">;
68
+ export declare type PercentageUnit = MapKey<typeof PERCENTAGE_UNITS>;
69
+ /** Point units. */
70
+ export declare const POINT_UNITS: UnitList<"basis-point" | "percentage-point">;
71
+ export declare type PointUnit = MapKey<typeof POINT_UNITS>;
72
+ /** Angle units. */
73
+ export declare const ANGLE_UNITS: UnitList<"degree" | "radian" | "gradian">;
74
+ export declare type AngleUnit = MapKey<typeof ANGLE_UNITS>;
75
+ /** Mass units. */
76
+ export declare const MASS_UNITS: UnitList<"milligram" | "gram" | "kilogram" | "ounce" | "pound" | "stone">;
77
+ export declare type MassUnit = MapKey<typeof MASS_UNITS>;
78
+ /** Time units. */
79
+ export declare const TIME_UNITS: UnitList<"millisecond" | "second" | "minute" | "hour" | "day" | "week" | "month" | "year">;
80
+ export declare type TimeUnit = MapKey<typeof TIME_UNITS>;
81
+ /** Length units. */
82
+ export declare const LENGTH_UNITS: UnitList<"millimeter" | "centimeter" | "meter" | "kilometer" | "inch" | "foot" | "yard" | "furlong" | "mile">;
83
+ export declare type LengthUnit = MapKey<typeof LENGTH_UNITS>;
84
+ /** Speed units. */
85
+ export declare const SPEED_UNITS: UnitList<"meter-per-second" | "kilometer-per-hour" | "mile-per-hour">;
86
+ export declare type SpeedUnit = MapKey<typeof SPEED_UNITS>;
87
+ /** Area units. */
88
+ export declare const AREA_UNITS: UnitList<"square-millimeter" | "square-centimeter" | "square-meter" | "square-kilometer" | "hectare" | "square-inch" | "square-foot" | "square-yard" | "acre">;
89
+ /** Volume units. */
90
+ export declare const VOLUME_UNITS: UnitList<"milliliter" | "liter" | "cubic-centimeter" | "cubic-meter" | "us-fluid-ounce" | "us-pint" | "us-quart" | "us-gallon" | "imperial-fluid-ounce" | "imperial-pint" | "imperial-quart" | "imperial-gallon" | "cubic-inch" | "cubic-foot" | "cubic-yard">;
91
+ export declare type VolumeUnit = MapKey<typeof VOLUME_UNITS>;
92
+ /** Temperature units. */
93
+ export declare const TEMPERATURE_UNITS: UnitList<"celsius" | "fahrenheit" | "kelvin">;
94
+ export declare type TemperatureUnit = MapKey<typeof TEMPERATURE_UNITS>;
95
+ /** Format a percentage (combines `getPercent()` and `formatUnits()` for convenience). */
61
96
  export declare const formatPercent: (numerator: number, denumerator: number, maxPrecision?: number, minPrecision?: number) => string;
62
- /** Format a full description of a duration of time using the most reasonable units e.g. `5 years` or `1 week` or `4 minutes` or `12 milliseconds`. */
63
- export declare const formatFullDuration: (ms: number, maxPrecision?: number, minPrecision?: number) => string;
97
+ /** Format a full format of a duration of time using the most reasonable units e.g. `5 years` or `1 week` or `4 minutes` or `12 milliseconds`. */
98
+ export declare function formatFullDuration(ms: number, maxPrecision?: number, minPrecision?: number): string;
64
99
  /** Format a description of a duration of time using the most reasonable units e.g. `5y` or `4m` or `12ms`. */
65
- export declare const formatDuration: (ms: number, maxPrecision?: number, minPrecision?: number) => string;
66
- /**
67
- * Return full description of the gap between two dates, e.g. `in 10 days` or `2 hours ago`
68
- *
69
- * @param target The date when the thing will happen or did happen.
70
- * @param current Today's date (or a different date to measure from).
71
- */
72
- export declare function formatFullWhen(target: PossibleDate, current?: PossibleDate): string;
73
- /**
74
- * Return full description of when a date will happen, e.g. `10 days` or `2 hours` or `-1 week`
75
- *
76
- * @param target The date when the thing happened.
77
- * @param current Today's date (or a different date to measure from).
78
- */
79
- export declare const formatFullAgo: (target: PossibleDate, current?: PossibleDate) => string;
80
- /**
81
- * Compact how long until a date happens, e.g. `in 10d` or `2h ago` or `in 1w`
82
- *
83
- * @param target The date when the thing will happen.
84
- * @param current Today's date (or a different date to measure from).
85
- */
86
- export declare function formatWhen(target: PossibleDate, current?: PossibleDate): string;
87
- /**
88
- * Return short description of when a date happened, e.g. `10d` or `2h` or `-1w`
89
- *
90
- * @param target The date when the thing will happen.
91
- * @param current Today's date (or a different date to measure from).
92
- */
100
+ export declare function formatDuration(ms: number, maxPrecision?: number, minPrecision?: number): string;
101
+ /** Full when a date happens/happened, e.g. `in 10 days` or `2 hours ago` */
102
+ export declare const formatFullWhen: (target: PossibleDate, current?: PossibleDate) => string;
103
+ /** Compact when a date happens/happened, e.g. `in 10d` or `2h ago` or `in 1w` */
104
+ export declare const formatWhen: (target: PossibleDate, current?: PossibleDate) => string;
105
+ /** Full when a date happens, e.g. `10 days` or `2 hours` or `-1 week` */
106
+ export declare const formatFullUntil: (target: PossibleDate, current?: PossibleDate) => string;
107
+ /** Compact when a date happens, e.g. `10d` or `2h` or `-1w` */
93
108
  export declare const formatUntil: (target: PossibleDate, current?: PossibleDate) => string;
94
- /**
95
- * Return short description of when a date will happen, e.g. `10d` or `2h` or `-1w`
96
- *
97
- * @param target The date when the thing happened.
98
- * @param current Today's date (or a different date to measure from).
99
- */
109
+ /** Full when a date happened, e.g. `10 days` or `2 hours` or `-1 week` */
110
+ export declare const formatFullAgo: (target: PossibleDate, current?: PossibleDate) => string;
111
+ /** Compact when a date will happen, e.g. `10d` or `2h` or `-1w` */
100
112
  export declare const formatAgo: (target: PossibleDate, current?: PossibleDate) => string;
113
+ export {};
package/util/units.js CHANGED
@@ -1,153 +1,256 @@
1
- import { AssertionError } from "../error/AssertionError.js";
1
+ import { ConditionError } from "../error/ConditionError.js";
2
+ import { DAY, HOUR, MILLION, MINUTE, MONTH, NNBSP, SECOND, WEEK, YEAR } from "./constants.js";
2
3
  import { getDuration } from "./date.js";
3
- import { formatFullQuantity, formatQuantity, getPercent, MILLION } from "./number.js";
4
- import { NNBSP } from "./string.js";
5
- /** One second in millseconds. */
6
- export const SECOND = 1000;
7
- /** One minute in millseconds. */
8
- export const MINUTE = 60 * SECOND;
9
- /** One hour in millseconds. */
10
- export const HOUR = 60 * MINUTE;
11
- /** One day in millseconds. */
12
- export const DAY = 24 * HOUR;
13
- /** One week in millseconds. */
14
- export const WEEK = 7 * DAY;
15
- /** One month in millseconds. */
16
- export const MONTH = 30 * DAY;
17
- /** One year in millseconds. */
18
- export const YEAR = 365 * DAY;
19
- /** List of units. */
20
- export const UNITS = {
21
- "percent": { type: "percentage", base: 1, suffix: "%" },
22
- "permille": { type: "percentage", base: 10, suffix: `${NNBSP}‰` },
23
- "permyriad": { type: "percentage", base: 100, suffix: `${NNBSP}‱` },
24
- "ppm": { type: "percentage", base: MILLION, suffix: `${NNBSP}ppm`, singular: "part per million", plural: "parts per million" },
25
- "percentage-point": { type: "percentage", base: 1, suffix: `${NNBSP}pp`, singular: "percentage point", plural: "percentage points" },
26
- "basis-point": { type: "percentage", base: 10000, suffix: `${NNBSP}bp`, singular: "basis point", plural: "basis points" },
27
- "degree": { type: "angle", base: 1, suffix: `${NNBSP}deg` },
28
- "millimeter": { type: "length", base: 1, suffix: `${NNBSP}mm` },
29
- "centimeter": { type: "length", base: 10, suffix: `${NNBSP}cm` },
30
- "meter": { type: "length", base: 1000, centimeter: 100, millimeter: 1000, suffix: `${NNBSP}m` },
31
- "kilometer": { type: "length", base: 1000000, centimeter: 100000, millimeter: 1000000, suffix: `${NNBSP}km` },
32
- "inch": { type: "length", base: 25.4, suffix: `${NNBSP}in` },
33
- "foot": { type: "length", base: 304.8, inch: 12, suffix: `${NNBSP}ft`, plural: "feet" },
34
- "yard": { type: "length", base: 914.4, inch: 36, foot: 3, suffix: `${NNBSP}yd` },
35
- "mile": { type: "length", base: 1609344, yard: 1760, foot: 5280, inch: 63360, suffix: `${NNBSP}mi` },
36
- "milliliter": { type: "volume", base: 1, suffix: `${NNBSP}ml` },
37
- "liter": { type: "volume", base: 1000, suffix: `${NNBSP}l` },
38
- "fluid-ounce": { type: "volume", base: 29.5735295625, gallon: 128, suffix: `${NNBSP}fl${NNBSP}oz`, singular: "fluid ounce", plural: "fluid ounces" },
39
- "gallon": { type: "volume", base: 3785.411784, suffix: `${NNBSP}gal` },
40
- "milligram": { type: "mass", base: 1, suffix: `${NNBSP}mg` },
41
- "gram": { type: "mass", base: 1000, suffix: `${NNBSP}g` },
42
- "kilogram": { type: "mass", base: 1000000, suffix: `${NNBSP}kg` },
43
- "ounce": { type: "mass", base: 28349.523125, pound: 0.0625, suffix: `${NNBSP}oz` },
44
- "pound": { type: "mass", base: 453592.37, ounce: 16, suffix: `${NNBSP}lb` },
45
- "stone": { type: "mass", base: 6350293.18, pound: 14, ounce: 224, suffix: `${NNBSP}st`, plural: "stone" },
46
- "millisecond": { type: "time", base: 1, suffix: `${NNBSP}ms` },
47
- "second": { type: "time", base: SECOND, suffix: `${NNBSP}s` },
48
- "minute": { type: "time", base: MINUTE, suffix: `${NNBSP}m` },
49
- "hour": { type: "time", base: HOUR, suffix: `${NNBSP}h` },
50
- "day": { type: "time", base: DAY, suffix: `${NNBSP}d` },
51
- "week": { type: "time", base: WEEK, suffix: `${NNBSP}w` },
52
- "month": { type: "time", base: MONTH, suffix: `${NNBSP}m` },
53
- "year": { type: "time", base: YEAR, suffix: `${NNBSP}y` },
54
- };
55
- /** Convert between two units of the same type. */
56
- export function convertUnits(num, from, to) {
57
- if (from === to)
58
- return num;
59
- const fromData = UNITS[from];
60
- const exact = fromData[to]; // Get the exact conversion if possible (e.g. 5280 feet in a mile).
61
- if (typeof exact === "number")
62
- return num * exact;
63
- const toData = UNITS[to];
64
- if (fromData.type !== toData.type)
65
- throw new AssertionError(`Target unit must be ${fromData.type}`, toData.type);
66
- return (num * fromData.base) / toData.base;
4
+ import { RequiredMap } from "./map.js";
5
+ import { formatFullQuantity, formatQuantity, getPercent } from "./number.js";
6
+ import { getProps } from "./object.js";
7
+ /** Convert an amount using a `Conversion. */
8
+ const _convert = (amount, conversion) => (typeof conversion === "function" ? conversion(amount) : conversion === 1 ? amount : amount * conversion);
9
+ /** Represent a unit. */
10
+ export class Unit {
11
+ constructor(
12
+ /** `UnitList` this unit belongs to. */
13
+ list,
14
+ /** Reference for this unit. */
15
+ ref,
16
+ /** Props to configure this unit. */
17
+ { abbr = ref.slice(0, 1), singular = ref.replace(/-/, " "), plural = `${singular}s`, to }) {
18
+ this.list = list;
19
+ this.ref = ref;
20
+ this.abbr = abbr;
21
+ this.singular = singular;
22
+ this.plural = plural;
23
+ this._to = to;
24
+ }
25
+ /** Convert an amount from this unit to another unit. */
26
+ to(amount, ref = this.list.base) {
27
+ return this._toUnit(amount, this._unit(ref));
28
+ }
29
+ /** Convert an amount from another unit to this unit. */
30
+ from(amount, ref = this.list.base) {
31
+ return this._unit(ref)._toUnit(amount, this);
32
+ }
33
+ /** Get a unit from a unit or unit reference. */
34
+ _unit(ref) {
35
+ return typeof ref === "string" ? this.list.get(ref) : ref;
36
+ }
37
+ /** Convert an amount from this unit to another unit (must specify another `Unit` instance). */
38
+ _toUnit(amount, unit) {
39
+ var _a, _b, _c;
40
+ // Convert to self.
41
+ if (unit === this)
42
+ return amount;
43
+ // Exact conversion.
44
+ const thisToUnit = (_a = this._to) === null || _a === void 0 ? void 0 : _a[unit.ref];
45
+ if (thisToUnit)
46
+ return _convert(amount, thisToUnit);
47
+ // Invert number conversion (can't do this for function conversions).
48
+ const unitToThis = (_b = unit._to) === null || _b === void 0 ? void 0 : _b[this.ref];
49
+ if (typeof unitToThis === "number")
50
+ return amount / unitToThis;
51
+ // Two step conversion via base.
52
+ const base = this.list.base;
53
+ const thisToBase = (_c = this._to) === null || _c === void 0 ? void 0 : _c[base.ref];
54
+ if (thisToBase)
55
+ return base._toUnit(_convert(amount, thisToBase), unit);
56
+ // Cannot convert.
57
+ throw new ConditionError(`Cannot convert "${this.ref}" to "${unit.ref}"`);
58
+ }
59
+ /** Format a number with a given unit of measure, e.g. `12 kg` or `29.5 l` */
60
+ format(amount, maxPrecision, minPrecision) {
61
+ return formatQuantity(amount, this.abbr, maxPrecision, minPrecision);
62
+ }
63
+ /** Format a number with a given unit of measure, e.g. `12 kilograms` or `29.5 liters` or `1 degree` */
64
+ formatFull(amount, maxPrecision, minPrecision) {
65
+ return formatFullQuantity(amount, this.singular, this.plural, maxPrecision, minPrecision);
66
+ }
67
67
  }
68
- /**
69
- * Format a number with a given unit of measure, e.g. `12 kg` or `29.5 l`
70
- *
71
- * @param num The number to format.
72
- * @param unit String reference for a unit of measure e.g. `kilometer`
73
- * @param maxPrecision Number of decimal places to round the number to e.g. `2`
74
- */
75
- export const formatUnits = (num, unit, maxPrecision, minPrecision) => formatQuantity(num, UNITS[unit].suffix, maxPrecision, minPrecision);
76
- /**
77
- * Format a number with a given unit of measure, e.g. `12 kilograms` or `29.5 liters` or `1 degree`
78
- *
79
- * @param num The number to format.
80
- * @param unit String reference for a unit of measure e.g. `kilometer`
81
- * @param maxPrecision Number of decimal places to round the number to e.g. `2`
82
- */
83
- export const formatFullUnits = (num, unit, maxPrecision, minPrecision) => formatFullQuantity(num, UNITS[unit].singular || unit, UNITS[unit].plural || `${unit}s`, maxPrecision, minPrecision);
84
- /**
85
- * Format a percentage.
86
- * - Combines `getPercent()` and `formatUnits()` for convenience.
87
- */
88
- export const formatPercent = (numerator, denumerator, maxPrecision, minPrecision) => formatQuantity(getPercent(numerator, denumerator), UNITS.percent.suffix, maxPrecision, minPrecision);
89
- function _formatDuration(formatter, ms, maxPrecision = 0, minPrecision) {
68
+ /** Represent a list of units. */
69
+ export class UnitList extends RequiredMap {
70
+ constructor(units) {
71
+ super();
72
+ for (const [ref, props] of getProps(units)) {
73
+ const unit = new Unit(this, ref, props);
74
+ if (!this.base)
75
+ this.base = unit;
76
+ super.set(ref, unit);
77
+ }
78
+ }
79
+ }
80
+ // Distance constants.
81
+ const IN_PER_FT = 12;
82
+ const IN_PER_YD = 36;
83
+ const IN_PER_MI = 63360;
84
+ const FT_PER_YD = 3;
85
+ const FT_PER_MI = 5280;
86
+ const YD_PER_MI = 1760;
87
+ const YD_PER_FUR = 220;
88
+ const MM_PER_CM = 10;
89
+ const MM_PER_M = 1000;
90
+ const MM_PER_KM = MILLION;
91
+ const MM_PER_IN = 25.4;
92
+ const MM_PER_MI = 1609344;
93
+ // Mass constants.
94
+ const MG_PER_LB = 453592.37;
95
+ const OZ_PER_LB = 16;
96
+ const LB_PER_ST = 14;
97
+ // Area constants.
98
+ const MM2_PER_IN2 = MM_PER_IN ** 2;
99
+ const FT2_PER_ACRE = 66 * 660;
100
+ const YD2_PER_ACRE = 22 * 220;
101
+ // Volume constants.
102
+ const MM3_PER_IN3 = MM_PER_IN ** 3;
103
+ const ML_PER_IN3 = MM3_PER_IN3 / 1000;
104
+ const US_IN3_PER_GAL = 231;
105
+ const IMP_ML_PER_GAL = 4546090 / 1000;
106
+ /** Percentage units. */
107
+ export const PERCENTAGE_UNITS = new UnitList({
108
+ percent: { abbr: "%", plural: "percent" },
109
+ });
110
+ /** Point units. */
111
+ export const POINT_UNITS = new UnitList({
112
+ "basis-point": { abbr: "bp" },
113
+ "percentage-point": { abbr: "pp", to: { "basis-point": 100 } },
114
+ });
115
+ /** Angle units. */
116
+ export const ANGLE_UNITS = new UnitList({
117
+ degree: { abbr: "deg" },
118
+ radian: { abbr: "rad", to: { degree: 180 / Math.PI } },
119
+ gradian: { abbr: "grad", to: { degree: 180 / 200 } },
120
+ });
121
+ /** Mass units. */
122
+ export const MASS_UNITS = new UnitList({
123
+ // Metric.
124
+ milligram: { abbr: "mg" },
125
+ gram: { abbr: "g", to: { milligram: 1000 } },
126
+ kilogram: { abbr: "kg", to: { milligram: MILLION } },
127
+ // Imperial.
128
+ ounce: { abbr: "oz", to: { milligram: MG_PER_LB / OZ_PER_LB } },
129
+ pound: { abbr: "lb", to: { milligram: MG_PER_LB, ounce: OZ_PER_LB } },
130
+ stone: { abbr: "st", plural: "stone", to: { milligram: MG_PER_LB * LB_PER_ST, pound: LB_PER_ST, ounce: OZ_PER_LB * LB_PER_ST } },
131
+ });
132
+ /** Time units. */
133
+ export const TIME_UNITS = new UnitList({
134
+ // Metric.
135
+ millisecond: { abbr: "ms" },
136
+ second: { to: { millisecond: SECOND } },
137
+ minute: { to: { millisecond: MINUTE } },
138
+ hour: { to: { millisecond: HOUR } },
139
+ day: { to: { millisecond: DAY } },
140
+ week: { to: { millisecond: WEEK } },
141
+ month: { to: { millisecond: MONTH } },
142
+ year: { to: { millisecond: YEAR } },
143
+ });
144
+ /** Length units. */
145
+ export const LENGTH_UNITS = new UnitList({
146
+ // Metric.
147
+ millimeter: { abbr: "mm" },
148
+ centimeter: { abbr: "cm", to: { millimeter: MM_PER_CM } },
149
+ meter: { to: { millimeter: MM_PER_M } },
150
+ kilometer: { abbr: "km", to: { millimeter: MM_PER_KM } },
151
+ // Imperial.
152
+ inch: { abbr: "in", plural: "inches", to: { millimeter: MM_PER_IN } },
153
+ foot: { abbr: "ft", plural: "feet", to: { millimeter: IN_PER_FT * MM_PER_IN, inch: IN_PER_FT } },
154
+ yard: { abbr: "yd", to: { millimeter: IN_PER_YD * MM_PER_IN, inch: IN_PER_YD, foot: FT_PER_YD } },
155
+ 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 } },
156
+ mile: { abbr: "mi", to: { millimeter: MM_PER_MI, yard: YD_PER_MI, foot: FT_PER_MI, inch: IN_PER_MI } },
157
+ });
158
+ /** Speed units. */
159
+ export const SPEED_UNITS = new UnitList({
160
+ // Metric.
161
+ "meter-per-second": { abbr: "m/s", singular: "meter per second", plural: "meters per second", to: { "kilometer-per-hour": 3.6 } },
162
+ "kilometer-per-hour": { abbr: "kph", singular: "kilometer per hour", plural: "kilometers per hour", to: { "meter-per-second": MM_PER_KM / HOUR } },
163
+ // Imperial.
164
+ "mile-per-hour": { abbr: "mph", singular: "mile per hour", plural: "miles per hour", to: { "meter-per-second": MM_PER_MI / HOUR } },
165
+ });
166
+ /** Area units. */
167
+ export const AREA_UNITS = new UnitList({
168
+ // Metric.
169
+ "square-millimeter": { abbr: "mm²" },
170
+ "square-centimeter": { abbr: "cm²", to: { "square-millimeter": MM_PER_CM ** 2 } },
171
+ "square-meter": { abbr: "m²", to: { "square-millimeter": MM_PER_M ** 2 } },
172
+ "square-kilometer": { abbr: "km²", to: { "square-millimeter": MM_PER_KM ** 2 } },
173
+ "hectare": { abbr: "ha", to: { "square-millimeter": (MM_PER_M * 100) ** 2 } },
174
+ // Imperial.
175
+ "square-inch": { abbr: `in²`, plural: "square inches", to: { "square-millimeter": MM2_PER_IN2 } },
176
+ "square-foot": { abbr: `ft²`, plural: "square feet", to: { "square-millimeter": IN_PER_FT ** 2 * MM2_PER_IN2, "square-inch": IN_PER_FT ** 2 } },
177
+ "square-yard": { abbr: `yd²`, to: { "square-millimeter": IN_PER_YD ** 2 * MM2_PER_IN2, "square-foot": FT_PER_YD ** 2, "square-inch": IN_PER_YD ** 2 } },
178
+ "acre": { abbr: "acre", to: { "square-millimeter": IN_PER_YD ** 2 * YD2_PER_ACRE * MM2_PER_IN2, "square-foot": FT2_PER_ACRE, "square-yard": YD2_PER_ACRE } },
179
+ });
180
+ /** Volume units. */
181
+ export const VOLUME_UNITS = new UnitList({
182
+ // Metric.
183
+ "milliliter": { abbr: "ml" },
184
+ "liter": { to: { milliliter: 1000 } },
185
+ "cubic-centimeter": { abbr: "cm³", to: { milliliter: 1 } },
186
+ "cubic-meter": { abbr: "m³", to: { milliliter: MILLION } },
187
+ // US.
188
+ "us-fluid-ounce": { abbr: `fl${NNBSP}oz`, singular: "US fluid ounce", plural: "US fluid ounces", to: { milliliter: (US_IN3_PER_GAL * ML_PER_IN3) / 128 } },
189
+ "us-pint": { abbr: "pt", singular: "US pint", to: { "milliliter": (US_IN3_PER_GAL * ML_PER_IN3) / 8, "us-fluid-ounce": 16 } },
190
+ "us-quart": { abbr: "qt", singular: "US quart", to: { "milliliter": (US_IN3_PER_GAL * ML_PER_IN3) / 4, "us-pint": 2, "us-fluid-ounce": 32 } },
191
+ "us-gallon": { abbr: "gal", singular: "US gallon", to: { "milliliter": US_IN3_PER_GAL * ML_PER_IN3, "us-quart": 4, "us-pint": 8, "us-fluid-ounce": 128 } },
192
+ // Imperial.
193
+ "imperial-fluid-ounce": { abbr: `fl${NNBSP}oz`, to: { milliliter: IMP_ML_PER_GAL / 160 } },
194
+ "imperial-pint": { abbr: `pt`, to: { "milliliter": IMP_ML_PER_GAL / 8, "imperial-fluid-ounce": 20 } },
195
+ "imperial-quart": { abbr: "qt", to: { "milliliter": IMP_ML_PER_GAL / 4, "imperial-pint": 2, "imperial-fluid-ounce": 40 } },
196
+ "imperial-gallon": { abbr: "gal", to: { "milliliter": IMP_ML_PER_GAL, "imperial-quart": 4, "imperial-pint": 8, "imperial-fluid-ounce": 160 } },
197
+ "cubic-inch": { abbr: "in³", plural: "cubic inches", to: { milliliter: ML_PER_IN3 } },
198
+ "cubic-foot": { abbr: "ft³", plural: "cubic feet", to: { "milliliter": IN_PER_FT ** 3 * ML_PER_IN3, "cubic-inch": IN_PER_FT ** 3 } },
199
+ "cubic-yard": { abbr: "yd³", to: { "milliliter": IN_PER_YD ** 3 * ML_PER_IN3, "cubic-foot": FT_PER_YD ** 3, "cubic-inch": IN_PER_YD ** 3 } },
200
+ });
201
+ /** Temperature units. */
202
+ export const TEMPERATURE_UNITS = new UnitList({
203
+ celsius: { abbr: "°C", singular: "degree Celsius", plural: "degrees Celsius", to: { fahrenheit: n => n * (9 / 5) + 32, kelvin: n => n + 273.15 } },
204
+ fahrenheit: { abbr: "°F", singular: "degree Fahrenheit", plural: "degrees Fahrenheit", to: { celsius: n => (n - 32) * (5 / 9) } },
205
+ kelvin: { abbr: "°K", singular: "degree Kelvin", plural: "degrees Kelvin", to: { celsius: n => n - 273.15 } },
206
+ });
207
+ /** Format a percentage (combines `getPercent()` and `formatUnits()` for convenience). */
208
+ export const formatPercent = (numerator, denumerator, maxPrecision, minPrecision) => formatQuantity(getPercent(numerator, denumerator), "%", maxPrecision, minPrecision);
209
+ /** Format a duration with a formatter. */
210
+ function _getTimeUnitReference(ms) {
90
211
  const abs = Math.abs(ms);
91
- if (abs <= 99 * SECOND)
92
- return formatter(ms, "second", maxPrecision, minPrecision); // Up to 99 seconds, e.g. '22 seconds ago'
93
- if (abs <= HOUR)
94
- return formatter(ms / MINUTE, "minute", maxPrecision, minPrecision); // Up to one hour — show minutes, e.g. '18 minutes ago'
95
- if (abs <= DAY)
96
- return formatter(ms / HOUR, "hour", maxPrecision, minPrecision); // Up to one day — show hours, e.g. '23 hours ago'
97
- if (abs <= 2 * WEEK)
98
- return formatter(ms / DAY, "day", maxPrecision, minPrecision); // Up to 2 weeks — show days, e.g. '13 days ago'
99
- if (abs <= 10 * WEEK)
100
- return formatter(ms / WEEK, "week", maxPrecision, minPrecision); // Up to 2 months — show weeks, e.g. '6 weeks ago'
101
- if (abs <= 18 * MONTH)
102
- return formatter(ms / MONTH, "month", maxPrecision, minPrecision); // Up to 18 months — show months, e.g. '6 months ago'
103
- return formatter(ms / YEAR, "year", maxPrecision, minPrecision); // Above 18 months — show years, e.g. '2 years ago'
212
+ if (abs > 18 * MONTH)
213
+ return "year";
214
+ if (abs > 10 * WEEK)
215
+ return "month";
216
+ if (abs > 2 * WEEK)
217
+ return "week";
218
+ if (abs > DAY)
219
+ return "day";
220
+ if (abs > HOUR)
221
+ return "hour";
222
+ if (abs > 9949)
223
+ return "minute";
224
+ if (abs > SECOND)
225
+ return "second";
226
+ return "millisecond";
227
+ }
228
+ /** Format a full format of a duration of time using the most reasonable units e.g. `5 years` or `1 week` or `4 minutes` or `12 milliseconds`. */
229
+ export function formatFullDuration(ms, maxPrecision, minPrecision) {
230
+ const unit = TIME_UNITS.get(_getTimeUnitReference(ms));
231
+ return unit.formatFull(unit.from(ms), maxPrecision, minPrecision);
104
232
  }
105
- /** Format a full description of a duration of time using the most reasonable units e.g. `5 years` or `1 week` or `4 minutes` or `12 milliseconds`. */
106
- export const formatFullDuration = (ms, maxPrecision, minPrecision) => _formatDuration(formatFullUnits, ms, maxPrecision, minPrecision);
107
233
  /** Format a description of a duration of time using the most reasonable units e.g. `5y` or `4m` or `12ms`. */
108
- export const formatDuration = (ms, maxPrecision, minPrecision) => _formatDuration(formatUnits, ms, maxPrecision, minPrecision);
109
- /**
110
- * Return full description of the gap between two dates, e.g. `in 10 days` or `2 hours ago`
111
- *
112
- * @param target The date when the thing will happen or did happen.
113
- * @param current Today's date (or a different date to measure from).
114
- */
115
- export function formatFullWhen(target, current) {
116
- const ms = getDuration(target, current);
117
- const abs = Math.abs(ms);
118
- const duration = formatFullDuration(abs);
119
- return abs < 10 * SECOND ? "just now" : ms > 0 ? `in ${duration}` : `${duration} ago`;
234
+ export function formatDuration(ms, maxPrecision, minPrecision) {
235
+ const unit = TIME_UNITS.get(_getTimeUnitReference(ms));
236
+ return unit.format(unit.from(ms), maxPrecision, minPrecision);
120
237
  }
121
- /**
122
- * Return full description of when a date will happen, e.g. `10 days` or `2 hours` or `-1 week`
123
- *
124
- * @param target The date when the thing happened.
125
- * @param current Today's date (or a different date to measure from).
126
- */
127
- export const formatFullAgo = (target, current) => formatFullDuration(getDuration(current, target));
128
- /**
129
- * Compact how long until a date happens, e.g. `in 10d` or `2h ago` or `in 1w`
130
- *
131
- * @param target The date when the thing will happen.
132
- * @param current Today's date (or a different date to measure from).
133
- */
134
- export function formatWhen(target, current) {
238
+ /** format when a data happens/happened. */
239
+ function _formatWhen(formatter, target, current) {
135
240
  const ms = getDuration(target, current);
136
241
  const abs = Math.abs(ms);
137
- const duration = formatDuration(abs);
242
+ const duration = formatter(ms);
138
243
  return abs < 10 * SECOND ? "just now" : ms > 0 ? `in ${duration}` : `${duration} ago`;
139
244
  }
140
- /**
141
- * Return short description of when a date happened, e.g. `10d` or `2h` or `-1w`
142
- *
143
- * @param target The date when the thing will happen.
144
- * @param current Today's date (or a different date to measure from).
145
- */
245
+ /** Full when a date happens/happened, e.g. `in 10 days` or `2 hours ago` */
246
+ export const formatFullWhen = (target, current) => _formatWhen(formatFullDuration, target, current);
247
+ /** Compact when a date happens/happened, e.g. `in 10d` or `2h ago` or `in 1w` */
248
+ export const formatWhen = (target, current) => _formatWhen(formatDuration, target, current);
249
+ /** Full when a date happens, e.g. `10 days` or `2 hours` or `-1 week` */
250
+ export const formatFullUntil = (target, current) => formatFullDuration(getDuration(target, current));
251
+ /** Compact when a date happens, e.g. `10d` or `2h` or `-1w` */
146
252
  export const formatUntil = (target, current) => formatDuration(getDuration(target, current));
147
- /**
148
- * Return short description of when a date will happen, e.g. `10d` or `2h` or `-1w`
149
- *
150
- * @param target The date when the thing happened.
151
- * @param current Today's date (or a different date to measure from).
152
- */
253
+ /** Full when a date happened, e.g. `10 days` or `2 hours` or `-1 week` */
254
+ export const formatFullAgo = (target, current) => formatFullDuration(getDuration(current, target));
255
+ /** Compact when a date will happen, e.g. `10d` or `2h` or `-1w` */
153
256
  export const formatAgo = (target, current) => formatDuration(getDuration(current, target));