@versatiles/style 5.2.6 → 5.2.7

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.
Files changed (130) hide show
  1. package/dist/index.d.ts +274 -14
  2. package/dist/index.js +3650 -11
  3. package/dist/index.js.map +1 -1
  4. package/package.json +8 -8
  5. package/src/color/abstract.ts +83 -0
  6. package/src/color/hsl.test.ts +182 -0
  7. package/src/color/hsl.ts +122 -0
  8. package/src/color/hsv.test.ts +174 -0
  9. package/src/color/hsv.ts +100 -0
  10. package/src/color/index.test.ts +119 -0
  11. package/src/color/index.ts +38 -0
  12. package/src/color/random.test.ts +35 -0
  13. package/src/color/random.ts +165 -0
  14. package/src/color/rgb.test.ts +227 -0
  15. package/src/color/rgb.ts +248 -0
  16. package/src/color/utils.test.ts +86 -0
  17. package/src/color/utils.ts +13 -0
  18. package/src/guess_style/guess_style.test.ts +134 -0
  19. package/src/guess_style/guess_style.ts +166 -0
  20. package/{dist/guess_style/index.d.ts → src/guess_style/index.ts} +1 -0
  21. package/src/index.test.ts +77 -0
  22. package/src/index.ts +18 -0
  23. package/src/lib/utils.test.ts +197 -0
  24. package/src/lib/utils.ts +134 -0
  25. package/{dist/shortbread/index.d.ts → src/shortbread/index.ts} +1 -0
  26. package/src/shortbread/layers.test.ts +36 -0
  27. package/src/shortbread/layers.ts +564 -0
  28. package/src/shortbread/properties.test.ts +44 -0
  29. package/src/shortbread/properties.ts +142 -0
  30. package/src/shortbread/template.test.ts +43 -0
  31. package/src/shortbread/template.ts +343 -0
  32. package/src/style_builder/decorator.test.ts +67 -0
  33. package/src/style_builder/decorator.ts +135 -0
  34. package/src/style_builder/recolor.test.ts +306 -0
  35. package/src/style_builder/recolor.ts +110 -0
  36. package/src/style_builder/style_builder.test.ts +103 -0
  37. package/src/style_builder/style_builder.ts +134 -0
  38. package/src/style_builder/types.ts +141 -0
  39. package/src/styles/LICENSE.md +41 -0
  40. package/src/styles/colorful.ts +1041 -0
  41. package/src/styles/eclipse.ts +11 -0
  42. package/{dist/styles/empty.d.ts → src/styles/empty.ts} +7 -3
  43. package/src/styles/graybeard.ts +11 -0
  44. package/src/styles/index.ts +33 -0
  45. package/src/styles/neutrino.ts +429 -0
  46. package/{dist/types/index.d.ts → src/types/index.ts} +1 -0
  47. package/{dist/types/maplibre.d.ts → src/types/maplibre.ts} +3 -0
  48. package/src/types/tilejson.test.ts +94 -0
  49. package/src/types/tilejson.ts +125 -0
  50. package/src/types/vector_layer.test.ts +64 -0
  51. package/src/types/vector_layer.ts +69 -0
  52. package/dist/color/abstract.d.ts +0 -34
  53. package/dist/color/abstract.js +0 -53
  54. package/dist/color/abstract.js.map +0 -1
  55. package/dist/color/hsl.d.ts +0 -23
  56. package/dist/color/hsl.js +0 -98
  57. package/dist/color/hsl.js.map +0 -1
  58. package/dist/color/hsv.d.ts +0 -20
  59. package/dist/color/hsv.js +0 -100
  60. package/dist/color/hsv.js.map +0 -1
  61. package/dist/color/index.d.ts +0 -6
  62. package/dist/color/index.js +0 -29
  63. package/dist/color/index.js.map +0 -1
  64. package/dist/color/random.d.ts +0 -9
  65. package/dist/color/random.js +0 -134
  66. package/dist/color/random.js.map +0 -1
  67. package/dist/color/rgb.d.ts +0 -28
  68. package/dist/color/rgb.js +0 -195
  69. package/dist/color/rgb.js.map +0 -1
  70. package/dist/color/utils.d.ts +0 -3
  71. package/dist/color/utils.js +0 -10
  72. package/dist/color/utils.js.map +0 -1
  73. package/dist/guess_style/guess_style.d.ts +0 -8
  74. package/dist/guess_style/guess_style.js +0 -147
  75. package/dist/guess_style/guess_style.js.map +0 -1
  76. package/dist/guess_style/index.js +0 -2
  77. package/dist/guess_style/index.js.map +0 -1
  78. package/dist/lib/utils.d.ts +0 -6
  79. package/dist/lib/utils.js +0 -126
  80. package/dist/lib/utils.js.map +0 -1
  81. package/dist/shortbread/index.js +0 -3
  82. package/dist/shortbread/index.js.map +0 -1
  83. package/dist/shortbread/layers.d.ts +0 -5
  84. package/dist/shortbread/layers.js +0 -521
  85. package/dist/shortbread/layers.js.map +0 -1
  86. package/dist/shortbread/properties.d.ts +0 -7
  87. package/dist/shortbread/properties.js +0 -125
  88. package/dist/shortbread/properties.js.map +0 -1
  89. package/dist/shortbread/template.d.ts +0 -4
  90. package/dist/shortbread/template.js +0 -339
  91. package/dist/shortbread/template.js.map +0 -1
  92. package/dist/style_builder/decorator.d.ts +0 -4
  93. package/dist/style_builder/decorator.js +0 -127
  94. package/dist/style_builder/decorator.js.map +0 -1
  95. package/dist/style_builder/recolor.d.ts +0 -22
  96. package/dist/style_builder/recolor.js +0 -89
  97. package/dist/style_builder/recolor.js.map +0 -1
  98. package/dist/style_builder/style_builder.d.ts +0 -15
  99. package/dist/style_builder/style_builder.js +0 -106
  100. package/dist/style_builder/style_builder.js.map +0 -1
  101. package/dist/style_builder/types.d.ts +0 -122
  102. package/dist/style_builder/types.js +0 -3
  103. package/dist/style_builder/types.js.map +0 -1
  104. package/dist/styles/colorful.d.ts +0 -11
  105. package/dist/styles/colorful.js +0 -956
  106. package/dist/styles/colorful.js.map +0 -1
  107. package/dist/styles/eclipse.d.ts +0 -5
  108. package/dist/styles/eclipse.js +0 -9
  109. package/dist/styles/eclipse.js.map +0 -1
  110. package/dist/styles/empty.js +0 -8
  111. package/dist/styles/empty.js.map +0 -1
  112. package/dist/styles/graybeard.d.ts +0 -5
  113. package/dist/styles/graybeard.js +0 -9
  114. package/dist/styles/graybeard.js.map +0 -1
  115. package/dist/styles/index.d.ts +0 -11
  116. package/dist/styles/index.js +0 -20
  117. package/dist/styles/index.js.map +0 -1
  118. package/dist/styles/neutrino.d.ts +0 -11
  119. package/dist/styles/neutrino.js +0 -401
  120. package/dist/styles/neutrino.js.map +0 -1
  121. package/dist/types/index.js +0 -3
  122. package/dist/types/index.js.map +0 -1
  123. package/dist/types/maplibre.js +0 -2
  124. package/dist/types/maplibre.js.map +0 -1
  125. package/dist/types/tilejson.d.ts +0 -32
  126. package/dist/types/tilejson.js +0 -87
  127. package/dist/types/tilejson.js.map +0 -1
  128. package/dist/types/vector_layer.d.ts +0 -14
  129. package/dist/types/vector_layer.js +0 -51
  130. package/dist/types/vector_layer.js.map +0 -1
@@ -0,0 +1,122 @@
1
+ import { Color } from './abstract.js';
2
+ import { HSV } from './hsv.js';
3
+ import { RGB } from './rgb.js';
4
+ import { clamp, formatFloat, mod } from './utils.js';
5
+
6
+ export class HSL extends Color {
7
+ readonly h: number = 0; // between 0 and 360
8
+ readonly s: number = 0; // between 0 and 100
9
+ readonly l: number = 0; // between 0 and 100
10
+ readonly a: number = 1; // between 0 and 1
11
+
12
+ constructor(h: number, s: number, l: number, a: number = 1) {
13
+ super();
14
+ this.h = mod(h, 360);
15
+ this.s = clamp(s, 0, 100);
16
+ this.l = clamp(l, 0, 100);
17
+ this.a = clamp(a, 0, 1);
18
+ }
19
+
20
+ asArray(): [number, number, number, number] {
21
+ return [this.h, this.s, this.l, this.a];
22
+ }
23
+
24
+ round(): HSL {
25
+ return new HSL(
26
+ Math.round(this.h),
27
+ Math.round(this.s),
28
+ Math.round(this.l),
29
+ Math.round(this.a * 1000) / 1000,
30
+ );
31
+ }
32
+
33
+ clone(): HSL {
34
+ return new HSL(this.h, this.s, this.l, this.a);
35
+ }
36
+
37
+ asString(): string {
38
+ if (this.a === 1) {
39
+ return `hsl(${this.h.toFixed(0)},${this.s.toFixed(0)}%,${this.l.toFixed(0)}%)`;
40
+ } else {
41
+ return `hsla(${this.h.toFixed(0)},${this.s.toFixed(0)}%,${this.l.toFixed(0)}%,${formatFloat(this.a, 3)})`;
42
+ }
43
+ }
44
+
45
+ asHSL(): HSL {
46
+ return this.clone();
47
+ }
48
+
49
+ toHSL(): HSL {
50
+ return this;
51
+ }
52
+
53
+ asHSV(): HSV {
54
+ const s = this.s / 100, l = this.l / 100;
55
+ const v = l + s * Math.min(l, 1 - l);
56
+ const sv = v === 0 ? 0 : 2 * (1 - l / v);
57
+ return new HSV(this.h, sv * 100, v * 100, this.a);
58
+ }
59
+
60
+ asRGB(): RGB {
61
+ const h = this.h / 360;
62
+ const s = this.s / 100;
63
+ const l = this.l / 100;
64
+
65
+ // Achromatic (grey)
66
+ if (s === 0) return new RGB(l * 255, l * 255, l * 255, this.a);
67
+
68
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
69
+ const p = 2 * l - q;
70
+
71
+ const hueToRgb = (t: number): number => {
72
+ if (t < 0) t += 1;
73
+ if (t > 1) t -= 1;
74
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
75
+ if (t < 1 / 2) return q;
76
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
77
+ return p;
78
+ };
79
+
80
+ // Convert to RGB in the 0-255 range and return
81
+ return new RGB(
82
+ 255 * hueToRgb(h + 1 / 3),
83
+ 255 * hueToRgb(h),
84
+ 255 * hueToRgb(h - 1 / 3),
85
+ this.a
86
+ );
87
+ }
88
+
89
+ static parse(input: string | Color): HSL {
90
+ if (input instanceof Color) return input.asHSL();
91
+
92
+ input = input.replace(/\s+/g, '').toLowerCase();
93
+
94
+ let match = input.match(/^hsl\((?<h>[-+0-9.]+)(?:deg)?,(?<s>[-+0-9.]+)%,(?<l>[-+0-9.]+)%\)$/);
95
+ if (match) {
96
+ return new HSL(parseFloat(match.groups!.h), parseFloat(match.groups!.s), parseFloat(match.groups!.l));
97
+ }
98
+
99
+ match = input.match(/^hsla\((?<h>[-+0-9.]+)(?:deg)?,(?<s>[-+0-9.]+)%,(?<l>[-+0-9.]+)%,(?<a>[-+0-9.]+)\)$/);
100
+ if (match) {
101
+ return new HSL(parseFloat(match.groups!.h), parseFloat(match.groups!.s), parseFloat(match.groups!.l), parseFloat(match.groups!.a));
102
+ }
103
+
104
+ throw new Error(`Invalid HSL color string: "${input}"`);
105
+ }
106
+
107
+ invertLuminosity(): HSL {
108
+ return new HSL(this.h, this.s, 100 - this.l, this.a);
109
+ }
110
+
111
+ rotateHue(offset: number): HSL {
112
+ return new HSL(mod(this.h + offset, 360), this.s, this.l, this.a);
113
+ }
114
+
115
+ saturate(ratio: number): HSL {
116
+ return new HSL(this.h, clamp(this.s * (1 + ratio), 0, 100), this.l, this.a);
117
+ }
118
+
119
+ fade(value: number): HSL {
120
+ return new HSL(this.h, this.s, this.l, this.a * (1 - value));
121
+ }
122
+ }
@@ -0,0 +1,174 @@
1
+ import { HSV } from './hsv.js';
2
+ import { HSL } from './hsl.js';
3
+ import { RGB } from './rgb.js';
4
+
5
+ describe('HSV Class', () => {
6
+
7
+ test('constructor initializes values correctly with clamping', () => {
8
+ const color = new HSV(400, 120, 120, 2);
9
+ expect(color.asArray()).toStrictEqual([40, 100, 100, 1]);
10
+
11
+ const colorNegative = new HSV(-60, -10, -10, -1);
12
+ expect(colorNegative.asArray()).toStrictEqual([300, 0, 0, 0]);
13
+ });
14
+
15
+ test('asArray returns correct array representation', () => {
16
+ const color = new HSV(120, 50, 50, 0.5);
17
+ expect(color.asArray()).toStrictEqual([120, 50, 50, 0.5]);
18
+ });
19
+
20
+ test('clone returns a new instance with identical values', () => {
21
+ const color = new HSV(180, 70, 70, 0.7);
22
+ const clone = color.clone();
23
+ expect(clone).toBeInstanceOf(HSV);
24
+ expect(clone).toEqual(color);
25
+ expect(clone).not.toBe(color);
26
+ });
27
+
28
+ describe('asString', () => {
29
+ test('converts fully saturated colors correctly', () => {
30
+ expect(new HSV(0, 100, 100).asString()).toBe('hsl(0,100%,50%)');
31
+ expect(new HSV(120, 100, 100).asString()).toBe('hsl(120,100%,50%)');
32
+ expect(new HSV(240, 100, 100).asString()).toBe('hsl(240,100%,50%)');
33
+ });
34
+
35
+ test('handles partially saturated colors', () => {
36
+ expect(new HSV(60, 50, 100).asString()).toBe('hsl(60,100%,75%)');
37
+ expect(new HSV(300, 25, 50).asString()).toBe('hsl(300,14%,44%)');
38
+ });
39
+
40
+ test('handles achromatic (grey) colors', () => {
41
+ expect(new HSV(0, 0, 0).asString()).toBe('hsl(0,0%,0%)');
42
+ expect(new HSV(0, 0, 50).asString()).toBe('hsl(0,0%,50%)');
43
+ expect(new HSV(0, 0, 100).asString()).toBe('hsl(0,0%,100%)');
44
+ });
45
+
46
+ test('handles hue wrapping and extreme values', () => {
47
+ expect(new HSV(-60, 100, 100).asString()).toBe('hsl(300,100%,50%)');
48
+ expect(new HSV(420, 100, 100).asString()).toBe('hsl(60,100%,50%)');
49
+ });
50
+
51
+ test('handles alpha transparency', () => {
52
+ expect(new HSV(0, 100, 100, 0.5).asString()).toBe('hsla(0,100%,50%,0.5)');
53
+ expect(new HSV(120, 100, 100, 0.25).asString()).toBe('hsla(120,100%,50%,0.25)');
54
+ expect(new HSV(240, 100, 100, 1).asString()).toBe('hsl(240,100%,50%)');
55
+ });
56
+
57
+ test('produces consistent results for repeated calls', () => {
58
+ const color = new HSV(60, 50, 50);
59
+ expect(color.asString()).toBe('hsl(60,33%,38%)');
60
+ expect(color.asString()).toBe('hsl(60,33%,38%)');
61
+ });
62
+ });
63
+
64
+ describe('color conversion', () => {
65
+
66
+ test('asHSL converts HSV to HSL correctly', () => {
67
+ function check(input: [number, number, number], output: [number, number, number]) {
68
+ const hsv = new HSV(...input);
69
+ const hsl = hsv.asHSL();
70
+ expect(hsl).toBeInstanceOf(HSL);
71
+ expect(hsl.asArray().map(Math.round))
72
+ .toStrictEqual([...output, 1]);
73
+
74
+ expect(hsv.asHex()).toStrictEqual(hsl.asHex());
75
+ }
76
+
77
+ check([10, 0, 0], [10, 0, 0]);
78
+ check([11, 0, 50], [11, 0, 50]);
79
+ check([12, 0, 100], [12, 0, 100]);
80
+ check([13, 50, 0], [13, 0, 0]);
81
+ check([14, 50, 50], [14, 33, 38]);
82
+ check([15, 50, 100], [15, 100, 75]);
83
+ check([16, 100, 0], [16, 0, 0]);
84
+ check([17, 100, 50], [17, 100, 25]);
85
+ check([18, 100, 100], [18, 100, 50]);
86
+ });
87
+
88
+ test('asRGB converts HSV to RGB correctly', () => {
89
+ const color = new HSV(120, 100, 100);
90
+ const rgb = color.asRGB();
91
+ expect(rgb).toBeInstanceOf(RGB);
92
+ expect(rgb.asArray().map(value => Math.round(value)))
93
+ .toStrictEqual([0, 255, 0, 1]);
94
+ });
95
+
96
+ test('asHSV and toHSV return the same instance or clone', () => {
97
+ const color = new HSV(240, 100, 50, 1);
98
+ expect(color.asHSV()).toStrictEqual(color);
99
+ expect(color.toHSV()).toStrictEqual(color);
100
+ });
101
+
102
+ test('asRGB conversion S and V', () => {
103
+ function check(input: [number, number, number], output: [number, number, number]) {
104
+ const color = new HSV(...input);
105
+ const rgb = color.asRGB();
106
+ expect(rgb.asArray().map(value => Math.round(value)))
107
+ .toStrictEqual([...output, 1]);
108
+ }
109
+
110
+ check([10, 0, 0], [0, 0, 0]);
111
+ check([11, 0, 50], [128, 128, 128]);
112
+ check([12, 0, 100], [255, 255, 255]);
113
+ check([13, 50, 0], [0, 0, 0]);
114
+ check([14, 50, 50], [128, 79, 64]);
115
+ check([15, 50, 100], [255, 159, 128]);
116
+ check([16, 100, 0], [0, 0, 0]);
117
+ check([17, 100, 50], [128, 36, 0]);
118
+ check([18, 100, 100], [255, 77, 0]);
119
+ });
120
+
121
+ test('asRGB conversion H', () => {
122
+ function check(hue: number, output: string) {
123
+ const color = new HSV(hue, 100, 100);
124
+ expect(color.asRGB().asHex()).toBe(output);
125
+ }
126
+
127
+ check(-1, '#FF0004');
128
+ check(0, '#FF0000');
129
+ check(1, '#FF0400');
130
+ check(30, '#FF8000');
131
+ check(60, '#FFFF00');
132
+ check(90, '#80FF00');
133
+ check(120, '#00FF00');
134
+ check(150, '#00FF80');
135
+ check(180, '#00FFFF');
136
+ check(210, '#0080FF');
137
+ check(240, '#0000FF');
138
+ check(270, '#8000FF');
139
+ check(300, '#FF00FF');
140
+ check(330, '#FF0080');
141
+ check(359, '#FF0004');
142
+ check(360, '#FF0000');
143
+ check(361, '#FF0400');
144
+ });
145
+
146
+ });
147
+
148
+ describe('parse errors and validations', () => {
149
+ test('constructor clamps out-of-bound values', () => {
150
+ const color = new HSV(400, 150, 150, 2);
151
+ expect(color.asArray()).toStrictEqual([40, 100, 100, 1]);
152
+ });
153
+
154
+ test('negative values are handled correctly', () => {
155
+ const color = new HSV(-360, -50, -50, -1);
156
+ expect(color.asArray()).toStrictEqual([0, 0, 0, 0]);
157
+ });
158
+ });
159
+
160
+ describe('fade', () => {
161
+ test('reduces alpha correctly', () => {
162
+ const color = new HSV(120, 50, 50, 0.8);
163
+ expect(color.fade(0.5).asArray()).toStrictEqual([120, 50, 50, 0.4]); // Alpha reduced by 50%
164
+ });
165
+
166
+ test('handles edge cases for fading', () => {
167
+ const opaque = new HSV(0, 50, 50, 1);
168
+ expect(opaque.fade(1).asArray()).toStrictEqual([0, 50, 50, 0]); // Fully faded to transparent
169
+
170
+ const transparent = new HSV(0, 50, 50, 0);
171
+ expect(transparent.fade(0.5).asArray()).toStrictEqual([0, 50, 50, 0]); // Remains fully transparent
172
+ });
173
+ });
174
+ });
@@ -0,0 +1,100 @@
1
+ import { Color } from './abstract.js';
2
+ import { HSL } from './hsl.js';
3
+ import { RGB } from './rgb.js';
4
+ import { clamp, mod } from './utils.js';
5
+
6
+ export class HSV extends Color {
7
+ readonly h: number = 0; // between 0 and 360
8
+ readonly s: number = 0; // between 0 and 100
9
+ readonly v: number = 0; // between 0 and 100
10
+ readonly a: number = 1; // between 0 and 1
11
+
12
+ constructor(h: number, s: number, v: number, a: number = 1) {
13
+ super();
14
+ this.h = mod(h, 360);
15
+ this.s = clamp(s, 0, 100);
16
+ this.v = clamp(v, 0, 100);
17
+ this.a = clamp(a, 0, 1);
18
+ }
19
+
20
+ asArray(): [number, number, number, number] {
21
+ return [this.h, this.s, this.v, this.a];
22
+ }
23
+
24
+ round(): HSV {
25
+ return new HSV(
26
+ Math.round(this.h),
27
+ Math.round(this.s),
28
+ Math.round(this.v),
29
+ Math.round(this.a * 1000) / 1000
30
+ );
31
+ }
32
+
33
+ asString(): string {
34
+ return this.asHSL().asString();
35
+ }
36
+
37
+ clone(): HSV {
38
+ return new HSV(this.h, this.s, this.v, this.a);
39
+ }
40
+
41
+ asHSL(): HSL {
42
+ const s = this.s / 100;
43
+ const v = this.v / 100;
44
+ const k = (2 - s) * v;
45
+ const q = k < 1 ? k : 2 - k
46
+ return new HSL(
47
+ this.h,
48
+ q == 0 ? 0 : 100 * s * v / q,
49
+ 100 * k / 2,
50
+ this.a
51
+ );
52
+ }
53
+
54
+ asHSV(): HSV {
55
+ return this.clone();
56
+ }
57
+
58
+ toHSV(): HSV {
59
+ return this;
60
+ }
61
+
62
+ asRGB(): RGB {
63
+ const h = this.h / 360; // Normalize h to range [0, 1]
64
+ const s = this.s / 100; // Normalize s to range [0, 1]
65
+ const v = this.v / 100; // Normalize v to range [0, 1]
66
+
67
+ let r = 0, g = 0, b = 0;
68
+
69
+ if (s === 0) {
70
+ // Achromatic (grey)
71
+ r = g = b = v;
72
+ } else {
73
+ const i = Math.floor(h * 6); // Determine the sector of the color wheel
74
+ const f = h * 6 - i; // Fractional part of h * 6
75
+ const p = v * (1 - s);
76
+ const q = v * (1 - s * f);
77
+ const t = v * (1 - s * (1 - f));
78
+
79
+ switch (i % 6) {
80
+ case 0: r = v; g = t; b = p; break;
81
+ case 1: r = q; g = v; b = p; break;
82
+ case 2: r = p; g = v; b = t; break;
83
+ case 3: r = p; g = q; b = v; break;
84
+ case 4: r = t; g = p; b = v; break;
85
+ case 5: r = v; g = p; b = q; break;
86
+ }
87
+ }
88
+
89
+ // Convert to RGB in the 0-255 range and return
90
+ return new RGB(r * 255, g * 255, b * 255, this.a);
91
+ }
92
+
93
+ fade(value: number): HSV {
94
+ return new HSV(this.h, this.s, this.v, this.a * (1 - value));
95
+ }
96
+
97
+ setHue(value: number): HSV {
98
+ return new HSV(value, this.s, this.v, this.a);
99
+ }
100
+ }
@@ -0,0 +1,119 @@
1
+ import { Color } from './index.js';
2
+ import { HSL } from './hsl.js';
3
+ import { HSV } from './hsv.js';
4
+ import { RGB } from './rgb.js';
5
+
6
+ describe('Color Conversions', () => {
7
+
8
+ const scenarios: [number, number, number, number][] = [
9
+ [-100, 14, 15, 0],
10
+ [0, 0, 0, 0.1],
11
+ [100, 0, 50, 0.2],
12
+ [200, 0, 100, 0.3],
13
+ [300, 50, 0, 0.4],
14
+ [400, 50, 50, 0.5],
15
+ [500, 50, 100, 0.6],
16
+ [600, 100, 0, 0.7],
17
+ [700, 100, 50, 0.8],
18
+ [800, 100, 100, 0.9],
19
+ [900, 12, 13, 1.0],
20
+ ]
21
+ test('test HSV -> HSL -> RGB', () => {
22
+ for (const v of scenarios) {
23
+ const hsv = new HSV(...v);
24
+ expect(hsv.a).toEqual(v[3]);
25
+ const hsl = hsv.asHSL();
26
+ const a1 = hsv.asRGB().asArray();
27
+ const a2 = hsl.asRGB().asArray();
28
+ for (let i = 0; i < 4; i++) expect(a1[i]).toBeCloseTo(a2[i]);
29
+ }
30
+ });
31
+
32
+ test('test HSL -> HSV -> RGB', () => {
33
+ for (const v of scenarios) {
34
+ const hsl = new HSL(...v);
35
+ expect(hsl.a).toEqual(v[3]);
36
+ const hsv = hsl.asHSV();
37
+ const a1 = hsv.asRGB().asArray();
38
+ const a2 = hsl.asRGB().asArray();
39
+ for (let i = 0; i < 4; i++) expect(a1[i]).toBeCloseTo(a2[i]);
40
+ }
41
+ });
42
+ })
43
+
44
+ describe('Color.parse', () => {
45
+ test('parses hexadecimal color strings correctly', () => {
46
+ const color = Color.parse('#ff8040');
47
+ expect(color).toBeInstanceOf(RGB);
48
+ expect(color.asArray()).toStrictEqual([255, 128, 64, 1]);
49
+
50
+ const colorWithAlpha = Color.parse('#ff80407f');
51
+ expect(colorWithAlpha).toBeInstanceOf(RGB);
52
+ expect(colorWithAlpha.asHex()).toStrictEqual('#FF80407F');
53
+ });
54
+
55
+ test('parses RGB strings correctly', () => {
56
+ const color = Color.parse('rgb(255, 128, 64)');
57
+ expect(color).toBeInstanceOf(RGB);
58
+ expect(color.asArray()).toStrictEqual([255, 128, 64, 1]);
59
+
60
+ const colorWithAlpha = Color.parse('rgba(255, 128, 64, 0.5)');
61
+ expect(colorWithAlpha).toBeInstanceOf(RGB);
62
+ expect(colorWithAlpha.asArray()).toStrictEqual([255, 128, 64, 0.5]);
63
+ });
64
+
65
+ test('parses HSL strings correctly', () => {
66
+ const color = Color.parse('hsl(120, 50%, 50%)');
67
+ expect(color).toBeInstanceOf(HSL);
68
+ expect(color.asArray()).toStrictEqual([120, 50, 50, 1]);
69
+
70
+ const colorWithAlpha = Color.parse('hsla(120, 50%, 50%, 0.5)');
71
+ expect(colorWithAlpha).toBeInstanceOf(HSL);
72
+ expect(colorWithAlpha.asArray()).toStrictEqual([120, 50, 50, 0.5]);
73
+ });
74
+
75
+ test('throws an error for unsupported formats', () => {
76
+ expect(() => Color.parse('invalid color string')).toThrow('Unknown color format: invalid color string');
77
+ });
78
+ });
79
+
80
+ describe('Color.random', () => {
81
+ test('generates random HSV colors', () => {
82
+ const random = Color.random();
83
+ expect(random).toBeInstanceOf(HSV);
84
+ const array = random.asArray();
85
+ expect(array[0]).toBeGreaterThanOrEqual(0);
86
+ expect(array[0]).toBeLessThanOrEqual(360);
87
+ expect(array[1]).toBeGreaterThanOrEqual(0);
88
+ expect(array[1]).toBeLessThanOrEqual(100);
89
+ expect(array[2]).toBeGreaterThanOrEqual(0);
90
+ expect(array[2]).toBeLessThanOrEqual(100);
91
+ });
92
+
93
+ test('supports options for generating random colors', () => {
94
+ const random = Color.random({ hue: 'red', luminosity: 'bright' });
95
+ expect(random).toBeInstanceOf(HSV);
96
+ // Additional checks based on the options provided can be added here
97
+ });
98
+ });
99
+
100
+ describe('Color Class Properties', () => {
101
+ test('Color.HSL is accessible', () => {
102
+ expect(Color.HSL).toBe(HSL);
103
+ });
104
+
105
+ test('Color.HSV is accessible', () => {
106
+ expect(Color.HSV).toBe(HSV);
107
+ });
108
+
109
+ test('Color.RGB is accessible', () => {
110
+ expect(Color.RGB).toBe(RGB);
111
+ });
112
+ });
113
+
114
+ describe('Exported Module', () => {
115
+ test('named export is Color', async () => {
116
+ const module = await import('./index.js');
117
+ expect(module.Color).toBe(Color);
118
+ });
119
+ });
@@ -0,0 +1,38 @@
1
+ import { Color } from './abstract.js';
2
+ import { HSL } from './hsl.js';
3
+ import { HSV } from './hsv.js';
4
+ import randomColor from './random.js';
5
+ import { RGB } from './rgb.js';
6
+
7
+ Color.parse = function (input: string | Color): Color {
8
+ if (input instanceof Color) return input;
9
+
10
+ input = input.trim().toLowerCase();
11
+
12
+ if (input.startsWith('#')) return RGB.parse(input);
13
+
14
+ const prefix = input.replace(/\d.*/, '').trim().toLowerCase();
15
+
16
+ switch (prefix) {
17
+ case 'rgb(':
18
+ case 'rgba(':
19
+ return RGB.parse(input);
20
+ case 'hsl(':
21
+ case 'hsla(':
22
+ return HSL.parse(input);
23
+ default:
24
+ throw Error('Unknown color format: ' + input);
25
+ }
26
+ }
27
+
28
+ Color.HSL = HSL;
29
+ Color.HSV = HSV;
30
+ Color.RGB = RGB;
31
+
32
+ Color.random = randomColor;
33
+
34
+ export type { RandomColorOptions } from './random.js';
35
+ export type { HSL } from './hsl.js';
36
+ export type { HSV } from './hsv.js';
37
+ export type { RGB } from './rgb.js';
38
+ export { Color };
@@ -0,0 +1,35 @@
1
+ import type { RandomColorOptions } from './random.js';
2
+ import randomColor from './random.js';
3
+
4
+ describe('RandomColor', () => {
5
+ test('constructor initializes without errors', () => {
6
+ expect(randomColor).toBeDefined();
7
+ });
8
+
9
+ describe('randomColor method', () => {
10
+ test('returns correct color string for some test cases', () => {
11
+ function t(options: RandomColorOptions): string {
12
+ return randomColor(options).asHSL().asString();
13
+ }
14
+ expect(t({ seed: 'testSeed', hue: 'red' })).toBe('hsl(356,90%,30%)');
15
+ expect(t({ seed: 'testSeed', hue: 120 })).toBe('hsl(120,92%,26%)');
16
+ expect(t({ seed: 'testSeed', luminosity: 'dark' })).toBe('hsl(185,98%,19%)');
17
+ expect(t({ seed: 'testSeed', luminosity: 12 })).toBe('hsl(185,90%,6%)');
18
+ expect(t({ seed: 'testSeed', saturation: 'strong' })).toBe('hsl(185,100%,48%)');
19
+ expect(t({ seed: 'testSeed', opacity: 0.5 })).toBe('hsla(185,90%,23%,0.5)');
20
+ expect(t({ seed: 'testSeed' })).toBe('hsl(185,90%,23%)');
21
+ });
22
+
23
+ test('consistent color generation with a seed', () => {
24
+ const color1 = randomColor({ seed: 'consistentSeed' });
25
+ const color2 = randomColor({ seed: 'consistentSeed' });
26
+ expect(color1.asHex()).toBe(color2.asHex());
27
+ });
28
+
29
+ test('different color generation without a seed', () => {
30
+ const color1 = randomColor({ seed: 'seed1' });
31
+ const color2 = randomColor({ seed: 'seed2' });
32
+ expect(color1.asHex()).not.toBe(color2.asHex());
33
+ });
34
+ });
35
+ });