@react-stately/color 3.0.0-beta.4 → 3.0.0-beta.5
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/main.js +210 -29
- package/dist/main.js.map +1 -1
- package/dist/module.js +207 -25
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -4
- package/src/Color.ts +201 -26
- package/src/useColorWheelState.ts +11 -7
package/src/Color.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {clamp} from '@react-
|
|
13
|
+
import {clamp, toFixedNumber} from '@react-stately/utils';
|
|
14
14
|
import {ColorChannel, ColorChannelRange, ColorFormat, Color as IColor} from '@react-types/color';
|
|
15
15
|
// @ts-ignore
|
|
16
16
|
import intlMessages from '../intl/*.json';
|
|
@@ -32,7 +32,7 @@ export function parseColor(value: string): IColor {
|
|
|
32
32
|
abstract class Color implements IColor {
|
|
33
33
|
abstract toFormat(format: ColorFormat): IColor;
|
|
34
34
|
abstract toString(format: ColorFormat | 'css'): string;
|
|
35
|
-
abstract clone():
|
|
35
|
+
abstract clone(): IColor;
|
|
36
36
|
abstract getChannelRange(channel: ColorChannel): ColorChannelRange;
|
|
37
37
|
abstract formatChannelValue(channel: ColorChannel, locale: string): string;
|
|
38
38
|
|
|
@@ -61,6 +61,8 @@ abstract class Color implements IColor {
|
|
|
61
61
|
getChannelName(channel: ColorChannel, locale: string) {
|
|
62
62
|
return messages.getStringForLocale(channel, locale);
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
abstract getColorSpace(): ColorFormat
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
const HEX_REGEX = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i;
|
|
@@ -90,7 +92,9 @@ class RGBColor extends Color {
|
|
|
90
92
|
let b = parseInt(m[2][4] + m[2][5], 16);
|
|
91
93
|
return new RGBColor(r, g, b, 1);
|
|
92
94
|
}
|
|
93
|
-
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ((m = value.match(RGB_REGEX))) {
|
|
94
98
|
const [r, g, b, a] = (m[1] ?? m[2]).split(',').map(n => Number(n.trim()));
|
|
95
99
|
return new RGBColor(clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), clamp(a ?? 1, 0, 1));
|
|
96
100
|
}
|
|
@@ -119,6 +123,12 @@ class RGBColor extends Color {
|
|
|
119
123
|
case 'rgb':
|
|
120
124
|
case 'rgba':
|
|
121
125
|
return this;
|
|
126
|
+
case 'hsb':
|
|
127
|
+
case 'hsba':
|
|
128
|
+
return this.toHSB();
|
|
129
|
+
case 'hsl':
|
|
130
|
+
case 'hsla':
|
|
131
|
+
return this.toHSL();
|
|
122
132
|
default:
|
|
123
133
|
throw new Error('Unsupported color conversion: rgb -> ' + format);
|
|
124
134
|
}
|
|
@@ -128,7 +138,89 @@ class RGBColor extends Color {
|
|
|
128
138
|
return this.red << 16 | this.green << 8 | this.blue;
|
|
129
139
|
}
|
|
130
140
|
|
|
131
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Converts an RGB color value to HSB.
|
|
143
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB.
|
|
144
|
+
* @returns An HSBColor object.
|
|
145
|
+
*/
|
|
146
|
+
private toHSB(): IColor {
|
|
147
|
+
const red = this.red / 255;
|
|
148
|
+
const green = this.green / 255;
|
|
149
|
+
const blue = this.blue / 255;
|
|
150
|
+
const min = Math.min(red, green, blue);
|
|
151
|
+
const brightness = Math.max(red, green, blue);
|
|
152
|
+
const chroma = brightness - min;
|
|
153
|
+
const saturation = brightness === 0 ? 0 : chroma / brightness;
|
|
154
|
+
let hue = 0; // achromatic
|
|
155
|
+
|
|
156
|
+
if (chroma !== 0) {
|
|
157
|
+
switch (brightness) {
|
|
158
|
+
case red:
|
|
159
|
+
hue = (green - blue) / chroma + (green < blue ? 6 : 0);
|
|
160
|
+
break;
|
|
161
|
+
case green:
|
|
162
|
+
hue = (blue - red) / chroma + 2;
|
|
163
|
+
break;
|
|
164
|
+
case blue:
|
|
165
|
+
hue = (red - green) / chroma + 4;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
hue /= 6;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return new HSBColor(
|
|
173
|
+
toFixedNumber(hue * 360, 2),
|
|
174
|
+
toFixedNumber(saturation * 100, 2),
|
|
175
|
+
toFixedNumber(brightness * 100, 2),
|
|
176
|
+
this.alpha
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Converts an RGB color value to HSL.
|
|
182
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB.
|
|
183
|
+
* @returns An HSLColor object.
|
|
184
|
+
*/
|
|
185
|
+
private toHSL(): IColor {
|
|
186
|
+
const red = this.red / 255;
|
|
187
|
+
const green = this.green / 255;
|
|
188
|
+
const blue = this.blue / 255;
|
|
189
|
+
const min = Math.min(red, green, blue);
|
|
190
|
+
const max = Math.max(red, green, blue);
|
|
191
|
+
const lightness = (max + min) / 2;
|
|
192
|
+
const chroma = max - min;
|
|
193
|
+
let hue: number;
|
|
194
|
+
let saturation: number;
|
|
195
|
+
|
|
196
|
+
if (chroma === 0) {
|
|
197
|
+
hue = saturation = 0; // achromatic
|
|
198
|
+
} else {
|
|
199
|
+
saturation = chroma / (lightness < .5 ? max + min : 2 - max - min);
|
|
200
|
+
|
|
201
|
+
switch (max) {
|
|
202
|
+
case red:
|
|
203
|
+
hue = (green - blue) / chroma + (green < blue ? 6 : 0);
|
|
204
|
+
break;
|
|
205
|
+
case green:
|
|
206
|
+
hue = (blue - red) / chroma + 2;
|
|
207
|
+
break;
|
|
208
|
+
case blue:
|
|
209
|
+
hue = (red - green) / chroma + 4;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
hue /= 6;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return new HSLColor(
|
|
217
|
+
toFixedNumber(hue * 360, 2),
|
|
218
|
+
toFixedNumber(saturation * 100, 2),
|
|
219
|
+
toFixedNumber(lightness * 100, 2),
|
|
220
|
+
this.alpha);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
clone(): IColor {
|
|
132
224
|
return new RGBColor(this.red, this.green, this.blue, this.alpha);
|
|
133
225
|
}
|
|
134
226
|
|
|
@@ -162,6 +254,10 @@ class RGBColor extends Color {
|
|
|
162
254
|
}
|
|
163
255
|
return new NumberFormatter(locale, options).format(value);
|
|
164
256
|
}
|
|
257
|
+
|
|
258
|
+
getColorSpace(): ColorFormat {
|
|
259
|
+
return 'rgb';
|
|
260
|
+
}
|
|
165
261
|
}
|
|
166
262
|
|
|
167
263
|
// X = <negative/positive number with/without decimal places>
|
|
@@ -187,10 +283,14 @@ class HSBColor extends Color {
|
|
|
187
283
|
switch (format) {
|
|
188
284
|
case 'css':
|
|
189
285
|
return this.toHSL().toString('css');
|
|
286
|
+
case 'hex':
|
|
287
|
+
return this.toRGB().toString('hex');
|
|
288
|
+
case 'hexa':
|
|
289
|
+
return this.toRGB().toString('hexa');
|
|
190
290
|
case 'hsb':
|
|
191
|
-
return `hsb(${this.hue}, ${this.saturation}%, ${this.brightness}%)`;
|
|
291
|
+
return `hsb(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.brightness, 2)}%)`;
|
|
192
292
|
case 'hsba':
|
|
193
|
-
return `hsba(${this.hue}, ${this.saturation}%, ${this.brightness}%, ${this.alpha})`;
|
|
293
|
+
return `hsba(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.brightness, 2)}%, ${this.alpha})`;
|
|
194
294
|
default:
|
|
195
295
|
return this.toFormat(format).toString(format);
|
|
196
296
|
}
|
|
@@ -204,29 +304,52 @@ class HSBColor extends Color {
|
|
|
204
304
|
case 'hsl':
|
|
205
305
|
case 'hsla':
|
|
206
306
|
return this.toHSL();
|
|
307
|
+
case 'rgb':
|
|
308
|
+
case 'rgba':
|
|
309
|
+
return this.toRGB();
|
|
207
310
|
default:
|
|
208
311
|
throw new Error('Unsupported color conversion: hsb -> ' + format);
|
|
209
312
|
}
|
|
210
313
|
}
|
|
211
314
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Converts a HSB color to HSL.
|
|
317
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL.
|
|
318
|
+
* @returns An HSLColor object.
|
|
319
|
+
*/
|
|
320
|
+
private toHSL(): IColor {
|
|
321
|
+
let saturation = this.saturation / 100;
|
|
322
|
+
let brightness = this.brightness / 100;
|
|
323
|
+
let lightness = brightness * (1 - saturation / 2);
|
|
324
|
+
saturation = lightness === 0 || lightness === 1 ? 0 : (brightness - lightness) / Math.min(lightness, 1 - lightness);
|
|
325
|
+
|
|
326
|
+
return new HSLColor(
|
|
327
|
+
toFixedNumber(this.hue, 2),
|
|
328
|
+
toFixedNumber(saturation * 100, 2),
|
|
329
|
+
toFixedNumber(lightness * 100, 2),
|
|
330
|
+
this.alpha
|
|
331
|
+
);
|
|
332
|
+
}
|
|
215
333
|
|
|
216
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Converts a HSV color value to RGB.
|
|
336
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative.
|
|
337
|
+
* @returns An RGBColor object.
|
|
338
|
+
*/
|
|
339
|
+
private toRGB(): IColor {
|
|
217
340
|
let hue = this.hue;
|
|
218
|
-
let saturation = this.saturation
|
|
219
|
-
let
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
341
|
+
let saturation = this.saturation / 100;
|
|
342
|
+
let brightness = this.brightness / 100;
|
|
343
|
+
let fn = (n: number, k = (n + hue / 60) % 6) => brightness - saturation * brightness * Math.max(Math.min(k, 4 - k, 1), 0);
|
|
344
|
+
return new RGBColor(
|
|
345
|
+
Math.round(fn(5) * 255),
|
|
346
|
+
Math.round(fn(3) * 255),
|
|
347
|
+
Math.round(fn(1) * 255),
|
|
348
|
+
this.alpha
|
|
349
|
+
);
|
|
227
350
|
}
|
|
228
351
|
|
|
229
|
-
clone():
|
|
352
|
+
clone(): IColor {
|
|
230
353
|
return new HSBColor(this.hue, this.saturation, this.brightness, this.alpha);
|
|
231
354
|
}
|
|
232
355
|
|
|
@@ -264,6 +387,10 @@ class HSBColor extends Color {
|
|
|
264
387
|
}
|
|
265
388
|
return new NumberFormatter(locale, options).format(value);
|
|
266
389
|
}
|
|
390
|
+
|
|
391
|
+
getColorSpace(): ColorFormat {
|
|
392
|
+
return 'hsb';
|
|
393
|
+
}
|
|
267
394
|
}
|
|
268
395
|
|
|
269
396
|
// X = <negative/positive number with/without decimal places>
|
|
@@ -276,13 +403,11 @@ function mod(n, m) {
|
|
|
276
403
|
return ((n % m) + m) % m;
|
|
277
404
|
}
|
|
278
405
|
|
|
279
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
280
406
|
class HSLColor extends Color {
|
|
281
407
|
constructor(private hue: number, private saturation: number, private lightness: number, private alpha: number) {
|
|
282
408
|
super();
|
|
283
409
|
}
|
|
284
410
|
|
|
285
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
286
411
|
static parse(value: string): HSLColor | void {
|
|
287
412
|
let m: RegExpMatchArray | void;
|
|
288
413
|
if ((m = value.match(HSL_REGEX))) {
|
|
@@ -293,27 +418,73 @@ class HSLColor extends Color {
|
|
|
293
418
|
|
|
294
419
|
toString(format: ColorFormat | 'css') {
|
|
295
420
|
switch (format) {
|
|
421
|
+
case 'hex':
|
|
422
|
+
return this.toRGB().toString('hex');
|
|
423
|
+
case 'hexa':
|
|
424
|
+
return this.toRGB().toString('hexa');
|
|
296
425
|
case 'hsl':
|
|
297
|
-
return `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`;
|
|
426
|
+
return `hsl(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.lightness, 2)}%)`;
|
|
298
427
|
case 'css':
|
|
299
428
|
case 'hsla':
|
|
300
|
-
return `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, ${this.alpha})`;
|
|
429
|
+
return `hsla(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.lightness, 2)}%, ${this.alpha})`;
|
|
301
430
|
default:
|
|
302
431
|
return this.toFormat(format).toString(format);
|
|
303
432
|
}
|
|
304
433
|
}
|
|
305
|
-
|
|
306
434
|
toFormat(format: ColorFormat): IColor {
|
|
307
435
|
switch (format) {
|
|
308
436
|
case 'hsl':
|
|
309
437
|
case 'hsla':
|
|
310
438
|
return this;
|
|
439
|
+
case 'hsb':
|
|
440
|
+
case 'hsba':
|
|
441
|
+
return this.toHSB();
|
|
442
|
+
case 'rgb':
|
|
443
|
+
case 'rgba':
|
|
444
|
+
return this.toRGB();
|
|
311
445
|
default:
|
|
312
446
|
throw new Error('Unsupported color conversion: hsl -> ' + format);
|
|
313
447
|
}
|
|
314
448
|
}
|
|
315
449
|
|
|
316
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Converts a HSL color to HSB.
|
|
452
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV.
|
|
453
|
+
* @returns An HSBColor object.
|
|
454
|
+
*/
|
|
455
|
+
private toHSB(): IColor {
|
|
456
|
+
let saturation = this.saturation / 100;
|
|
457
|
+
let lightness = this.lightness / 100;
|
|
458
|
+
let brightness = lightness + saturation * Math.min(lightness, 1 - lightness);
|
|
459
|
+
saturation = brightness === 0 ? 0 : 2 * (1 - lightness / brightness);
|
|
460
|
+
return new HSBColor(
|
|
461
|
+
toFixedNumber(this.hue, 2),
|
|
462
|
+
toFixedNumber(saturation * 100, 2),
|
|
463
|
+
toFixedNumber(brightness * 100, 2),
|
|
464
|
+
this.alpha
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Converts a HSL color to RGB.
|
|
470
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative.
|
|
471
|
+
* @returns An RGBColor object.
|
|
472
|
+
*/
|
|
473
|
+
private toRGB(): IColor {
|
|
474
|
+
let hue = this.hue;
|
|
475
|
+
let saturation = this.saturation / 100;
|
|
476
|
+
let lightness = this.lightness / 100;
|
|
477
|
+
let a = saturation * Math.min(lightness, 1 - lightness);
|
|
478
|
+
let fn = (n: number, k = (n + hue / 30) % 12) => lightness - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
479
|
+
return new RGBColor(
|
|
480
|
+
Math.round(fn(0) * 255),
|
|
481
|
+
Math.round(fn(8) * 255),
|
|
482
|
+
Math.round(fn(4) * 255),
|
|
483
|
+
this.alpha
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
clone(): IColor {
|
|
317
488
|
return new HSLColor(this.hue, this.saturation, this.lightness, this.alpha);
|
|
318
489
|
}
|
|
319
490
|
|
|
@@ -351,4 +522,8 @@ class HSLColor extends Color {
|
|
|
351
522
|
}
|
|
352
523
|
return new NumberFormatter(locale, options).format(value);
|
|
353
524
|
}
|
|
525
|
+
|
|
526
|
+
getColorSpace(): ColorFormat {
|
|
527
|
+
return 'hsl';
|
|
528
|
+
}
|
|
354
529
|
}
|
|
@@ -108,6 +108,7 @@ export function useColorWheelState(props: ColorWheelProps): ColorWheelState {
|
|
|
108
108
|
valueRef.current = value;
|
|
109
109
|
|
|
110
110
|
let [isDragging, setDragging] = useState(false);
|
|
111
|
+
let isDraggingRef = useRef(false).current;
|
|
111
112
|
|
|
112
113
|
let hue = value.getChannelValue('hue');
|
|
113
114
|
function setHue(v: number) {
|
|
@@ -117,7 +118,9 @@ export function useColorWheelState(props: ColorWheelProps): ColorWheelState {
|
|
|
117
118
|
}
|
|
118
119
|
v = roundToStep(mod(v, 360), step);
|
|
119
120
|
if (hue !== v) {
|
|
120
|
-
|
|
121
|
+
let color = value.withChannelValue('hue', v);
|
|
122
|
+
valueRef.current = color;
|
|
123
|
+
setValue(color);
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
|
|
@@ -155,13 +158,14 @@ export function useColorWheelState(props: ColorWheelProps): ColorWheelState {
|
|
|
155
158
|
}
|
|
156
159
|
},
|
|
157
160
|
setDragging(isDragging) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
let wasDragging = isDraggingRef;
|
|
162
|
+
isDraggingRef = isDragging;
|
|
163
|
+
|
|
164
|
+
if (onChangeEnd && !isDragging && wasDragging) {
|
|
165
|
+
onChangeEnd(valueRef.current);
|
|
166
|
+
}
|
|
162
167
|
|
|
163
|
-
|
|
164
|
-
});
|
|
168
|
+
setDragging(isDragging);
|
|
165
169
|
},
|
|
166
170
|
isDragging,
|
|
167
171
|
getDisplayColor() {
|