shelving 1.30.2 → 1.31.1
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/db/Database.d.ts +6 -2
- package/db/Database.js +12 -3
- package/db/errors.d.ts +5 -0
- package/db/errors.js +8 -0
- package/package.json +1 -1
- package/react/index.d.ts +1 -0
- package/react/index.js +1 -0
- package/react/useDocumentData.d.ts +1 -1
- package/react/useQueryFirst.d.ts +36 -0
- package/react/useQueryFirst.js +9 -0
- package/schema/NumberSchema.js +2 -2
- package/util/assert.d.ts +4 -0
- package/util/assert.js +10 -0
- package/util/color.d.ts +29 -0
- package/util/color.js +70 -0
- package/util/index.d.ts +1 -0
- package/util/index.js +1 -0
- package/util/number.d.ts +18 -8
- package/util/number.js +26 -27
package/db/Database.d.ts
CHANGED
|
@@ -56,9 +56,11 @@ export declare class DataQuery<T extends Data = Data> extends Query<T> implement
|
|
|
56
56
|
get count(): number | PromiseLike<number>;
|
|
57
57
|
/**
|
|
58
58
|
* Get an entry for the first document matching this query.
|
|
59
|
-
*
|
|
59
|
+
*
|
|
60
|
+
* @return Entry in `[id, data]` format for the first document.
|
|
61
|
+
* @throws RequiredError if there were no results for this query.
|
|
60
62
|
*/
|
|
61
|
-
get first(): Entry<T> |
|
|
63
|
+
get first(): Entry<T> | PromiseLike<Entry<T>>;
|
|
62
64
|
/**
|
|
63
65
|
* Subscribe to all matching documents.
|
|
64
66
|
* - `next()` is called once with the initial results, and again any time the results change.
|
|
@@ -100,6 +102,8 @@ export declare class DataQuery<T extends Data = Data> extends Query<T> implement
|
|
|
100
102
|
validate(unsafeEntries: Results): Results<T>;
|
|
101
103
|
toString(): string;
|
|
102
104
|
}
|
|
105
|
+
/** Get the data for a document from a result for that document. */
|
|
106
|
+
export declare function getQueryFirst<T extends Data>(results: Results<T>, ref: DataQuery<T>): Entry<T>;
|
|
103
107
|
/** A document reference within a specific database. */
|
|
104
108
|
export declare class DataDocument<T extends Data = Data> implements Observable<Result<T>>, Validatable<T> {
|
|
105
109
|
readonly provider: Provider;
|
package/db/Database.js
CHANGED
|
@@ -2,7 +2,7 @@ import { callAsync, getFirstItem, throwAsync, validate, toMap, countItems, Trans
|
|
|
2
2
|
import { DataUpdate, Update } from "../update/index.js";
|
|
3
3
|
import { Feedback, InvalidFeedback } from "../feedback/index.js";
|
|
4
4
|
import { Filters, Query, EqualFilter } from "../query/index.js";
|
|
5
|
-
import { DocumentRequiredError, DocumentValidationError, QueryValidationError } from "./errors.js";
|
|
5
|
+
import { DocumentRequiredError, DocumentValidationError, QueryRequiredError, QueryValidationError } from "./errors.js";
|
|
6
6
|
import { DocumentDelete, DocumentSet, DocumentUpdate, Writes } from "./Write.js";
|
|
7
7
|
/**
|
|
8
8
|
* Combines a database model and a provider.
|
|
@@ -81,10 +81,12 @@ export class DataQuery extends Query {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* Get an entry for the first document matching this query.
|
|
84
|
-
*
|
|
84
|
+
*
|
|
85
|
+
* @return Entry in `[id, data]` format for the first document.
|
|
86
|
+
* @throws RequiredError if there were no results for this query.
|
|
85
87
|
*/
|
|
86
88
|
get first() {
|
|
87
|
-
return callAsync(
|
|
89
|
+
return callAsync(getQueryFirst, this.max(1).results, this);
|
|
88
90
|
}
|
|
89
91
|
/**
|
|
90
92
|
* Subscribe to all matching documents.
|
|
@@ -158,6 +160,13 @@ export class DataQuery extends Query {
|
|
|
158
160
|
return `${this.collection}?${super.toString()}`;
|
|
159
161
|
}
|
|
160
162
|
}
|
|
163
|
+
/** Get the data for a document from a result for that document. */
|
|
164
|
+
export function getQueryFirst(results, ref) {
|
|
165
|
+
const first = getFirstItem(results);
|
|
166
|
+
if (first)
|
|
167
|
+
return first;
|
|
168
|
+
throw new QueryRequiredError(ref);
|
|
169
|
+
}
|
|
161
170
|
/** A document reference within a specific database. */
|
|
162
171
|
export class DataDocument {
|
|
163
172
|
constructor(provider, validator, collection, id) {
|
package/db/errors.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export declare class DocumentRequiredError<T extends Data> extends RequiredError
|
|
|
7
7
|
ref: DataDocument<T>;
|
|
8
8
|
constructor(ref: DataDocument<T>);
|
|
9
9
|
}
|
|
10
|
+
/** Thrown if a query doesn't exist. */
|
|
11
|
+
export declare class QueryRequiredError<T extends Data> extends RequiredError {
|
|
12
|
+
ref: DataQuery<T>;
|
|
13
|
+
constructor(ref: DataQuery<T>);
|
|
14
|
+
}
|
|
10
15
|
/** Thrown if a document can't validate. */
|
|
11
16
|
export declare class DocumentValidationError<T extends Data> extends ValidationError {
|
|
12
17
|
ref: DataDocument<T>;
|
package/db/errors.js
CHANGED
|
@@ -7,6 +7,14 @@ export class DocumentRequiredError extends RequiredError {
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
DocumentRequiredError.prototype.name = "DocumentRequiredError";
|
|
10
|
+
/** Thrown if a query doesn't exist. */
|
|
11
|
+
export class QueryRequiredError extends RequiredError {
|
|
12
|
+
constructor(ref) {
|
|
13
|
+
super(`Query "${ref.toString()}" has no results`);
|
|
14
|
+
this.ref = ref;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
QueryRequiredError.prototype.name = "QueryRequiredError";
|
|
10
18
|
/** Thrown if a document can't validate. */
|
|
11
19
|
export class DocumentValidationError extends ValidationError {
|
|
12
20
|
constructor(ref, feedback) {
|
package/package.json
CHANGED
package/react/index.d.ts
CHANGED
package/react/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { DataDocument, Data } from "../index.js";
|
|
|
14
14
|
* @trhows `Error` if a `CacheProvider` is not part of the database's provider chain.
|
|
15
15
|
* @throws `Error` if there was a problem retrieving the data.
|
|
16
16
|
*/
|
|
17
|
-
export declare function useAsyncDocumentData<T extends Data>(ref: DataDocument<T>, maxAge?: number | true): T |
|
|
17
|
+
export declare function useAsyncDocumentData<T extends Data>(ref: DataDocument<T>, maxAge?: number | true): T | PromiseLike<T>;
|
|
18
18
|
export declare function useAsyncDocumentData<T extends Data>(ref: DataDocument<T> | undefined, maxAge?: number | true): T | PromiseLike<T> | undefined;
|
|
19
19
|
/**
|
|
20
20
|
* Use the cached data of a document in a React component.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DataQuery, Data, Entry } from "../index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Use the cached data of a document in a React component (or a `Promise` to indicate the data is still loading).
|
|
4
|
+
* - Requires database to use `CacheProvider` and will error if this does not exist.
|
|
5
|
+
*
|
|
6
|
+
* @param ref Query reference or `undefined`.
|
|
7
|
+
* - If `undefined` is set this function will always return `undefined` (this simplifies scenarios where no document is needed, as hooks must always be called in the same order).
|
|
8
|
+
* @param maxAge How 'out of date' data is allowed to be before it'll be refetched.
|
|
9
|
+
* - If `maxAge` is true, a realtime subscription to the data will be created for the lifetime of the component.
|
|
10
|
+
*
|
|
11
|
+
* @returns The data of the document, or `Promise` that resolves when the data has loaded.
|
|
12
|
+
*
|
|
13
|
+
* @throws `RequiredError` if the query had no results.
|
|
14
|
+
* @trhows `Error` if a `CacheProvider` is not part of the database's provider chain.
|
|
15
|
+
* @throws `Error` if there was a problem retrieving the data.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useAsyncQueryFirst<T extends Data>(ref: DataQuery<T>, maxAge?: number | true): Entry<T> | PromiseLike<Entry<T>>;
|
|
18
|
+
export declare function useAsyncQueryFirst<T extends Data>(ref: DataQuery<T> | undefined, maxAge?: number | true): Entry<T> | PromiseLike<Entry<T>> | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Use the cached data of a document in a React component.
|
|
21
|
+
* - Requires database to use `CacheProvider` and will error if this does not exist.
|
|
22
|
+
*
|
|
23
|
+
* @param ref Query reference or `undefined`.
|
|
24
|
+
* - If `undefined` is set this function will always return `undefined` (this simplifies scenarios where no document is needed, as hooks must always be called in the same order).
|
|
25
|
+
* @param maxAge How 'out of date' data is allowed to be before it'll be refetched.
|
|
26
|
+
* - If `maxAge` is true, a realtime subscription to the data will be created for the lifetime of the component.
|
|
27
|
+
*
|
|
28
|
+
* @returns The data of the document.
|
|
29
|
+
*
|
|
30
|
+
* @throws `Promise` that resolves when the data has loaded.
|
|
31
|
+
* @throws `RequiredError` if the query had no results.
|
|
32
|
+
* @trhows `Error` if a `CacheProvider` is not part of the database's provider chain.
|
|
33
|
+
* @throws `Error` if there was a problem retrieving the data.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useQueryFirst<T extends Data>(ref: DataQuery<T>, maxAge?: number | true): Entry<T>;
|
|
36
|
+
export declare function useQueryFirst<T extends Data>(ref: DataQuery<T> | undefined, maxAge?: number | true): Entry<T> | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { callAsync, getQueryFirst, throwAsync } from "../index.js";
|
|
2
|
+
import { useAsyncQuery } from "./useQuery.js";
|
|
3
|
+
export function useAsyncQueryFirst(ref, maxAge) {
|
|
4
|
+
const results = useAsyncQuery(ref ? ref.max(1) : undefined, maxAge);
|
|
5
|
+
return ref && results ? callAsync(getQueryFirst, results, ref) : undefined;
|
|
6
|
+
}
|
|
7
|
+
export function useQueryFirst(ref, maxAge) {
|
|
8
|
+
return throwAsync(useAsyncQueryFirst(ref, maxAge));
|
|
9
|
+
}
|
package/schema/NumberSchema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toNumber,
|
|
1
|
+
import { toNumber, roundStep } from "../util/index.js";
|
|
2
2
|
import { InvalidFeedback } from "../feedback/index.js";
|
|
3
3
|
import { Schema } from "./Schema.js";
|
|
4
4
|
import { OPTIONAL } from "./OptionalSchema.js";
|
|
@@ -15,7 +15,7 @@ export class NumberSchema extends Schema {
|
|
|
15
15
|
const unsafeNumber = toNumber(unsafeValue);
|
|
16
16
|
if (typeof unsafeNumber !== "number")
|
|
17
17
|
throw new InvalidFeedback("Must be number", { value: unsafeValue });
|
|
18
|
-
const safeNumber = typeof this.step === "number" ?
|
|
18
|
+
const safeNumber = typeof this.step === "number" ? roundStep(unsafeNumber, this.step) : unsafeNumber;
|
|
19
19
|
if (typeof this.max === "number" && safeNumber > this.max)
|
|
20
20
|
throw new InvalidFeedback(`Maximum ${this.max}`, { value: safeNumber });
|
|
21
21
|
if (typeof this.min === "number" && safeNumber < this.min)
|
package/util/assert.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ export declare function assertArray<T extends ImmutableArray>(value: T | unknown
|
|
|
29
29
|
export declare function assertLength<T extends {
|
|
30
30
|
length: number;
|
|
31
31
|
}>(value: T | unknown, min: number, max?: number): asserts value is T;
|
|
32
|
+
/** Assert that a value is a number greater than. */
|
|
33
|
+
export declare function assertGreater(value: number | unknown, target: number): asserts value is number;
|
|
34
|
+
/** Assert that a value is a number less than. */
|
|
35
|
+
export declare function assertLess(value: number | unknown, target: number): asserts value is number;
|
|
32
36
|
/** Assert that a value is an instance of something. */
|
|
33
37
|
export declare function assertInstance<T>(value: T | unknown, type: Class<T>): asserts value is T;
|
|
34
38
|
/** Assert that a value is a function. */
|
package/util/assert.js
CHANGED
|
@@ -58,6 +58,16 @@ export function assertLength(value, min, max = min) {
|
|
|
58
58
|
if (!isObject(value) || typeof value.length !== "number" || value.length < min || value.length > max)
|
|
59
59
|
throw new AssertionError(`Must have length ${min}–${max}`, value);
|
|
60
60
|
}
|
|
61
|
+
/** Assert that a value is a number greater than. */
|
|
62
|
+
export function assertGreater(value, target) {
|
|
63
|
+
if (typeof value !== "number" || value <= target)
|
|
64
|
+
throw new AssertionError(`Must be greater than ${target}`, value);
|
|
65
|
+
}
|
|
66
|
+
/** Assert that a value is a number less than. */
|
|
67
|
+
export function assertLess(value, target) {
|
|
68
|
+
if (typeof value !== "number" || value >= target)
|
|
69
|
+
throw new AssertionError(`Must be less than ${target}`, value);
|
|
70
|
+
}
|
|
61
71
|
/** Assert that a value is an instance of something. */
|
|
62
72
|
export function assertInstance(value, type) {
|
|
63
73
|
if (!(value instanceof type))
|
package/util/color.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Things that can be converted to a `Color` instance. */
|
|
2
|
+
export declare type PossibleColor = Color | string;
|
|
3
|
+
/** Represent a color. */
|
|
4
|
+
export declare class Color {
|
|
5
|
+
readonly r: number;
|
|
6
|
+
readonly g: number;
|
|
7
|
+
readonly b: number;
|
|
8
|
+
readonly a: number;
|
|
9
|
+
constructor(r?: number | string, g?: number | string, b?: number | string, a?: number | string);
|
|
10
|
+
/** Convert this color to a six or eight digit hex color. */
|
|
11
|
+
get hex(): string;
|
|
12
|
+
/** Convert this color to an `rgb()` string. */
|
|
13
|
+
get rgb(): string;
|
|
14
|
+
/** Convert this color to an `rgba()` string. */
|
|
15
|
+
get rgba(): string;
|
|
16
|
+
/** Get the sRGB luminance of this color. */
|
|
17
|
+
get luminance(): number;
|
|
18
|
+
toString(): string;
|
|
19
|
+
}
|
|
20
|
+
/** Convert a number or string to a color channel number that's within bounds (strings like `0a` or `ff` are parsed as hexadecimal). */
|
|
21
|
+
export declare function getColorChannel(channel: number | string): number;
|
|
22
|
+
/** Convert a possible color to a `Color` instance or `null` */
|
|
23
|
+
export declare function toColor(color: PossibleColor): Color | null;
|
|
24
|
+
/** Convert a possible color to a `Color` instance */
|
|
25
|
+
export declare function getColor(input: PossibleColor): Color;
|
|
26
|
+
/** Is a color light? */
|
|
27
|
+
export declare const isLight: (input: PossibleColor) => boolean;
|
|
28
|
+
/** Is a color dark? */
|
|
29
|
+
export declare const isDark: (input: PossibleColor) => boolean;
|
package/util/color.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AssertionError } from "../error/index.js";
|
|
2
|
+
import { getBetween, roundNumber } from "./number.js";
|
|
3
|
+
// Constants.
|
|
4
|
+
const DARK = 140; // Anything with a luminance > 140 is considered light.
|
|
5
|
+
const MIN = 0; // Maximum value of a channel.
|
|
6
|
+
const MAX = 255; // Maximum value of a channel.
|
|
7
|
+
// Regular expressions.
|
|
8
|
+
const HEX3 = /^#?([0-F])([0-F])([0-F])$/i;
|
|
9
|
+
const HEX6 = /^#?([0-F]{2})([0-F]{2})([0-F]{2})([0-F]{2})?$/i;
|
|
10
|
+
/** Represent a color. */
|
|
11
|
+
export class Color {
|
|
12
|
+
constructor(r = 255, g = 255, b = 255, a = 255) {
|
|
13
|
+
this.r = getColorChannel(r);
|
|
14
|
+
this.g = getColorChannel(g);
|
|
15
|
+
this.b = getColorChannel(b);
|
|
16
|
+
this.a = getColorChannel(a);
|
|
17
|
+
}
|
|
18
|
+
/** Convert this color to a six or eight digit hex color. */
|
|
19
|
+
get hex() {
|
|
20
|
+
return `#${_hex(this.r)}${_hex(this.g)}${_hex(this.b)}${this.a < MAX ? _hex(this.a) : ""}`;
|
|
21
|
+
}
|
|
22
|
+
/** Convert this color to an `rgb()` string. */
|
|
23
|
+
get rgb() {
|
|
24
|
+
return `rgb(${this.r}, ${this.g}, ${this.b})`;
|
|
25
|
+
}
|
|
26
|
+
/** Convert this color to an `rgba()` string. */
|
|
27
|
+
get rgba() {
|
|
28
|
+
return `rgba(${this.r}, ${this.g}, ${this.b}, ${roundNumber(this.a / 256, 4)})`;
|
|
29
|
+
}
|
|
30
|
+
/** Get the sRGB luminance of this color. */
|
|
31
|
+
get luminance() {
|
|
32
|
+
// Green is the largest component of the luminence, etc.
|
|
33
|
+
return Math.round(0.2126 * this.r + 0.7152 * this.g + 0.0722 * this.b);
|
|
34
|
+
}
|
|
35
|
+
toString() {
|
|
36
|
+
return this.rgba;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Convert number channel to a hex string (results will be unpredictable if number is outside 0-MAX). */
|
|
40
|
+
const _hex = (channel) => channel.toString(16).padStart(2, "00");
|
|
41
|
+
/** Convert a number or string to a color channel number that's within bounds (strings like `0a` or `ff` are parsed as hexadecimal). */
|
|
42
|
+
export function getColorChannel(channel) {
|
|
43
|
+
const num = typeof channel === "string" ? parseInt(channel.padStart(2, "00"), 16) : Math.round(channel);
|
|
44
|
+
if (Number.isFinite(num))
|
|
45
|
+
return getBetween(num, MIN, MAX);
|
|
46
|
+
throw new AssertionError("Invalid color channel", channel);
|
|
47
|
+
}
|
|
48
|
+
/** Convert a possible color to a `Color` instance or `null` */
|
|
49
|
+
export function toColor(color) {
|
|
50
|
+
if (color instanceof Color)
|
|
51
|
+
return color;
|
|
52
|
+
const hex3 = color.match(HEX3);
|
|
53
|
+
if (hex3)
|
|
54
|
+
return new Color(hex3[1], hex3[2], hex3[3]);
|
|
55
|
+
const hex6 = color.match(HEX6);
|
|
56
|
+
if (hex6)
|
|
57
|
+
return new Color(hex6[1], hex6[2], hex6[3], hex6[4]);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/** Convert a possible color to a `Color` instance */
|
|
61
|
+
export function getColor(input) {
|
|
62
|
+
const color = toColor(input);
|
|
63
|
+
if (color)
|
|
64
|
+
return color;
|
|
65
|
+
throw new AssertionError("Invalid color", color);
|
|
66
|
+
}
|
|
67
|
+
/** Is a color light? */
|
|
68
|
+
export const isLight = (input) => getColor(input).luminance > DARK;
|
|
69
|
+
/** Is a color dark? */
|
|
70
|
+
export const isDark = (input) => getColor(input).luminance <= DARK;
|
package/util/index.d.ts
CHANGED
package/util/index.js
CHANGED
package/util/number.d.ts
CHANGED
|
@@ -11,13 +11,6 @@ export declare const IS_NUMBER: (v: unknown) => v is number;
|
|
|
11
11
|
* - Everything else returns `null`
|
|
12
12
|
*/
|
|
13
13
|
export declare function toNumber(value: unknown): number | null;
|
|
14
|
-
/**
|
|
15
|
-
* Convert a stringy number into a number.
|
|
16
|
-
*
|
|
17
|
-
* @param str The string to convert.
|
|
18
|
-
* @return The number, null (valid, meaning no number).
|
|
19
|
-
*/
|
|
20
|
-
export declare const stringToNumber: (str: string) => number | null;
|
|
21
14
|
/**
|
|
22
15
|
* Round numbers to a given step.
|
|
23
16
|
*
|
|
@@ -25,7 +18,16 @@ export declare const stringToNumber: (str: string) => number | null;
|
|
|
25
18
|
* @param step The rounding to round to, e.g. `2` or `0.1` (defaults to `1`, i.e. round numbers).
|
|
26
19
|
* @returns The number rounded to the step.
|
|
27
20
|
*/
|
|
28
|
-
export declare
|
|
21
|
+
export declare function roundStep(num: number, step?: number): number;
|
|
22
|
+
/**
|
|
23
|
+
* Round a number to a specified set of decimal places.
|
|
24
|
+
* - Doesn't include excess `0` zeroes like `num.toFixed()` and `num.toPrecision()` do.
|
|
25
|
+
*
|
|
26
|
+
* @param num The number to format.
|
|
27
|
+
* @param precision Maximum of digits shown after the decimal point (defaults to 10), with zeroes trimmed.
|
|
28
|
+
* @returns The number formatted as a string in the browser's current locale.
|
|
29
|
+
*/
|
|
30
|
+
export declare const roundNumber: (num: number, precision?: number) => string;
|
|
29
31
|
/**
|
|
30
32
|
* Format a number (based on the user's browser settings).
|
|
31
33
|
* @param num The number to format.
|
|
@@ -41,3 +43,11 @@ export declare const formatNumber: (num: number, precision?: number) => string;
|
|
|
41
43
|
* @param end The end of the range, e.g. `20`
|
|
42
44
|
*/
|
|
43
45
|
export declare const isBetween: (num: number, start: number, end: number) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Apply a min/max to a number to return a number that's definitely in the specified range.
|
|
48
|
+
*
|
|
49
|
+
* @param num The number to apply the min/max to, e.g. `17`
|
|
50
|
+
* @param start The start of the range, e.g. `10`
|
|
51
|
+
* @param end The end of the range, e.g. `20`
|
|
52
|
+
*/
|
|
53
|
+
export declare const getBetween: (num: number, start: number, end: number) => number;
|
package/util/number.js
CHANGED
|
@@ -12,33 +12,15 @@ export const IS_NUMBER = (v) => typeof v === "number";
|
|
|
12
12
|
* - Everything else returns `null`
|
|
13
13
|
*/
|
|
14
14
|
export function toNumber(value) {
|
|
15
|
-
if (typeof value === "number"
|
|
16
|
-
return value;
|
|
17
|
-
if (typeof value === "string")
|
|
18
|
-
return
|
|
19
|
-
if (value instanceof Date)
|
|
15
|
+
if (typeof value === "number")
|
|
16
|
+
return Number.isFinite(value) ? value : null;
|
|
17
|
+
else if (typeof value === "string")
|
|
18
|
+
return toNumber(parseFloat(value.replace(NUMERIC, "")));
|
|
19
|
+
else if (value instanceof Date)
|
|
20
20
|
return value.getTime();
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
|
-
const
|
|
24
|
-
const R_PREFIX = /^[^0-9.]*/; // Characters that come before any digits or decimal points
|
|
25
|
-
/**
|
|
26
|
-
* Convert a stringy number into a number.
|
|
27
|
-
*
|
|
28
|
-
* @param str The string to convert.
|
|
29
|
-
* @return The number, null (valid, meaning no number).
|
|
30
|
-
*/
|
|
31
|
-
export const stringToNumber = (str) => {
|
|
32
|
-
var _a;
|
|
33
|
-
if (!str)
|
|
34
|
-
return null;
|
|
35
|
-
const [a, ...b] = str.split(".");
|
|
36
|
-
const num = Number.parseFloat(`${a.replace(R_NON_NUMERIC, "")}.${b.join("").replace(R_NON_NUMERIC, "")}`);
|
|
37
|
-
if (Number.isNaN(num))
|
|
38
|
-
return null;
|
|
39
|
-
const isNeg = !((((_a = str.match(R_PREFIX)) === null || _a === void 0 ? void 0 : _a[0]) || "").split("-").length % 2);
|
|
40
|
-
return Number.isNaN(num) ? null : isNeg ? 0 - num : num;
|
|
41
|
-
};
|
|
23
|
+
const NUMERIC = /[^0-9-.]/g;
|
|
42
24
|
/**
|
|
43
25
|
* Round numbers to a given step.
|
|
44
26
|
*
|
|
@@ -46,11 +28,20 @@ export const stringToNumber = (str) => {
|
|
|
46
28
|
* @param step The rounding to round to, e.g. `2` or `0.1` (defaults to `1`, i.e. round numbers).
|
|
47
29
|
* @returns The number rounded to the step.
|
|
48
30
|
*/
|
|
49
|
-
export
|
|
31
|
+
export function roundStep(num, step = 1) {
|
|
50
32
|
if (step < 0.00001)
|
|
51
|
-
throw new AssertionError("
|
|
33
|
+
throw new AssertionError("roundToStep() does not work accurately with steps smaller than 0.00001", step);
|
|
52
34
|
return Math.round(num / step) * step;
|
|
53
|
-
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Round a number to a specified set of decimal places.
|
|
38
|
+
* - Doesn't include excess `0` zeroes like `num.toFixed()` and `num.toPrecision()` do.
|
|
39
|
+
*
|
|
40
|
+
* @param num The number to format.
|
|
41
|
+
* @param precision Maximum of digits shown after the decimal point (defaults to 10), with zeroes trimmed.
|
|
42
|
+
* @returns The number formatted as a string in the browser's current locale.
|
|
43
|
+
*/
|
|
44
|
+
export const roundNumber = (num, precision = 10) => new Intl.NumberFormat("en-US", { maximumFractionDigits: precision }).format(num);
|
|
54
45
|
/**
|
|
55
46
|
* Format a number (based on the user's browser settings).
|
|
56
47
|
* @param num The number to format.
|
|
@@ -66,3 +57,11 @@ export const formatNumber = (num, precision = 10) => new Intl.NumberFormat(undef
|
|
|
66
57
|
* @param end The end of the range, e.g. `20`
|
|
67
58
|
*/
|
|
68
59
|
export const isBetween = (num, start, end) => num >= start && num <= end;
|
|
60
|
+
/**
|
|
61
|
+
* Apply a min/max to a number to return a number that's definitely in the specified range.
|
|
62
|
+
*
|
|
63
|
+
* @param num The number to apply the min/max to, e.g. `17`
|
|
64
|
+
* @param start The start of the range, e.g. `10`
|
|
65
|
+
* @param end The end of the range, e.g. `20`
|
|
66
|
+
*/
|
|
67
|
+
export const getBetween = (num, start, end) => Math.max(start, Math.min(end, num));
|