@react-stately/color 3.0.0-beta.1 → 3.0.0-beta.10
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 +1019 -859
- package/dist/main.js.map +1 -1
- package/dist/module.js +1009 -834
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +61 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/Color.ts +242 -35
- package/src/index.ts +2 -1
- package/src/useColorAreaState.ts +184 -0
- package/src/useColorFieldState.ts +87 -48
- package/src/useColorSliderState.ts +6 -11
- package/src/useColorWheelState.ts +32 -29
package/src/Color.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {clamp} from '@react-
|
|
14
|
-
import {ColorChannel, ColorChannelRange, ColorFormat, Color as IColor} from '@react-types/color';
|
|
13
|
+
import {clamp, toFixedNumber} from '@react-stately/utils';
|
|
14
|
+
import {ColorAxes, ColorChannel, ColorChannelRange, ColorFormat, Color as IColor} from '@react-types/color';
|
|
15
15
|
// @ts-ignore
|
|
16
16
|
import intlMessages from '../intl/*.json';
|
|
17
17
|
import {MessageDictionary} from '@internationalized/message';
|
|
@@ -29,10 +29,18 @@ export function parseColor(value: string): IColor {
|
|
|
29
29
|
throw new Error('Invalid color value: ' + value);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function normalizeColor(v: string | IColor) {
|
|
33
|
+
if (typeof v === 'string') {
|
|
34
|
+
return parseColor(v);
|
|
35
|
+
} else {
|
|
36
|
+
return v;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
abstract class Color implements IColor {
|
|
33
41
|
abstract toFormat(format: ColorFormat): IColor;
|
|
34
42
|
abstract toString(format: ColorFormat | 'css'): string;
|
|
35
|
-
abstract clone():
|
|
43
|
+
abstract clone(): IColor;
|
|
36
44
|
abstract getChannelRange(channel: ColorChannel): ColorChannelRange;
|
|
37
45
|
abstract formatChannelValue(channel: ColorChannel, locale: string): string;
|
|
38
46
|
|
|
@@ -61,6 +69,17 @@ abstract class Color implements IColor {
|
|
|
61
69
|
getChannelName(channel: ColorChannel, locale: string) {
|
|
62
70
|
return messages.getStringForLocale(channel, locale);
|
|
63
71
|
}
|
|
72
|
+
|
|
73
|
+
abstract getColorSpace(): ColorFormat
|
|
74
|
+
getColorSpaceAxes(xyChannels: {xChannel?: ColorChannel, yChannel?: ColorChannel}): ColorAxes {
|
|
75
|
+
let {xChannel, yChannel} = xyChannels;
|
|
76
|
+
let xCh = xChannel || this.getColorChannels().find(c => c !== yChannel);
|
|
77
|
+
let yCh = yChannel || this.getColorChannels().find(c => c !== xCh);
|
|
78
|
+
let zCh = this.getColorChannels().find(c => c !== xCh && c !== yCh);
|
|
79
|
+
|
|
80
|
+
return {xChannel: xCh, yChannel: yCh, zChannel: zCh};
|
|
81
|
+
}
|
|
82
|
+
abstract getColorChannels(): [ColorChannel, ColorChannel, ColorChannel]
|
|
64
83
|
}
|
|
65
84
|
|
|
66
85
|
const HEX_REGEX = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i;
|
|
@@ -90,7 +109,9 @@ class RGBColor extends Color {
|
|
|
90
109
|
let b = parseInt(m[2][4] + m[2][5], 16);
|
|
91
110
|
return new RGBColor(r, g, b, 1);
|
|
92
111
|
}
|
|
93
|
-
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if ((m = value.match(RGB_REGEX))) {
|
|
94
115
|
const [r, g, b, a] = (m[1] ?? m[2]).split(',').map(n => Number(n.trim()));
|
|
95
116
|
return new RGBColor(clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), clamp(a ?? 1, 0, 1));
|
|
96
117
|
}
|
|
@@ -119,6 +140,12 @@ class RGBColor extends Color {
|
|
|
119
140
|
case 'rgb':
|
|
120
141
|
case 'rgba':
|
|
121
142
|
return this;
|
|
143
|
+
case 'hsb':
|
|
144
|
+
case 'hsba':
|
|
145
|
+
return this.toHSB();
|
|
146
|
+
case 'hsl':
|
|
147
|
+
case 'hsla':
|
|
148
|
+
return this.toHSL();
|
|
122
149
|
default:
|
|
123
150
|
throw new Error('Unsupported color conversion: rgb -> ' + format);
|
|
124
151
|
}
|
|
@@ -128,7 +155,89 @@ class RGBColor extends Color {
|
|
|
128
155
|
return this.red << 16 | this.green << 8 | this.blue;
|
|
129
156
|
}
|
|
130
157
|
|
|
131
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Converts an RGB color value to HSB.
|
|
160
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB.
|
|
161
|
+
* @returns An HSBColor object.
|
|
162
|
+
*/
|
|
163
|
+
private toHSB(): IColor {
|
|
164
|
+
const red = this.red / 255;
|
|
165
|
+
const green = this.green / 255;
|
|
166
|
+
const blue = this.blue / 255;
|
|
167
|
+
const min = Math.min(red, green, blue);
|
|
168
|
+
const brightness = Math.max(red, green, blue);
|
|
169
|
+
const chroma = brightness - min;
|
|
170
|
+
const saturation = brightness === 0 ? 0 : chroma / brightness;
|
|
171
|
+
let hue = 0; // achromatic
|
|
172
|
+
|
|
173
|
+
if (chroma !== 0) {
|
|
174
|
+
switch (brightness) {
|
|
175
|
+
case red:
|
|
176
|
+
hue = (green - blue) / chroma + (green < blue ? 6 : 0);
|
|
177
|
+
break;
|
|
178
|
+
case green:
|
|
179
|
+
hue = (blue - red) / chroma + 2;
|
|
180
|
+
break;
|
|
181
|
+
case blue:
|
|
182
|
+
hue = (red - green) / chroma + 4;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
hue /= 6;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return new HSBColor(
|
|
190
|
+
toFixedNumber(hue * 360, 2),
|
|
191
|
+
toFixedNumber(saturation * 100, 2),
|
|
192
|
+
toFixedNumber(brightness * 100, 2),
|
|
193
|
+
this.alpha
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Converts an RGB color value to HSL.
|
|
199
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB.
|
|
200
|
+
* @returns An HSLColor object.
|
|
201
|
+
*/
|
|
202
|
+
private toHSL(): IColor {
|
|
203
|
+
const red = this.red / 255;
|
|
204
|
+
const green = this.green / 255;
|
|
205
|
+
const blue = this.blue / 255;
|
|
206
|
+
const min = Math.min(red, green, blue);
|
|
207
|
+
const max = Math.max(red, green, blue);
|
|
208
|
+
const lightness = (max + min) / 2;
|
|
209
|
+
const chroma = max - min;
|
|
210
|
+
let hue: number;
|
|
211
|
+
let saturation: number;
|
|
212
|
+
|
|
213
|
+
if (chroma === 0) {
|
|
214
|
+
hue = saturation = 0; // achromatic
|
|
215
|
+
} else {
|
|
216
|
+
saturation = chroma / (lightness < .5 ? max + min : 2 - max - min);
|
|
217
|
+
|
|
218
|
+
switch (max) {
|
|
219
|
+
case red:
|
|
220
|
+
hue = (green - blue) / chroma + (green < blue ? 6 : 0);
|
|
221
|
+
break;
|
|
222
|
+
case green:
|
|
223
|
+
hue = (blue - red) / chroma + 2;
|
|
224
|
+
break;
|
|
225
|
+
case blue:
|
|
226
|
+
hue = (red - green) / chroma + 4;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
hue /= 6;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return new HSLColor(
|
|
234
|
+
toFixedNumber(hue * 360, 2),
|
|
235
|
+
toFixedNumber(saturation * 100, 2),
|
|
236
|
+
toFixedNumber(lightness * 100, 2),
|
|
237
|
+
this.alpha);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
clone(): IColor {
|
|
132
241
|
return new RGBColor(this.red, this.green, this.blue, this.alpha);
|
|
133
242
|
}
|
|
134
243
|
|
|
@@ -137,9 +246,9 @@ class RGBColor extends Color {
|
|
|
137
246
|
case 'red':
|
|
138
247
|
case 'green':
|
|
139
248
|
case 'blue':
|
|
140
|
-
return {minValue:
|
|
249
|
+
return {minValue: 0x0, maxValue: 0xFF, step: 0x1, pageSize: 0x11};
|
|
141
250
|
case 'alpha':
|
|
142
|
-
return {minValue: 0, maxValue: 1, step: 0.01};
|
|
251
|
+
return {minValue: 0, maxValue: 1, step: 0.01, pageSize: 0.1};
|
|
143
252
|
default:
|
|
144
253
|
throw new Error('Unknown color channel: ' + channel);
|
|
145
254
|
}
|
|
@@ -162,6 +271,15 @@ class RGBColor extends Color {
|
|
|
162
271
|
}
|
|
163
272
|
return new NumberFormatter(locale, options).format(value);
|
|
164
273
|
}
|
|
274
|
+
|
|
275
|
+
getColorSpace(): ColorFormat {
|
|
276
|
+
return 'rgb';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private static colorChannels: [ColorChannel, ColorChannel, ColorChannel] = ['red', 'green', 'blue'];
|
|
280
|
+
getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
|
|
281
|
+
return RGBColor.colorChannels;
|
|
282
|
+
}
|
|
165
283
|
}
|
|
166
284
|
|
|
167
285
|
// X = <negative/positive number with/without decimal places>
|
|
@@ -187,10 +305,14 @@ class HSBColor extends Color {
|
|
|
187
305
|
switch (format) {
|
|
188
306
|
case 'css':
|
|
189
307
|
return this.toHSL().toString('css');
|
|
308
|
+
case 'hex':
|
|
309
|
+
return this.toRGB().toString('hex');
|
|
310
|
+
case 'hexa':
|
|
311
|
+
return this.toRGB().toString('hexa');
|
|
190
312
|
case 'hsb':
|
|
191
|
-
return `hsb(${this.hue}, ${this.saturation}%, ${this.brightness}%)`;
|
|
313
|
+
return `hsb(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.brightness, 2)}%)`;
|
|
192
314
|
case 'hsba':
|
|
193
|
-
return `hsba(${this.hue}, ${this.saturation}%, ${this.brightness}%, ${this.alpha})`;
|
|
315
|
+
return `hsba(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.brightness, 2)}%, ${this.alpha})`;
|
|
194
316
|
default:
|
|
195
317
|
return this.toFormat(format).toString(format);
|
|
196
318
|
}
|
|
@@ -204,41 +326,64 @@ class HSBColor extends Color {
|
|
|
204
326
|
case 'hsl':
|
|
205
327
|
case 'hsla':
|
|
206
328
|
return this.toHSL();
|
|
329
|
+
case 'rgb':
|
|
330
|
+
case 'rgba':
|
|
331
|
+
return this.toRGB();
|
|
207
332
|
default:
|
|
208
333
|
throw new Error('Unsupported color conversion: hsb -> ' + format);
|
|
209
334
|
}
|
|
210
335
|
}
|
|
211
336
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Converts a HSB color to HSL.
|
|
339
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL.
|
|
340
|
+
* @returns An HSLColor object.
|
|
341
|
+
*/
|
|
342
|
+
private toHSL(): IColor {
|
|
343
|
+
let saturation = this.saturation / 100;
|
|
344
|
+
let brightness = this.brightness / 100;
|
|
345
|
+
let lightness = brightness * (1 - saturation / 2);
|
|
346
|
+
saturation = lightness === 0 || lightness === 1 ? 0 : (brightness - lightness) / Math.min(lightness, 1 - lightness);
|
|
347
|
+
|
|
348
|
+
return new HSLColor(
|
|
349
|
+
toFixedNumber(this.hue, 2),
|
|
350
|
+
toFixedNumber(saturation * 100, 2),
|
|
351
|
+
toFixedNumber(lightness * 100, 2),
|
|
352
|
+
this.alpha
|
|
353
|
+
);
|
|
354
|
+
}
|
|
215
355
|
|
|
216
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Converts a HSV color value to RGB.
|
|
358
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative.
|
|
359
|
+
* @returns An RGBColor object.
|
|
360
|
+
*/
|
|
361
|
+
private toRGB(): IColor {
|
|
217
362
|
let hue = this.hue;
|
|
218
|
-
let saturation = this.saturation
|
|
219
|
-
let
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
363
|
+
let saturation = this.saturation / 100;
|
|
364
|
+
let brightness = this.brightness / 100;
|
|
365
|
+
let fn = (n: number, k = (n + hue / 60) % 6) => brightness - saturation * brightness * Math.max(Math.min(k, 4 - k, 1), 0);
|
|
366
|
+
return new RGBColor(
|
|
367
|
+
Math.round(fn(5) * 255),
|
|
368
|
+
Math.round(fn(3) * 255),
|
|
369
|
+
Math.round(fn(1) * 255),
|
|
370
|
+
this.alpha
|
|
371
|
+
);
|
|
227
372
|
}
|
|
228
373
|
|
|
229
|
-
clone():
|
|
374
|
+
clone(): IColor {
|
|
230
375
|
return new HSBColor(this.hue, this.saturation, this.brightness, this.alpha);
|
|
231
376
|
}
|
|
232
377
|
|
|
233
378
|
getChannelRange(channel: ColorChannel): ColorChannelRange {
|
|
234
379
|
switch (channel) {
|
|
235
380
|
case 'hue':
|
|
236
|
-
return {minValue: 0, maxValue: 360, step: 1};
|
|
381
|
+
return {minValue: 0, maxValue: 360, step: 1, pageSize: 15};
|
|
237
382
|
case 'saturation':
|
|
238
383
|
case 'brightness':
|
|
239
|
-
return {minValue: 0, maxValue: 100, step: 1};
|
|
384
|
+
return {minValue: 0, maxValue: 100, step: 1, pageSize: 10};
|
|
240
385
|
case 'alpha':
|
|
241
|
-
return {minValue: 0, maxValue: 1, step: 0.01};
|
|
386
|
+
return {minValue: 0, maxValue: 1, step: 0.01, pageSize: 0.1};
|
|
242
387
|
default:
|
|
243
388
|
throw new Error('Unknown color channel: ' + channel);
|
|
244
389
|
}
|
|
@@ -264,6 +409,15 @@ class HSBColor extends Color {
|
|
|
264
409
|
}
|
|
265
410
|
return new NumberFormatter(locale, options).format(value);
|
|
266
411
|
}
|
|
412
|
+
|
|
413
|
+
getColorSpace(): ColorFormat {
|
|
414
|
+
return 'hsb';
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private static colorChannels: [ColorChannel, ColorChannel, ColorChannel] = ['hue', 'saturation', 'brightness'];
|
|
418
|
+
getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
|
|
419
|
+
return HSBColor.colorChannels;
|
|
420
|
+
}
|
|
267
421
|
}
|
|
268
422
|
|
|
269
423
|
// X = <negative/positive number with/without decimal places>
|
|
@@ -276,13 +430,11 @@ function mod(n, m) {
|
|
|
276
430
|
return ((n % m) + m) % m;
|
|
277
431
|
}
|
|
278
432
|
|
|
279
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
280
433
|
class HSLColor extends Color {
|
|
281
434
|
constructor(private hue: number, private saturation: number, private lightness: number, private alpha: number) {
|
|
282
435
|
super();
|
|
283
436
|
}
|
|
284
437
|
|
|
285
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
286
438
|
static parse(value: string): HSLColor | void {
|
|
287
439
|
let m: RegExpMatchArray | void;
|
|
288
440
|
if ((m = value.match(HSL_REGEX))) {
|
|
@@ -293,39 +445,85 @@ class HSLColor extends Color {
|
|
|
293
445
|
|
|
294
446
|
toString(format: ColorFormat | 'css') {
|
|
295
447
|
switch (format) {
|
|
448
|
+
case 'hex':
|
|
449
|
+
return this.toRGB().toString('hex');
|
|
450
|
+
case 'hexa':
|
|
451
|
+
return this.toRGB().toString('hexa');
|
|
296
452
|
case 'hsl':
|
|
297
|
-
return `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`;
|
|
453
|
+
return `hsl(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.lightness, 2)}%)`;
|
|
298
454
|
case 'css':
|
|
299
455
|
case 'hsla':
|
|
300
|
-
return `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, ${this.alpha})`;
|
|
456
|
+
return `hsla(${this.hue}, ${toFixedNumber(this.saturation, 2)}%, ${toFixedNumber(this.lightness, 2)}%, ${this.alpha})`;
|
|
301
457
|
default:
|
|
302
458
|
return this.toFormat(format).toString(format);
|
|
303
459
|
}
|
|
304
460
|
}
|
|
305
|
-
|
|
306
461
|
toFormat(format: ColorFormat): IColor {
|
|
307
462
|
switch (format) {
|
|
308
463
|
case 'hsl':
|
|
309
464
|
case 'hsla':
|
|
310
465
|
return this;
|
|
466
|
+
case 'hsb':
|
|
467
|
+
case 'hsba':
|
|
468
|
+
return this.toHSB();
|
|
469
|
+
case 'rgb':
|
|
470
|
+
case 'rgba':
|
|
471
|
+
return this.toRGB();
|
|
311
472
|
default:
|
|
312
473
|
throw new Error('Unsupported color conversion: hsl -> ' + format);
|
|
313
474
|
}
|
|
314
475
|
}
|
|
315
476
|
|
|
316
|
-
|
|
477
|
+
/**
|
|
478
|
+
* Converts a HSL color to HSB.
|
|
479
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV.
|
|
480
|
+
* @returns An HSBColor object.
|
|
481
|
+
*/
|
|
482
|
+
private toHSB(): IColor {
|
|
483
|
+
let saturation = this.saturation / 100;
|
|
484
|
+
let lightness = this.lightness / 100;
|
|
485
|
+
let brightness = lightness + saturation * Math.min(lightness, 1 - lightness);
|
|
486
|
+
saturation = brightness === 0 ? 0 : 2 * (1 - lightness / brightness);
|
|
487
|
+
return new HSBColor(
|
|
488
|
+
toFixedNumber(this.hue, 2),
|
|
489
|
+
toFixedNumber(saturation * 100, 2),
|
|
490
|
+
toFixedNumber(brightness * 100, 2),
|
|
491
|
+
this.alpha
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Converts a HSL color to RGB.
|
|
497
|
+
* Conversion formula adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative.
|
|
498
|
+
* @returns An RGBColor object.
|
|
499
|
+
*/
|
|
500
|
+
private toRGB(): IColor {
|
|
501
|
+
let hue = this.hue;
|
|
502
|
+
let saturation = this.saturation / 100;
|
|
503
|
+
let lightness = this.lightness / 100;
|
|
504
|
+
let a = saturation * Math.min(lightness, 1 - lightness);
|
|
505
|
+
let fn = (n: number, k = (n + hue / 30) % 12) => lightness - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
506
|
+
return new RGBColor(
|
|
507
|
+
Math.round(fn(0) * 255),
|
|
508
|
+
Math.round(fn(8) * 255),
|
|
509
|
+
Math.round(fn(4) * 255),
|
|
510
|
+
this.alpha
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
clone(): IColor {
|
|
317
515
|
return new HSLColor(this.hue, this.saturation, this.lightness, this.alpha);
|
|
318
516
|
}
|
|
319
517
|
|
|
320
518
|
getChannelRange(channel: ColorChannel): ColorChannelRange {
|
|
321
519
|
switch (channel) {
|
|
322
520
|
case 'hue':
|
|
323
|
-
return {minValue: 0, maxValue: 360, step: 1};
|
|
521
|
+
return {minValue: 0, maxValue: 360, step: 1, pageSize: 15};
|
|
324
522
|
case 'saturation':
|
|
325
523
|
case 'lightness':
|
|
326
|
-
return {minValue: 0, maxValue: 100, step: 1};
|
|
524
|
+
return {minValue: 0, maxValue: 100, step: 1, pageSize: 10};
|
|
327
525
|
case 'alpha':
|
|
328
|
-
return {minValue: 0, maxValue: 1, step: 0.01};
|
|
526
|
+
return {minValue: 0, maxValue: 1, step: 0.01, pageSize: 0.1};
|
|
329
527
|
default:
|
|
330
528
|
throw new Error('Unknown color channel: ' + channel);
|
|
331
529
|
}
|
|
@@ -351,4 +549,13 @@ class HSLColor extends Color {
|
|
|
351
549
|
}
|
|
352
550
|
return new NumberFormatter(locale, options).format(value);
|
|
353
551
|
}
|
|
552
|
+
|
|
553
|
+
getColorSpace(): ColorFormat {
|
|
554
|
+
return 'hsl';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private static colorChannels: [ColorChannel, ColorChannel, ColorChannel] = ['hue', 'saturation', 'lightness'];
|
|
558
|
+
getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
|
|
559
|
+
return HSLColor.colorChannels;
|
|
560
|
+
}
|
|
354
561
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
export
|
|
13
|
+
export {parseColor} from './Color';
|
|
14
|
+
export * from './useColorAreaState';
|
|
14
15
|
export * from './useColorSliderState';
|
|
15
16
|
export * from './useColorWheelState';
|
|
16
17
|
export * from './useColorFieldState';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {clamp, snapValueToStep, useControlledState} from '@react-stately/utils';
|
|
14
|
+
import {Color, ColorAreaProps, ColorChannel} from '@react-types/color';
|
|
15
|
+
import {normalizeColor, parseColor} from './Color';
|
|
16
|
+
import {useMemo, useRef, useState} from 'react';
|
|
17
|
+
|
|
18
|
+
export interface ColorAreaState {
|
|
19
|
+
/** The current color value displayed by the color area. */
|
|
20
|
+
readonly value: Color,
|
|
21
|
+
/** Sets the current color value. If a string is passed, it will be parsed to a Color. */
|
|
22
|
+
setValue(value: string | Color): void,
|
|
23
|
+
|
|
24
|
+
/** The current value of the horizontal axis channel displayed by the color area. */
|
|
25
|
+
xValue: number,
|
|
26
|
+
/** Sets the value for the horizontal axis channel displayed by the color area, and triggers `onChange`. */
|
|
27
|
+
setXValue(value: number): void,
|
|
28
|
+
|
|
29
|
+
/** The current value of the vertical axis channel displayed by the color area. */
|
|
30
|
+
yValue: number,
|
|
31
|
+
/** Sets the value for the vertical axis channel displayed by the color area, and triggers `onChange`. */
|
|
32
|
+
setYValue(value: number): void,
|
|
33
|
+
|
|
34
|
+
/** Sets the x and y channels of the current color value based on a percentage of the width and height of the color area, and triggers `onChange`. */
|
|
35
|
+
setColorFromPoint(x: number, y: number): void,
|
|
36
|
+
/** Returns the coordinates of the thumb relative to the upper left corner of the color area as a percentage. */
|
|
37
|
+
getThumbPosition(): {x: number, y: number},
|
|
38
|
+
|
|
39
|
+
/** Increments the value of the horizontal axis channel by the channel step or page amount. */
|
|
40
|
+
incrementX(stepSize?: number): void,
|
|
41
|
+
/** Decrements the value of the horizontal axis channel by the channel step or page amount. */
|
|
42
|
+
decrementX(stepSize?: number): void,
|
|
43
|
+
|
|
44
|
+
/** Increments the value of the vertical axis channel by the channel step or page amount. */
|
|
45
|
+
incrementY(stepSize?: number): void,
|
|
46
|
+
/** Decrements the value of the vertical axis channel by the channel step or page amount. */
|
|
47
|
+
decrementY(stepSize?: number): void,
|
|
48
|
+
|
|
49
|
+
/** Whether the color area is currently being dragged. */
|
|
50
|
+
readonly isDragging: boolean,
|
|
51
|
+
/** Sets whether the color area is being dragged. */
|
|
52
|
+
setDragging(value: boolean): void,
|
|
53
|
+
|
|
54
|
+
/** Returns the xChannel, yChannel and zChannel names based on the color value. */
|
|
55
|
+
channels: {xChannel: ColorChannel, yChannel: ColorChannel, zChannel: ColorChannel},
|
|
56
|
+
xChannelStep: number,
|
|
57
|
+
yChannelStep: number,
|
|
58
|
+
xChannelPageStep: number,
|
|
59
|
+
yChannelPageStep: number,
|
|
60
|
+
|
|
61
|
+
/** Returns the color that should be displayed in the color area thumb instead of `value`. */
|
|
62
|
+
getDisplayColor(): Color
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const DEFAULT_COLOR = parseColor('#ffffff');
|
|
66
|
+
/**
|
|
67
|
+
* Provides state management for a color area component.
|
|
68
|
+
* Color area allows users to adjust two channels of an HSL, HSB or RGB color value against a two-dimensional gradient background.
|
|
69
|
+
*/
|
|
70
|
+
export function useColorAreaState(props: ColorAreaProps): ColorAreaState {
|
|
71
|
+
let {
|
|
72
|
+
value,
|
|
73
|
+
defaultValue,
|
|
74
|
+
xChannel,
|
|
75
|
+
yChannel,
|
|
76
|
+
onChange,
|
|
77
|
+
onChangeEnd
|
|
78
|
+
} = props;
|
|
79
|
+
|
|
80
|
+
if (!value && !defaultValue) {
|
|
81
|
+
defaultValue = DEFAULT_COLOR;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let [color, setColor] = useControlledState(value && normalizeColor(value), defaultValue && normalizeColor(defaultValue), onChange);
|
|
85
|
+
let valueRef = useRef(color);
|
|
86
|
+
valueRef.current = color;
|
|
87
|
+
|
|
88
|
+
let channels = useMemo(() =>
|
|
89
|
+
valueRef.current.getColorSpaceAxes({xChannel, yChannel}),
|
|
90
|
+
[xChannel, yChannel]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
let xChannelRange = color.getChannelRange(channels.xChannel);
|
|
94
|
+
let yChannelRange = color.getChannelRange(channels.yChannel);
|
|
95
|
+
let {minValue: minValueX, maxValue: maxValueX, step: stepX, pageSize: pageSizeX} = xChannelRange;
|
|
96
|
+
let {minValue: minValueY, maxValue: maxValueY, step: stepY, pageSize: pageSizeY} = yChannelRange;
|
|
97
|
+
|
|
98
|
+
let [isDragging, setDragging] = useState(false);
|
|
99
|
+
let isDraggingRef = useRef(false).current;
|
|
100
|
+
|
|
101
|
+
let xValue = color.getChannelValue(channels.xChannel);
|
|
102
|
+
let yValue = color.getChannelValue(channels.yChannel);
|
|
103
|
+
let setXValue = (v: number) => {
|
|
104
|
+
if (v === xValue) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
valueRef.current = color.withChannelValue(channels.xChannel, v);
|
|
108
|
+
setColor(valueRef.current);
|
|
109
|
+
};
|
|
110
|
+
let setYValue = (v: number) => {
|
|
111
|
+
if (v === yValue) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
valueRef.current = color.withChannelValue(channels.yChannel, v);
|
|
115
|
+
setColor(valueRef.current);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
channels,
|
|
120
|
+
xChannelStep: stepX,
|
|
121
|
+
yChannelStep: stepY,
|
|
122
|
+
xChannelPageStep: pageSizeX,
|
|
123
|
+
yChannelPageStep: pageSizeY,
|
|
124
|
+
value: color,
|
|
125
|
+
setValue(value) {
|
|
126
|
+
let c = normalizeColor(value);
|
|
127
|
+
valueRef.current = c;
|
|
128
|
+
setColor(c);
|
|
129
|
+
},
|
|
130
|
+
xValue,
|
|
131
|
+
setXValue,
|
|
132
|
+
yValue,
|
|
133
|
+
setYValue,
|
|
134
|
+
setColorFromPoint(x: number, y: number) {
|
|
135
|
+
let newXValue = minValueX + clamp(x, 0, 1) * (maxValueX - minValueX);
|
|
136
|
+
let newYValue = minValueY + (1 - clamp(y, 0, 1)) * (maxValueY - minValueY);
|
|
137
|
+
let newColor:Color;
|
|
138
|
+
if (newXValue !== xValue) {
|
|
139
|
+
// Round new value to multiple of step, clamp value between min and max
|
|
140
|
+
newXValue = snapValueToStep(newXValue, minValueX, maxValueX, stepX);
|
|
141
|
+
newColor = color.withChannelValue(channels.xChannel, newXValue);
|
|
142
|
+
}
|
|
143
|
+
if (newYValue !== yValue) {
|
|
144
|
+
// Round new value to multiple of step, clamp value between min and max
|
|
145
|
+
newYValue = snapValueToStep(newYValue, minValueY, maxValueY, stepY);
|
|
146
|
+
newColor = (newColor || color).withChannelValue(channels.yChannel, newYValue);
|
|
147
|
+
}
|
|
148
|
+
if (newColor) {
|
|
149
|
+
setColor(newColor);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
getThumbPosition() {
|
|
153
|
+
let x = (xValue - minValueX) / (maxValueX - minValueX);
|
|
154
|
+
let y = 1 - (yValue - minValueY) / (maxValueY - minValueY);
|
|
155
|
+
return {x, y};
|
|
156
|
+
},
|
|
157
|
+
incrementX(stepSize) {
|
|
158
|
+
setXValue(xValue + stepSize > maxValueX ? maxValueX : snapValueToStep(xValue + stepSize, minValueX, maxValueX, stepX));
|
|
159
|
+
},
|
|
160
|
+
incrementY(stepSize) {
|
|
161
|
+
setYValue(yValue + stepSize > maxValueY ? maxValueY : snapValueToStep(yValue + stepSize, minValueY, maxValueY, stepY));
|
|
162
|
+
},
|
|
163
|
+
decrementX(stepSize) {
|
|
164
|
+
setXValue(snapValueToStep(xValue - stepSize, minValueX, maxValueX, stepX));
|
|
165
|
+
},
|
|
166
|
+
decrementY(stepSize) {
|
|
167
|
+
setYValue(snapValueToStep(yValue - stepSize, minValueY, maxValueY, stepY));
|
|
168
|
+
},
|
|
169
|
+
setDragging(isDragging) {
|
|
170
|
+
let wasDragging = isDraggingRef;
|
|
171
|
+
isDraggingRef = isDragging;
|
|
172
|
+
|
|
173
|
+
if (onChangeEnd && !isDragging && wasDragging) {
|
|
174
|
+
onChangeEnd(valueRef.current);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
setDragging(isDragging);
|
|
178
|
+
},
|
|
179
|
+
isDragging,
|
|
180
|
+
getDisplayColor() {
|
|
181
|
+
return color.withChannelValue('alpha', 1);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|