@thednp/color-picker 2.0.2 → 2.0.4
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/README.md +25 -14
- package/dist/css/color-picker.css +3 -6
- package/dist/css/color-picker.css.map +1 -0
- package/dist/css/color-picker.min.css +1 -2
- package/dist/css/color-picker.min.css.map +1 -0
- package/dist/css/color-picker.rtl.css +3 -6
- package/dist/css/color-picker.rtl.css.map +1 -0
- package/dist/css/color-picker.rtl.min.css +1 -2
- package/dist/css/color-picker.rtl.min.css.map +1 -0
- package/dist/js/color-picker.cjs +2 -2
- package/dist/js/color-picker.cjs.map +1 -1
- package/dist/js/color-picker.d.ts +307 -300
- package/dist/js/color-picker.js +2 -2
- package/dist/js/color-picker.js.map +1 -1
- package/dist/js/color-picker.mjs +390 -491
- package/dist/js/color-picker.mjs.map +1 -1
- package/package.json +40 -47
- package/.eslintrc.cjs +0 -199
- package/.lgtm.yml +0 -9
- package/.prettierrc.json +0 -15
- package/.stylelintrc.json +0 -236
- package/compile.cjs +0 -51
- package/dts.config.ts +0 -15
- package/src/scss/_variables.scss +0 -6
- package/src/scss/color-picker.rtl.scss +0 -27
- package/src/scss/color-picker.scss +0 -536
- package/src/ts/colorPalette.ts +0 -89
- package/src/ts/index.ts +0 -1237
- package/src/ts/interface/ColorNames.ts +0 -20
- package/src/ts/interface/colorPickerLabels.ts +0 -20
- package/src/ts/interface/colorPickerOptions.ts +0 -11
- package/src/ts/interface/paletteOptions.ts +0 -6
- package/src/ts/util/colorNames.ts +0 -21
- package/src/ts/util/colorPickerLabels.ts +0 -24
- package/src/ts/util/getColorControls.ts +0 -90
- package/src/ts/util/getColorForm.ts +0 -75
- package/src/ts/util/getColorMenu.ts +0 -83
- package/src/ts/util/isValidJSON.ts +0 -19
- package/src/ts/util/setMarkup.ts +0 -130
- package/src/ts/util/vHidden.ts +0 -2
- package/test/color-palette.test.ts +0 -137
- package/test/color-picker.test.ts +0 -705
- package/test/fixtures/colorNamesFrench.js +0 -3
- package/test/fixtures/componentLabelsFrench.js +0 -21
- package/test/fixtures/format.js +0 -3
- package/test/fixtures/getMarkup.js +0 -36
- package/test/fixtures/getRandomInt.js +0 -6
- package/test/fixtures/sampleWebcolors.js +0 -18
- package/test/fixtures/swipe.ts +0 -33
- package/test/fixtures/testSample.js +0 -8
- package/test/fixtures/write.ts +0 -37
- package/tsconfig.json +0 -47
- package/vite.config.mts +0 -27
- package/vitest.config-ui.ts +0 -26
- package/vitest.config.ts +0 -26
package/src/ts/index.ts
DELETED
@@ -1,1237 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
ariaDescription,
|
3
|
-
ariaSelected,
|
4
|
-
ariaValueNow,
|
5
|
-
ariaValueText,
|
6
|
-
ariaExpanded,
|
7
|
-
keyArrowDown,
|
8
|
-
keyArrowUp,
|
9
|
-
keyArrowLeft,
|
10
|
-
keyArrowRight,
|
11
|
-
keyEnter,
|
12
|
-
keySpace,
|
13
|
-
keyEscape,
|
14
|
-
focusinEvent,
|
15
|
-
mouseclickEvent,
|
16
|
-
keydownEvent,
|
17
|
-
changeEvent,
|
18
|
-
touchmoveEvent,
|
19
|
-
pointerdownEvent,
|
20
|
-
pointermoveEvent,
|
21
|
-
pointerupEvent,
|
22
|
-
tabindex,
|
23
|
-
focusoutEvent,
|
24
|
-
resizeEvent,
|
25
|
-
keyupEvent,
|
26
|
-
scrollEvent,
|
27
|
-
dispatchEvent,
|
28
|
-
getElementsByClassName,
|
29
|
-
closest,
|
30
|
-
querySelector,
|
31
|
-
getElementTransitionDuration,
|
32
|
-
getBoundingClientRect,
|
33
|
-
getUID,
|
34
|
-
getElementStyle,
|
35
|
-
getDocumentElement,
|
36
|
-
getDocument,
|
37
|
-
ObjectAssign,
|
38
|
-
ObjectFromEntries,
|
39
|
-
Data,
|
40
|
-
getInstance,
|
41
|
-
setElementStyle,
|
42
|
-
normalizeOptions,
|
43
|
-
reflow,
|
44
|
-
focus,
|
45
|
-
hasClass,
|
46
|
-
addClass,
|
47
|
-
removeClass,
|
48
|
-
setAttribute,
|
49
|
-
getAttribute,
|
50
|
-
removeAttribute,
|
51
|
-
isArray,
|
52
|
-
isString,
|
53
|
-
getWindow,
|
54
|
-
on,
|
55
|
-
off,
|
56
|
-
} from '@thednp/shorty';
|
57
|
-
|
58
|
-
// ColorPicker Util
|
59
|
-
// ================
|
60
|
-
import Color from '@thednp/color';
|
61
|
-
import type { RGBA, HWBA, HSLA, HSVA } from '@thednp/color';
|
62
|
-
|
63
|
-
import ColorPalette from './colorPalette';
|
64
|
-
import colorPickerLabels from './util/colorPickerLabels';
|
65
|
-
import colorNames from './util/colorNames';
|
66
|
-
import isValidJSON from './util/isValidJSON';
|
67
|
-
import setMarkup from './util/setMarkup';
|
68
|
-
|
69
|
-
import ColorPickerOptions from './interface/colorPickerOptions';
|
70
|
-
import ColorPickerLabels from './interface/colorPickerLabels';
|
71
|
-
import type ColorNames from './interface/ColorNames';
|
72
|
-
import { version } from '../../package.json';
|
73
|
-
|
74
|
-
// ColorPicker GC
|
75
|
-
// ==============
|
76
|
-
const colorPickerString = 'color-picker';
|
77
|
-
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
78
|
-
const colorPickerParentSelector = `.${colorPickerString}`;
|
79
|
-
const colorPickerDefaults: ColorPickerOptions = {
|
80
|
-
componentLabels: colorPickerLabels,
|
81
|
-
colorLabels: colorNames,
|
82
|
-
format: 'rgb',
|
83
|
-
colorPresets: false,
|
84
|
-
colorKeywords: false,
|
85
|
-
};
|
86
|
-
const { roundPart, nonColors } = Color;
|
87
|
-
|
88
|
-
// ColorPicker Static Methods
|
89
|
-
// ==========================
|
90
|
-
const getColorPickerInstance = (element: HTMLInputElement) => getInstance<ColorPicker>(element, colorPickerString);
|
91
|
-
const initColorPicker = (element: HTMLInputElement) => new ColorPicker(element);
|
92
|
-
|
93
|
-
// ColorPicker Private Methods
|
94
|
-
// ===========================
|
95
|
-
|
96
|
-
/**
|
97
|
-
* Add / remove `ColorPicker` main event listeners.
|
98
|
-
*/
|
99
|
-
const toggleEvents = (self: ColorPicker, action?: boolean) => {
|
100
|
-
const fn = action ? on : off;
|
101
|
-
const { input, pickerToggle, menuToggle } = self;
|
102
|
-
|
103
|
-
fn(input, focusinEvent, self.showPicker);
|
104
|
-
fn(pickerToggle, mouseclickEvent, self.togglePicker);
|
105
|
-
|
106
|
-
if (menuToggle) {
|
107
|
-
fn(menuToggle, mouseclickEvent, self.toggleMenu);
|
108
|
-
}
|
109
|
-
};
|
110
|
-
|
111
|
-
/**
|
112
|
-
* Add / remove `ColorPicker` event listeners active only when open.
|
113
|
-
*/
|
114
|
-
const toggleEventsOnShown = (self: ColorPicker, action?: boolean) => {
|
115
|
-
const fn = action ? on : off;
|
116
|
-
const { input, colorMenu, parent } = self;
|
117
|
-
const doc = getDocument(input);
|
118
|
-
const win = getWindow(doc);
|
119
|
-
|
120
|
-
fn(self.controls, pointerdownEvent, self.pointerDown);
|
121
|
-
self.controlKnobs.forEach(x => fn(x, keydownEvent, self.handleKnobs));
|
122
|
-
|
123
|
-
fn(win, scrollEvent, self.handleScroll);
|
124
|
-
fn(win, resizeEvent, self.update);
|
125
|
-
|
126
|
-
[input, ...self.inputs].forEach(x => fn(x, changeEvent, self.changeHandler));
|
127
|
-
|
128
|
-
if (colorMenu) {
|
129
|
-
fn(colorMenu, mouseclickEvent, self.menuClickHandler);
|
130
|
-
fn(colorMenu, keydownEvent, self.menuKeyHandler);
|
131
|
-
}
|
132
|
-
|
133
|
-
fn(doc, pointermoveEvent, self.pointerMove);
|
134
|
-
fn(doc, pointerupEvent, self.pointerUp);
|
135
|
-
fn(parent, focusoutEvent, self.handleFocusOut);
|
136
|
-
fn(doc, keyupEvent, self.handleDismiss);
|
137
|
-
};
|
138
|
-
|
139
|
-
/**
|
140
|
-
* Triggers the `ColorPicker` original event.
|
141
|
-
*/
|
142
|
-
const firePickerChange = (self: ColorPicker) => {
|
143
|
-
dispatchEvent(self.input, new CustomEvent('colorpicker.change'));
|
144
|
-
};
|
145
|
-
|
146
|
-
/**
|
147
|
-
* Hides a visible dropdown.
|
148
|
-
*/
|
149
|
-
const removePosition = (element: HTMLElement) => {
|
150
|
-
/* istanbul ignore else */
|
151
|
-
if (element) {
|
152
|
-
['bottom', 'top'].forEach(x => removeClass(element, x));
|
153
|
-
}
|
154
|
-
};
|
155
|
-
|
156
|
-
/**
|
157
|
-
* Shows a `ColorPicker` dropdown and close the curent open dropdown.
|
158
|
-
*/
|
159
|
-
const showDropdown = (self: ColorPicker, dropdown: HTMLElement) => {
|
160
|
-
const { colorPicker, colorMenu, menuToggle, pickerToggle, parent } = self;
|
161
|
-
const isPicker = dropdown === colorPicker;
|
162
|
-
const openDropdown = isPicker ? colorMenu : colorPicker;
|
163
|
-
const activeBtn = isPicker ? menuToggle : pickerToggle;
|
164
|
-
const nextBtn = !isPicker ? menuToggle : pickerToggle;
|
165
|
-
|
166
|
-
if (!hasClass(parent, 'open')) {
|
167
|
-
addClass(parent, 'open');
|
168
|
-
}
|
169
|
-
if (openDropdown) {
|
170
|
-
removeClass(openDropdown, 'show');
|
171
|
-
removePosition(openDropdown);
|
172
|
-
}
|
173
|
-
addClass(dropdown, 'bottom');
|
174
|
-
reflow(dropdown);
|
175
|
-
addClass(dropdown, 'show');
|
176
|
-
|
177
|
-
if (isPicker) self.update();
|
178
|
-
|
179
|
-
if (!self.isOpen) {
|
180
|
-
toggleEventsOnShown(self, true);
|
181
|
-
self.updateDropdownPosition();
|
182
|
-
self.isOpen = true;
|
183
|
-
setAttribute(self.input, tabindex, '0');
|
184
|
-
if (menuToggle) {
|
185
|
-
setAttribute(menuToggle, tabindex, '0');
|
186
|
-
}
|
187
|
-
}
|
188
|
-
|
189
|
-
setAttribute(nextBtn, ariaExpanded, 'true');
|
190
|
-
if (activeBtn) {
|
191
|
-
setAttribute(activeBtn, ariaExpanded, 'false');
|
192
|
-
}
|
193
|
-
};
|
194
|
-
|
195
|
-
/**
|
196
|
-
* Color Picker Web Component
|
197
|
-
*
|
198
|
-
* @see http://thednp.github.io/color-picker
|
199
|
-
*/
|
200
|
-
export default class ColorPicker {
|
201
|
-
// bring utils to staic
|
202
|
-
public static Color = Color;
|
203
|
-
public static ColorPalette = ColorPalette;
|
204
|
-
public static getInstance = getColorPickerInstance;
|
205
|
-
public static init = initColorPicker;
|
206
|
-
public static selector = colorPickerSelector;
|
207
|
-
// utils important for render
|
208
|
-
public static roundPart = roundPart;
|
209
|
-
public static setElementStyle = setElementStyle;
|
210
|
-
public static setAttribute = setAttribute;
|
211
|
-
public static getBoundingClientRect = getBoundingClientRect;
|
212
|
-
public static version = version;
|
213
|
-
public static colorNames = colorNames;
|
214
|
-
public static colorPickerLabels = colorPickerLabels;
|
215
|
-
|
216
|
-
id: number;
|
217
|
-
input: HTMLInputElement;
|
218
|
-
color: Color;
|
219
|
-
format = 'rgb';
|
220
|
-
parent: HTMLElement;
|
221
|
-
dragElement: HTMLElement | undefined;
|
222
|
-
isOpen = false;
|
223
|
-
controlPositions: {
|
224
|
-
c1x: number;
|
225
|
-
c1y: number;
|
226
|
-
c2y: number;
|
227
|
-
c3y: number;
|
228
|
-
};
|
229
|
-
colorLabels: ColorNames = ObjectFromEntries(colorNames.map(c => [c, c])) as ColorNames;
|
230
|
-
colorKeywords: string[] | false;
|
231
|
-
colorPresets: ColorPalette | string[] | false;
|
232
|
-
componentLabels: ColorPickerLabels;
|
233
|
-
pickerToggle: HTMLElement;
|
234
|
-
menuToggle: HTMLElement;
|
235
|
-
colorPicker: HTMLElement;
|
236
|
-
colorMenu: HTMLElement;
|
237
|
-
controls: HTMLElement;
|
238
|
-
inputs: HTMLInputElement[];
|
239
|
-
controlKnobs: HTMLElement[];
|
240
|
-
visuals: HTMLElement[];
|
241
|
-
|
242
|
-
/**
|
243
|
-
* Returns a new `ColorPicker` instance. The target of this constructor
|
244
|
-
* must be an `HTMLInputElement`.
|
245
|
-
*
|
246
|
-
* @param target the target `<input>` element
|
247
|
-
* @param config instance options
|
248
|
-
*/
|
249
|
-
constructor(target: (HTMLElement & HTMLInputElement) | string, config?: Partial<ColorPickerOptions>) {
|
250
|
-
const input = querySelector(target) as HTMLInputElement;
|
251
|
-
|
252
|
-
// invalidate
|
253
|
-
if (typeof target === 'undefined') throw new TypeError(`ColorPicker target not specified.`);
|
254
|
-
if (isString(target) && !input) throw new TypeError(`ColorPicker target "${target}" cannot be found.`);
|
255
|
-
this.input = input;
|
256
|
-
|
257
|
-
const parent = closest(input, colorPickerParentSelector);
|
258
|
-
if (!parent) throw new TypeError('ColorPicker requires a specific markup to work.');
|
259
|
-
|
260
|
-
this.parent = parent;
|
261
|
-
this.id = getUID(input, colorPickerString);
|
262
|
-
this.dragElement = undefined;
|
263
|
-
this.isOpen = false;
|
264
|
-
this.controlPositions = {
|
265
|
-
c1x: 0,
|
266
|
-
c1y: 0,
|
267
|
-
c2y: 0,
|
268
|
-
c3y: 0,
|
269
|
-
};
|
270
|
-
// this.colorLabels = {};
|
271
|
-
this.colorKeywords = false;
|
272
|
-
this.colorPresets = false;
|
273
|
-
|
274
|
-
// process options
|
275
|
-
const { format, componentLabels, colorLabels, colorKeywords, colorPresets } = normalizeOptions(
|
276
|
-
input,
|
277
|
-
colorPickerDefaults,
|
278
|
-
config || {},
|
279
|
-
);
|
280
|
-
|
281
|
-
let translatedColorLabels = colorNames;
|
282
|
-
/* istanbul ignore else */
|
283
|
-
if (isArray(colorLabels) && colorLabels.length === 17) {
|
284
|
-
translatedColorLabels = colorLabels;
|
285
|
-
} else if (isString(colorLabels) && colorLabels.split(',').length === 17) {
|
286
|
-
translatedColorLabels = colorLabels.split(',');
|
287
|
-
}
|
288
|
-
|
289
|
-
// expose colour labels to all methods
|
290
|
-
ObjectAssign(this.colorLabels, ObjectFromEntries(translatedColorLabels.map((c, i) => [colorNames[i], c])));
|
291
|
-
|
292
|
-
// update and expose component labels
|
293
|
-
const tempComponentLabels =
|
294
|
-
isString(componentLabels) && isValidJSON(componentLabels)
|
295
|
-
? (JSON.parse(componentLabels) as ColorPickerLabels)
|
296
|
-
: componentLabels;
|
297
|
-
this.componentLabels = ObjectAssign({ ...colorPickerLabels }, tempComponentLabels);
|
298
|
-
this.color = new Color(input.value || '#fff', format);
|
299
|
-
this.format = format;
|
300
|
-
|
301
|
-
// set colour defaults
|
302
|
-
if (isArray(colorKeywords) && colorKeywords.length) {
|
303
|
-
this.colorKeywords = colorKeywords;
|
304
|
-
} else if (isString(colorKeywords) && colorKeywords.length) {
|
305
|
-
this.colorKeywords = colorKeywords.split(',').map(x => x.trim());
|
306
|
-
}
|
307
|
-
|
308
|
-
// set colour presets
|
309
|
-
if (isArray(colorPresets) && colorPresets.length) {
|
310
|
-
this.colorPresets = colorPresets;
|
311
|
-
} else if (colorPresets && isValidJSON(colorPresets)) {
|
312
|
-
const { hue, hueSteps, lightSteps, saturation } = JSON.parse(colorPresets) as {
|
313
|
-
hue: number;
|
314
|
-
hueSteps: number;
|
315
|
-
lightSteps: number;
|
316
|
-
saturation: number;
|
317
|
-
};
|
318
|
-
this.colorPresets = new ColorPalette(hue, hueSteps, lightSteps, saturation);
|
319
|
-
} else if (isString(colorPresets)) {
|
320
|
-
this.colorPresets = colorPresets.split(',').map((x: string) => x.trim());
|
321
|
-
}
|
322
|
-
|
323
|
-
// generate markup
|
324
|
-
setMarkup(this);
|
325
|
-
|
326
|
-
const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
|
327
|
-
// set main elements
|
328
|
-
this.pickerToggle = querySelector('.picker-toggle', parent) as HTMLElement;
|
329
|
-
this.menuToggle = querySelector('.menu-toggle', parent) as HTMLElement;
|
330
|
-
this.colorPicker = colorPicker;
|
331
|
-
this.colorMenu = colorMenu;
|
332
|
-
this.inputs = [...getElementsByClassName('color-input', parent)] as HTMLInputElement[];
|
333
|
-
const [controls] = getElementsByClassName('color-controls', parent);
|
334
|
-
this.controls = controls;
|
335
|
-
this.controlKnobs = [...getElementsByClassName('knob', controls)];
|
336
|
-
this.visuals = [...getElementsByClassName('visual-control', controls)];
|
337
|
-
|
338
|
-
// update colour picker controls, inputs and visuals
|
339
|
-
this.update();
|
340
|
-
// console.log(this)
|
341
|
-
|
342
|
-
// add main events listeners
|
343
|
-
toggleEvents(this, true);
|
344
|
-
|
345
|
-
// set component data
|
346
|
-
Data.set(input, colorPickerString, this);
|
347
|
-
}
|
348
|
-
|
349
|
-
/** Returns the current colour value */
|
350
|
-
get value(): string {
|
351
|
-
return this.input.value;
|
352
|
-
}
|
353
|
-
|
354
|
-
/**
|
355
|
-
* Sets a new colour value.
|
356
|
-
*
|
357
|
-
* @param {string} v new colour value
|
358
|
-
*/
|
359
|
-
set value(v: string) {
|
360
|
-
this.input.value = v;
|
361
|
-
}
|
362
|
-
|
363
|
-
/** Check if the colour presets include any non-colour. */
|
364
|
-
get hasNonColor(): boolean {
|
365
|
-
return this.colorKeywords instanceof Array && this.colorKeywords.some(x => nonColors.includes(x));
|
366
|
-
}
|
367
|
-
|
368
|
-
/** Returns hexadecimal value of the current colour. */
|
369
|
-
get hex(): string {
|
370
|
-
return this.color.toHex(true);
|
371
|
-
}
|
372
|
-
|
373
|
-
/** Returns the current colour value in {h,s,v,a} object format. */
|
374
|
-
get hsv(): HSVA {
|
375
|
-
return this.color.toHsv();
|
376
|
-
}
|
377
|
-
|
378
|
-
/** Returns the current colour value in {h,s,l,a} object format. */
|
379
|
-
get hsl(): HSLA {
|
380
|
-
return this.color.toHsl();
|
381
|
-
}
|
382
|
-
|
383
|
-
/** Returns the current colour value in {h,w,b,a} object format. */
|
384
|
-
get hwb(): HWBA {
|
385
|
-
return this.color.toHwb();
|
386
|
-
}
|
387
|
-
|
388
|
-
/** Returns the current colour value in {r,g,b,a} object format. */
|
389
|
-
get rgb(): RGBA {
|
390
|
-
return this.color.toRgb();
|
391
|
-
}
|
392
|
-
|
393
|
-
/** Returns the current colour brightness. */
|
394
|
-
get brightness(): number {
|
395
|
-
return this.color.brightness;
|
396
|
-
}
|
397
|
-
|
398
|
-
/** Returns the current colour luminance. */
|
399
|
-
get luminance(): number {
|
400
|
-
return this.color.luminance;
|
401
|
-
}
|
402
|
-
|
403
|
-
/** Checks if the current colour requires a light text colour. */
|
404
|
-
get isDark(): boolean {
|
405
|
-
const { color, brightness } = this;
|
406
|
-
return brightness < 120 && color.a > 0.33;
|
407
|
-
}
|
408
|
-
|
409
|
-
/** Checks if the current input value is a valid colour. */
|
410
|
-
get isValid(): boolean {
|
411
|
-
const inputValue = this.input.value;
|
412
|
-
return inputValue !== '' && new Color(inputValue).isValid;
|
413
|
-
}
|
414
|
-
|
415
|
-
/** Returns the colour appearance, usually the closest colour name for the current value. */
|
416
|
-
get appearance(): string {
|
417
|
-
const { colorLabels, hsl, hsv, format } = this;
|
418
|
-
|
419
|
-
const hue = roundPart(hsl.h * 360);
|
420
|
-
const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
|
421
|
-
const saturation = roundPart(saturationSource * 100);
|
422
|
-
const lightness = roundPart(hsl.l * 100);
|
423
|
-
const hsvl = hsv.v * 100;
|
424
|
-
|
425
|
-
let colorName = 'black';
|
426
|
-
|
427
|
-
// determine color appearance
|
428
|
-
/* istanbul ignore else */
|
429
|
-
if (lightness === 100 && saturation === 0) {
|
430
|
-
colorName = colorLabels.white;
|
431
|
-
} else if (lightness === 0) {
|
432
|
-
colorName = colorLabels.black;
|
433
|
-
} else if (saturation === 0) {
|
434
|
-
colorName = colorLabels.grey;
|
435
|
-
} else if (hue < 15 || hue >= 345) {
|
436
|
-
colorName = colorLabels.red;
|
437
|
-
} else if (hue >= 15 && hue < 45) {
|
438
|
-
colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
|
439
|
-
} else if (hue >= 45 && hue < 75) {
|
440
|
-
const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
|
441
|
-
const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
|
442
|
-
colorName = isGold ? colorLabels.gold : colorLabels.yellow;
|
443
|
-
colorName = isOlive ? colorLabels.olive : colorName;
|
444
|
-
} else if (hue >= 75 && hue < 155) {
|
445
|
-
colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
|
446
|
-
} else if (hue >= 155 && hue < 175) {
|
447
|
-
colorName = colorLabels.teal;
|
448
|
-
} else if (hue >= 175 && hue < 195) {
|
449
|
-
colorName = colorLabels.cyan;
|
450
|
-
} else if (hue >= 195 && hue < 255) {
|
451
|
-
colorName = colorLabels.blue;
|
452
|
-
} else if (hue >= 255 && hue < 270) {
|
453
|
-
colorName = colorLabels.violet;
|
454
|
-
} else if (hue >= 270 && hue < 295) {
|
455
|
-
colorName = colorLabels.magenta;
|
456
|
-
} else if (hue >= 295 && hue < 345) {
|
457
|
-
colorName = colorLabels.pink;
|
458
|
-
}
|
459
|
-
return colorName;
|
460
|
-
}
|
461
|
-
|
462
|
-
/** Updates `ColorPicker` visuals. */
|
463
|
-
updateVisuals(): void {
|
464
|
-
const { controlPositions, visuals } = this;
|
465
|
-
const [v1, v2, v3] = visuals;
|
466
|
-
const { offsetHeight } = v1;
|
467
|
-
const hue = controlPositions.c2y / offsetHeight;
|
468
|
-
const { r, g, b } = new Color({ h: hue * 360, s: 100, l: 50 }).toRgb();
|
469
|
-
const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
|
470
|
-
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
471
|
-
const roundA = roundPart(alpha * 100) / 100;
|
472
|
-
|
473
|
-
const fill = new Color({
|
474
|
-
h: hue * 360,
|
475
|
-
s: 100,
|
476
|
-
l: 50,
|
477
|
-
a: alpha,
|
478
|
-
}).toRgbString();
|
479
|
-
const hueGradient = `linear-gradient(
|
480
|
-
rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
|
481
|
-
rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
|
482
|
-
rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
|
483
|
-
rgb(255,0,0) 100%)`;
|
484
|
-
setElementStyle(v1, {
|
485
|
-
background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
|
486
|
-
linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
|
487
|
-
${whiteGrad}`,
|
488
|
-
});
|
489
|
-
setElementStyle(v2, { background: hueGradient });
|
490
|
-
|
491
|
-
setElementStyle(v3, {
|
492
|
-
background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
|
493
|
-
});
|
494
|
-
}
|
495
|
-
|
496
|
-
/**
|
497
|
-
* The `ColorPicker` *focusout* event listener when open.
|
498
|
-
*
|
499
|
-
* @param e
|
500
|
-
* @this {ColorPicker}
|
501
|
-
*/
|
502
|
-
handleFocusOut = ({ relatedTarget }: FocusEvent & { relatedTarget: HTMLElement }): void => {
|
503
|
-
if (relatedTarget && !this.parent.contains(relatedTarget)) {
|
504
|
-
this.hide(true);
|
505
|
-
}
|
506
|
-
};
|
507
|
-
|
508
|
-
/**
|
509
|
-
* The `ColorPicker` *keyup* event listener when open.
|
510
|
-
*
|
511
|
-
* @param e
|
512
|
-
* @this {ColorPicker}
|
513
|
-
*/
|
514
|
-
handleDismiss = ({ code }: KeyboardEvent): void => {
|
515
|
-
if (this.isOpen && code === keyEscape) {
|
516
|
-
this.hide();
|
517
|
-
}
|
518
|
-
};
|
519
|
-
|
520
|
-
/**
|
521
|
-
* The `ColorPicker` *scroll* event listener when open.
|
522
|
-
*
|
523
|
-
* @param e
|
524
|
-
*/
|
525
|
-
handleScroll = (e: Event) => {
|
526
|
-
const { activeElement } = getDocument(this.input);
|
527
|
-
|
528
|
-
this.updateDropdownPosition();
|
529
|
-
|
530
|
-
/* istanbul ignore next */
|
531
|
-
if (
|
532
|
-
([pointermoveEvent, touchmoveEvent].includes(e.type) && this.dragElement) ||
|
533
|
-
(activeElement && this.controlKnobs.includes(activeElement as HTMLElement))
|
534
|
-
) {
|
535
|
-
e.stopPropagation();
|
536
|
-
e.preventDefault();
|
537
|
-
}
|
538
|
-
};
|
539
|
-
|
540
|
-
/**
|
541
|
-
* The `ColorPicker` keyboard event listener for menu navigation.
|
542
|
-
*
|
543
|
-
* @param e
|
544
|
-
*/
|
545
|
-
menuKeyHandler = (e: KeyboardEvent & { target: HTMLElement }) => {
|
546
|
-
const { target, code } = e;
|
547
|
-
const { previousElementSibling, nextElementSibling, parentElement } = target;
|
548
|
-
const isColorOptionsMenu = parentElement && hasClass(parentElement, 'color-options');
|
549
|
-
const allSiblings = parentElement ? [...parentElement.children] : [];
|
550
|
-
// const columnsCount =
|
551
|
-
// isColorOptionsMenu && getElementStyle(parentElement, 'grid-template-columns').split(' ').length;
|
552
|
-
const columnsCount = isColorOptionsMenu && Number(getElementStyle(parentElement, '--grid-fit'));
|
553
|
-
const currentIndex = allSiblings.indexOf(target);
|
554
|
-
const previousElement = currentIndex > -1 && columnsCount && allSiblings[currentIndex - columnsCount];
|
555
|
-
const nextElement = currentIndex > -1 && columnsCount && allSiblings[currentIndex + columnsCount];
|
556
|
-
|
557
|
-
if ([keyArrowDown, keyArrowUp, keySpace].includes(code)) {
|
558
|
-
// prevent scroll when navigating the menu via arrow keys / Space
|
559
|
-
e.preventDefault();
|
560
|
-
}
|
561
|
-
if (isColorOptionsMenu) {
|
562
|
-
if (previousElement && code === keyArrowUp) {
|
563
|
-
focus(previousElement as HTMLElement);
|
564
|
-
} else if (nextElement && code === keyArrowDown) {
|
565
|
-
focus(nextElement as HTMLElement);
|
566
|
-
} else if (previousElementSibling && code === keyArrowLeft) {
|
567
|
-
focus(previousElementSibling as HTMLElement);
|
568
|
-
} else if (nextElementSibling && code === keyArrowRight) {
|
569
|
-
focus(nextElementSibling as HTMLElement);
|
570
|
-
}
|
571
|
-
} else if (previousElementSibling && [keyArrowLeft, keyArrowUp].includes(code)) {
|
572
|
-
focus(previousElementSibling as HTMLElement);
|
573
|
-
} else if (nextElementSibling && [keyArrowRight, keyArrowDown].includes(code)) {
|
574
|
-
focus(nextElementSibling as HTMLElement);
|
575
|
-
}
|
576
|
-
|
577
|
-
if ([keyEnter, keySpace].includes(code)) {
|
578
|
-
this.menuClickHandler(e);
|
579
|
-
}
|
580
|
-
};
|
581
|
-
|
582
|
-
/**
|
583
|
-
* The `ColorPicker` click event listener for the colour menu presets / defaults.
|
584
|
-
*
|
585
|
-
* @param e
|
586
|
-
* @this {ColorPicker}
|
587
|
-
*/
|
588
|
-
menuClickHandler = (e: Event) => {
|
589
|
-
const { target } = e;
|
590
|
-
const { colorMenu } = this;
|
591
|
-
const newOption = (getAttribute(target as HTMLElement, 'data-value') || '').trim();
|
592
|
-
// invalidate for targets other than color options
|
593
|
-
if (!newOption.length) return;
|
594
|
-
const currentActive = querySelector('li.active', colorMenu);
|
595
|
-
let newColor = newOption;
|
596
|
-
newColor = nonColors.includes(newColor) ? 'white' : newColor;
|
597
|
-
newColor = newColor === 'transparent' ? 'rgba(0,0,0,0)' : newColor;
|
598
|
-
|
599
|
-
const { r, g, b, a } = new Color(newColor);
|
600
|
-
|
601
|
-
ObjectAssign(this.color, {
|
602
|
-
r,
|
603
|
-
g,
|
604
|
-
b,
|
605
|
-
a,
|
606
|
-
});
|
607
|
-
|
608
|
-
this.update();
|
609
|
-
|
610
|
-
/* istanbul ignore else */
|
611
|
-
if (currentActive !== target) {
|
612
|
-
/* istanbul ignore else */
|
613
|
-
if (currentActive) {
|
614
|
-
removeClass(currentActive, 'active');
|
615
|
-
removeAttribute(currentActive, ariaSelected);
|
616
|
-
}
|
617
|
-
|
618
|
-
addClass(target as HTMLElement, 'active');
|
619
|
-
setAttribute(target as HTMLElement, ariaSelected, 'true');
|
620
|
-
|
621
|
-
if (nonColors.includes(newOption)) {
|
622
|
-
this.value = newOption;
|
623
|
-
}
|
624
|
-
firePickerChange(this);
|
625
|
-
}
|
626
|
-
};
|
627
|
-
|
628
|
-
/**
|
629
|
-
* The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
|
630
|
-
*
|
631
|
-
* @param e
|
632
|
-
*/
|
633
|
-
pointerDown = (e: PointerEvent & { target: HTMLElement }) => {
|
634
|
-
if (e.button !== 0) return;
|
635
|
-
const { target, pageX, pageY } = e;
|
636
|
-
const { colorMenu, visuals, controlKnobs } = this;
|
637
|
-
const [v1, v2, v3] = visuals;
|
638
|
-
const [c1, c2, c3] = controlKnobs;
|
639
|
-
const visual = controlKnobs.includes(target) ? (target.previousElementSibling as HTMLElement) : target;
|
640
|
-
const visualRect = getBoundingClientRect(visual);
|
641
|
-
const html = getDocumentElement(v1);
|
642
|
-
const offsetX = pageX - html.scrollLeft - visualRect.left;
|
643
|
-
const offsetY = pageY - html.scrollTop - visualRect.top;
|
644
|
-
|
645
|
-
/* istanbul ignore else */
|
646
|
-
if (target === v1 || target === c1) {
|
647
|
-
this.dragElement = visual;
|
648
|
-
this.changeControl1(offsetX, offsetY);
|
649
|
-
} else if (target === v2 || target === c2) {
|
650
|
-
this.dragElement = visual;
|
651
|
-
this.changeControl2(offsetY);
|
652
|
-
} else if (target === v3 || target === c3) {
|
653
|
-
this.dragElement = visual;
|
654
|
-
this.changeAlpha(offsetY);
|
655
|
-
}
|
656
|
-
|
657
|
-
if (colorMenu) {
|
658
|
-
const currentActive = querySelector('li.active', colorMenu);
|
659
|
-
if (currentActive) {
|
660
|
-
removeClass(currentActive, 'active');
|
661
|
-
removeAttribute(currentActive, ariaSelected);
|
662
|
-
}
|
663
|
-
}
|
664
|
-
e.preventDefault();
|
665
|
-
};
|
666
|
-
|
667
|
-
/**
|
668
|
-
* The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
|
669
|
-
*
|
670
|
-
* @param e
|
671
|
-
* @this
|
672
|
-
*/
|
673
|
-
pointerUp = ({ target }: PointerEvent & { target: HTMLElement }) => {
|
674
|
-
const { parent } = this;
|
675
|
-
const doc = getDocument(parent);
|
676
|
-
const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
|
677
|
-
const selection = doc.getSelection();
|
678
|
-
|
679
|
-
if (!this.dragElement && (!selection || !selection.toString().length) && !parent.contains(target)) {
|
680
|
-
this.hide(currentOpen);
|
681
|
-
}
|
682
|
-
|
683
|
-
this.dragElement = undefined;
|
684
|
-
};
|
685
|
-
|
686
|
-
/**
|
687
|
-
* The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
|
688
|
-
*
|
689
|
-
* @param {PointerEvent} e
|
690
|
-
*/
|
691
|
-
pointerMove = (e: PointerEvent) => {
|
692
|
-
const { dragElement, visuals } = this;
|
693
|
-
const [v1, v2, v3] = visuals;
|
694
|
-
const { pageX, pageY } = e;
|
695
|
-
|
696
|
-
if (!dragElement) return;
|
697
|
-
|
698
|
-
const controlRect = getBoundingClientRect(dragElement);
|
699
|
-
const win = getDocumentElement(v1);
|
700
|
-
const offsetX = pageX - win.scrollLeft - controlRect.left;
|
701
|
-
const offsetY = pageY - win.scrollTop - controlRect.top;
|
702
|
-
|
703
|
-
if (dragElement === v1) {
|
704
|
-
this.changeControl1(offsetX, offsetY);
|
705
|
-
}
|
706
|
-
|
707
|
-
if (dragElement === v2) {
|
708
|
-
this.changeControl2(offsetY);
|
709
|
-
}
|
710
|
-
|
711
|
-
if (dragElement === v3) {
|
712
|
-
this.changeAlpha(offsetY);
|
713
|
-
}
|
714
|
-
};
|
715
|
-
|
716
|
-
/**
|
717
|
-
* The `ColorPicker` *keydown* event listener for control knobs.
|
718
|
-
*
|
719
|
-
* @param e
|
720
|
-
*/
|
721
|
-
handleKnobs = (e: Event & { code: string }) => {
|
722
|
-
const { target, code } = e;
|
723
|
-
|
724
|
-
// only react to arrow buttons
|
725
|
-
if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
|
726
|
-
e.preventDefault();
|
727
|
-
|
728
|
-
const { controlKnobs, visuals } = this;
|
729
|
-
const { offsetWidth, offsetHeight } = visuals[0];
|
730
|
-
const [c1, c2, c3] = controlKnobs;
|
731
|
-
const { activeElement } = getDocument(c1);
|
732
|
-
const currentKnob = controlKnobs.find(x => x === activeElement);
|
733
|
-
const yRatio = offsetHeight / 360;
|
734
|
-
|
735
|
-
/* istanbul ignore else */
|
736
|
-
if (currentKnob) {
|
737
|
-
let offsetX = 0;
|
738
|
-
let offsetY = 0;
|
739
|
-
|
740
|
-
/* istanbul ignore else */
|
741
|
-
if (target === c1) {
|
742
|
-
const xRatio = offsetWidth / 100;
|
743
|
-
|
744
|
-
/* istanbul ignore else */
|
745
|
-
if ([keyArrowLeft, keyArrowRight].includes(code)) {
|
746
|
-
this.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
|
747
|
-
} else if ([keyArrowUp, keyArrowDown].includes(code)) {
|
748
|
-
this.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
|
749
|
-
}
|
750
|
-
|
751
|
-
offsetX = this.controlPositions.c1x;
|
752
|
-
offsetY = this.controlPositions.c1y;
|
753
|
-
this.changeControl1(offsetX, offsetY);
|
754
|
-
} else if (target === c2) {
|
755
|
-
this.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? yRatio : -yRatio;
|
756
|
-
|
757
|
-
offsetY = this.controlPositions.c2y;
|
758
|
-
this.changeControl2(offsetY);
|
759
|
-
} else if (target === c3) {
|
760
|
-
this.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? yRatio : -yRatio;
|
761
|
-
|
762
|
-
offsetY = this.controlPositions.c3y;
|
763
|
-
this.changeAlpha(offsetY);
|
764
|
-
}
|
765
|
-
this.handleScroll(e);
|
766
|
-
}
|
767
|
-
};
|
768
|
-
|
769
|
-
/** The event listener of the colour form inputs. */
|
770
|
-
changeHandler = (): void => {
|
771
|
-
let colorSource;
|
772
|
-
const { inputs, format, value: currentValue, input, controlPositions, visuals } = this;
|
773
|
-
const { activeElement } = getDocument(input);
|
774
|
-
const { offsetHeight } = visuals[0];
|
775
|
-
const [i1, , , i4] = inputs;
|
776
|
-
const [v1, v2, v3, v4] =
|
777
|
-
format === 'rgb'
|
778
|
-
? inputs.map(i => parseFloat(i.value) / (i === i4 ? 100 : 1))
|
779
|
-
: inputs.map(i => parseFloat(i.value) / (i !== i1 ? 100 : 360));
|
780
|
-
const isNonColorValue = this.hasNonColor && nonColors.includes(currentValue);
|
781
|
-
const alpha = i4 ? v4 : 1 - controlPositions.c3y / offsetHeight;
|
782
|
-
|
783
|
-
/* istanbul ignore else */
|
784
|
-
if (activeElement === input || (activeElement && inputs.includes(activeElement as HTMLInputElement))) {
|
785
|
-
if (activeElement === input) {
|
786
|
-
if (isNonColorValue) {
|
787
|
-
colorSource = currentValue === 'transparent' ? 'rgba(0,0,0,0)' : 'rgb(0,0,0)';
|
788
|
-
} else {
|
789
|
-
colorSource = currentValue;
|
790
|
-
}
|
791
|
-
} else if (format === 'hex') {
|
792
|
-
colorSource = i1.value;
|
793
|
-
} else if (format === 'hsl') {
|
794
|
-
colorSource = {
|
795
|
-
h: v1,
|
796
|
-
s: v2,
|
797
|
-
l: v3,
|
798
|
-
a: alpha,
|
799
|
-
};
|
800
|
-
} else if (format === 'hwb') {
|
801
|
-
colorSource = {
|
802
|
-
h: v1,
|
803
|
-
w: v2,
|
804
|
-
b: v3,
|
805
|
-
a: alpha,
|
806
|
-
};
|
807
|
-
} else {
|
808
|
-
colorSource = {
|
809
|
-
r: v1,
|
810
|
-
g: v2,
|
811
|
-
b: v3,
|
812
|
-
a: alpha,
|
813
|
-
};
|
814
|
-
}
|
815
|
-
|
816
|
-
const { r, g, b, a } = new Color(colorSource);
|
817
|
-
|
818
|
-
ObjectAssign(this.color, {
|
819
|
-
r,
|
820
|
-
g,
|
821
|
-
b,
|
822
|
-
a,
|
823
|
-
});
|
824
|
-
this.setControlPositions();
|
825
|
-
this.updateAppearance();
|
826
|
-
this.updateInputs();
|
827
|
-
this.updateControls();
|
828
|
-
this.updateVisuals();
|
829
|
-
|
830
|
-
// set non-color keyword
|
831
|
-
if (activeElement === input && isNonColorValue) {
|
832
|
-
this.value = currentValue;
|
833
|
-
}
|
834
|
-
}
|
835
|
-
};
|
836
|
-
|
837
|
-
/**
|
838
|
-
* Updates `ColorPicker` first control:
|
839
|
-
* * `lightness` and `saturation` for HEX/RGB;
|
840
|
-
* * `lightness` and `hue` for HSL.
|
841
|
-
*
|
842
|
-
* @param X the X component of the offset
|
843
|
-
* @param Y the Y component of the offset
|
844
|
-
*/
|
845
|
-
changeControl1(X: number, Y: number): void {
|
846
|
-
let [offsetX, offsetY] = [0, 0];
|
847
|
-
const { controlPositions, visuals } = this;
|
848
|
-
const { offsetHeight, offsetWidth } = visuals[0];
|
849
|
-
|
850
|
-
if (X > offsetWidth) offsetX = offsetWidth;
|
851
|
-
else if (X >= 0) offsetX = X;
|
852
|
-
|
853
|
-
if (Y > offsetHeight) offsetY = offsetHeight;
|
854
|
-
else if (Y >= 0) offsetY = Y;
|
855
|
-
|
856
|
-
const hue = controlPositions.c2y / offsetHeight;
|
857
|
-
|
858
|
-
const saturation = offsetX / offsetWidth;
|
859
|
-
|
860
|
-
const lightness = 1 - offsetY / offsetHeight;
|
861
|
-
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
862
|
-
|
863
|
-
// new color
|
864
|
-
const { r, g, b, a } = new Color({
|
865
|
-
h: hue * 360,
|
866
|
-
s: saturation * 100,
|
867
|
-
v: lightness * 100,
|
868
|
-
a: alpha,
|
869
|
-
});
|
870
|
-
|
871
|
-
ObjectAssign(this.color, {
|
872
|
-
r,
|
873
|
-
g,
|
874
|
-
b,
|
875
|
-
a,
|
876
|
-
});
|
877
|
-
|
878
|
-
// new positions
|
879
|
-
this.controlPositions.c1x = offsetX;
|
880
|
-
this.controlPositions.c1y = offsetY;
|
881
|
-
|
882
|
-
// update color picker
|
883
|
-
this.updateAppearance();
|
884
|
-
this.updateInputs();
|
885
|
-
this.updateControls();
|
886
|
-
this.updateVisuals();
|
887
|
-
}
|
888
|
-
|
889
|
-
/**
|
890
|
-
* Updates `ColorPicker` second control:
|
891
|
-
* * `hue` for HEX/RGB/HWB;
|
892
|
-
* * `saturation` for HSL.
|
893
|
-
*
|
894
|
-
* @param Y the Y offset
|
895
|
-
*/
|
896
|
-
changeControl2(Y: number) {
|
897
|
-
const { controlPositions, visuals } = this;
|
898
|
-
const { offsetHeight, offsetWidth } = visuals[0];
|
899
|
-
|
900
|
-
let offsetY = 0;
|
901
|
-
|
902
|
-
if (Y > offsetHeight) offsetY = offsetHeight;
|
903
|
-
else if (Y >= 0) offsetY = Y;
|
904
|
-
|
905
|
-
const hue = offsetY / offsetHeight;
|
906
|
-
const saturation = controlPositions.c1x / offsetWidth;
|
907
|
-
const lightness = 1 - controlPositions.c1y / offsetHeight;
|
908
|
-
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
909
|
-
|
910
|
-
// new color
|
911
|
-
const { r, g, b, a } = new Color({
|
912
|
-
h: hue * 360,
|
913
|
-
s: saturation * 100,
|
914
|
-
v: lightness * 100,
|
915
|
-
a: alpha,
|
916
|
-
});
|
917
|
-
|
918
|
-
ObjectAssign(this.color, {
|
919
|
-
r,
|
920
|
-
g,
|
921
|
-
b,
|
922
|
-
a,
|
923
|
-
});
|
924
|
-
|
925
|
-
// new position
|
926
|
-
this.controlPositions.c2y = offsetY;
|
927
|
-
// update color picker
|
928
|
-
this.updateAppearance();
|
929
|
-
this.updateInputs();
|
930
|
-
this.updateControls();
|
931
|
-
this.updateVisuals();
|
932
|
-
}
|
933
|
-
|
934
|
-
/**
|
935
|
-
* Updates `ColorPicker` last control,
|
936
|
-
* the `alpha` channel.
|
937
|
-
*
|
938
|
-
* @param Y
|
939
|
-
*/
|
940
|
-
changeAlpha(Y: number) {
|
941
|
-
const { visuals } = this;
|
942
|
-
const { offsetHeight } = visuals[0];
|
943
|
-
let offsetY = 0;
|
944
|
-
|
945
|
-
if (Y > offsetHeight) offsetY = offsetHeight;
|
946
|
-
else if (Y >= 0) offsetY = Y;
|
947
|
-
|
948
|
-
// update color alpha
|
949
|
-
const alpha = 1 - offsetY / offsetHeight;
|
950
|
-
this.color.setAlpha(alpha);
|
951
|
-
// update position
|
952
|
-
this.controlPositions.c3y = offsetY;
|
953
|
-
// update color picker
|
954
|
-
this.updateAppearance();
|
955
|
-
this.updateInputs();
|
956
|
-
this.updateControls();
|
957
|
-
this.updateVisuals();
|
958
|
-
}
|
959
|
-
|
960
|
-
/**
|
961
|
-
* Updates `ColorPicker` control positions on:
|
962
|
-
* * initialization
|
963
|
-
* * window resize
|
964
|
-
*/
|
965
|
-
update = () => {
|
966
|
-
this.updateDropdownPosition();
|
967
|
-
this.updateAppearance();
|
968
|
-
this.setControlPositions();
|
969
|
-
this.updateInputs(true);
|
970
|
-
this.updateControls();
|
971
|
-
this.updateVisuals();
|
972
|
-
};
|
973
|
-
|
974
|
-
/** Updates the open dropdown position on *scroll* event. */
|
975
|
-
updateDropdownPosition() {
|
976
|
-
const { input, colorPicker, colorMenu } = this;
|
977
|
-
const elRect = getBoundingClientRect(input);
|
978
|
-
const { top, bottom } = elRect;
|
979
|
-
const { offsetHeight: elHeight } = input;
|
980
|
-
const windowHeight = getDocumentElement(input).clientHeight;
|
981
|
-
const isPicker = hasClass(colorPicker, 'show');
|
982
|
-
const dropdown = isPicker ? colorPicker : colorMenu;
|
983
|
-
if (!dropdown) return;
|
984
|
-
const { offsetHeight: dropHeight } = dropdown;
|
985
|
-
const distanceBottom = windowHeight - bottom;
|
986
|
-
const distanceTop = top;
|
987
|
-
const bottomExceed = top + dropHeight + elHeight > windowHeight; // show
|
988
|
-
const topExceed = top - dropHeight < 0; // show-top
|
989
|
-
|
990
|
-
if ((hasClass(dropdown, 'bottom') || !topExceed) && distanceBottom < distanceTop && bottomExceed) {
|
991
|
-
removeClass(dropdown, 'bottom');
|
992
|
-
addClass(dropdown, 'top');
|
993
|
-
} else {
|
994
|
-
removeClass(dropdown, 'top');
|
995
|
-
addClass(dropdown, 'bottom');
|
996
|
-
}
|
997
|
-
}
|
998
|
-
|
999
|
-
/** Updates control knobs' positions. */
|
1000
|
-
setControlPositions() {
|
1001
|
-
const { visuals, color, hsv } = this;
|
1002
|
-
const { offsetHeight, offsetWidth } = visuals[0];
|
1003
|
-
const alpha = color.a;
|
1004
|
-
const hue = hsv.h;
|
1005
|
-
|
1006
|
-
const saturation = hsv.s;
|
1007
|
-
const lightness = hsv.v;
|
1008
|
-
|
1009
|
-
this.controlPositions.c1x = saturation * offsetWidth;
|
1010
|
-
this.controlPositions.c1y = (1 - lightness) * offsetHeight;
|
1011
|
-
this.controlPositions.c2y = hue * offsetHeight;
|
1012
|
-
this.controlPositions.c3y = (1 - alpha) * offsetHeight;
|
1013
|
-
}
|
1014
|
-
|
1015
|
-
/** Update the visual appearance label and control knob labels. */
|
1016
|
-
updateAppearance() {
|
1017
|
-
const { componentLabels, color, parent, hsv, hex, format, controlKnobs } = this;
|
1018
|
-
const { appearanceLabel, hexLabel, valueLabel } = componentLabels;
|
1019
|
-
let { r, g, b } = color.toRgb();
|
1020
|
-
const [knob1, knob2, knob3] = controlKnobs;
|
1021
|
-
const hue = roundPart(hsv.h * 360);
|
1022
|
-
const alpha = color.a;
|
1023
|
-
const saturation = roundPart(hsv.s * 100);
|
1024
|
-
const lightness = roundPart(hsv.v * 100);
|
1025
|
-
const colorName = this.appearance;
|
1026
|
-
|
1027
|
-
let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
|
1028
|
-
|
1029
|
-
if (format === 'hwb') {
|
1030
|
-
const { hwb } = this;
|
1031
|
-
const whiteness = roundPart(hwb.w * 100);
|
1032
|
-
const blackness = roundPart(hwb.b * 100);
|
1033
|
-
colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
|
1034
|
-
setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
|
1035
|
-
setAttribute(knob1, ariaValueNow, `${whiteness}`);
|
1036
|
-
setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
1037
|
-
setAttribute(knob2, ariaValueText, `${hue}%`);
|
1038
|
-
setAttribute(knob2, ariaValueNow, `${hue}`);
|
1039
|
-
} else {
|
1040
|
-
[r, g, b] = [r, g, b].map(roundPart);
|
1041
|
-
colorLabel = format === 'hsl' ? `HSL: ${hue}°, ${saturation}%, ${lightness}%` : colorLabel;
|
1042
|
-
colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
|
1043
|
-
|
1044
|
-
setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
|
1045
|
-
setAttribute(knob1, ariaValueNow, `${lightness}`);
|
1046
|
-
setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
1047
|
-
setAttribute(knob2, ariaValueText, `${hue}°`);
|
1048
|
-
setAttribute(knob2, ariaValueNow, `${hue}`);
|
1049
|
-
}
|
1050
|
-
|
1051
|
-
const alphaValue = roundPart(alpha * 100);
|
1052
|
-
setAttribute(knob3, ariaValueText, `${alphaValue}%`);
|
1053
|
-
setAttribute(knob3, ariaValueNow, `${alphaValue}`);
|
1054
|
-
|
1055
|
-
// update the input backgroundColor
|
1056
|
-
const newColor = color.toString();
|
1057
|
-
setElementStyle(this.input, { backgroundColor: newColor });
|
1058
|
-
|
1059
|
-
// toggle dark/light classes will also style the placeholder
|
1060
|
-
// dark sets color white, light sets color black
|
1061
|
-
// isDark ? '#000' : '#fff'
|
1062
|
-
if (!this.isDark) {
|
1063
|
-
if (hasClass(parent, 'txt-dark')) removeClass(parent, 'txt-dark');
|
1064
|
-
if (!hasClass(parent, 'txt-light')) addClass(parent, 'txt-light');
|
1065
|
-
} else {
|
1066
|
-
if (hasClass(parent, 'txt-light')) removeClass(parent, 'txt-light');
|
1067
|
-
if (!hasClass(parent, 'txt-dark')) addClass(parent, 'txt-dark');
|
1068
|
-
}
|
1069
|
-
}
|
1070
|
-
|
1071
|
-
/** Updates the control knobs actual positions. */
|
1072
|
-
updateControls() {
|
1073
|
-
const { controlKnobs, controlPositions } = this;
|
1074
|
-
let { c1x, c1y, c2y, c3y } = controlPositions;
|
1075
|
-
const [control1, control2, control3] = controlKnobs;
|
1076
|
-
// round control positions
|
1077
|
-
[c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
|
1078
|
-
|
1079
|
-
setElementStyle(control1, {
|
1080
|
-
transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)`,
|
1081
|
-
});
|
1082
|
-
setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
|
1083
|
-
setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
|
1084
|
-
}
|
1085
|
-
|
1086
|
-
/**
|
1087
|
-
* Updates all color form inputs.
|
1088
|
-
*
|
1089
|
-
* @param isPrevented when `true`, the component original event is prevented
|
1090
|
-
*/
|
1091
|
-
updateInputs(isPrevented?: boolean) {
|
1092
|
-
const { value: oldColor, format, inputs, color, hsl } = this;
|
1093
|
-
const [i1, i2, i3, i4] = inputs;
|
1094
|
-
const alpha = roundPart(color.a * 100);
|
1095
|
-
const hue = roundPart(hsl.h * 360);
|
1096
|
-
let newColor = color.toString();
|
1097
|
-
|
1098
|
-
/* istanbul ignore else */
|
1099
|
-
if (format === 'hex') {
|
1100
|
-
newColor = this.color.toHexString(true);
|
1101
|
-
i1.value = this.hex;
|
1102
|
-
} else if (format === 'hsl') {
|
1103
|
-
const lightness = roundPart(hsl.l * 100);
|
1104
|
-
const saturation = roundPart(hsl.s * 100);
|
1105
|
-
newColor = this.color.toHslString();
|
1106
|
-
i1.value = `${hue}`;
|
1107
|
-
i2.value = `${saturation}`;
|
1108
|
-
i3.value = `${lightness}`;
|
1109
|
-
i4.value = `${alpha}`;
|
1110
|
-
} else if (format === 'hwb') {
|
1111
|
-
const { w, b } = this.hwb;
|
1112
|
-
const whiteness = roundPart(w * 100);
|
1113
|
-
const blackness = roundPart(b * 100);
|
1114
|
-
|
1115
|
-
newColor = this.color.toHwbString();
|
1116
|
-
i1.value = `${hue}`;
|
1117
|
-
i2.value = `${whiteness}`;
|
1118
|
-
i3.value = `${blackness}`;
|
1119
|
-
i4.value = `${alpha}`;
|
1120
|
-
} else if (format === 'rgb') {
|
1121
|
-
let { r, g, b } = this.rgb;
|
1122
|
-
[r, g, b] = [r, g, b].map(roundPart);
|
1123
|
-
|
1124
|
-
newColor = this.color.toRgbString();
|
1125
|
-
i1.value = `${r}`;
|
1126
|
-
i2.value = `${g}`;
|
1127
|
-
i3.value = `${b}`;
|
1128
|
-
i4.value = `${alpha}`;
|
1129
|
-
}
|
1130
|
-
|
1131
|
-
// update the color value
|
1132
|
-
this.value = newColor;
|
1133
|
-
|
1134
|
-
// don't trigger the custom event unless it's really changed
|
1135
|
-
if (!isPrevented && newColor !== oldColor) {
|
1136
|
-
firePickerChange(this);
|
1137
|
-
}
|
1138
|
-
}
|
1139
|
-
|
1140
|
-
/**
|
1141
|
-
* Toggle the `ColorPicker` dropdown visibility.
|
1142
|
-
*
|
1143
|
-
* @param e
|
1144
|
-
*/
|
1145
|
-
togglePicker = (e?: Event) => {
|
1146
|
-
if (e) e.preventDefault();
|
1147
|
-
const { colorPicker } = this;
|
1148
|
-
|
1149
|
-
if (this.isOpen && hasClass(colorPicker, 'show')) {
|
1150
|
-
this.hide(true);
|
1151
|
-
} else {
|
1152
|
-
showDropdown(this, colorPicker);
|
1153
|
-
}
|
1154
|
-
};
|
1155
|
-
|
1156
|
-
/** Shows the `ColorPicker` dropdown. */
|
1157
|
-
showPicker = () => {
|
1158
|
-
const { colorPicker } = this;
|
1159
|
-
|
1160
|
-
if (!['top', 'bottom'].some(c => hasClass(colorPicker, c))) {
|
1161
|
-
showDropdown(this, colorPicker);
|
1162
|
-
}
|
1163
|
-
};
|
1164
|
-
|
1165
|
-
/**
|
1166
|
-
* Toggles the visibility of the `ColorPicker` presets menu.
|
1167
|
-
*
|
1168
|
-
* @param e
|
1169
|
-
* @this {ColorPicker}
|
1170
|
-
*/
|
1171
|
-
toggleMenu = (e?: Event) => {
|
1172
|
-
if (e) e.preventDefault();
|
1173
|
-
const { colorMenu } = this;
|
1174
|
-
|
1175
|
-
if (this.isOpen && hasClass(colorMenu, 'show')) {
|
1176
|
-
this.hide(true);
|
1177
|
-
} else {
|
1178
|
-
showDropdown(this, colorMenu);
|
1179
|
-
}
|
1180
|
-
};
|
1181
|
-
|
1182
|
-
/**
|
1183
|
-
* Hides the currently open `ColorPicker` dropdown.
|
1184
|
-
*
|
1185
|
-
* @param {boolean=} focusPrevented
|
1186
|
-
*/
|
1187
|
-
hide(focusPrevented?: boolean) {
|
1188
|
-
if (this.isOpen) {
|
1189
|
-
const { pickerToggle, menuToggle, colorPicker, colorMenu, parent, input } = this;
|
1190
|
-
const openPicker = hasClass(colorPicker, 'show');
|
1191
|
-
const openDropdown = openPicker ? colorPicker : colorMenu;
|
1192
|
-
const relatedBtn = openPicker ? pickerToggle : menuToggle;
|
1193
|
-
const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
|
1194
|
-
|
1195
|
-
this.value = this.color.toString(true);
|
1196
|
-
|
1197
|
-
/* istanbul ignore else */
|
1198
|
-
if (openDropdown) {
|
1199
|
-
removeClass(openDropdown, 'show');
|
1200
|
-
setAttribute(relatedBtn, ariaExpanded, 'false');
|
1201
|
-
setTimeout(() => {
|
1202
|
-
removePosition(openDropdown);
|
1203
|
-
/* istanbul ignore else */
|
1204
|
-
if (!querySelector('.show', parent)) {
|
1205
|
-
removeClass(parent, 'open');
|
1206
|
-
toggleEventsOnShown(this);
|
1207
|
-
this.isOpen = false;
|
1208
|
-
}
|
1209
|
-
}, animationDuration);
|
1210
|
-
}
|
1211
|
-
|
1212
|
-
if (!focusPrevented) {
|
1213
|
-
focus(pickerToggle);
|
1214
|
-
}
|
1215
|
-
setAttribute(input, tabindex, '-1');
|
1216
|
-
if (relatedBtn === menuToggle) {
|
1217
|
-
setAttribute(menuToggle, tabindex, '-1');
|
1218
|
-
}
|
1219
|
-
}
|
1220
|
-
}
|
1221
|
-
|
1222
|
-
/** Removes `ColorPicker` from target `<input>`. */
|
1223
|
-
dispose() {
|
1224
|
-
const { input, parent } = this;
|
1225
|
-
this.hide(true);
|
1226
|
-
toggleEvents(this);
|
1227
|
-
[...parent.children].forEach(el => {
|
1228
|
-
if (el !== input) el.remove();
|
1229
|
-
});
|
1230
|
-
|
1231
|
-
removeAttribute(input, tabindex);
|
1232
|
-
setElementStyle(input, { backgroundColor: '' });
|
1233
|
-
|
1234
|
-
['txt-light', 'txt-dark'].forEach(c => removeClass(parent, c));
|
1235
|
-
Data.remove(input, colorPickerString);
|
1236
|
-
}
|
1237
|
-
}
|