@oscarpalmer/atoms 0.191.0 → 0.193.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/beacon.d.mts +6 -44
- package/dist/beacon.mjs +20 -67
- package/dist/color/constants.d.mts +82 -41
- package/dist/color/constants.mjs +102 -63
- package/dist/color/instance.mjs +36 -13
- package/dist/color/misc/alpha.mjs +9 -10
- package/dist/color/misc/format.mjs +4 -4
- package/dist/color/misc/get.mjs +18 -18
- package/dist/color/misc/is.mjs +22 -21
- package/dist/color/misc/state.d.mts +6 -5
- package/dist/color/misc/state.mjs +31 -26
- package/dist/color/models.d.mts +78 -1
- package/dist/color/space/hex.mjs +12 -12
- package/dist/color/space/hsl.mjs +16 -16
- package/dist/color/space/hwb.mjs +10 -10
- package/dist/color/space/rgb.mjs +16 -16
- package/dist/herald.d.mts +2 -3
- package/dist/index.d.mts +141 -67
- package/dist/index.mjs +552 -391
- package/dist/internal/subscription.d.mts +52 -0
- package/dist/internal/subscription.mjs +127 -0
- package/dist/models.d.mts +5 -1
- package/dist/queue.d.mts +25 -23
- package/dist/queue.mjs +27 -28
- package/package.json +1 -1
- package/src/beacon.ts +32 -117
- package/src/color/constants.ts +115 -78
- package/src/color/instance.ts +65 -12
- package/src/color/misc/alpha.ts +13 -21
- package/src/color/misc/format.ts +4 -4
- package/src/color/misc/get.ts +23 -40
- package/src/color/misc/is.ts +29 -39
- package/src/color/misc/state.ts +77 -41
- package/src/color/models.ts +94 -1
- package/src/color/space/hex.ts +18 -23
- package/src/color/space/hsl.ts +16 -24
- package/src/color/space/hwb.ts +10 -10
- package/src/color/space/rgb.ts +19 -16
- package/src/herald.ts +1 -3
- package/src/index.ts +1 -0
- package/src/internal/subscription.ts +264 -0
- package/src/models.ts +5 -0
- package/src/queue.ts +92 -92
- package/src/string/fuzzy.ts +1 -1
- package/src/value/freeze.ts +2 -2
- package/src/value/shake.ts +1 -1
- package/src/value/transform.ts +1 -1
package/src/color/space/hex.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import {join} from '../../internal/string';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
LENGTH_SHORT,
|
|
10
|
-
MAX_HEX,
|
|
11
|
-
TYPE_HSL,
|
|
12
|
-
TYPE_HWB,
|
|
13
|
-
TYPE_RGB,
|
|
3
|
+
COLOR_ALPHA,
|
|
4
|
+
COLOR_DEFAULTS,
|
|
5
|
+
COLOR_EXPRESSION,
|
|
6
|
+
COLOR_LENGTHS,
|
|
7
|
+
COLOR_MAX,
|
|
8
|
+
COLOR_TYPE,
|
|
14
9
|
} from '../constants';
|
|
15
10
|
import {getHexValue, getPercentage} from '../misc/get';
|
|
16
11
|
import {isHexColor} from '../misc/is';
|
|
@@ -30,7 +25,7 @@ import {convertRgbToHsla, convertRgbToHwba} from './rgb';
|
|
|
30
25
|
|
|
31
26
|
function convertHexToRgba(value: string): RGBAColor {
|
|
32
27
|
const normalized = getNormalizedHex(value, true);
|
|
33
|
-
const pairs =
|
|
28
|
+
const pairs = COLOR_EXPRESSION.hexLong.exec(normalized) as RegExpExecArray;
|
|
34
29
|
const values: number[] = [];
|
|
35
30
|
|
|
36
31
|
const {length} = pairs;
|
|
@@ -40,7 +35,7 @@ function convertHexToRgba(value: string): RGBAColor {
|
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
return {
|
|
43
|
-
alpha: getPercentage((values[3] /
|
|
38
|
+
alpha: getPercentage((values[3] / COLOR_MAX.hex) * 100),
|
|
44
39
|
blue: getHexValue(values[2]),
|
|
45
40
|
green: getHexValue(values[1]),
|
|
46
41
|
red: getHexValue(values[0]),
|
|
@@ -52,15 +47,15 @@ export function getColorFromHex<Type extends ColorType>(
|
|
|
52
47
|
type: Type,
|
|
53
48
|
): NonNullable<ColorState[Type]> {
|
|
54
49
|
switch (type) {
|
|
55
|
-
case
|
|
50
|
+
case COLOR_TYPE.hsl:
|
|
56
51
|
state.hsl ??= hexToHsl(state.hex!);
|
|
57
52
|
break;
|
|
58
53
|
|
|
59
|
-
case
|
|
54
|
+
case COLOR_TYPE.hwb:
|
|
60
55
|
state.hwb ??= hexToHwb(state.hex!);
|
|
61
56
|
break;
|
|
62
57
|
|
|
63
|
-
case
|
|
58
|
+
case COLOR_TYPE.rgb:
|
|
64
59
|
state.rgb ??= hexToRgb(state.hex!);
|
|
65
60
|
break;
|
|
66
61
|
}
|
|
@@ -81,21 +76,21 @@ export function getNormalizedHex(value: unknown, alpha?: boolean): string {
|
|
|
81
76
|
const includeAlpha = alpha ?? false;
|
|
82
77
|
|
|
83
78
|
if (!isHexColor(value)) {
|
|
84
|
-
return `${
|
|
79
|
+
return `${COLOR_DEFAULTS.hexBlack}${includeAlpha ? COLOR_ALPHA.fullHexLong : ''}`;
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
const normalized = value.replace(
|
|
82
|
+
const normalized = value.replace(COLOR_EXPRESSION.prefix, '');
|
|
88
83
|
|
|
89
|
-
if (normalized.length <
|
|
90
|
-
const hex = normalized.slice(0,
|
|
91
|
-
const a = includeAlpha ? (normalized[
|
|
84
|
+
if (normalized.length < COLOR_LENGTHS.hexLong) {
|
|
85
|
+
const hex = normalized.slice(0, COLOR_LENGTHS.hexShort);
|
|
86
|
+
const a = includeAlpha ? (normalized[COLOR_LENGTHS.hexShort] ?? COLOR_ALPHA.fullHexShort) : '';
|
|
92
87
|
|
|
93
88
|
return join(`${hex}${a}`.split('').map(character => character.repeat(2)));
|
|
94
89
|
}
|
|
95
90
|
|
|
96
|
-
const hex = normalized.slice(0,
|
|
91
|
+
const hex = normalized.slice(0, COLOR_LENGTHS.hexLong);
|
|
97
92
|
|
|
98
|
-
const a = includeAlpha ? normalized.slice(
|
|
93
|
+
const a = includeAlpha ? normalized.slice(COLOR_LENGTHS.hexLong) || COLOR_ALPHA.fullHexLong : '';
|
|
99
94
|
|
|
100
95
|
return `${hex}${a}`;
|
|
101
96
|
}
|
package/src/color/space/hsl.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_HSL,
|
|
3
|
-
MAX_DEGREE,
|
|
4
|
-
MAX_HEX,
|
|
5
|
-
MAX_PERCENT,
|
|
6
|
-
TYPE_HEX,
|
|
7
|
-
TYPE_HWB,
|
|
8
|
-
TYPE_RGB,
|
|
9
|
-
} from '../constants';
|
|
1
|
+
import {COLOR_DEFAULTS, COLOR_MAX, COLOR_TYPE} from '../constants';
|
|
10
2
|
import {getAlphaValue} from '../misc/alpha';
|
|
11
3
|
import {getDegrees, getHexValue, getPercentage} from '../misc/get';
|
|
12
4
|
import {isHslLike} from '../misc/is';
|
|
@@ -25,12 +17,12 @@ import {convertRgbToHex} from './rgb';
|
|
|
25
17
|
// #region Functions
|
|
26
18
|
|
|
27
19
|
function convertHslToHwba(input: unknown): HWBAColor {
|
|
28
|
-
const hsl = isHslLike(input) ? getHslValue(input) : {...
|
|
20
|
+
const hsl = isHslLike(input) ? getHslValue(input) : {...COLOR_DEFAULTS.hsl};
|
|
29
21
|
|
|
30
22
|
let {hue, lightness, saturation} = hsl;
|
|
31
23
|
|
|
32
|
-
lightness /=
|
|
33
|
-
saturation /=
|
|
24
|
+
lightness /= COLOR_MAX.percent;
|
|
25
|
+
saturation /= COLOR_MAX.percent;
|
|
34
26
|
|
|
35
27
|
const value = lightness + saturation * Math.min(lightness, 1 - lightness);
|
|
36
28
|
|
|
@@ -45,21 +37,21 @@ function convertHslToHwba(input: unknown): HWBAColor {
|
|
|
45
37
|
|
|
46
38
|
return {
|
|
47
39
|
hue,
|
|
48
|
-
whiteness: whiteness *
|
|
49
|
-
blackness: blackness *
|
|
50
|
-
alpha: getAlphaValue((input as HSLAColor)?.alpha ??
|
|
40
|
+
whiteness: whiteness * COLOR_MAX.percent,
|
|
41
|
+
blackness: blackness * COLOR_MAX.percent,
|
|
42
|
+
alpha: getAlphaValue((input as HSLAColor)?.alpha ?? COLOR_MAX.percent),
|
|
51
43
|
};
|
|
52
44
|
}
|
|
53
45
|
|
|
54
46
|
function convertHslToRgba(input: unknown): RGBAColor {
|
|
55
|
-
const hsl = isHslLike(input) ? getHslValue(input) : {...
|
|
47
|
+
const hsl = isHslLike(input) ? getHslValue(input) : {...COLOR_DEFAULTS.hsl};
|
|
56
48
|
|
|
57
|
-
const hue = hsl.hue %
|
|
58
|
-
const saturation = hsl.saturation /
|
|
59
|
-
const lightness = hsl.lightness /
|
|
49
|
+
const hue = hsl.hue % COLOR_MAX.degree;
|
|
50
|
+
const saturation = hsl.saturation / COLOR_MAX.percent;
|
|
51
|
+
const lightness = hsl.lightness / COLOR_MAX.percent;
|
|
60
52
|
|
|
61
53
|
return {
|
|
62
|
-
alpha: getAlphaValue((input as HSLAColor)?.alpha ??
|
|
54
|
+
alpha: getAlphaValue((input as HSLAColor)?.alpha ?? COLOR_MAX.percent),
|
|
63
55
|
blue: getHexValue(getHexyValue(hue, lightness, saturation, 4)),
|
|
64
56
|
green: getHexValue(getHexyValue(hue, lightness, saturation, 8)),
|
|
65
57
|
red: getHexValue(getHexyValue(hue, lightness, saturation, 0)),
|
|
@@ -71,15 +63,15 @@ export function getColorFromHsl<Type extends ColorType>(
|
|
|
71
63
|
type: Type,
|
|
72
64
|
): NonNullable<ColorState[Type]> {
|
|
73
65
|
switch (type) {
|
|
74
|
-
case
|
|
66
|
+
case COLOR_TYPE.hex:
|
|
75
67
|
state.hex = hslToHex(state.hsl!);
|
|
76
68
|
break;
|
|
77
69
|
|
|
78
|
-
case
|
|
70
|
+
case COLOR_TYPE.hwb:
|
|
79
71
|
state.hwb = hslToHwb(state.hsl!);
|
|
80
72
|
break;
|
|
81
73
|
|
|
82
|
-
case
|
|
74
|
+
case COLOR_TYPE.rgb:
|
|
83
75
|
state.rgb = hslToRgb(state.hsl!);
|
|
84
76
|
break;
|
|
85
77
|
}
|
|
@@ -91,7 +83,7 @@ function getHexyValue(hue: number, lightness: number, saturation: number, value:
|
|
|
91
83
|
const part = (value + hue / 30) % 12;
|
|
92
84
|
const mod = saturation * Math.min(lightness, 1 - lightness);
|
|
93
85
|
|
|
94
|
-
return (lightness - mod * Math.max(-1, Math.min(part - 3, 9 - part, 1))) *
|
|
86
|
+
return (lightness - mod * Math.max(-1, Math.min(part - 3, 9 - part, 1))) * COLOR_MAX.hex;
|
|
95
87
|
}
|
|
96
88
|
|
|
97
89
|
export function getHslValue(value: Record<keyof HSLColor, unknown>): HSLColor {
|
package/src/color/space/hwb.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {COLOR_DEFAULTS, COLOR_MAX, COLOR_TYPE} from '../constants';
|
|
2
2
|
import {getAlphaValue} from '../misc/alpha';
|
|
3
3
|
import {getDegrees, getPercentage} from '../misc/get';
|
|
4
4
|
import {isHwbLike} from '../misc/is';
|
|
@@ -18,10 +18,10 @@ import {rgbToHex} from './rgb';
|
|
|
18
18
|
// #region Functions
|
|
19
19
|
|
|
20
20
|
export function convertHwbToHsl(input: HWBAColor | HWBColor): HSLAColor {
|
|
21
|
-
let {blackness, hue, whiteness} = isHwbLike(input) ? getHwbValue(input) :
|
|
21
|
+
let {blackness, hue, whiteness} = isHwbLike(input) ? getHwbValue(input) : COLOR_DEFAULTS.hwb;
|
|
22
22
|
|
|
23
|
-
blackness /=
|
|
24
|
-
whiteness /=
|
|
23
|
+
blackness /= COLOR_MAX.percent;
|
|
24
|
+
whiteness /= COLOR_MAX.percent;
|
|
25
25
|
|
|
26
26
|
if (blackness + whiteness > 1) {
|
|
27
27
|
const total = blackness + whiteness;
|
|
@@ -36,9 +36,9 @@ export function convertHwbToHsl(input: HWBAColor | HWBColor): HSLAColor {
|
|
|
36
36
|
|
|
37
37
|
return {
|
|
38
38
|
hue,
|
|
39
|
-
saturation: saturation *
|
|
40
|
-
lightness: lightness *
|
|
41
|
-
alpha: getAlphaValue((input as HWBAColor)?.alpha ??
|
|
39
|
+
saturation: saturation * COLOR_MAX.percent,
|
|
40
|
+
lightness: lightness * COLOR_MAX.percent,
|
|
41
|
+
alpha: getAlphaValue((input as HWBAColor)?.alpha ?? COLOR_MAX.percent),
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -47,15 +47,15 @@ export function getColorFromHwb<Type extends ColorType>(
|
|
|
47
47
|
type: Type,
|
|
48
48
|
): NonNullable<ColorState[Type]> {
|
|
49
49
|
switch (type) {
|
|
50
|
-
case
|
|
50
|
+
case COLOR_TYPE.hex:
|
|
51
51
|
state.hex = hwbToHex(state.hwb!);
|
|
52
52
|
break;
|
|
53
53
|
|
|
54
|
-
case
|
|
54
|
+
case COLOR_TYPE.hsl:
|
|
55
55
|
state.hsl = hwbToHsl(state.hwb!);
|
|
56
56
|
break;
|
|
57
57
|
|
|
58
|
-
case
|
|
58
|
+
case COLOR_TYPE.rgb:
|
|
59
59
|
state.rgb = hwbToRgb(state.hwb!);
|
|
60
60
|
break;
|
|
61
61
|
}
|
package/src/color/space/rgb.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {join} from '../../internal/string';
|
|
2
|
-
import {
|
|
2
|
+
import {COLOR_DEFAULTS, COLOR_MAX, COLOR_TYPE} from '../constants';
|
|
3
3
|
import {getAlpha, getAlphaValue} from '../misc/alpha';
|
|
4
4
|
import {getHexValue} from '../misc/get';
|
|
5
5
|
import {isRgbLike} from '../misc/is';
|
|
@@ -48,7 +48,7 @@ export function convertRgbToHex(rgb: RGBAColor | RGBColor, alpha: boolean): stri
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export function convertRgbToHsla(value: unknown): HSLAColor {
|
|
51
|
-
const rgb = isRgbLike(value) ? getRgbValue(value) : {...
|
|
51
|
+
const rgb = isRgbLike(value) ? getRgbValue(value) : {...COLOR_DEFAULTS.rgb};
|
|
52
52
|
const values = getRgbValues(rgb);
|
|
53
53
|
|
|
54
54
|
const {delta, max, min} = values;
|
|
@@ -68,14 +68,14 @@ export function convertRgbToHsla(value: unknown): HSLAColor {
|
|
|
68
68
|
|
|
69
69
|
return {
|
|
70
70
|
hue,
|
|
71
|
-
saturation: saturation *
|
|
72
|
-
lightness: lightness *
|
|
73
|
-
alpha: getAlphaValue((value as RGBAColor)?.alpha ??
|
|
71
|
+
saturation: saturation * COLOR_MAX.percent,
|
|
72
|
+
lightness: lightness * COLOR_MAX.percent,
|
|
73
|
+
alpha: getAlphaValue((value as RGBAColor)?.alpha ?? COLOR_MAX.percent),
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export function convertRgbToHwba(value: unknown): HWBAColor {
|
|
78
|
-
const rgb = isRgbLike(value) ? getRgbValue(value) : {...
|
|
78
|
+
const rgb = isRgbLike(value) ? getRgbValue(value) : {...COLOR_DEFAULTS.rgb};
|
|
79
79
|
const values = getRgbValues(rgb);
|
|
80
80
|
|
|
81
81
|
const {delta, max, min} = values;
|
|
@@ -90,9 +90,9 @@ export function convertRgbToHwba(value: unknown): HWBAColor {
|
|
|
90
90
|
|
|
91
91
|
return {
|
|
92
92
|
hue,
|
|
93
|
-
whiteness: min *
|
|
94
|
-
blackness: (1 - max) *
|
|
95
|
-
alpha: getAlphaValue((value as RGBAColor)?.alpha ??
|
|
93
|
+
whiteness: min * COLOR_MAX.percent,
|
|
94
|
+
blackness: (1 - max) * COLOR_MAX.percent,
|
|
95
|
+
alpha: getAlphaValue((value as RGBAColor)?.alpha ?? COLOR_MAX.percent),
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -101,15 +101,15 @@ export function getColorFromRgb<Type extends ColorType>(
|
|
|
101
101
|
type: Type,
|
|
102
102
|
): NonNullable<ColorState[Type]> {
|
|
103
103
|
switch (type) {
|
|
104
|
-
case
|
|
104
|
+
case COLOR_TYPE.hex:
|
|
105
105
|
state.hex = rgbToHex(state.rgb!);
|
|
106
106
|
break;
|
|
107
107
|
|
|
108
|
-
case
|
|
108
|
+
case COLOR_TYPE.hsl:
|
|
109
109
|
state.hsl = rgbToHsl(state.rgb!);
|
|
110
110
|
break;
|
|
111
111
|
|
|
112
|
-
case
|
|
112
|
+
case COLOR_TYPE.hwb:
|
|
113
113
|
state.hwb = rgbToHwb(state.rgb!);
|
|
114
114
|
break;
|
|
115
115
|
}
|
|
@@ -141,9 +141,9 @@ export function getRgbValue(value: Record<keyof RGBColor, unknown>): RGBColor {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
function getRgbValues(rgb: RGBColor): RgbValues {
|
|
144
|
-
const blue = rgb.blue /
|
|
145
|
-
const green = rgb.green /
|
|
146
|
-
const red = rgb.red /
|
|
144
|
+
const blue = rgb.blue / COLOR_MAX.hex;
|
|
145
|
+
const green = rgb.green / COLOR_MAX.hex;
|
|
146
|
+
const red = rgb.red / COLOR_MAX.hex;
|
|
147
147
|
|
|
148
148
|
const max = Math.max(blue, green, red);
|
|
149
149
|
const min = Math.min(blue, green, red);
|
|
@@ -163,7 +163,10 @@ function getRgbValues(rgb: RGBColor): RgbValues {
|
|
|
163
163
|
* @returns Hex color string
|
|
164
164
|
*/
|
|
165
165
|
export function rgbToHex(rgb: RGBAColor | RGBColor, alpha?: boolean): string {
|
|
166
|
-
return convertRgbToHex(
|
|
166
|
+
return convertRgbToHex(
|
|
167
|
+
isRgbLike(rgb) ? getRgbValue(rgb) : {...COLOR_DEFAULTS.rgb},
|
|
168
|
+
alpha ?? false,
|
|
169
|
+
);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
/**
|
package/src/herald.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {noop} from './internal/function/misc';
|
|
2
|
-
import type {GenericCallback} from './models';
|
|
2
|
+
import type {GenericCallback, Unsubscriber} from './models';
|
|
3
3
|
|
|
4
4
|
// #region Types
|
|
5
5
|
|
|
@@ -57,8 +57,6 @@ export type Herald<Map extends Record<string, GenericCallback>> = {
|
|
|
57
57
|
unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
export type Unsubscriber = () => void;
|
|
61
|
-
|
|
62
60
|
// #endregion
|
|
63
61
|
|
|
64
62
|
// #region Functions
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from './herald';
|
|
|
21
21
|
|
|
22
22
|
export * from './internal/function/misc';
|
|
23
23
|
export * from './internal/string';
|
|
24
|
+
export {isSubscription, type Subscription} from './internal/subscription';
|
|
24
25
|
export * from './internal/value/compare';
|
|
25
26
|
export * from './internal/value/equal';
|
|
26
27
|
export * from './internal/value/get';
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import type {Key} from '../models';
|
|
2
|
+
import {isPlainObject} from './is';
|
|
3
|
+
|
|
4
|
+
// #region Types
|
|
5
|
+
|
|
6
|
+
export type Subscription = {
|
|
7
|
+
/**
|
|
8
|
+
* Is the subscription active?
|
|
9
|
+
*/
|
|
10
|
+
get active(): boolean;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Unsubscribe from changes
|
|
14
|
+
*/
|
|
15
|
+
unsubscribe(): void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type SubscriptionParameters = {
|
|
19
|
+
isActive?: () => boolean;
|
|
20
|
+
key?: Key;
|
|
21
|
+
property: SubscriptionProperty;
|
|
22
|
+
subscriptions: Subscriptions;
|
|
23
|
+
value: unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type SubscriptionProperty = {
|
|
27
|
+
key: string;
|
|
28
|
+
value: unknown;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type SubscriptionState = {
|
|
32
|
+
active: boolean;
|
|
33
|
+
parameters: SubscriptionParameters;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type Subscriptions<Value = unknown> = {
|
|
37
|
+
items: SubscriptionsItems;
|
|
38
|
+
keys?: Set<Key>;
|
|
39
|
+
values: SubscriptionsValues<Value>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type SubscriptionsItems = {
|
|
43
|
+
any: Set<Subscription>;
|
|
44
|
+
keyed?: Map<Key, Set<Subscription>>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type SubscriptionsValues<Value = unknown> = {
|
|
48
|
+
from: SubscriptionsValuesItem<Value, Subscription>;
|
|
49
|
+
to: SubscriptionsValuesItem<Subscription, Value>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type SubscriptionsValuesItem<MapKey, MapValue> = {
|
|
53
|
+
any: Map<MapKey, MapValue>;
|
|
54
|
+
keyed?: Map<Key, Map<MapKey, MapValue>>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// #endregion
|
|
58
|
+
|
|
59
|
+
// #region Functions
|
|
60
|
+
|
|
61
|
+
function addSubscription(subscription: Subscription, state: SubscriptionState): void {
|
|
62
|
+
const {key, subscriptions, value} = state.parameters;
|
|
63
|
+
const {items, values} = subscriptions;
|
|
64
|
+
|
|
65
|
+
if (key == null) {
|
|
66
|
+
items.any.add(subscription);
|
|
67
|
+
values.from.any.set(value, subscription);
|
|
68
|
+
values.to.any.set(subscription, value);
|
|
69
|
+
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (items.keyed == null || values.from.keyed == null || values.to.keyed == null) {
|
|
74
|
+
// TODO: fix tests or document
|
|
75
|
+
// istanbul ignore next
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let keyedItems = items.keyed.get(key);
|
|
80
|
+
|
|
81
|
+
if (keyedItems == null) {
|
|
82
|
+
keyedItems = new Set();
|
|
83
|
+
|
|
84
|
+
items.keyed.set(key, keyedItems);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
keyedItems.add(subscription);
|
|
88
|
+
|
|
89
|
+
let keyedFrom = values.from.keyed.get(key);
|
|
90
|
+
|
|
91
|
+
if (keyedFrom == null) {
|
|
92
|
+
keyedFrom = new Map();
|
|
93
|
+
|
|
94
|
+
values.from.keyed.set(key, keyedFrom);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
keyedFrom.set(value, subscription);
|
|
98
|
+
|
|
99
|
+
let keyedTo = values.to.keyed.get(key);
|
|
100
|
+
|
|
101
|
+
if (keyedTo == null) {
|
|
102
|
+
keyedTo = new Map();
|
|
103
|
+
|
|
104
|
+
values.to.keyed.set(key, keyedTo);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
keyedTo.set(subscription, value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function clearSubscriptions(store: Subscriptions): void {
|
|
111
|
+
for (const susbcription of store.values.to.any.keys()) {
|
|
112
|
+
susbcription.unsubscribe();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (store.values.to.keyed != null) {
|
|
116
|
+
for (const values of store.values.to.keyed.values()) {
|
|
117
|
+
for (const susbcription of values.keys()) {
|
|
118
|
+
susbcription.unsubscribe();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getExistingSubscription(state: SubscriptionState): Subscription | undefined {
|
|
125
|
+
const {parameters} = state;
|
|
126
|
+
const {values} = parameters.subscriptions;
|
|
127
|
+
|
|
128
|
+
return parameters.key == null
|
|
129
|
+
? values.from.any.get(parameters.value)
|
|
130
|
+
: values.from.keyed?.get(parameters.key)?.get(parameters.value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getSubscription(parameters: SubscriptionParameters): Subscription {
|
|
134
|
+
const state: SubscriptionState = {
|
|
135
|
+
parameters,
|
|
136
|
+
active: true,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const existing = getExistingSubscription(state);
|
|
140
|
+
|
|
141
|
+
if (existing != null) {
|
|
142
|
+
return existing;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
parameters.subscriptions.keys != null &&
|
|
147
|
+
parameters.key != null &&
|
|
148
|
+
!parameters.subscriptions.keys.has(parameters.key)
|
|
149
|
+
) {
|
|
150
|
+
throw new Error();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const instance = {
|
|
154
|
+
unsubscribe: () => {
|
|
155
|
+
removeSubscription(instance as Subscription, state);
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
Object.defineProperties(instance, {
|
|
160
|
+
[PROPERTY]: {
|
|
161
|
+
enumerable: false,
|
|
162
|
+
value: true,
|
|
163
|
+
},
|
|
164
|
+
[parameters.property.key]: {
|
|
165
|
+
enumerable: false,
|
|
166
|
+
value: parameters.property.value,
|
|
167
|
+
},
|
|
168
|
+
active: {
|
|
169
|
+
enumerable: true,
|
|
170
|
+
get: () => (parameters.isActive?.() ?? true) && state.active,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
addSubscription(instance as Subscription, state);
|
|
175
|
+
|
|
176
|
+
return Object.freeze(instance) as Subscription;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getSubscriptions<Value = unknown>(keys?: Set<Key>): Subscriptions<Value> {
|
|
180
|
+
return {
|
|
181
|
+
keys,
|
|
182
|
+
items: {
|
|
183
|
+
any: new Set(),
|
|
184
|
+
keyed: keys == null ? undefined : new Map(),
|
|
185
|
+
},
|
|
186
|
+
values: {
|
|
187
|
+
from: {
|
|
188
|
+
any: new Map(),
|
|
189
|
+
keyed: keys == null ? undefined : new Map(),
|
|
190
|
+
},
|
|
191
|
+
to: {
|
|
192
|
+
any: new Map(),
|
|
193
|
+
keyed: keys == null ? undefined : new Map(),
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Is the value a subscription?
|
|
201
|
+
*
|
|
202
|
+
* @param value Value to check
|
|
203
|
+
* @returns `true` if the value is a subscription, otherwise `false`
|
|
204
|
+
*/
|
|
205
|
+
export function isSubscription(value: unknown): value is Subscription {
|
|
206
|
+
return isPlainObject(value) && PROPERTY in value && value[PROPERTY] === true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function removeFromStore(
|
|
210
|
+
items: Set<Subscription>,
|
|
211
|
+
values: SubscriptionsValues,
|
|
212
|
+
subscription: Subscription,
|
|
213
|
+
value: unknown,
|
|
214
|
+
key?: Key,
|
|
215
|
+
): void {
|
|
216
|
+
items.delete(subscription);
|
|
217
|
+
|
|
218
|
+
if (key == null) {
|
|
219
|
+
values.from.any.delete(value);
|
|
220
|
+
values.to.any.delete(subscription);
|
|
221
|
+
} else {
|
|
222
|
+
values.from.keyed?.get(key)?.delete(value);
|
|
223
|
+
values.to.keyed?.get(key)?.delete(subscription);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function removeSubscription(subscription: Subscription, state: SubscriptionState): void {
|
|
228
|
+
if (!state.active) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
state.active = false;
|
|
233
|
+
|
|
234
|
+
const {key, subscriptions, value} = state.parameters;
|
|
235
|
+
const {items, values} = subscriptions;
|
|
236
|
+
|
|
237
|
+
if (key == null) {
|
|
238
|
+
removeFromStore(items.any, values, subscription, value);
|
|
239
|
+
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const keyed = items.keyed?.get(key);
|
|
244
|
+
|
|
245
|
+
if (items.keyed == null || keyed == null) {
|
|
246
|
+
// TODO, fix tests or document
|
|
247
|
+
// istanbul ignore next
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
removeFromStore(keyed, values, subscription, value, key);
|
|
252
|
+
|
|
253
|
+
if (keyed.size === 0) {
|
|
254
|
+
items.keyed.delete(key);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// #endregion
|
|
259
|
+
|
|
260
|
+
// #region Variables
|
|
261
|
+
|
|
262
|
+
const PROPERTY = '$subscription';
|
|
263
|
+
|
|
264
|
+
// #endregion
|