shelving 1.30.4 → 1.31.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 +1 -1
- 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/package.json
CHANGED
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));
|