@versatiles/style 5.5.2 → 5.7.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/index.js CHANGED
@@ -1,71 +1,188 @@
1
+ /**
2
+ * The abstract `Color` class provides a blueprint for color manipulation and conversion.
3
+ * It includes methods for converting between different color models ({@link HSL}, {@link HSV}, {@link RGB}),
4
+ * as well as various color transformations such as inversion, rotation, saturation, and blending.
5
+ *
6
+ * @abstract
7
+ */
8
+ /**
9
+ * Abstract class representing a color.
10
+ */
1
11
  class Color {
12
+ /**
13
+ * Parses a color from a string or another Color instance.
14
+ * @param input - The input color as a string or Color instance.
15
+ * @returns The parsed Color instance.
16
+ */
2
17
  static parse;
18
+ /**
19
+ * The HSL color model.
20
+ */
3
21
  static HSL;
22
+ /**
23
+ * The HSV color model.
24
+ */
4
25
  static HSV;
26
+ /**
27
+ * The RGB color model.
28
+ */
5
29
  static RGB;
6
- static random;
30
+ /**
31
+ * Converts the color to a hexadecimal string.
32
+ * @returns The hexadecimal representation of the color.
33
+ */
7
34
  asHex() {
8
- return this.toRGB().asHex();
9
- }
10
- toHSL() {
11
- return this.asHSL();
12
- }
13
- toHSV() {
14
- return this.asHSV();
15
- }
16
- toRGB() {
17
- return this.asRGB();
35
+ return this.asRGB().asHex();
18
36
  }
37
+ /**
38
+ * Inverts the luminosity of the color.
39
+ * @returns A new HSL color with inverted luminosity.
40
+ */
19
41
  invertLuminosity() {
20
- return this.toHSL().invertLuminosity();
42
+ return this.asHSL().invertLuminosity();
21
43
  }
44
+ /**
45
+ * Rotates the hue of the color by a given offset.
46
+ * @param offset - The amount to rotate the hue.
47
+ * @returns A new HSL color with the hue rotated.
48
+ */
22
49
  rotateHue(offset) {
23
- return this.toHSL().rotateHue(offset);
50
+ return this.asHSL().rotateHue(offset);
24
51
  }
52
+ /**
53
+ * Saturates the color by a given ratio.
54
+ * @param ratio - The ratio to saturate the color.
55
+ * @returns A new HSL color with increased saturation.
56
+ */
25
57
  saturate(ratio) {
26
- return this.toHSL().saturate(ratio);
58
+ return this.asHSL().saturate(ratio);
27
59
  }
60
+ /**
61
+ * Applies gamma correction to the color.
62
+ * @param value - The gamma correction value.
63
+ * @returns A new RGB color with gamma correction applied.
64
+ */
28
65
  gamma(value) {
29
- return this.toRGB().gamma(value);
66
+ return this.asRGB().gamma(value);
30
67
  }
68
+ /**
69
+ * Inverts the color.
70
+ * @returns A new RGB color with inverted values.
71
+ */
31
72
  invert() {
32
- return this.toRGB().invert();
73
+ return this.asRGB().invert();
33
74
  }
75
+ /**
76
+ * Adjusts the contrast of the color.
77
+ * @param value - The contrast adjustment value.
78
+ * @returns A new RGB color with adjusted contrast.
79
+ */
34
80
  contrast(value) {
35
- return this.toRGB().contrast(value);
81
+ return this.asRGB().contrast(value);
36
82
  }
83
+ /**
84
+ * Adjusts the brightness of the color.
85
+ * @param value - The brightness adjustment value.
86
+ * @returns A new RGB color with adjusted brightness.
87
+ */
37
88
  brightness(value) {
38
- return this.toRGB().brightness(value);
89
+ return this.asRGB().brightness(value);
39
90
  }
91
+ /**
92
+ * Lightens the color by a given value.
93
+ * @param value - The amount to lighten the color.
94
+ * @returns A new RGB color that is lightened.
95
+ */
40
96
  lighten(value) {
41
- return this.toRGB().lighten(value);
97
+ return this.asRGB().lighten(value);
42
98
  }
99
+ /**
100
+ * Darkens the color by a given value.
101
+ * @param value - The amount to darken the color.
102
+ * @returns A new RGB color that is darkened.
103
+ */
43
104
  darken(value) {
44
- return this.toRGB().darken(value);
105
+ return this.asRGB().darken(value);
45
106
  }
107
+ /**
108
+ * Tints the color by blending it with another color.
109
+ * @param value - The blend ratio.
110
+ * @param tintColor - The color to blend with.
111
+ * @returns A new RGB color that is tinted.
112
+ */
46
113
  tint(value, tintColor) {
47
- return this.toRGB().tint(value, tintColor);
114
+ return this.asRGB().tint(value, tintColor);
115
+ }
116
+ /**
117
+ * Blends the color with another color.
118
+ * @param value - The blend ratio.
119
+ * @param blendColor - The color to blend with.
120
+ * @returns A new RGB color that is blended.
121
+ */
122
+ blend(value, blendColor) {
123
+ return this.asRGB().blend(value, blendColor);
48
124
  }
125
+ /**
126
+ * Sets the hue of the color.
127
+ * @param value - The new hue value.
128
+ * @returns A new HSV color with the hue set.
129
+ */
49
130
  setHue(value) {
50
- return this.toHSV().setHue(value);
131
+ return this.asHSV().setHue(value);
51
132
  }
52
133
  }
53
134
 
54
- function clamp(num, min, max) {
55
- return Math.min(Math.max(min, num), max);
135
+ function clamp(value, min, max) {
136
+ if (value == null || isNaN(value))
137
+ return min;
138
+ if (value < min)
139
+ return min;
140
+ if (value > max)
141
+ return max;
142
+ return value;
56
143
  }
57
- function mod(num, max) {
58
- return ((num % max) + max) % max;
144
+ function mod(value, max) {
145
+ value = value % max;
146
+ if (value < 0)
147
+ value += max;
148
+ if (value == 0)
149
+ return 0;
150
+ return value;
59
151
  }
60
152
  function formatFloat(num, precision) {
61
153
  return num.toFixed(precision).replace(/0+$/, '').replace(/\.$/, '');
62
154
  }
63
155
 
156
+ /**
157
+ * Represents an RGB color with optional alpha transparency.
158
+ *
159
+ * @extends Color
160
+ */
64
161
  class RGB extends Color {
65
- r = 0; // between 0 and 255
66
- g = 0; // between 0 and 255
67
- b = 0; // between 0 and 255
68
- a = 1; // between 0 and 1
162
+ /**
163
+ * Red component (0-255).
164
+ */
165
+ r;
166
+ /**
167
+ * Green component (0-255).
168
+ */
169
+ g;
170
+ /**
171
+ * Blue component (0-255).
172
+ */
173
+ b;
174
+ /**
175
+ * Alpha component (0-1).
176
+ */
177
+ a;
178
+ /**
179
+ * Creates an instance of RGB.
180
+ *
181
+ * @param r - Red component (0-255).
182
+ * @param g - Green component (0-255).
183
+ * @param b - Blue component (0-255).
184
+ * @param a - Alpha component (0-1), defaults to 1.
185
+ */
69
186
  constructor(r, g, b, a = 1) {
70
187
  super();
71
188
  this.r = clamp(r, 0, 255);
@@ -73,15 +190,35 @@ class RGB extends Color {
73
190
  this.b = clamp(b, 0, 255);
74
191
  this.a = clamp(a, 0, 1);
75
192
  }
193
+ /**
194
+ * Creates a clone of the current RGB color.
195
+ *
196
+ * @returns A new RGB instance with the same color values.
197
+ */
76
198
  clone() {
77
199
  return new RGB(this.r, this.g, this.b, this.a);
78
200
  }
201
+ /**
202
+ * Returns the RGB color as an array.
203
+ *
204
+ * @returns An array containing the red, green, blue, and alpha components.
205
+ */
79
206
  asArray() {
80
207
  return [this.r, this.g, this.b, this.a];
81
208
  }
209
+ /**
210
+ * Rounds the RGB color components to the nearest integer.
211
+ *
212
+ * @returns A new RGB instance with rounded color values.
213
+ */
82
214
  round() {
83
215
  return new RGB(Math.round(this.r), Math.round(this.g), Math.round(this.b), Math.round(this.a * 1000) / 1000);
84
216
  }
217
+ /**
218
+ * Returns the RGB color as a string.
219
+ *
220
+ * @returns A string representation of the RGB color in either `rgb` or `rgba` format.
221
+ */
85
222
  asString() {
86
223
  if (this.a === 1) {
87
224
  return `rgb(${this.r.toFixed(0)},${this.g.toFixed(0)},${this.b.toFixed(0)})`;
@@ -90,6 +227,11 @@ class RGB extends Color {
90
227
  return `rgba(${this.r.toFixed(0)},${this.g.toFixed(0)},${this.b.toFixed(0)},${formatFloat(this.a, 3)})`;
91
228
  }
92
229
  }
230
+ /**
231
+ * Returns the RGB color as a hexadecimal string.
232
+ *
233
+ * @returns A string representation of the RGB color in hexadecimal format.
234
+ */
93
235
  asHex() {
94
236
  const r = Math.round(this.r).toString(16).padStart(2, '0');
95
237
  const g = Math.round(this.g).toString(16).padStart(2, '0');
@@ -102,6 +244,11 @@ class RGB extends Color {
102
244
  return `#${r}${g}${b}${a}`.toUpperCase();
103
245
  }
104
246
  }
247
+ /**
248
+ * Converts the RGB color to an HSL color.
249
+ *
250
+ * @returns An HSL instance representing the same color.
251
+ */
105
252
  asHSL() {
106
253
  const r = this.r / 255;
107
254
  const g = this.g / 255;
@@ -132,6 +279,11 @@ class RGB extends Color {
132
279
  return new HSL(h, s * 100, l * 100, this.a);
133
280
  }
134
281
  ;
282
+ /**
283
+ * Converts the RGB color to an HSV color.
284
+ *
285
+ * @returns An HSV instance representing the same color.
286
+ */
135
287
  asHSV() {
136
288
  const r = this.r / 255;
137
289
  const g = this.g / 255;
@@ -161,12 +313,29 @@ class RGB extends Color {
161
313
  }
162
314
  return new HSV(h * 360, s * 100, v * 100, this.a);
163
315
  }
316
+ /**
317
+ * Returns the RGB color.
318
+ *
319
+ * @returns The current RGB instance.
320
+ */
164
321
  asRGB() {
165
322
  return this.clone();
166
323
  }
324
+ /**
325
+ * Returns the RGB color.
326
+ *
327
+ * @returns The current RGB instance.
328
+ */
167
329
  toRGB() {
168
330
  return this;
169
331
  }
332
+ /**
333
+ * Parses a string or Color instance into an RGB color.
334
+ *
335
+ * @param input - The input string or Color instance to parse.
336
+ * @returns A new RGB instance representing the parsed color.
337
+ * @throws Will throw an error if the input string is not a valid RGB color string.
338
+ */
170
339
  static parse(input) {
171
340
  if (input instanceof Color)
172
341
  return input.asRGB();
@@ -206,6 +375,12 @@ class RGB extends Color {
206
375
  }
207
376
  throw new Error(`Invalid RGB color string: "${input}"`);
208
377
  }
378
+ /**
379
+ * Adjusts the gamma of the RGB color.
380
+ *
381
+ * @param value - The gamma value to apply.
382
+ * @returns A new RGB instance with the adjusted gamma.
383
+ */
209
384
  gamma(value) {
210
385
  if (value < 1e-3)
211
386
  value = 1e-3;
@@ -213,9 +388,20 @@ class RGB extends Color {
213
388
  value = 1e3;
214
389
  return new RGB(Math.pow(this.r / 255, value) * 255, Math.pow(this.g / 255, value) * 255, Math.pow(this.b / 255, value) * 255, this.a);
215
390
  }
391
+ /**
392
+ * Inverts the RGB color.
393
+ *
394
+ * @returns A new RGB instance with the inverted color values.
395
+ */
216
396
  invert() {
217
397
  return new RGB(255 - this.r, 255 - this.g, 255 - this.b, this.a);
218
398
  }
399
+ /**
400
+ * Adjusts the contrast of the RGB color.
401
+ *
402
+ * @param value - The contrast value to apply.
403
+ * @returns A new RGB instance with the adjusted contrast.
404
+ */
219
405
  contrast(value) {
220
406
  if (value < 0)
221
407
  value = 0;
@@ -223,6 +409,12 @@ class RGB extends Color {
223
409
  value = 1e6;
224
410
  return new RGB(clamp((this.r - 127.5) * value + 127.5, 0, 255), clamp((this.g - 127.5) * value + 127.5, 0, 255), clamp((this.b - 127.5) * value + 127.5, 0, 255), this.a);
225
411
  }
412
+ /**
413
+ * Adjusts the brightness of the RGB color.
414
+ *
415
+ * @param value - The brightness value to apply.
416
+ * @returns A new RGB instance with the adjusted brightness.
417
+ */
226
418
  brightness(value) {
227
419
  if (value < -1)
228
420
  value = -1;
@@ -232,30 +424,90 @@ class RGB extends Color {
232
424
  const b = (value < 0) ? 0 : 255 * value;
233
425
  return new RGB(this.r * a + b, this.g * a + b, this.b * a + b, this.a);
234
426
  }
427
+ /**
428
+ * Tints the RGB color with another color.
429
+ *
430
+ * @param value - The tint value to apply.
431
+ * @param tintColor - The color to use for tinting.
432
+ * @returns A new RGB instance with the applied tint.
433
+ */
235
434
  tint(value, tintColor) {
236
435
  if (value < 0)
237
436
  value = 0;
238
437
  if (value > 1)
239
438
  value = 1;
240
- const rgbNew = this.setHue(tintColor.toHSV().h).toRGB();
439
+ const rgbNew = this.setHue(tintColor.asHSV().h).asRGB();
241
440
  return new RGB(this.r * (1 - value) + value * rgbNew.r, this.g * (1 - value) + value * rgbNew.g, this.b * (1 - value) + value * rgbNew.b, this.a);
242
441
  }
442
+ /**
443
+ * Blends the RGB color with another color.
444
+ *
445
+ * @param value - The blend value to apply.
446
+ * @param blendColor - The color to blend with.
447
+ * @returns A new RGB instance with the blended color.
448
+ */
449
+ blend(value, blendColor) {
450
+ value = clamp(value ?? 0, 0, 1);
451
+ const rgbNew = blendColor.asRGB();
452
+ return new RGB(this.r * (1 - value) + value * rgbNew.r, this.g * (1 - value) + value * rgbNew.g, this.b * (1 - value) + value * rgbNew.b, this.a);
453
+ }
454
+ /**
455
+ * Lightens the RGB color.
456
+ *
457
+ * @param ratio - The ratio to lighten the color by.
458
+ * @returns A new RGB instance with the lightened color.
459
+ */
243
460
  lighten(ratio) {
244
461
  return new RGB(clamp(255 - (255 - this.r) * (1 - ratio), 0, 255), clamp(255 - (255 - this.g) * (1 - ratio), 0, 255), clamp(255 - (255 - this.b) * (1 - ratio), 0, 255), this.a);
245
462
  }
463
+ /**
464
+ * Darkens the RGB color.
465
+ *
466
+ * @param ratio - The ratio to darken the color by.
467
+ * @returns A new RGB instance with the darkened color.
468
+ */
246
469
  darken(ratio) {
247
470
  return new RGB(clamp(this.r * (1 - ratio), 0, 255), clamp(this.g * (1 - ratio), 0, 255), clamp(this.b * (1 - ratio), 0, 255), this.a);
248
471
  }
472
+ /**
473
+ * Fades the RGB color by reducing its alpha value.
474
+ *
475
+ * @param value - The fade value to apply.
476
+ * @returns A new RGB instance with the faded color.
477
+ */
249
478
  fade(value) {
250
479
  return new RGB(this.r, this.g, this.b, this.a * (1 - value));
251
480
  }
252
481
  }
253
482
 
483
+ /**
484
+ * Represents a color in the HSV (Hue, Saturation, Value) color space.
485
+ * Extends the base `Color` class.
486
+ */
254
487
  class HSV extends Color {
255
- h = 0; // between 0 and 360
256
- s = 0; // between 0 and 100
257
- v = 0; // between 0 and 100
258
- a = 1; // between 0 and 1
488
+ /**
489
+ * The hue component of the color, in the range [0, 360].
490
+ */
491
+ h;
492
+ /**
493
+ * The saturation component of the color, in the range [0, 100].
494
+ */
495
+ s;
496
+ /**
497
+ * The value (brightness) component of the color, in the range [0, 100].
498
+ */
499
+ v;
500
+ /**
501
+ * The alpha (opacity) component of the color, in the range [0, 1].
502
+ */
503
+ a;
504
+ /**
505
+ * Constructs a new HSV color.
506
+ * @param h - The hue component, in the range [0, 360].
507
+ * @param s - The saturation component, in the range [0, 100].
508
+ * @param v - The value (brightness) component, in the range [0, 100].
509
+ * @param a - The alpha (opacity) component, in the range [0, 1]. Defaults to 1.
510
+ */
259
511
  constructor(h, s, v, a = 1) {
260
512
  super();
261
513
  this.h = mod(h, 360);
@@ -263,18 +515,38 @@ class HSV extends Color {
263
515
  this.v = clamp(v, 0, 100);
264
516
  this.a = clamp(a, 0, 1);
265
517
  }
518
+ /**
519
+ * Returns the HSV color as an array of numbers.
520
+ * @returns An array containing the hue, saturation, value, and alpha components.
521
+ */
266
522
  asArray() {
267
523
  return [this.h, this.s, this.v, this.a];
268
524
  }
525
+ /**
526
+ * Returns a new HSV color with the components rounded to the nearest integer.
527
+ * @returns A new HSV color with rounded components.
528
+ */
269
529
  round() {
270
530
  return new HSV(Math.round(this.h), Math.round(this.s), Math.round(this.v), Math.round(this.a * 1000) / 1000);
271
531
  }
532
+ /**
533
+ * Returns the color as a string representation.
534
+ * @returns A string representation of the color.
535
+ */
272
536
  asString() {
273
537
  return this.asHSL().asString();
274
538
  }
539
+ /**
540
+ * Creates a new HSV color that is a copy of the current color.
541
+ * @returns A new HSV color that is a clone of the current color.
542
+ */
275
543
  clone() {
276
544
  return new HSV(this.h, this.s, this.v, this.a);
277
545
  }
546
+ /**
547
+ * Converts the HSV color to an HSL color.
548
+ * @returns An HSL representation of the color.
549
+ */
278
550
  asHSL() {
279
551
  const s = this.s / 100;
280
552
  const v = this.v / 100;
@@ -282,12 +554,24 @@ class HSV extends Color {
282
554
  const q = k < 1 ? k : 2 - k;
283
555
  return new HSL(this.h, q == 0 ? 0 : 100 * s * v / q, 100 * k / 2, this.a);
284
556
  }
557
+ /**
558
+ * Returns the current HSV color.
559
+ * @returns The current HSV color.
560
+ */
285
561
  asHSV() {
286
562
  return this.clone();
287
563
  }
564
+ /**
565
+ * Returns the current HSV color.
566
+ * @returns The current HSV color.
567
+ */
288
568
  toHSV() {
289
569
  return this;
290
570
  }
571
+ /**
572
+ * Converts the HSV color to an RGB color.
573
+ * @returns An RGB representation of the color.
574
+ */
291
575
  asRGB() {
292
576
  const h = this.h / 360; // Normalize h to range [0, 1]
293
577
  const s = this.s / 100; // Normalize s to range [0, 1]
@@ -339,19 +623,52 @@ class HSV extends Color {
339
623
  // Convert to RGB in the 0-255 range and return
340
624
  return new RGB(r * 255, g * 255, b * 255, this.a);
341
625
  }
626
+ /**
627
+ * Fades the color by a given value.
628
+ * @param value - The amount to fade the color by, in the range [0, 1].
629
+ * @returns A new HSV color with the alpha component faded by the given value.
630
+ */
342
631
  fade(value) {
343
632
  return new HSV(this.h, this.s, this.v, this.a * (1 - value));
344
633
  }
634
+ /**
635
+ * Sets the hue component of the color.
636
+ * @param value - The new hue value, in the range [0, 360].
637
+ * @returns A new HSV color with the updated hue component.
638
+ */
345
639
  setHue(value) {
346
640
  return new HSV(value, this.s, this.v, this.a);
347
641
  }
348
642
  }
349
643
 
644
+ /**
645
+ * Represents a color in the HSL (Hue, Saturation, Lightness) color space.
646
+ * Extends the base `Color` class.
647
+ */
350
648
  class HSL extends Color {
351
- h = 0; // between 0 and 360
352
- s = 0; // between 0 and 100
353
- l = 0; // between 0 and 100
354
- a = 1; // between 0 and 1
649
+ /**
650
+ * The hue component of the color, in the range [0, 360].
651
+ */
652
+ h;
653
+ /**
654
+ * The saturation component of the color, in the range [0, 100].
655
+ */
656
+ s;
657
+ /**
658
+ * The lightness component of the color, in the range [0, 100].
659
+ */
660
+ l;
661
+ /**
662
+ * The alpha (opacity) component of the color, in the range [0, 1].
663
+ */
664
+ a;
665
+ /**
666
+ * Creates a new HSL color.
667
+ * @param h - The hue component, in the range [0, 360].
668
+ * @param s - The saturation component, in the range [0, 100].
669
+ * @param l - The lightness component, in the range [0, 100].
670
+ * @param a - The alpha (opacity) component, in the range [0, 1]. Defaults to 1.
671
+ */
355
672
  constructor(h, s, l, a = 1) {
356
673
  super();
357
674
  this.h = mod(h, 360);
@@ -359,15 +676,31 @@ class HSL extends Color {
359
676
  this.l = clamp(l, 0, 100);
360
677
  this.a = clamp(a, 0, 1);
361
678
  }
679
+ /**
680
+ * Returns the HSL color as an array of numbers.
681
+ * @returns An array containing the hue, saturation, lightness, and alpha components.
682
+ */
362
683
  asArray() {
363
684
  return [this.h, this.s, this.l, this.a];
364
685
  }
686
+ /**
687
+ * Returns a new HSL color with rounded components.
688
+ * @returns A new HSL color with rounded hue, saturation, lightness, and alpha components.
689
+ */
365
690
  round() {
366
691
  return new HSL(Math.round(this.h), Math.round(this.s), Math.round(this.l), Math.round(this.a * 1000) / 1000);
367
692
  }
693
+ /**
694
+ * Creates a copy of the current HSL color.
695
+ * @returns A new HSL color with the same components as the current color.
696
+ */
368
697
  clone() {
369
698
  return new HSL(this.h, this.s, this.l, this.a);
370
699
  }
700
+ /**
701
+ * Returns the HSL color as a CSS-compatible string.
702
+ * @returns A string representing the HSL color in CSS format.
703
+ */
371
704
  asString() {
372
705
  if (this.a === 1) {
373
706
  return `hsl(${this.h.toFixed(0)},${this.s.toFixed(0)}%,${this.l.toFixed(0)}%)`;
@@ -376,18 +709,34 @@ class HSL extends Color {
376
709
  return `hsla(${this.h.toFixed(0)},${this.s.toFixed(0)}%,${this.l.toFixed(0)}%,${formatFloat(this.a, 3)})`;
377
710
  }
378
711
  }
712
+ /**
713
+ * Returns the current HSL color.
714
+ * @returns The current HSL color.
715
+ */
379
716
  asHSL() {
380
717
  return this.clone();
381
718
  }
719
+ /**
720
+ * Returns the current HSL color.
721
+ * @returns The current HSL color.
722
+ */
382
723
  toHSL() {
383
724
  return this;
384
725
  }
726
+ /**
727
+ * Converts the HSL color to an HSV color.
728
+ * @returns A new HSV color representing the same color.
729
+ */
385
730
  asHSV() {
386
731
  const s = this.s / 100, l = this.l / 100;
387
732
  const v = l + s * Math.min(l, 1 - l);
388
733
  const sv = v === 0 ? 0 : 2 * (1 - l / v);
389
734
  return new HSV(this.h, sv * 100, v * 100, this.a);
390
735
  }
736
+ /**
737
+ * Converts the HSL color to an RGB color.
738
+ * @returns A new RGB color representing the same color.
739
+ */
391
740
  asRGB() {
392
741
  const h = this.h / 360;
393
742
  const s = this.s / 100;
@@ -413,6 +762,12 @@ class HSL extends Color {
413
762
  // Convert to RGB in the 0-255 range and return
414
763
  return new RGB(255 * hueToRgb(h + 1 / 3), 255 * hueToRgb(h), 255 * hueToRgb(h - 1 / 3), this.a);
415
764
  }
765
+ /**
766
+ * Parses a string or Color object into an HSL color.
767
+ * @param input - The input string or Color object to parse.
768
+ * @returns A new HSL color parsed from the input.
769
+ * @throws Will throw an error if the input string is not a valid HSL color string.
770
+ */
416
771
  static parse(input) {
417
772
  if (input instanceof Color)
418
773
  return input.asHSL();
@@ -427,150 +782,39 @@ class HSL extends Color {
427
782
  }
428
783
  throw new Error(`Invalid HSL color string: "${input}"`);
429
784
  }
785
+ /**
786
+ * Inverts the lightness component of the HSL color.
787
+ * @returns A new HSL color with the lightness component inverted.
788
+ */
430
789
  invertLuminosity() {
431
790
  return new HSL(this.h, this.s, 100 - this.l, this.a);
432
791
  }
792
+ /**
793
+ * Rotates the hue component of the HSL color by a given offset.
794
+ * @param offset - The amount to rotate the hue by, in degrees.
795
+ * @returns A new HSL color with the hue rotated by the given offset.
796
+ */
433
797
  rotateHue(offset) {
434
798
  return new HSL(mod(this.h + offset, 360), this.s, this.l, this.a);
435
799
  }
800
+ /**
801
+ * Increases the saturation of the HSL color by a given ratio.
802
+ * @param ratio - The ratio by which to increase the saturation.
803
+ * @returns A new HSL color with increased saturation.
804
+ */
436
805
  saturate(ratio) {
437
806
  return new HSL(this.h, clamp(this.s * (1 + ratio), 0, 100), this.l, this.a);
438
807
  }
808
+ /**
809
+ * Decreases the alpha (opacity) of the HSL color by a given value.
810
+ * @param value - The value by which to decrease the alpha.
811
+ * @returns A new HSL color with decreased alpha.
812
+ */
439
813
  fade(value) {
440
814
  return new HSL(this.h, this.s, this.l, this.a * (1 - value));
441
815
  }
442
816
  }
443
817
 
444
- let colorDictionary = new Map();
445
- function randomColor(options) {
446
- if (colorDictionary.size === 0)
447
- colorDictionary = initColorDictionary();
448
- options ??= {};
449
- let seed = inputToSeed(options.seed);
450
- const H = pickHue(options);
451
- const S = pickSaturation(H, options);
452
- const V = pickBrightness(H, S, options);
453
- return new HSV(H, S, V, options.opacity ?? 1);
454
- function pickHue(options) {
455
- return mod(randomWithin(getHueRange(options.hue)), 360);
456
- function getHueRange(hue) {
457
- if (typeof hue === 'number') {
458
- hue = mod(hue, 360);
459
- return [hue, hue];
460
- }
461
- if (typeof hue === 'string') {
462
- const color = colorDictionary.get(hue);
463
- if (color?.hueRange)
464
- return color.hueRange;
465
- }
466
- return [0, 360];
467
- }
468
- }
469
- function pickSaturation(hue, options) {
470
- if (options.hue === 'monochrome')
471
- return 0;
472
- if (options.luminosity === 'random')
473
- return randomWithin([0, 100]);
474
- let [sMin, sMax] = getColorInfo(hue).saturationRange;
475
- if (options.saturation === 'strong')
476
- return sMax;
477
- switch (options.luminosity) {
478
- case 'bright':
479
- sMin = 55;
480
- break;
481
- case 'dark':
482
- sMin = sMax - 10;
483
- break;
484
- case 'light':
485
- sMax = 55;
486
- break;
487
- }
488
- return randomWithin([sMin, sMax]);
489
- }
490
- function pickBrightness(h, s, options) {
491
- let bMin = getMinimumBrightness(h, s), bMax = 100;
492
- if (typeof options.luminosity === 'number') {
493
- bMin = options.luminosity;
494
- bMax = options.luminosity;
495
- }
496
- else {
497
- switch (options.luminosity) {
498
- case 'dark':
499
- bMax = Math.min(100, bMin + 20);
500
- break;
501
- case 'light':
502
- bMin = (bMax + bMin) / 2;
503
- break;
504
- case 'random':
505
- bMin = 0;
506
- bMax = 100;
507
- break;
508
- }
509
- }
510
- return randomWithin([bMin, bMax]);
511
- function getMinimumBrightness(h, s) {
512
- const { lowerBounds } = getColorInfo(h);
513
- for (let i = 0; i < lowerBounds.length - 1; i++) {
514
- const [s1, v1] = lowerBounds[i];
515
- const [s2, v2] = lowerBounds[i + 1];
516
- if (s >= s1 && s <= s2) {
517
- const m = (v2 - v1) / (s2 - s1), b = v1 - m * s1;
518
- return m * s + b;
519
- }
520
- }
521
- return 0;
522
- }
523
- }
524
- function randomWithin(range) {
525
- //Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
526
- seed = (seed * 9301 + 49297) % 233280;
527
- return Math.floor(range[0] + seed / 233280.0 * (range[1] - range[0]));
528
- }
529
- }
530
- function inputToSeed(input) {
531
- if (input == null)
532
- return 0;
533
- if (typeof input === 'number')
534
- return input;
535
- let i = 0;
536
- for (let p = 0; p < input.length; p++)
537
- i = (i * 0x101 + input.charCodeAt(p)) % 0x100000000;
538
- return i;
539
- }
540
- function initColorDictionary() {
541
- const dict = new Map();
542
- const defineColor = (name, hueRange, lowerBounds) => {
543
- const [greyest] = lowerBounds;
544
- const colorful = lowerBounds[lowerBounds.length - 1];
545
- dict.set(name, {
546
- hueRange,
547
- lowerBounds,
548
- saturationRange: [greyest[0], colorful[0]],
549
- brightnessRange: [colorful[1], greyest[1]],
550
- });
551
- };
552
- defineColor('monochrome', null, [[0, 0], [100, 0]]);
553
- defineColor('red', [-26, 18], [[20, 100], [30, 92], [40, 89], [50, 85], [60, 78], [70, 70], [80, 60], [90, 55], [100, 50]]);
554
- defineColor('orange', [18, 46], [[20, 100], [30, 93], [40, 88], [50, 86], [60, 85], [70, 70], [100, 70]]);
555
- defineColor('yellow', [46, 62], [[25, 100], [40, 94], [50, 89], [60, 86], [70, 84], [80, 82], [90, 80], [100, 75]]);
556
- defineColor('green', [62, 178], [[30, 100], [40, 90], [50, 85], [60, 81], [70, 74], [80, 64], [90, 50], [100, 40]]);
557
- defineColor('blue', [178, 257], [[20, 100], [30, 86], [40, 80], [50, 74], [60, 60], [70, 52], [80, 44], [90, 39], [100, 35]]);
558
- defineColor('purple', [257, 282], [[20, 100], [30, 87], [40, 79], [50, 70], [60, 65], [70, 59], [80, 52], [90, 45], [100, 42]]);
559
- defineColor('pink', [282, 334], [[20, 100], [30, 90], [40, 86], [60, 84], [80, 80], [90, 75], [100, 73]]);
560
- return dict;
561
- }
562
- function getColorInfo(hue) {
563
- hue = mod(hue, 360);
564
- if (hue >= 334)
565
- hue -= 360;
566
- for (const color of colorDictionary.values()) {
567
- if (color.hueRange && hue >= color.hueRange[0] && hue <= color.hueRange[1]) {
568
- return color;
569
- }
570
- }
571
- throw Error('Color hue value not found');
572
- }
573
-
574
818
  Color.parse = function (input) {
575
819
  if (input instanceof Color)
576
820
  return input;
@@ -592,7 +836,6 @@ Color.parse = function (input) {
592
836
  Color.HSL = HSL;
593
837
  Color.HSV = HSV;
594
838
  Color.RGB = RGB;
595
- Color.random = randomColor;
596
839
 
597
840
  const maxzoom = 14;
598
841
  function getShortbreadTemplate() {
@@ -796,7 +1039,6 @@ function getShortbreadLayers(option) {
796
1039
  });
797
1040
  });
798
1041
  // no links
799
- const noDrivewayExpression = ['!=', 'service', 'driveway'];
800
1042
  ['track', 'pedestrian', 'service', 'living_street', 'residential', 'unclassified'].forEach(t => {
801
1043
  results.push({
802
1044
  id: prefix + 'street-' + t.replace(/_/g, '') + suffix,
@@ -805,7 +1047,6 @@ function getShortbreadLayers(option) {
805
1047
  filter: ['all',
806
1048
  ['==', 'kind', t],
807
1049
  ...filter,
808
- ...(t === 'service') ? [noDrivewayExpression] : [], // ignore driveways
809
1050
  ],
810
1051
  });
811
1052
  });
@@ -820,7 +1061,6 @@ function getShortbreadLayers(option) {
820
1061
  ['==', 'kind', t],
821
1062
  ['==', 'bicycle', 'designated'],
822
1063
  ...filter,
823
- ...(t === 'service') ? [noDrivewayExpression] : [], // ignore driveways
824
1064
  ],
825
1065
  });
826
1066
  });
@@ -853,8 +1093,9 @@ function getShortbreadLayers(option) {
853
1093
  });
854
1094
  // separate outline for trains
855
1095
  [':outline', ''].forEach(suffix => {
856
- // transport
857
- ['rail', 'light_rail', 'subway', 'narrow_gauge', 'tram', 'funicular', 'monorail', 'bus_guideway', 'busway'].reverse().forEach((t) => {
1096
+ // with service distinction
1097
+ ['rail', 'light_rail', 'subway', 'narrow_gauge', 'tram'].reverse().forEach((t) => {
1098
+ // main rail
858
1099
  results.push({
859
1100
  id: prefix + 'transport-' + t.replace(/_/g, '') + suffix,
860
1101
  type: 'line',
@@ -865,6 +1106,29 @@ function getShortbreadLayers(option) {
865
1106
  ...filter,
866
1107
  ],
867
1108
  });
1109
+ // service rail (crossover, siding, spur, yard)
1110
+ results.push({
1111
+ id: prefix + 'transport-' + t.replace(/_/g, '') + '-service' + suffix,
1112
+ type: 'line',
1113
+ 'source-layer': 'streets',
1114
+ filter: ['all',
1115
+ ['in', 'kind', t],
1116
+ ['has', 'service'],
1117
+ ...filter,
1118
+ ],
1119
+ });
1120
+ });
1121
+ // other transport
1122
+ ['funicular', 'monorail', 'bus_guideway', 'busway'].reverse().forEach((t) => {
1123
+ results.push({
1124
+ id: prefix + 'transport-' + t.replace(/_/g, '') + suffix,
1125
+ type: 'line',
1126
+ 'source-layer': 'streets',
1127
+ filter: ['all',
1128
+ ['in', 'kind', t],
1129
+ ...filter,
1130
+ ],
1131
+ });
868
1132
  });
869
1133
  if (c === 'street') {
870
1134
  // aerialway, no bridges, above street evel
@@ -1377,7 +1641,7 @@ function expand (str, isTop) {
1377
1641
  const isOptions = m.body.indexOf(',') >= 0;
1378
1642
  if (!isSequence && !isOptions) {
1379
1643
  // {a},b}
1380
- if (m.post.match(/,.*\}/)) {
1644
+ if (m.post.match(/,(?!,).*\}/)) {
1381
1645
  str = m.pre + '{' + m.body + escClose + m.post;
1382
1646
  return expand(str)
1383
1647
  }
@@ -1828,6 +2092,13 @@ function decorate(layers, rules, recolor) {
1828
2092
  }
1829
2093
  }
1830
2094
 
2095
+ /**
2096
+ * Module for applying various color transformations such as hue rotation, saturation, contrast, brightness,
2097
+ * tinting, and blending. These transformations are defined through the `RecolorOptions` interface.
2098
+ */
2099
+ /**
2100
+ * Returns the default recolor settings.
2101
+ */
1831
2102
  function getDefaultRecolorFlags() {
1832
2103
  return {
1833
2104
  invertBrightness: false,
@@ -1838,70 +2109,90 @@ function getDefaultRecolorFlags() {
1838
2109
  brightness: 0,
1839
2110
  tint: 0,
1840
2111
  tintColor: '#FF0000',
2112
+ blend: 0,
2113
+ blendColor: '#000000',
1841
2114
  };
1842
2115
  }
2116
+ /**
2117
+ * Checks if the given options object contains any active recolor transformations.
2118
+ * @param opt The recolor options to validate.
2119
+ */
1843
2120
  function isValidRecolorOptions(opt) {
1844
2121
  if (!opt)
1845
2122
  return false;
1846
- if ((opt.invertBrightness != null) && opt.invertBrightness)
1847
- return true;
1848
- if ((opt.rotate != null) && (opt.rotate !== 0))
1849
- return true;
1850
- if ((opt.saturate != null) && (opt.saturate !== 0))
1851
- return true;
1852
- if ((opt.gamma != null) && (opt.gamma !== 1))
1853
- return true;
1854
- if ((opt.contrast != null) && (opt.contrast !== 1))
1855
- return true;
1856
- if ((opt.brightness != null) && (opt.brightness !== 0))
1857
- return true;
1858
- if ((opt.tint != null) && (opt.tint !== 0))
1859
- return true;
1860
- if ((opt.tintColor != null) && (opt.tintColor !== '#FF0000'))
1861
- return true;
1862
- return false;
2123
+ return (opt.invertBrightness ||
2124
+ (opt.rotate != null && opt.rotate !== 0) ||
2125
+ (opt.saturate != null && opt.saturate !== 0) ||
2126
+ (opt.gamma != null && opt.gamma !== 1) ||
2127
+ (opt.contrast != null && opt.contrast !== 1) ||
2128
+ (opt.brightness != null && opt.brightness !== 0) ||
2129
+ (opt.tint != null && opt.tint !== 0) ||
2130
+ (opt.tintColor != null && opt.tintColor !== '#FF0000') ||
2131
+ (opt.blend != null && opt.blend !== 0) ||
2132
+ (opt.blendColor != null && opt.blendColor !== '#000000'));
1863
2133
  }
2134
+ /**
2135
+ * Caches recolored colors to optimize performance.
2136
+ */
1864
2137
  class CachedRecolor {
1865
2138
  skip;
1866
2139
  opt;
1867
2140
  cache;
2141
+ /**
2142
+ * Creates a cached recolor instance.
2143
+ * @param opt Optional recolor options.
2144
+ */
1868
2145
  constructor(opt) {
1869
2146
  this.skip = !isValidRecolorOptions(opt);
1870
2147
  this.cache = new Map();
1871
2148
  this.opt = opt;
1872
2149
  }
2150
+ /**
2151
+ * Applies cached recoloring to a given color.
2152
+ * @param color The color to recolor.
2153
+ * @returns The recolored color, either from cache or newly computed.
2154
+ */
1873
2155
  do(color) {
1874
2156
  if (this.skip)
1875
2157
  return color;
1876
2158
  const key = color.asHex();
1877
- const result = this.cache.get(key);
1878
- if (result)
1879
- return result;
1880
- color = recolor(color, this.opt);
1881
- this.cache.set(key, color);
1882
- return color;
2159
+ if (this.cache.has(key))
2160
+ return this.cache.get(key);
2161
+ const recolored = recolor(color, this.opt);
2162
+ this.cache.set(key, recolored);
2163
+ return recolored;
1883
2164
  }
1884
2165
  }
2166
+ /**
2167
+ * Applies the specified recoloring transformations to a single color.
2168
+ * @param color The original color.
2169
+ * @param opt Optional recolor options.
2170
+ * @returns A new `Color` instance with applied transformations.
2171
+ */
1885
2172
  function recolor(color, opt) {
1886
2173
  if (!isValidRecolorOptions(opt))
1887
2174
  return color;
1888
- if (opt.invertBrightness ?? false)
2175
+ if (opt.invertBrightness)
1889
2176
  color = color.invertLuminosity();
1890
- if ((opt.rotate !== undefined) && (opt.rotate !== 0))
2177
+ if (opt.rotate)
1891
2178
  color = color.rotateHue(opt.rotate);
1892
- if ((opt.saturate !== undefined) && (opt.saturate !== 0))
2179
+ if (opt.saturate)
1893
2180
  color = color.saturate(opt.saturate);
1894
- if ((opt.gamma !== undefined) && (opt.gamma !== 1))
2181
+ if (opt.gamma != null && opt.gamma != 1)
1895
2182
  color = color.gamma(opt.gamma);
1896
- if ((opt.contrast !== undefined) && (opt.contrast !== 1))
2183
+ if (opt.contrast != null && opt.contrast != 1)
1897
2184
  color = color.contrast(opt.contrast);
1898
- if ((opt.brightness !== undefined) && (opt.brightness !== 0))
2185
+ if (opt.brightness)
1899
2186
  color = color.brightness(opt.brightness);
1900
- if ((opt.tint !== undefined) && (opt.tintColor !== undefined) && (opt.tint !== 0))
2187
+ if (opt.tint && opt.tintColor != null)
1901
2188
  color = color.tint(opt.tint, Color.parse(opt.tintColor));
2189
+ if (opt.blend && opt.blendColor != null)
2190
+ color = color.blend(opt.blend, Color.parse(opt.blendColor));
1902
2191
  return color;
1903
2192
  }
1904
2193
 
2194
+ const styleBuilderColorKeys = ['agriculture', 'boundary', 'building', 'buildingbg', 'burial', 'commercial', 'construction', 'cycle', 'danger', 'disputed', 'education', 'foot', 'glacier', 'grass', 'hospital', 'industrial', 'label', 'labelHalo', 'land', 'leisure', 'motorway', 'motorwaybg', 'park', 'parking', 'poi', 'prison', 'rail', 'residential', 'rock', 'sand', 'shield', 'street', 'streetbg', 'subway', 'symbol', 'trunk', 'trunkbg', 'waste', 'water', 'wetland', 'wood'];
2195
+
1905
2196
  // StyleBuilder class definition
1906
2197
  class StyleBuilder {
1907
2198
  #sourceName = 'versatiles-shortbread';
@@ -2005,8 +2296,7 @@ class StyleBuilder {
2005
2296
  }
2006
2297
  transformDefaultColors(callback) {
2007
2298
  const colors = this.getColors(this.defaultColors);
2008
- let key;
2009
- for (key in colors) {
2299
+ for (const key of styleBuilderColorKeys) {
2010
2300
  this.defaultColors[key] = callback(colors[key]);
2011
2301
  }
2012
2302
  }
@@ -2463,15 +2753,26 @@ class Colorful extends StyleBuilder {
2463
2753
  size: { 12: 1, 14: 2, 16: 5, 18: 24, 19: 60, 20: 120 },
2464
2754
  opacity: { 12: 0, 13: 1 },
2465
2755
  },
2466
- // service and tracks
2467
- '{bridge-,tunnel-,}street-{service,track}:outline': {
2756
+ // tracks
2757
+ '{bridge-,tunnel-,}street-track:outline': {
2468
2758
  size: { 14: 2, 16: 4, 18: 18, 19: 48, 20: 96 },
2469
2759
  opacity: { 14: 0, 15: 1 },
2470
2760
  },
2471
- '{bridge-,tunnel-,}street-{service,track}': {
2761
+ '{bridge-,tunnel-,}street-track': {
2472
2762
  size: { 14: 1, 16: 3, 18: 16, 19: 44, 20: 88 },
2473
2763
  opacity: { 14: 0, 15: 1 },
2474
2764
  },
2765
+ // service
2766
+ '{bridge-,tunnel-,}street-service:outline': {
2767
+ size: { 14: 1, 16: 3, 18: 12, 19: 32, 20: 48 },
2768
+ opacity: { 15: 0, 16: 1 },
2769
+ color: colors.streetbg.lighten(0.3),
2770
+ },
2771
+ '{bridge-,tunnel-,}street-service': {
2772
+ size: { 14: 1, 16: 2, 18: 10, 19: 28, 20: 40 },
2773
+ opacity: { 15: 0, 16: 1 },
2774
+ color: colors.street.darken(0.03),
2775
+ },
2475
2776
  // ways
2476
2777
  '{bridge-,tunnel-,}way-*:outline': {
2477
2778
  size: { 15: 0, 16: 5, 18: 7, 19: 12, 20: 22 },
@@ -2511,7 +2812,8 @@ class Colorful extends StyleBuilder {
2511
2812
  },
2512
2813
  // cycle streets overlay
2513
2814
  '{bridge-,tunnel-,}street-{tertiary,tertiary-link,unclassified,residential,livingstreet,pedestrian}-bicycle': {
2514
- lineCap: 'butt',
2815
+ lineJoin: 'round',
2816
+ lineCap: 'round',
2515
2817
  color: colors.cycle,
2516
2818
  },
2517
2819
  // pedestrian
@@ -2527,11 +2829,25 @@ class Colorful extends StyleBuilder {
2527
2829
  // rail, lightrail
2528
2830
  '{tunnel-,bridge-,}transport-{rail,lightrail}:outline': {
2529
2831
  color: colors.rail,
2530
- size: { 8: 1, 13: 1, 15: 3, 16: 4, 18: 8, 19: 11, 20: 14 },
2832
+ minzoom: 8,
2833
+ size: { 8: 1, 13: 1, 15: 1, 20: 14 },
2531
2834
  },
2532
2835
  '{tunnel-,bridge-,}transport-{rail,lightrail}': {
2533
2836
  color: colors.rail.lighten(0.25),
2534
- size: { 8: 1, 13: 1, 15: 2, 16: 3, 18: 6, 19: 8, 20: 10 },
2837
+ minzoom: 14,
2838
+ size: { 14: 0, 15: 1, 20: 10 },
2839
+ lineDasharray: [2, 2],
2840
+ },
2841
+ // rail-service, lightrail-service
2842
+ '{tunnel-,bridge-,}transport-{rail,lightrail}-service:outline': {
2843
+ color: colors.rail,
2844
+ minzoom: 14,
2845
+ size: { 14: 0, 15: 1, 16: 1, 20: 14 },
2846
+ },
2847
+ '{tunnel-,bridge-,}transport-{rail,lightrail}-service': {
2848
+ color: colors.rail.lighten(0.25),
2849
+ minzoom: 15,
2850
+ size: { 15: 0, 16: 1, 20: 10 },
2535
2851
  lineDasharray: [2, 2],
2536
2852
  },
2537
2853
  // subway
@@ -2707,7 +3023,7 @@ class Colorful extends StyleBuilder {
2707
3023
  'marking-oneway{-reverse,}': {
2708
3024
  minzoom: 16,
2709
3025
  image: 'basics:marking-arrow',
2710
- opacity: { 16: 0, 17: 0.7 },
3026
+ opacity: { 16: 0, 17: 0.4, 20: 0.4 },
2711
3027
  font: fonts.regular,
2712
3028
  },
2713
3029
  // TODO: bicycle and pedestrian
@@ -2983,6 +3299,14 @@ class Graybeard extends Colorful {
2983
3299
  }
2984
3300
  }
2985
3301
 
3302
+ class Shadow extends Colorful {
3303
+ name = 'Shadow';
3304
+ constructor() {
3305
+ super();
3306
+ this.transformDefaultColors(color => color.saturate(-1).invert().brightness(0.2));
3307
+ }
3308
+ }
3309
+
2986
3310
  class Neutrino extends Colorful {
2987
3311
  name = 'Neutrino';
2988
3312
  defaultFonts = {
@@ -3402,6 +3726,7 @@ function getStyleBuilder(styleBuilder) {
3402
3726
  const colorful = getStyleBuilder(Colorful);
3403
3727
  const eclipse = getStyleBuilder(Eclipse);
3404
3728
  const graybeard = getStyleBuilder(Graybeard);
3729
+ const shadow = getStyleBuilder(Shadow);
3405
3730
  const neutrino = getStyleBuilder(Neutrino);
3406
3731
  getStyleBuilder(Empty);
3407
3732
 
@@ -3492,6 +3817,147 @@ function isRasterTileJSONSpecification(spec) {
3492
3817
  return true;
3493
3818
  }
3494
3819
 
3820
+ let colorDictionary = new Map();
3821
+ function randomColor(options) {
3822
+ if (colorDictionary.size === 0)
3823
+ colorDictionary = initColorDictionary();
3824
+ options ??= {};
3825
+ let seed = inputToSeed(options.seed);
3826
+ const H = pickHue(options);
3827
+ const S = pickSaturation(H, options);
3828
+ const V = pickBrightness(H, S, options);
3829
+ return new HSV(H, S, V, options.opacity ?? 1);
3830
+ function pickHue(options) {
3831
+ return mod(randomWithin(getHueRange(options.hue)), 360);
3832
+ function getHueRange(hue) {
3833
+ if (typeof hue === 'number') {
3834
+ hue = mod(hue, 360);
3835
+ return [hue, hue];
3836
+ }
3837
+ if (typeof hue === 'string') {
3838
+ const color = colorDictionary.get(hue);
3839
+ if (color?.hueRange)
3840
+ return color.hueRange;
3841
+ }
3842
+ return [0, 360];
3843
+ }
3844
+ }
3845
+ function pickSaturation(hue, options) {
3846
+ if (options.hue === 'monochrome')
3847
+ return 0;
3848
+ if (options.luminosity === 'random')
3849
+ return randomWithin([0, 100]);
3850
+ let [sMin, sMax] = getColorInfo(hue).saturationRange;
3851
+ if (options.saturation === 'strong')
3852
+ return sMax;
3853
+ switch (options.luminosity) {
3854
+ case 'bright':
3855
+ sMin = 55;
3856
+ break;
3857
+ case 'dark':
3858
+ sMin = sMax - 10;
3859
+ break;
3860
+ case 'light':
3861
+ sMax = 55;
3862
+ break;
3863
+ }
3864
+ return randomWithin([sMin, sMax]);
3865
+ }
3866
+ function pickBrightness(h, s, options) {
3867
+ let bMin = getMinimumBrightness(h, s), bMax = 100;
3868
+ if (typeof options.luminosity === 'number') {
3869
+ bMin = options.luminosity;
3870
+ bMax = options.luminosity;
3871
+ }
3872
+ else {
3873
+ switch (options.luminosity) {
3874
+ case 'dark':
3875
+ bMax = Math.min(100, bMin + 20);
3876
+ break;
3877
+ case 'light':
3878
+ bMin = (bMax + bMin) / 2;
3879
+ break;
3880
+ case 'random':
3881
+ bMin = 0;
3882
+ bMax = 100;
3883
+ break;
3884
+ }
3885
+ }
3886
+ return randomWithin([bMin, bMax]);
3887
+ function getMinimumBrightness(h, s) {
3888
+ const { lowerBounds } = getColorInfo(h);
3889
+ for (let i = 0; i < lowerBounds.length - 1; i++) {
3890
+ const [s1, v1] = lowerBounds[i];
3891
+ const [s2, v2] = lowerBounds[i + 1];
3892
+ if (s >= s1 && s <= s2) {
3893
+ const m = (v2 - v1) / (s2 - s1), b = v1 - m * s1;
3894
+ return m * s + b;
3895
+ }
3896
+ }
3897
+ return 0;
3898
+ }
3899
+ }
3900
+ function randomWithin(range) {
3901
+ //Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
3902
+ seed = (seed * 9301 + 49297) % 233280;
3903
+ return Math.floor(range[0] + seed / 233280.0 * (range[1] - range[0]));
3904
+ }
3905
+ }
3906
+ function inputToSeed(input) {
3907
+ if (input == null)
3908
+ return 0;
3909
+ if (typeof input === 'number')
3910
+ return input;
3911
+ let i = 0;
3912
+ for (let p = 0; p < input.length; p++)
3913
+ i = (i * 0x101 + input.charCodeAt(p)) % 0x100000000;
3914
+ return i;
3915
+ }
3916
+ function initColorDictionary() {
3917
+ const dict = new Map();
3918
+ const defineColor = (name, hueRange, lowerBounds) => {
3919
+ const [greyest] = lowerBounds;
3920
+ const colorful = lowerBounds[lowerBounds.length - 1];
3921
+ dict.set(name, {
3922
+ hueRange,
3923
+ lowerBounds,
3924
+ saturationRange: [greyest[0], colorful[0]],
3925
+ brightnessRange: [colorful[1], greyest[1]],
3926
+ });
3927
+ };
3928
+ defineColor('monochrome', null, [[0, 0], [100, 0]]);
3929
+ defineColor('red', [-26, 18], [[20, 100], [30, 92], [40, 89], [50, 85], [60, 78], [70, 70], [80, 60], [90, 55], [100, 50]]);
3930
+ defineColor('orange', [18, 46], [[20, 100], [30, 93], [40, 88], [50, 86], [60, 85], [70, 70], [100, 70]]);
3931
+ defineColor('yellow', [46, 62], [[25, 100], [40, 94], [50, 89], [60, 86], [70, 84], [80, 82], [90, 80], [100, 75]]);
3932
+ defineColor('green', [62, 178], [[30, 100], [40, 90], [50, 85], [60, 81], [70, 74], [80, 64], [90, 50], [100, 40]]);
3933
+ defineColor('blue', [178, 257], [[20, 100], [30, 86], [40, 80], [50, 74], [60, 60], [70, 52], [80, 44], [90, 39], [100, 35]]);
3934
+ defineColor('purple', [257, 282], [[20, 100], [30, 87], [40, 79], [50, 70], [60, 65], [70, 59], [80, 52], [90, 45], [100, 42]]);
3935
+ defineColor('pink', [282, 334], [[20, 100], [30, 90], [40, 86], [60, 84], [80, 80], [90, 75], [100, 73]]);
3936
+ return dict;
3937
+ }
3938
+ function getColorInfo(hue) {
3939
+ hue = mod(hue, 360);
3940
+ if (hue >= 334)
3941
+ hue -= 360;
3942
+ for (const color of colorDictionary.values()) {
3943
+ if (color.hueRange && hue >= color.hueRange[0] && hue <= color.hueRange[1]) {
3944
+ return color;
3945
+ }
3946
+ }
3947
+ throw Error('Color hue value not found');
3948
+ }
3949
+
3950
+ /**
3951
+ * Generates a style specification based on the provided TileJSON specification and optional parameters.
3952
+ *
3953
+ * @param {TileJSONSpecification} tileJSON - The TileJSON specification to generate the style from.
3954
+ * @param {GuessStyleOptions} [options] - Optional parameters to customize the style generation.
3955
+ * @param {string} [options.baseUrl] - Base URL to resolve tile URLs.
3956
+ * @param {string} [options.glyphs] - URL template for glyphs.
3957
+ * @param {string} [options.sprite] - URL template for sprites.
3958
+ * @returns {StyleSpecification} The generated style specification.
3959
+ * @throws {Error} If the provided TileJSON specification is invalid.
3960
+ */
3495
3961
  function guessStyle(tileJSON, options) {
3496
3962
  tileJSON = deepClone(tileJSON);
3497
3963
  if (options && options.baseUrl) {
@@ -3537,6 +4003,23 @@ function getShortbreadStyle(spec, builderOption) {
3537
4003
  sprite: builderOption.sprite,
3538
4004
  });
3539
4005
  }
4006
+ /**
4007
+ * Generates a Mapbox GL style specification based on the provided TileJSON vector specification.
4008
+ *
4009
+ * @param {TileJSONSpecificationVector} spec - The TileJSON vector specification containing vector layers.
4010
+ * @returns {StyleSpecification} The generated Mapbox GL style specification.
4011
+ *
4012
+ * This function creates a style specification with background, circle, line, and fill layers.
4013
+ * It assigns colors to the layers based on the vector layer IDs using predefined rules for hue, luminosity, and saturation.
4014
+ *
4015
+ * The resulting style specification includes:
4016
+ * - A white background layer.
4017
+ * - Circle layers for point features.
4018
+ * - Line layers for line features.
4019
+ * - Fill layers for polygon features.
4020
+ *
4021
+ * The source for the layers is created using the `sourceFromSpec` function with the provided vector specification.
4022
+ */
3540
4023
  function getInspectorStyle(spec) {
3541
4024
  const sourceName = 'vectorSource';
3542
4025
  const layers = { background: [], circle: [], line: [], fill: [] };
@@ -3561,7 +4044,7 @@ function getInspectorStyle(spec) {
3561
4044
  saturation = 'strong';
3562
4045
  luminosity = 'light';
3563
4046
  }
3564
- const color = Color.random({
4047
+ const color = randomColor({
3565
4048
  hue,
3566
4049
  luminosity,
3567
4050
  saturation,
@@ -3634,12 +4117,94 @@ function sourceFromSpec(spec, type) {
3634
4117
  return source;
3635
4118
  }
3636
4119
 
4120
+ /**
4121
+ * This library provides everything you need to build a map style.
4122
+ *
4123
+ * You can use it in the browser:
4124
+ * ```html
4125
+ * <html>
4126
+ * <head>
4127
+ * <script src="https://tiles.versatiles.org/assets/lib/versatiles-style/versatiles-style.js"></script>
4128
+ * </head>
4129
+ * <body>
4130
+ * <!-- ... -->
4131
+ * <script>
4132
+ * const style = VersatilesStyle.colorful();
4133
+ * // ...
4134
+ * </script>
4135
+ * </body>
4136
+ * </html>
4137
+ * ```
4138
+ *
4139
+ * or in Node.js:
4140
+ * ```shell
4141
+ * npm i @versatiles/style
4142
+ * ```
4143
+ * ```
4144
+ * import { colorful } from 'versatiles-style';
4145
+ * // OR: const { colorful } = require('versatiles-style');
4146
+ * const style = colorful();
4147
+ * ```
4148
+ *
4149
+ * You probably want to use one of the following functions:
4150
+ *
4151
+ * ---
4152
+ *
4153
+ * ## Generate a style for OpenStreetMap data:
4154
+ *
4155
+ * To generate a style from scratch you can use on of the prepared style functions:
4156
+ * - {@link colorful}
4157
+ * - {@link eclipse}
4158
+ * - {@link graybeard}
4159
+ * - {@link neutrino}
4160
+ * - {@link shadow}
4161
+ *
4162
+ * Each function accepts optional {@link StyleBuilderOptions} as argument to customize the style.
4163
+ *
4164
+ * Example:
4165
+ * ```
4166
+ * import { colorful } from 'versatiles-style';
4167
+ * const style = colorful({
4168
+ * baseUrl: 'https://tiles.example.org',
4169
+ * recolor: {
4170
+ * blend: 0.5,
4171
+ * blendColor: '#FFF', // make all colors lighter
4172
+ * }
4173
+ * });
4174
+ * ```
4175
+ *
4176
+ * ---
4177
+ *
4178
+ * ## Guess a style based on a TileJSON:
4179
+ *
4180
+ * To guess a style from a TileJSON you can use {@link guessStyle}.
4181
+ * This function needs a {@link TileJSONSpecification} and an optional {@link GuessStyleOptions} object.
4182
+ * Example:
4183
+ * ```
4184
+ * import { guessStyle } from 'versatiles-style';
4185
+ * const style = guessStyle(tilejson);
4186
+ * ```
4187
+ *
4188
+ * ---
4189
+ *
4190
+ * ## Please help us to improve this library:
4191
+ *
4192
+ * This library is used in quite some projects of the VersaTiles ecosystem but it is still in an early stage.
4193
+ * We are always looking for feedback, contributions, ideas, bug reports and help with the documentation.
4194
+ *
4195
+ * If you have any suggestions, please [open an issue](https://github.com/versatiles-org/versatiles-style/issues) or a pull request on [GitHub](https://github.com/versatiles-org/versatiles-style).
4196
+ *
4197
+ * If you want to know more about the VersaTiles project, please visit [versatiles.org](https://versatiles.org).
4198
+ *
4199
+ * @module
4200
+ */
3637
4201
  const styles = {
3638
4202
  colorful,
3639
4203
  eclipse,
3640
4204
  graybeard,
4205
+ shadow,
3641
4206
  neutrino,
3642
4207
  };
3643
4208
 
3644
- export { Color, colorful, eclipse, graybeard, guessStyle, neutrino, styles };
4209
+ export { Color, colorful, eclipse, graybeard, guessStyle, neutrino, shadow, styles };
3645
4210
  //# sourceMappingURL=index.js.map