@carto/ps-utils 2.0.1 → 2.1.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.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +436 -74
- package/dist/index.js.map +1 -1
- package/dist/types/clipboard/copy.d.ts +4 -6
- package/dist/types/colors/carto-palettes/carto-palettes.d.ts +47 -0
- package/dist/types/colors/hex-to-rgba/hex-to-rgba.d.ts +7 -16
- package/dist/types/colors/palette-to-hex/palette-to-hex.d.ts +19 -0
- package/dist/types/colors/palette-to-hex/palette-to-hex.test.d.ts +1 -0
- package/dist/types/colors/rgba-to-hex/rgba-to-hex.d.ts +7 -19
- package/dist/types/debounce/debounce.d.ts +12 -2
- package/dist/types/formatters/constants.d.ts +23 -1
- package/dist/types/formatters/date/format-date.d.ts +13 -8
- package/dist/types/formatters/number/format-number.d.ts +17 -20
- package/dist/types/index.d.ts +3 -0
- package/dist/types/strings/match-text/match-text.d.ts +10 -6
- package/dist/types/strings/normalize/normalize.d.ts +6 -9
- package/dist/types/strings/routes/replace-route.d.ts +10 -14
- package/package.json +7 -3
- package/src/clipboard/copy.test.ts +49 -0
- package/src/clipboard/copy.ts +18 -0
- package/src/colors/carto-palettes/carto-palettes.ts +355 -0
- package/src/colors/hex-to-rgba/hex-to-rgba.test.ts +82 -0
- package/src/colors/hex-to-rgba/hex-to-rgba.ts +67 -0
- package/src/colors/palette-to-hex/palette-to-hex.test.ts +60 -0
- package/src/colors/palette-to-hex/palette-to-hex.ts +45 -0
- package/src/colors/rgba-to-hex/rgba-to-hex.test.ts +34 -0
- package/src/colors/rgba-to-hex/rgba-to-hex.ts +42 -0
- package/src/colors/rgba-to-hex/types.ts +9 -0
- package/src/debounce/debounce.test.ts +21 -0
- package/src/debounce/debounce.ts +29 -0
- package/src/formatters/constants.ts +49 -0
- package/src/formatters/date/format-date.test.ts +36 -0
- package/src/formatters/date/format-date.ts +34 -0
- package/src/formatters/number/format-number.test.ts +49 -0
- package/src/formatters/number/format-number.ts +68 -0
- package/src/index.ts +18 -0
- package/src/strings/match-text/match-text.test.ts +38 -0
- package/src/strings/match-text/match-text.ts +36 -0
- package/src/strings/match-text/types.ts +15 -0
- package/src/strings/normalize/normalize.test.ts +12 -0
- package/src/strings/normalize/normalize.ts +22 -0
- package/src/strings/normalize/types.ts +15 -0
- package/src/strings/routes/replace-route.test.ts +64 -0
- package/src/strings/routes/replace-route.ts +39 -0
- package/src/strings/routes/types.ts +2 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CARTOColors palettes (name -> hex ramp), full CARTO-native set: sequential
|
|
3
|
+
* (incl. aggregation), diverging and qualitative. Each entry is the palette's
|
|
4
|
+
* largest class-count ramp, verbatim. Kept inline so the package stays
|
|
5
|
+
* zero-dependency; ColorBrewer (`cb_*`) palettes are intentionally excluded.
|
|
6
|
+
*
|
|
7
|
+
* Data: CARTOColors - https://github.com/CartoDB/cartocolor - CC-BY-4.0.
|
|
8
|
+
* Generated from cartocolor@5.0.2 (unmodified beyond taking each max ramp).
|
|
9
|
+
*/
|
|
10
|
+
export const CARTO_PALETTES = {
|
|
11
|
+
// Sequential (+ aggregation)
|
|
12
|
+
ag_GrnYl: [
|
|
13
|
+
'#245668',
|
|
14
|
+
'#0F7279',
|
|
15
|
+
'#0D8F81',
|
|
16
|
+
'#39AB7E',
|
|
17
|
+
'#6EC574',
|
|
18
|
+
'#A9DC67',
|
|
19
|
+
'#EDEF5D',
|
|
20
|
+
],
|
|
21
|
+
ag_Sunset: [
|
|
22
|
+
'#4b2991',
|
|
23
|
+
'#872ca2',
|
|
24
|
+
'#c0369d',
|
|
25
|
+
'#ea4f88',
|
|
26
|
+
'#fa7876',
|
|
27
|
+
'#f6a97a',
|
|
28
|
+
'#edd9a3',
|
|
29
|
+
],
|
|
30
|
+
BluGrn: [
|
|
31
|
+
'#c4e6c3',
|
|
32
|
+
'#96d2a4',
|
|
33
|
+
'#6dbc90',
|
|
34
|
+
'#4da284',
|
|
35
|
+
'#36877a',
|
|
36
|
+
'#266b6e',
|
|
37
|
+
'#1d4f60',
|
|
38
|
+
],
|
|
39
|
+
BluYl: [
|
|
40
|
+
'#f7feae',
|
|
41
|
+
'#b7e6a5',
|
|
42
|
+
'#7ccba2',
|
|
43
|
+
'#46aea0',
|
|
44
|
+
'#089099',
|
|
45
|
+
'#00718b',
|
|
46
|
+
'#045275',
|
|
47
|
+
],
|
|
48
|
+
BrwnYl: [
|
|
49
|
+
'#ede5cf',
|
|
50
|
+
'#e0c2a2',
|
|
51
|
+
'#d39c83',
|
|
52
|
+
'#c1766f',
|
|
53
|
+
'#a65461',
|
|
54
|
+
'#813753',
|
|
55
|
+
'#541f3f',
|
|
56
|
+
],
|
|
57
|
+
Burg: [
|
|
58
|
+
'#ffc6c4',
|
|
59
|
+
'#f4a3a8',
|
|
60
|
+
'#e38191',
|
|
61
|
+
'#cc607d',
|
|
62
|
+
'#ad466c',
|
|
63
|
+
'#8b3058',
|
|
64
|
+
'#672044',
|
|
65
|
+
],
|
|
66
|
+
BurgYl: [
|
|
67
|
+
'#fbe6c5',
|
|
68
|
+
'#f5ba98',
|
|
69
|
+
'#ee8a82',
|
|
70
|
+
'#dc7176',
|
|
71
|
+
'#c8586c',
|
|
72
|
+
'#9c3f5d',
|
|
73
|
+
'#70284a',
|
|
74
|
+
],
|
|
75
|
+
DarkMint: [
|
|
76
|
+
'#d2fbd4',
|
|
77
|
+
'#a5dbc2',
|
|
78
|
+
'#7bbcb0',
|
|
79
|
+
'#559c9e',
|
|
80
|
+
'#3a7c89',
|
|
81
|
+
'#235d72',
|
|
82
|
+
'#123f5a',
|
|
83
|
+
],
|
|
84
|
+
Emrld: [
|
|
85
|
+
'#d3f2a3',
|
|
86
|
+
'#97e196',
|
|
87
|
+
'#6cc08b',
|
|
88
|
+
'#4c9b82',
|
|
89
|
+
'#217a79',
|
|
90
|
+
'#105965',
|
|
91
|
+
'#074050',
|
|
92
|
+
],
|
|
93
|
+
Magenta: [
|
|
94
|
+
'#f3cbd3',
|
|
95
|
+
'#eaa9bd',
|
|
96
|
+
'#dd88ac',
|
|
97
|
+
'#ca699d',
|
|
98
|
+
'#b14d8e',
|
|
99
|
+
'#91357d',
|
|
100
|
+
'#6c2167',
|
|
101
|
+
],
|
|
102
|
+
Mint: [
|
|
103
|
+
'#e4f1e1',
|
|
104
|
+
'#b4d9cc',
|
|
105
|
+
'#89c0b6',
|
|
106
|
+
'#63a6a0',
|
|
107
|
+
'#448c8a',
|
|
108
|
+
'#287274',
|
|
109
|
+
'#0d585f',
|
|
110
|
+
],
|
|
111
|
+
OrYel: [
|
|
112
|
+
'#ecda9a',
|
|
113
|
+
'#efc47e',
|
|
114
|
+
'#f3ad6a',
|
|
115
|
+
'#f7945d',
|
|
116
|
+
'#f97b57',
|
|
117
|
+
'#f66356',
|
|
118
|
+
'#ee4d5a',
|
|
119
|
+
],
|
|
120
|
+
Peach: [
|
|
121
|
+
'#fde0c5',
|
|
122
|
+
'#facba6',
|
|
123
|
+
'#f8b58b',
|
|
124
|
+
'#f59e72',
|
|
125
|
+
'#f2855d',
|
|
126
|
+
'#ef6a4c',
|
|
127
|
+
'#eb4a40',
|
|
128
|
+
],
|
|
129
|
+
PinkYl: [
|
|
130
|
+
'#fef6b5',
|
|
131
|
+
'#ffdd9a',
|
|
132
|
+
'#ffc285',
|
|
133
|
+
'#ffa679',
|
|
134
|
+
'#fa8a76',
|
|
135
|
+
'#f16d7a',
|
|
136
|
+
'#e15383',
|
|
137
|
+
],
|
|
138
|
+
Purp: [
|
|
139
|
+
'#f3e0f7',
|
|
140
|
+
'#e4c7f1',
|
|
141
|
+
'#d1afe8',
|
|
142
|
+
'#b998dd',
|
|
143
|
+
'#9f82ce',
|
|
144
|
+
'#826dba',
|
|
145
|
+
'#63589f',
|
|
146
|
+
],
|
|
147
|
+
PurpOr: [
|
|
148
|
+
'#f9ddda',
|
|
149
|
+
'#f2b9c4',
|
|
150
|
+
'#e597b9',
|
|
151
|
+
'#ce78b3',
|
|
152
|
+
'#ad5fad',
|
|
153
|
+
'#834ba0',
|
|
154
|
+
'#573b88',
|
|
155
|
+
],
|
|
156
|
+
RedOr: [
|
|
157
|
+
'#f6d2a9',
|
|
158
|
+
'#f5b78e',
|
|
159
|
+
'#f19c7c',
|
|
160
|
+
'#ea8171',
|
|
161
|
+
'#dd686c',
|
|
162
|
+
'#ca5268',
|
|
163
|
+
'#b13f64',
|
|
164
|
+
],
|
|
165
|
+
Sunset: [
|
|
166
|
+
'#f3e79b',
|
|
167
|
+
'#fac484',
|
|
168
|
+
'#f8a07e',
|
|
169
|
+
'#eb7f86',
|
|
170
|
+
'#ce6693',
|
|
171
|
+
'#a059a0',
|
|
172
|
+
'#5c53a5',
|
|
173
|
+
],
|
|
174
|
+
SunsetDark: [
|
|
175
|
+
'#fcde9c',
|
|
176
|
+
'#faa476',
|
|
177
|
+
'#f0746e',
|
|
178
|
+
'#e34f6f',
|
|
179
|
+
'#dc3977',
|
|
180
|
+
'#b9257a',
|
|
181
|
+
'#7c1d6f',
|
|
182
|
+
],
|
|
183
|
+
Teal: [
|
|
184
|
+
'#d1eeea',
|
|
185
|
+
'#a8dbd9',
|
|
186
|
+
'#85c4c9',
|
|
187
|
+
'#68abb8',
|
|
188
|
+
'#4f90a6',
|
|
189
|
+
'#3b738f',
|
|
190
|
+
'#2a5674',
|
|
191
|
+
],
|
|
192
|
+
TealGrn: [
|
|
193
|
+
'#b0f2bc',
|
|
194
|
+
'#89e8ac',
|
|
195
|
+
'#67dba5',
|
|
196
|
+
'#4cc8a3',
|
|
197
|
+
'#38b2a3',
|
|
198
|
+
'#2c98a0',
|
|
199
|
+
'#257d98',
|
|
200
|
+
],
|
|
201
|
+
|
|
202
|
+
// Diverging
|
|
203
|
+
ArmyRose: [
|
|
204
|
+
'#798234',
|
|
205
|
+
'#a3ad62',
|
|
206
|
+
'#d0d3a2',
|
|
207
|
+
'#fdfbe4',
|
|
208
|
+
'#f0c6c3',
|
|
209
|
+
'#df91a3',
|
|
210
|
+
'#d46780',
|
|
211
|
+
],
|
|
212
|
+
Earth: [
|
|
213
|
+
'#A16928',
|
|
214
|
+
'#bd925a',
|
|
215
|
+
'#d6bd8d',
|
|
216
|
+
'#edeac2',
|
|
217
|
+
'#b5c8b8',
|
|
218
|
+
'#79a7ac',
|
|
219
|
+
'#2887a1',
|
|
220
|
+
],
|
|
221
|
+
Fall: [
|
|
222
|
+
'#3d5941',
|
|
223
|
+
'#778868',
|
|
224
|
+
'#b5b991',
|
|
225
|
+
'#f6edbd',
|
|
226
|
+
'#edbb8a',
|
|
227
|
+
'#de8a5a',
|
|
228
|
+
'#ca562c',
|
|
229
|
+
],
|
|
230
|
+
Geyser: [
|
|
231
|
+
'#008080',
|
|
232
|
+
'#70a494',
|
|
233
|
+
'#b4c8a8',
|
|
234
|
+
'#f6edbd',
|
|
235
|
+
'#edbb8a',
|
|
236
|
+
'#de8a5a',
|
|
237
|
+
'#ca562c',
|
|
238
|
+
],
|
|
239
|
+
TealRose: [
|
|
240
|
+
'#009392',
|
|
241
|
+
'#72aaa1',
|
|
242
|
+
'#b1c7b3',
|
|
243
|
+
'#f1eac8',
|
|
244
|
+
'#e5b9ad',
|
|
245
|
+
'#d98994',
|
|
246
|
+
'#d0587e',
|
|
247
|
+
],
|
|
248
|
+
Temps: [
|
|
249
|
+
'#009392',
|
|
250
|
+
'#39b185',
|
|
251
|
+
'#9ccb86',
|
|
252
|
+
'#e9e29c',
|
|
253
|
+
'#eeb479',
|
|
254
|
+
'#e88471',
|
|
255
|
+
'#cf597e',
|
|
256
|
+
],
|
|
257
|
+
Tropic: [
|
|
258
|
+
'#009B9E',
|
|
259
|
+
'#42B7B9',
|
|
260
|
+
'#A7D3D4',
|
|
261
|
+
'#F1F1F1',
|
|
262
|
+
'#E4C1D9',
|
|
263
|
+
'#D691C1',
|
|
264
|
+
'#C75DAB',
|
|
265
|
+
],
|
|
266
|
+
|
|
267
|
+
// Qualitative
|
|
268
|
+
Antique: [
|
|
269
|
+
'#855C75',
|
|
270
|
+
'#D9AF6B',
|
|
271
|
+
'#AF6458',
|
|
272
|
+
'#736F4C',
|
|
273
|
+
'#526A83',
|
|
274
|
+
'#625377',
|
|
275
|
+
'#68855C',
|
|
276
|
+
'#9C9C5E',
|
|
277
|
+
'#A06177',
|
|
278
|
+
'#8C785D',
|
|
279
|
+
'#467378',
|
|
280
|
+
'#7C7C7C',
|
|
281
|
+
],
|
|
282
|
+
Bold: [
|
|
283
|
+
'#7F3C8D',
|
|
284
|
+
'#11A579',
|
|
285
|
+
'#3969AC',
|
|
286
|
+
'#F2B701',
|
|
287
|
+
'#E73F74',
|
|
288
|
+
'#80BA5A',
|
|
289
|
+
'#E68310',
|
|
290
|
+
'#008695',
|
|
291
|
+
'#CF1C90',
|
|
292
|
+
'#f97b72',
|
|
293
|
+
'#4b4b8f',
|
|
294
|
+
'#A5AA99',
|
|
295
|
+
],
|
|
296
|
+
Pastel: [
|
|
297
|
+
'#66C5CC',
|
|
298
|
+
'#F6CF71',
|
|
299
|
+
'#F89C74',
|
|
300
|
+
'#DCB0F2',
|
|
301
|
+
'#87C55F',
|
|
302
|
+
'#9EB9F3',
|
|
303
|
+
'#FE88B1',
|
|
304
|
+
'#C9DB74',
|
|
305
|
+
'#8BE0A4',
|
|
306
|
+
'#B497E7',
|
|
307
|
+
'#D3B484',
|
|
308
|
+
'#B3B3B3',
|
|
309
|
+
],
|
|
310
|
+
Prism: [
|
|
311
|
+
'#5F4690',
|
|
312
|
+
'#1D6996',
|
|
313
|
+
'#38A6A5',
|
|
314
|
+
'#0F8554',
|
|
315
|
+
'#73AF48',
|
|
316
|
+
'#EDAD08',
|
|
317
|
+
'#E17C05',
|
|
318
|
+
'#CC503E',
|
|
319
|
+
'#94346E',
|
|
320
|
+
'#6F4070',
|
|
321
|
+
'#994E95',
|
|
322
|
+
'#666666',
|
|
323
|
+
],
|
|
324
|
+
Safe: [
|
|
325
|
+
'#88CCEE',
|
|
326
|
+
'#CC6677',
|
|
327
|
+
'#DDCC77',
|
|
328
|
+
'#117733',
|
|
329
|
+
'#332288',
|
|
330
|
+
'#AA4499',
|
|
331
|
+
'#44AA99',
|
|
332
|
+
'#999933',
|
|
333
|
+
'#882255',
|
|
334
|
+
'#661100',
|
|
335
|
+
'#6699CC',
|
|
336
|
+
'#888888',
|
|
337
|
+
],
|
|
338
|
+
Vivid: [
|
|
339
|
+
'#E58606',
|
|
340
|
+
'#5D69B1',
|
|
341
|
+
'#52BCA3',
|
|
342
|
+
'#99C945',
|
|
343
|
+
'#CC61B0',
|
|
344
|
+
'#24796C',
|
|
345
|
+
'#DAA51B',
|
|
346
|
+
'#2F8AC4',
|
|
347
|
+
'#764E9F',
|
|
348
|
+
'#ED645A',
|
|
349
|
+
'#CC3A8E',
|
|
350
|
+
'#A5AA99',
|
|
351
|
+
],
|
|
352
|
+
} satisfies Record<string, readonly string[]>
|
|
353
|
+
|
|
354
|
+
/** A known CARTOColors palette name. */
|
|
355
|
+
export type PaletteName = keyof typeof CARTO_PALETTES
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
import { hexToRgba } from './hex-to-rgba'
|
|
3
|
+
|
|
4
|
+
describe.each([
|
|
5
|
+
{ hex: '#000000', op: 0, expected: [0, 0, 0, 0] },
|
|
6
|
+
{ hex: '#000', op: 0, expected: [0, 0, 0, 0] },
|
|
7
|
+
{ hex: '#00', op: 0, expected: [0, 0, 0, 0] },
|
|
8
|
+
{ hex: '000000', op: 0, expected: [0, 0, 0, 0] },
|
|
9
|
+
{ hex: '000', op: 0, expected: [0, 0, 0, 0] },
|
|
10
|
+
{ hex: '00', op: 0, expected: [0, 0, 0, 0] },
|
|
11
|
+
{ hex: '#ff', op: 0, expected: [255, 255, 255, 0] },
|
|
12
|
+
{ hex: '#fff', op: 0, expected: [255, 255, 255, 0] },
|
|
13
|
+
{ hex: '#ffffff', op: 0, expected: [255, 255, 255, 0] },
|
|
14
|
+
{ hex: '#ffffff', op: 0.5, expected: [255, 255, 255, 127.5] },
|
|
15
|
+
{ hex: '#ffffff', op: 0.1, expected: [255, 255, 255, 25.5] },
|
|
16
|
+
{ hex: '#ffffff', op: 0.75, expected: [255, 255, 255, 191.25] },
|
|
17
|
+
{ hex: 'ffffff', op: 0.75, expected: [255, 255, 255, 191.25] },
|
|
18
|
+
{ hex: '#abc', op: 1, expected: [171, 202, 188, 255] },
|
|
19
|
+
{ hex: 'abc', op: 0.5, expected: [171, 202, 188, 127.5] },
|
|
20
|
+
{ hex: '#123456', op: 1, expected: [18, 52, 86, 255] },
|
|
21
|
+
{ hex: '123456', op: 0, expected: [18, 52, 86, 0] },
|
|
22
|
+
{ hex: '#a1', op: 1, expected: [161, 161, 161, 255] },
|
|
23
|
+
{ hex: 'a1', op: 0.5, expected: [161, 161, 161, 127.5] },
|
|
24
|
+
])('hexToRgba(%s, %i)', ({ hex, op, expected }) => {
|
|
25
|
+
test(`returns ${JSON.stringify(expected)}`, () => {
|
|
26
|
+
expect(hexToRgba(hex, op)).toEqual(expected)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe.each([
|
|
31
|
+
{ hex: '#000000', op: -1, error: 'hexToRgba: -1 invalid alpha format' },
|
|
32
|
+
{
|
|
33
|
+
hex: '#000000',
|
|
34
|
+
op: 1.1,
|
|
35
|
+
error: 'hexToRgba: 1.1 invalid alpha format',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
hex: 'null',
|
|
39
|
+
op: 0,
|
|
40
|
+
error: 'hexToRgba: null invalid hexadecimal format',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
hex: null,
|
|
44
|
+
op: 0,
|
|
45
|
+
error: 'hexToRgba: null invalid hexadecimal format',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
hex: 'nul',
|
|
49
|
+
op: 0,
|
|
50
|
+
error: 'hexToRgba: nul invalid hexadecimal format',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
hex: '',
|
|
54
|
+
op: 0,
|
|
55
|
+
error: 'hexToRgba: invalid hexadecimal format',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
hex: 'gggggg',
|
|
59
|
+
op: 1,
|
|
60
|
+
error: 'hexToRgba: gggggg invalid hexadecimal format',
|
|
61
|
+
},
|
|
62
|
+
])('hexToRgba fails', ({ hex, op, error }) => {
|
|
63
|
+
test(`returns ${error}`, () => {
|
|
64
|
+
const err = vi.spyOn(console, 'warn').mockImplementation(() => vi.fn())
|
|
65
|
+
expect(hexToRgba(hex!, op)).toBeUndefined()
|
|
66
|
+
expect(err).toHaveBeenLastCalledWith(error)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('hexToRgba with default alpha', () => {
|
|
71
|
+
test('should use default alpha value of 1', () => {
|
|
72
|
+
expect(hexToRgba('#ffffff')).toEqual([255, 255, 255, 255])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('should use default alpha value of 1 for 3-digit hex', () => {
|
|
76
|
+
expect(hexToRgba('#abc')).toEqual([171, 202, 188, 255])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('should use default alpha value of 1 for 2-digit hex', () => {
|
|
80
|
+
expect(hexToRgba('#ff')).toEqual([255, 255, 255, 255])
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a hexadecimal color string to an RGBA array, useful for deck.gl layer styling and canvas operations.
|
|
3
|
+
*
|
|
4
|
+
* @param hexadecimal - Hex color string (supports `#ffffff`, `ffffff`, `#fff`, `fff`, `#ff` formats).
|
|
5
|
+
* @param alpha - Opacity level from 0 (transparent) to 1 (opaque). Default is `1`.
|
|
6
|
+
* @returns An `[r, g, b, a]` array with values 0-255, or `undefined` for invalid inputs.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* hexToRgba('#ff0000', 0.8) // [255, 0, 0, 204]
|
|
11
|
+
* hexToRgba('#f00', 0.5) // [255, 0, 0, 127.5]
|
|
12
|
+
* hexToRgba('00ff00') // [0, 255, 0, 255]
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function hexToRgba(
|
|
16
|
+
hexadecimal: string,
|
|
17
|
+
alpha = 1,
|
|
18
|
+
): [number, number, number, number] | undefined {
|
|
19
|
+
if (!hexadecimal) {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.warn(`hexToRgba: ${hexadecimal} invalid hexadecimal format`)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
if (alpha < 0 || alpha > 1) {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.warn(`hexToRgba: ${alpha} invalid alpha format`)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
if (hexadecimal?.includes('#')) {
|
|
30
|
+
hexadecimal = hexadecimal.slice(1)
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
!/[0-9A-Fa-f]{6}/g.test(hexadecimal) &&
|
|
34
|
+
!/[0-9A-Fa-f]{3}/g.test(hexadecimal) &&
|
|
35
|
+
!/[0-9A-Fa-f]{2}/g.test(hexadecimal)
|
|
36
|
+
) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.warn(`hexToRgba: ${hexadecimal} invalid hexadecimal format`)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const length = hexadecimal.length
|
|
43
|
+
|
|
44
|
+
if (length < 6) {
|
|
45
|
+
hexadecimal = hexadecimal.slice(0, 6 - length).repeat(6 / length)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const r = parseInt(
|
|
49
|
+
hexadecimal.length === 3
|
|
50
|
+
? hexadecimal.slice(0, 1).repeat(2)
|
|
51
|
+
: hexadecimal.slice(0, 2),
|
|
52
|
+
16,
|
|
53
|
+
)
|
|
54
|
+
const g = parseInt(
|
|
55
|
+
hexadecimal.length === 3
|
|
56
|
+
? hexadecimal.slice(1, 2).repeat(2)
|
|
57
|
+
: hexadecimal.slice(2, 4),
|
|
58
|
+
16,
|
|
59
|
+
)
|
|
60
|
+
const b = parseInt(
|
|
61
|
+
hexadecimal.length === 3
|
|
62
|
+
? hexadecimal.slice(2, 3).repeat(2)
|
|
63
|
+
: hexadecimal.slice(4, 6),
|
|
64
|
+
16,
|
|
65
|
+
)
|
|
66
|
+
return [r, g, b, alpha * 255]
|
|
67
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
import { CARTO_PALETTES } from '../carto-palettes/carto-palettes'
|
|
3
|
+
import { paletteToHex } from './palette-to-hex'
|
|
4
|
+
|
|
5
|
+
const SUNSET = [
|
|
6
|
+
'#f3e79b',
|
|
7
|
+
'#fac484',
|
|
8
|
+
'#f8a07e',
|
|
9
|
+
'#eb7f86',
|
|
10
|
+
'#ce6693',
|
|
11
|
+
'#a059a0',
|
|
12
|
+
'#5c53a5',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
describe('paletteToHex', () => {
|
|
16
|
+
test('returns the palette ramp in order', () => {
|
|
17
|
+
expect(paletteToHex('Sunset')).toEqual(SUNSET)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('resolves the name case-insensitively', () => {
|
|
21
|
+
expect(paletteToHex('sunset')).toEqual(SUNSET)
|
|
22
|
+
expect(paletteToHex('SUNSET')).toEqual(SUNSET)
|
|
23
|
+
expect(paletteToHex('sUnSeT')).toEqual(SUNSET)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('returns the full ramp for a qualitative palette', () => {
|
|
27
|
+
// Bold carries 12 categorical colors, longer than the seq/div 7.
|
|
28
|
+
expect(paletteToHex('Bold')).toHaveLength(12)
|
|
29
|
+
expect(paletteToHex('Bold')).toEqual([...CARTO_PALETTES.Bold])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('returns undefined and warns for an unknown palette', () => {
|
|
33
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
34
|
+
expect(paletteToHex('nope')).toBeUndefined()
|
|
35
|
+
expect(warn).toHaveBeenLastCalledWith('paletteToHex: nope unknown palette')
|
|
36
|
+
warn.mockRestore()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('does not resolve inherited object keys', () => {
|
|
40
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
41
|
+
for (const key of ['__proto__', 'constructor', 'toString']) {
|
|
42
|
+
expect(paletteToHex(key)).toBeUndefined()
|
|
43
|
+
}
|
|
44
|
+
warn.mockRestore()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('returns a fresh copy that does not mutate the shared map', () => {
|
|
48
|
+
const ramp = paletteToHex('Sunset')!
|
|
49
|
+
ramp[0] = '#000000'
|
|
50
|
+
ramp.reverse()
|
|
51
|
+
expect(CARTO_PALETTES.Sunset).toEqual(SUNSET)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('has no case-insensitive name collisions', () => {
|
|
55
|
+
const lowered = new Set(
|
|
56
|
+
Object.keys(CARTO_PALETTES).map((n) => n.toLowerCase()),
|
|
57
|
+
)
|
|
58
|
+
expect(lowered.size).toBe(Object.keys(CARTO_PALETTES).length)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CARTO_PALETTES,
|
|
3
|
+
type PaletteName,
|
|
4
|
+
} from '../carto-palettes/carto-palettes'
|
|
5
|
+
|
|
6
|
+
/** Lowercased name → ramp, for case-insensitive resolution. */
|
|
7
|
+
const PALETTES_BY_LOWER = new Map<string, readonly string[]>(
|
|
8
|
+
Object.entries(CARTO_PALETTES).map(([name, colors]) => [
|
|
9
|
+
name.toLowerCase(),
|
|
10
|
+
colors,
|
|
11
|
+
]),
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a CARTOColors palette name to its hex ramp.
|
|
16
|
+
*
|
|
17
|
+
* Matching is case-insensitive (`'sunset'` resolves `'Sunset'`). Returns the
|
|
18
|
+
* palette's largest class-count ramp. To get RGBA tuples, compose with
|
|
19
|
+
* `hexToRgba`: `paletteToHex('Sunset')?.map((hex) => hexToRgba(hex))`.
|
|
20
|
+
*
|
|
21
|
+
* @param name - CARTOColors palette name (e.g. `'Sunset'`), any casing.
|
|
22
|
+
* @returns A fresh `string[]` of hex stops (a copy — safe to mutate), or
|
|
23
|
+
* `undefined` for an unknown palette.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* paletteToHex('Sunset') // ['#f3e79b', …, '#5c53a5']
|
|
28
|
+
* paletteToHex('sunset') // ['#f3e79b', …, '#5c53a5']
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function paletteToHex(
|
|
32
|
+
// `PaletteName | (string & {})` keeps autocomplete for known names while
|
|
33
|
+
// still accepting arbitrary casing (which isn't a `PaletteName` literal).
|
|
34
|
+
name: PaletteName | (string & {}),
|
|
35
|
+
): string[] | undefined {
|
|
36
|
+
// Static snapshot of cartocolor's max class-count ramp per palette; if
|
|
37
|
+
// per-class-count ramps are ever needed, source them from `cartocolor`.
|
|
38
|
+
const hexes = PALETTES_BY_LOWER.get(name.toLowerCase())
|
|
39
|
+
if (!hexes) {
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.warn(`paletteToHex: ${name} unknown palette`)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
return hexes.slice()
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
import { rgbaToHex } from './rgba-to-hex'
|
|
3
|
+
|
|
4
|
+
describe.each([
|
|
5
|
+
{ rgba: [0, 0, 0, 255], expected: '#000000ff' },
|
|
6
|
+
{ rgba: [255, 255, 255, 0], expected: '#ffffff00' },
|
|
7
|
+
{ rgba: [255, 255, 255, 127], expected: '#ffffff7f' },
|
|
8
|
+
{ rgba: [255, 255, 255], options: { withPrefix: false }, expected: 'ffffff' },
|
|
9
|
+
])('rgbaToHex(%s, %i)', ({ rgba, options, expected }) => {
|
|
10
|
+
test(`returns ${expected}`, () => {
|
|
11
|
+
expect(rgbaToHex(rgba, options)).toEqual(expected)
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe.each([
|
|
16
|
+
{
|
|
17
|
+
rgba: undefined,
|
|
18
|
+
error: 'rgbaToHex: invalid array or empty values',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
rgba: [],
|
|
22
|
+
error: 'rgbaToHex: invalid array or empty values',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
rgba: [0, -1, 0],
|
|
26
|
+
error: 'rgbaToHex: [0,-1,0] invalid array value format',
|
|
27
|
+
},
|
|
28
|
+
])('rgbaToHex fails', ({ rgba, error }) => {
|
|
29
|
+
test(`returns ${error}`, () => {
|
|
30
|
+
const err = vi.spyOn(console, 'warn').mockImplementation(() => vi.fn())
|
|
31
|
+
expect(rgbaToHex(rgba)).toBeUndefined()
|
|
32
|
+
expect(err).toHaveBeenLastCalledWith(error)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RgbaToHexOptions } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts an RGBA color array to a hexadecimal string, ideal for converting deck.gl colors to CSS-compatible format.
|
|
5
|
+
*
|
|
6
|
+
* @param rgba - Array of RGBA values (each 0-255). Accepts `[r, g, b]` or `[r, g, b, a]`.
|
|
7
|
+
* @param options - Options including `withPrefix` to control `#` prefix (default: `true`).
|
|
8
|
+
* @returns The hex color string, or `undefined` for invalid inputs.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* rgbaToHex([255, 0, 0]) // '#ff0000'
|
|
13
|
+
* rgbaToHex([255, 0, 0, 128]) // '#ff000080'
|
|
14
|
+
* rgbaToHex([0, 255, 0], { withPrefix: false }) // '00ff00'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function rgbaToHex(
|
|
18
|
+
rgba: number[] | undefined,
|
|
19
|
+
{ withPrefix = true }: RgbaToHexOptions = {},
|
|
20
|
+
): string | undefined {
|
|
21
|
+
if (!rgba?.length) {
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.warn(`rgbaToHex: invalid array or empty values`)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const hasInvalidValue = rgba.some((value) => value < 0 || value > 255)
|
|
28
|
+
if (hasInvalidValue) {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.warn(`rgbaToHex: [${rgba.toString()}] invalid array value format`)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const hexadecimal = rgba
|
|
35
|
+
.map((value) => {
|
|
36
|
+
const hex = value.toString(16)
|
|
37
|
+
return hex.padStart(2, '0')
|
|
38
|
+
})
|
|
39
|
+
.join('')
|
|
40
|
+
|
|
41
|
+
return `${withPrefix ? '#' : ''}${hexadecimal}`
|
|
42
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RgbaToHexOptions` is an object with an optional property `withPrefix` of type
|
|
3
|
+
* `boolean`.
|
|
4
|
+
* @property {boolean} withPrefix - boolean - Whether to include the '#' prefix in the hex
|
|
5
|
+
* string.
|
|
6
|
+
*/
|
|
7
|
+
export interface RgbaToHexOptions {
|
|
8
|
+
withPrefix?: boolean
|
|
9
|
+
}
|