@thednp/color-picker 0.0.1-alpha1 → 0.0.1-alpha2
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +1 -1
- package/README.md +40 -19
- package/dist/css/color-picker.css +481 -337
- package/dist/css/color-picker.min.css +2 -0
- package/dist/css/color-picker.rtl.css +506 -0
- package/dist/css/color-picker.rtl.min.css +2 -0
- package/dist/js/color-picker-element-esm.js +3810 -2
- package/dist/js/color-picker-element-esm.min.js +2 -0
- package/dist/js/color-picker-element.js +2009 -1242
- package/dist/js/color-picker-element.min.js +2 -2
- package/dist/js/color-picker-esm.js +3704 -0
- package/dist/js/color-picker-esm.min.js +2 -0
- package/dist/js/color-picker.js +1962 -1256
- package/dist/js/color-picker.min.js +2 -2
- package/package.json +18 -9
- package/src/js/color-palette.js +62 -0
- package/src/js/color-picker-element.js +55 -13
- package/src/js/color-picker.js +686 -595
- package/src/js/color.js +615 -349
- package/src/js/index.js +0 -9
- package/src/js/util/colorNames.js +2 -152
- package/src/js/util/colorPickerLabels.js +22 -0
- package/src/js/util/getColorControls.js +103 -0
- package/src/js/util/getColorForm.js +27 -19
- package/src/js/util/getColorMenu.js +95 -0
- package/src/js/util/isValidJSON.js +13 -0
- package/src/js/util/nonColors.js +5 -0
- package/src/js/util/templates.js +1 -0
- package/src/scss/color-picker.rtl.scss +23 -0
- package/src/scss/color-picker.scss +430 -0
- package/types/cp.d.ts +263 -160
- package/types/index.d.ts +9 -2
- package/types/source/source.ts +2 -1
- package/types/source/types.d.ts +28 -5
- package/dist/js/color-picker.esm.js +0 -2998
- package/dist/js/color-picker.esm.min.js +0 -2
- package/src/js/util/getColorControl.js +0 -49
- package/src/js/util/init.js +0 -14
package/src/js/color-picker.js
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
import { addListener, removeListener } from 'event-listener.js';
|
2
2
|
|
3
|
+
import ariaDescription from 'shorter-js/src/strings/ariaDescription';
|
4
|
+
// import ariaLabel from 'shorter-js/src/strings/ariaLabel';
|
3
5
|
import ariaSelected from 'shorter-js/src/strings/ariaSelected';
|
4
6
|
import ariaExpanded from 'shorter-js/src/strings/ariaExpanded';
|
7
|
+
import ariaValueText from 'shorter-js/src/strings/ariaValueText';
|
8
|
+
import ariaValueNow from 'shorter-js/src/strings/ariaValueNow';
|
9
|
+
import ariaHasPopup from 'shorter-js/src/strings/ariaHasPopup';
|
5
10
|
import ariaHidden from 'shorter-js/src/strings/ariaHidden';
|
6
11
|
import ariaLabelledBy from 'shorter-js/src/strings/ariaLabelledBy';
|
7
12
|
import keyArrowDown from 'shorter-js/src/strings/keyArrowDown';
|
@@ -11,55 +16,73 @@ import keyArrowRight from 'shorter-js/src/strings/keyArrowRight';
|
|
11
16
|
import keyEnter from 'shorter-js/src/strings/keyEnter';
|
12
17
|
import keySpace from 'shorter-js/src/strings/keySpace';
|
13
18
|
import keyEscape from 'shorter-js/src/strings/keyEscape';
|
19
|
+
import focusinEvent from 'shorter-js/src/strings/focusinEvent';
|
20
|
+
import mouseclickEvent from 'shorter-js/src/strings/mouseclickEvent';
|
21
|
+
import keydownEvent from 'shorter-js/src/strings/keydownEvent';
|
22
|
+
import changeEvent from 'shorter-js/src/strings/changeEvent';
|
23
|
+
import touchstartEvent from 'shorter-js/src/strings/touchstartEvent';
|
24
|
+
import touchmoveEvent from 'shorter-js/src/strings/touchmoveEvent';
|
25
|
+
import touchendEvent from 'shorter-js/src/strings/touchendEvent';
|
26
|
+
import mousedownEvent from 'shorter-js/src/strings/mousedownEvent';
|
27
|
+
import mousemoveEvent from 'shorter-js/src/strings/mousemoveEvent';
|
28
|
+
import mouseupEvent from 'shorter-js/src/strings/mouseupEvent';
|
29
|
+
import scrollEvent from 'shorter-js/src/strings/scrollEvent';
|
30
|
+
import keyupEvent from 'shorter-js/src/strings/keyupEvent';
|
31
|
+
import resizeEvent from 'shorter-js/src/strings/resizeEvent';
|
32
|
+
import focusoutEvent from 'shorter-js/src/strings/focusoutEvent';
|
14
33
|
|
15
34
|
import isMobile from 'shorter-js/src/boolean/isMobile';
|
35
|
+
import getDocument from 'shorter-js/src/get/getDocument';
|
36
|
+
import getDocumentElement from 'shorter-js/src/get/getDocumentElement';
|
37
|
+
import getWindow from 'shorter-js/src/get/getWindow';
|
38
|
+
import getElementStyle from 'shorter-js/src/get/getElementStyle';
|
16
39
|
import getUID from 'shorter-js/src/get/getUID';
|
17
40
|
import getBoundingClientRect from 'shorter-js/src/get/getBoundingClientRect';
|
41
|
+
import getElementTransitionDuration from 'shorter-js/src/get/getElementTransitionDuration';
|
18
42
|
import querySelector from 'shorter-js/src/selectors/querySelector';
|
19
|
-
import querySelectorAll from 'shorter-js/src/selectors/querySelectorAll';
|
20
43
|
import closest from 'shorter-js/src/selectors/closest';
|
44
|
+
import getElementsByClassName from 'shorter-js/src/selectors/getElementsByClassName';
|
21
45
|
import createElement from 'shorter-js/src/misc/createElement';
|
22
46
|
import createElementNS from 'shorter-js/src/misc/createElementNS';
|
23
47
|
import dispatchEvent from 'shorter-js/src/misc/dispatchEvent';
|
24
48
|
import ObjectAssign from 'shorter-js/src/misc/ObjectAssign';
|
25
49
|
import Data, { getInstance } from 'shorter-js/src/misc/data';
|
50
|
+
import setElementStyle from 'shorter-js/src/misc/setElementStyle';
|
51
|
+
import normalizeOptions from 'shorter-js/src/misc/normalizeOptions';
|
52
|
+
import reflow from 'shorter-js/src/misc/reflow';
|
53
|
+
import focus from 'shorter-js/src/misc/focus';
|
26
54
|
import hasClass from 'shorter-js/src/class/hasClass';
|
27
55
|
import addClass from 'shorter-js/src/class/addClass';
|
28
56
|
import removeClass from 'shorter-js/src/class/removeClass';
|
29
|
-
import hasAttribute from 'shorter-js/src/attr/hasAttribute';
|
30
57
|
import setAttribute from 'shorter-js/src/attr/setAttribute';
|
31
58
|
import getAttribute from 'shorter-js/src/attr/getAttribute';
|
32
59
|
import removeAttribute from 'shorter-js/src/attr/removeAttribute';
|
33
60
|
|
61
|
+
// ColorPicker Util
|
62
|
+
// ================
|
63
|
+
import colorPickerLabels from './util/colorPickerLabels';
|
64
|
+
import colorNames from './util/colorNames';
|
65
|
+
import nonColors from './util/nonColors';
|
34
66
|
import getColorForm from './util/getColorForm';
|
35
|
-
import
|
67
|
+
import getColorControls from './util/getColorControls';
|
68
|
+
import getColorMenu from './util/getColorMenu';
|
36
69
|
import vHidden from './util/vHidden';
|
70
|
+
import isValidJSON from './util/isValidJSON';
|
37
71
|
import Color from './color';
|
72
|
+
import ColorPalette from './color-palette';
|
73
|
+
import Version from './version';
|
38
74
|
|
39
75
|
// ColorPicker GC
|
40
76
|
// ==============
|
41
77
|
const colorPickerString = 'color-picker';
|
42
78
|
const colorPickerSelector = `[data-function="${colorPickerString}"]`;
|
43
|
-
const
|
44
|
-
const
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
formatLabel: 'Colour Format',
|
51
|
-
formatHEX: 'Hexadecimal Format',
|
52
|
-
formatRGB: 'RGB Format',
|
53
|
-
formatHSL: 'HSL Format',
|
54
|
-
alphaLabel: 'Alpha',
|
55
|
-
appearanceLabel: 'Colour Appearance',
|
56
|
-
hexLabel: 'Hexadecimal',
|
57
|
-
hueLabel: 'Hue',
|
58
|
-
saturationLabel: 'Saturation',
|
59
|
-
lightnessLabel: 'Lightness',
|
60
|
-
redLabel: 'Red',
|
61
|
-
greenLabel: 'Green',
|
62
|
-
blueLabel: 'Blue',
|
79
|
+
const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
|
80
|
+
const colorPickerDefaults = {
|
81
|
+
componentLabels: colorPickerLabels,
|
82
|
+
colorLabels: colorNames,
|
83
|
+
format: 'rgb',
|
84
|
+
colorPresets: undefined,
|
85
|
+
colorKeywords: nonColors,
|
63
86
|
};
|
64
87
|
|
65
88
|
// ColorPicker Static Methods
|
@@ -74,165 +97,94 @@ const initColorPicker = (element) => new ColorPicker(element);
|
|
74
97
|
// ColorPicker Private Methods
|
75
98
|
// ===========================
|
76
99
|
|
77
|
-
/**
|
78
|
-
* Add / remove `ColorPicker` main event listeners.
|
79
|
-
* @param {ColorPicker} self
|
80
|
-
* @param {boolean=} action
|
81
|
-
*/
|
82
|
-
function toggleEvents(self, action) {
|
83
|
-
const fn = action ? addListener : removeListener;
|
84
|
-
const { input, pickerToggle, menuToggle } = self;
|
85
|
-
|
86
|
-
fn(input, 'focusin', self.showPicker);
|
87
|
-
fn(pickerToggle, 'click', self.togglePicker);
|
88
|
-
|
89
|
-
fn(input, 'keydown', self.keyHandler);
|
90
|
-
|
91
|
-
if (menuToggle) {
|
92
|
-
fn(menuToggle, 'click', self.toggleMenu);
|
93
|
-
}
|
94
|
-
}
|
95
|
-
|
96
100
|
/**
|
97
101
|
* Generate HTML markup and update instance properties.
|
98
102
|
* @param {ColorPicker} self
|
99
103
|
*/
|
100
104
|
function initCallback(self) {
|
101
105
|
const {
|
102
|
-
input, parent, format, id, componentLabels,
|
106
|
+
input, parent, format, id, componentLabels, colorKeywords, colorPresets,
|
103
107
|
} = self;
|
104
108
|
const colorValue = getAttribute(input, 'value') || '#fff';
|
105
109
|
|
106
110
|
const {
|
107
|
-
toggleLabel,
|
111
|
+
toggleLabel, pickerLabel, formatLabel, hexLabel,
|
108
112
|
} = componentLabels;
|
109
113
|
|
110
114
|
// update color
|
111
115
|
const color = nonColors.includes(colorValue) ? '#fff' : colorValue;
|
112
|
-
self.color = new Color(color,
|
116
|
+
self.color = new Color(color, format);
|
113
117
|
|
114
118
|
// set initial controls dimensions
|
115
119
|
// make the controls smaller on mobile
|
116
|
-
const cv1w = isMobile ? 150 : 230;
|
117
|
-
const cvh = isMobile ? 150 : 230;
|
118
|
-
const cv2w = 21;
|
119
120
|
const dropClass = isMobile ? ' mobile' : '';
|
120
|
-
const
|
121
|
-
const ctrl2Labelledby = format === 'hsl' ? `appearance2_${id}` : `appearance_${id} appearance2_${id}`;
|
121
|
+
const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
|
122
122
|
|
123
123
|
const pickerBtn = createElement({
|
124
|
+
id: `picker-btn-${id}`,
|
124
125
|
tagName: 'button',
|
125
|
-
className: 'picker-toggle
|
126
|
-
ariaExpanded: 'false',
|
127
|
-
ariaHasPopup: 'true',
|
128
|
-
ariaLive: 'polite',
|
126
|
+
className: 'picker-toggle btn-appearance',
|
129
127
|
});
|
130
|
-
setAttribute(pickerBtn,
|
128
|
+
setAttribute(pickerBtn, ariaExpanded, 'false');
|
129
|
+
setAttribute(pickerBtn, ariaHasPopup, 'true');
|
131
130
|
pickerBtn.append(createElement({
|
132
131
|
tagName: 'span',
|
133
132
|
className: vHidden,
|
134
|
-
innerText:
|
133
|
+
innerText: `${pickerLabel}. ${formatLabel}: ${formatString}`,
|
135
134
|
}));
|
136
135
|
|
137
|
-
const
|
136
|
+
const pickerDropdown = createElement({
|
138
137
|
tagName: 'div',
|
139
138
|
className: `color-dropdown picker${dropClass}`,
|
140
139
|
});
|
141
|
-
setAttribute(
|
142
|
-
setAttribute(
|
143
|
-
colorPickerDropdown.append(
|
144
|
-
createElement({
|
145
|
-
tagName: 'label',
|
146
|
-
className: vHidden,
|
147
|
-
ariaHidden: 'true',
|
148
|
-
id: `picker-label-${id}`,
|
149
|
-
innerText: `${pickerLabel}`,
|
150
|
-
}),
|
151
|
-
createElement({
|
152
|
-
tagName: 'label',
|
153
|
-
className: vHidden,
|
154
|
-
ariaHidden: 'true',
|
155
|
-
id: `format-label-${id}`,
|
156
|
-
innerText: `${formatLabel}`,
|
157
|
-
}),
|
158
|
-
createElement({
|
159
|
-
tagName: 'label',
|
160
|
-
className: `color-appearance ${vHidden}`,
|
161
|
-
ariaHidden: 'true',
|
162
|
-
ariaLive: 'polite',
|
163
|
-
id: `appearance_${id}`,
|
164
|
-
innerText: `${appearanceLabel}`,
|
165
|
-
}),
|
166
|
-
);
|
167
|
-
|
168
|
-
const colorControls = createElement({
|
169
|
-
tagName: 'div',
|
170
|
-
className: `color-controls ${format}`,
|
171
|
-
});
|
172
|
-
|
173
|
-
colorControls.append(
|
174
|
-
getColorControl(1, id, cv1w, cvh, ctrl1Labelledby),
|
175
|
-
getColorControl(2, id, cv2w, cvh, ctrl2Labelledby),
|
176
|
-
);
|
140
|
+
setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
|
141
|
+
setAttribute(pickerDropdown, 'role', 'group');
|
177
142
|
|
178
|
-
|
179
|
-
colorControls.append(
|
180
|
-
getColorControl(3, id, cv2w, cvh),
|
181
|
-
);
|
182
|
-
}
|
183
|
-
|
184
|
-
// @ts-ignore
|
143
|
+
const colorControls = getColorControls(self);
|
185
144
|
const colorForm = getColorForm(self);
|
186
|
-
colorPickerDropdown.append(colorControls, colorForm);
|
187
|
-
parent.append(pickerBtn, colorPickerDropdown);
|
188
145
|
|
189
|
-
|
190
|
-
|
191
|
-
|
146
|
+
pickerDropdown.append(colorControls, colorForm);
|
147
|
+
input.before(pickerBtn);
|
148
|
+
parent.append(pickerDropdown);
|
149
|
+
|
150
|
+
// set colour key menu template
|
151
|
+
if (colorKeywords || colorPresets) {
|
192
152
|
const presetsDropdown = createElement({
|
193
153
|
tagName: 'div',
|
194
|
-
className: `color-dropdown menu${dropClass}`,
|
195
|
-
});
|
196
|
-
const presetsMenu = createElement({
|
197
|
-
tagName: 'ul',
|
198
|
-
ariaLabel: `${menuLabel}`,
|
199
|
-
className: 'color-menu',
|
200
|
-
});
|
201
|
-
setAttribute(presetsMenu, 'role', 'listbox');
|
202
|
-
presetsDropdown.append(presetsMenu);
|
203
|
-
|
204
|
-
colorKeys.forEach((x) => {
|
205
|
-
const xKey = x.trim();
|
206
|
-
const xRealColor = new Color(xKey, { format }).toString();
|
207
|
-
const isActive = xRealColor === getAttribute(input, 'value');
|
208
|
-
const active = isActive ? ' active' : '';
|
209
|
-
|
210
|
-
const keyOption = createElement({
|
211
|
-
tagName: 'li',
|
212
|
-
className: `color-option${active}`,
|
213
|
-
ariaSelected: isActive ? 'true' : 'false',
|
214
|
-
innerText: `${x}`,
|
215
|
-
});
|
216
|
-
setAttribute(keyOption, 'role', 'option');
|
217
|
-
setAttribute(keyOption, 'tabindex', '0');
|
218
|
-
setAttribute(keyOption, 'data-value', `${xKey}`);
|
219
|
-
presetsMenu.append(keyOption);
|
154
|
+
className: `color-dropdown scrollable menu${dropClass}`,
|
220
155
|
});
|
156
|
+
|
157
|
+
// color presets
|
158
|
+
if ((colorPresets instanceof Array && colorPresets.length)
|
159
|
+
|| (colorPresets instanceof ColorPalette && colorPresets.colors)) {
|
160
|
+
const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
|
161
|
+
presetsDropdown.append(presetsMenu);
|
162
|
+
}
|
163
|
+
|
164
|
+
// explicit defaults [reset, initial, inherit, transparent, currentColor]
|
165
|
+
if (colorKeywords && colorKeywords.length) {
|
166
|
+
const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
|
167
|
+
presetsDropdown.append(keywordsMenu);
|
168
|
+
}
|
169
|
+
|
221
170
|
const presetsBtn = createElement({
|
222
171
|
tagName: 'button',
|
223
|
-
className: 'menu-toggle
|
224
|
-
ariaExpanded: 'false',
|
225
|
-
ariaHasPopup: 'true',
|
172
|
+
className: 'menu-toggle btn-appearance',
|
226
173
|
});
|
174
|
+
setAttribute(presetsBtn, 'tabindex', '-1');
|
175
|
+
setAttribute(presetsBtn, ariaExpanded, 'false');
|
176
|
+
setAttribute(presetsBtn, ariaHasPopup, 'true');
|
177
|
+
|
227
178
|
const xmlns = encodeURI('http://www.w3.org/2000/svg');
|
228
179
|
const presetsIcon = createElementNS(xmlns, { tagName: 'svg' });
|
229
180
|
setAttribute(presetsIcon, 'xmlns', xmlns);
|
230
|
-
setAttribute(presetsIcon, ariaHidden, 'true');
|
231
181
|
setAttribute(presetsIcon, 'viewBox', '0 0 512 512');
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
182
|
+
setAttribute(presetsIcon, ariaHidden, 'true');
|
183
|
+
|
184
|
+
const path = createElementNS(xmlns, { tagName: 'path' });
|
185
|
+
setAttribute(path, 'd', 'M98,158l157,156L411,158l27,27L255,368L71,185L98,158z');
|
186
|
+
setAttribute(path, 'fill', '#fff');
|
187
|
+
presetsIcon.append(path);
|
236
188
|
presetsBtn.append(createElement({
|
237
189
|
tagName: 'span',
|
238
190
|
className: vHidden,
|
@@ -243,9 +195,29 @@ function initCallback(self) {
|
|
243
195
|
}
|
244
196
|
|
245
197
|
// solve non-colors after settings save
|
246
|
-
if (
|
198
|
+
if (colorKeywords && nonColors.includes(colorValue)) {
|
247
199
|
self.value = colorValue;
|
248
200
|
}
|
201
|
+
setAttribute(input, 'tabindex', '-1');
|
202
|
+
}
|
203
|
+
|
204
|
+
/**
|
205
|
+
* Add / remove `ColorPicker` main event listeners.
|
206
|
+
* @param {ColorPicker} self
|
207
|
+
* @param {boolean=} action
|
208
|
+
*/
|
209
|
+
function toggleEvents(self, action) {
|
210
|
+
const fn = action ? addListener : removeListener;
|
211
|
+
const { input, pickerToggle, menuToggle } = self;
|
212
|
+
|
213
|
+
fn(input, focusinEvent, self.showPicker);
|
214
|
+
fn(pickerToggle, mouseclickEvent, self.togglePicker);
|
215
|
+
|
216
|
+
fn(input, keydownEvent, self.keyToggle);
|
217
|
+
|
218
|
+
if (menuToggle) {
|
219
|
+
fn(menuToggle, mouseclickEvent, self.toggleMenu);
|
220
|
+
}
|
249
221
|
}
|
250
222
|
|
251
223
|
/**
|
@@ -255,26 +227,33 @@ function initCallback(self) {
|
|
255
227
|
*/
|
256
228
|
function toggleEventsOnShown(self, action) {
|
257
229
|
const fn = action ? addListener : removeListener;
|
258
|
-
const
|
259
|
-
|
260
|
-
|
230
|
+
const { input, colorMenu, parent } = self;
|
231
|
+
const doc = getDocument(input);
|
232
|
+
const win = getWindow(input);
|
233
|
+
const pointerEvents = `on${touchstartEvent}` in doc
|
234
|
+
? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
|
235
|
+
: { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
|
261
236
|
|
262
237
|
fn(self.controls, pointerEvents.down, self.pointerDown);
|
263
|
-
self.controlKnobs.forEach((x) => fn(x,
|
238
|
+
self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
|
264
239
|
|
265
|
-
|
240
|
+
// @ts-ignore -- this is `Window`
|
241
|
+
fn(win, scrollEvent, self.handleScroll);
|
242
|
+
// @ts-ignore -- this is `Window`
|
243
|
+
fn(win, resizeEvent, self.update);
|
266
244
|
|
267
|
-
[
|
245
|
+
[input, ...self.inputs].forEach((x) => fn(x, changeEvent, self.changeHandler));
|
268
246
|
|
269
|
-
if (
|
270
|
-
fn(
|
271
|
-
fn(
|
247
|
+
if (colorMenu) {
|
248
|
+
fn(colorMenu, mouseclickEvent, self.menuClickHandler);
|
249
|
+
fn(colorMenu, keydownEvent, self.menuKeyHandler);
|
272
250
|
}
|
273
251
|
|
274
|
-
fn(
|
275
|
-
fn(
|
276
|
-
fn(
|
277
|
-
|
252
|
+
fn(doc, pointerEvents.move, self.pointerMove);
|
253
|
+
fn(doc, pointerEvents.up, self.pointerUp);
|
254
|
+
fn(parent, focusoutEvent, self.handleFocusOut);
|
255
|
+
// @ts-ignore -- this is `Window`
|
256
|
+
fn(win, keyupEvent, self.handleDismiss);
|
278
257
|
}
|
279
258
|
|
280
259
|
/**
|
@@ -286,61 +265,82 @@ function firePickerChange(self) {
|
|
286
265
|
}
|
287
266
|
|
288
267
|
/**
|
289
|
-
*
|
268
|
+
* Hides a visible dropdown.
|
290
269
|
* @param {HTMLElement} element
|
291
|
-
* @
|
292
|
-
* @returns {void | boolean}
|
270
|
+
* @returns {void}
|
293
271
|
*/
|
294
|
-
function
|
295
|
-
const fn1 = !check ? 'forEach' : 'some';
|
296
|
-
const fn2 = !check ? removeClass : hasClass;
|
297
|
-
|
272
|
+
function removePosition(element) {
|
298
273
|
if (element) {
|
299
|
-
|
274
|
+
['bottom', 'top'].forEach((x) => removeClass(element, x));
|
300
275
|
}
|
301
|
-
|
302
|
-
return false;
|
303
276
|
}
|
304
277
|
|
305
278
|
/**
|
306
|
-
* Shows
|
279
|
+
* Shows a `ColorPicker` dropdown and close the curent open dropdown.
|
307
280
|
* @param {ColorPicker} self
|
281
|
+
* @param {HTMLElement | Element} dropdown
|
308
282
|
*/
|
309
|
-
function
|
310
|
-
|
311
|
-
|
283
|
+
function showDropdown(self, dropdown) {
|
284
|
+
const {
|
285
|
+
colorPicker, colorMenu, menuToggle, pickerToggle, parent,
|
286
|
+
} = self;
|
287
|
+
const isPicker = dropdown === colorPicker;
|
288
|
+
const openDropdown = isPicker ? colorMenu : colorPicker;
|
289
|
+
const activeBtn = isPicker ? menuToggle : pickerToggle;
|
290
|
+
const nextBtn = !isPicker ? menuToggle : pickerToggle;
|
291
|
+
|
292
|
+
if (!hasClass(parent, 'open')) {
|
293
|
+
addClass(parent, 'open');
|
294
|
+
}
|
295
|
+
if (openDropdown) {
|
296
|
+
removeClass(openDropdown, 'show');
|
297
|
+
removePosition(openDropdown);
|
298
|
+
}
|
299
|
+
addClass(dropdown, 'bottom');
|
300
|
+
reflow(dropdown);
|
301
|
+
addClass(dropdown, 'show');
|
302
|
+
if (isPicker) self.update();
|
312
303
|
self.show();
|
313
|
-
setAttribute(
|
304
|
+
setAttribute(nextBtn, ariaExpanded, 'true');
|
305
|
+
if (activeBtn) {
|
306
|
+
setAttribute(activeBtn, ariaExpanded, 'false');
|
307
|
+
}
|
314
308
|
}
|
315
309
|
|
316
310
|
/**
|
317
|
-
* Color Picker
|
311
|
+
* Color Picker Web Component
|
318
312
|
* @see http://thednp.github.io/color-picker
|
319
313
|
*/
|
320
314
|
export default class ColorPicker {
|
321
315
|
/**
|
322
|
-
* Returns a new ColorPicker instance.
|
316
|
+
* Returns a new `ColorPicker` instance. The target of this constructor
|
317
|
+
* must be an `HTMLInputElement`.
|
318
|
+
*
|
323
319
|
* @param {HTMLInputElement | string} target the target `<input>` element
|
320
|
+
* @param {CP.ColorPickerOptions=} config instance options
|
324
321
|
*/
|
325
|
-
constructor(target) {
|
322
|
+
constructor(target, config) {
|
326
323
|
const self = this;
|
327
324
|
/** @type {HTMLInputElement} */
|
328
325
|
// @ts-ignore
|
329
|
-
|
326
|
+
const input = querySelector(target);
|
327
|
+
|
330
328
|
// invalidate
|
331
|
-
if (!
|
332
|
-
|
329
|
+
if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
|
330
|
+
self.input = input;
|
331
|
+
|
332
|
+
const parent = closest(input, colorPickerParentSelector);
|
333
|
+
if (!parent) throw new TypeError('ColorPicker requires a specific markup to work.');
|
333
334
|
|
334
335
|
/** @type {HTMLElement} */
|
335
336
|
// @ts-ignore
|
336
|
-
self.parent =
|
337
|
-
if (!self.parent) throw new TypeError('ColorPicker requires a specific markup to work.');
|
337
|
+
self.parent = parent;
|
338
338
|
|
339
339
|
/** @type {number} */
|
340
340
|
self.id = getUID(input, colorPickerString);
|
341
341
|
|
342
342
|
// set initial state
|
343
|
-
/** @type {
|
343
|
+
/** @type {HTMLElement?} */
|
344
344
|
self.dragElement = null;
|
345
345
|
/** @type {boolean} */
|
346
346
|
self.isOpen = false;
|
@@ -350,26 +350,59 @@ export default class ColorPicker {
|
|
350
350
|
};
|
351
351
|
/** @type {Record<string, string>} */
|
352
352
|
self.colorLabels = {};
|
353
|
-
/** @type {
|
354
|
-
self.
|
355
|
-
/** @type {
|
356
|
-
self.
|
353
|
+
/** @type {string[]=} */
|
354
|
+
self.colorKeywords = undefined;
|
355
|
+
/** @type {(ColorPalette | string[])=} */
|
356
|
+
self.colorPresets = undefined;
|
357
|
+
|
358
|
+
// process options
|
359
|
+
const {
|
360
|
+
format, componentLabels, colorLabels, colorKeywords, colorPresets,
|
361
|
+
} = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
|
362
|
+
|
363
|
+
let translatedColorLabels = colorNames;
|
364
|
+
if (colorLabels instanceof Array && colorLabels.length === 17) {
|
365
|
+
translatedColorLabels = colorLabels;
|
366
|
+
} else if (colorLabels && colorLabels.split(',').length === 17) {
|
367
|
+
translatedColorLabels = colorLabels.split(',');
|
368
|
+
}
|
369
|
+
|
370
|
+
// expose colour labels to all methods
|
371
|
+
colorNames.forEach((c, i) => {
|
372
|
+
self.colorLabels[c] = translatedColorLabels[i].trim();
|
373
|
+
});
|
374
|
+
|
375
|
+
// update and expose component labels
|
376
|
+
const tempLabels = ObjectAssign({}, colorPickerLabels);
|
377
|
+
const jsonLabels = componentLabels && isValidJSON(componentLabels)
|
378
|
+
? JSON.parse(componentLabels) : componentLabels || {};
|
379
|
+
|
357
380
|
/** @type {Record<string, string>} */
|
358
|
-
self.componentLabels = ObjectAssign(
|
381
|
+
self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
|
359
382
|
|
360
|
-
|
361
|
-
|
362
|
-
self.componentLabels = ObjectAssign(self.componentLabels, temp);
|
383
|
+
/** @type {Color} */
|
384
|
+
self.color = new Color('white', format);
|
363
385
|
|
364
|
-
|
365
|
-
|
386
|
+
/** @type {CP.ColorFormats} */
|
387
|
+
self.format = format;
|
366
388
|
|
367
|
-
//
|
368
|
-
|
389
|
+
// set colour defaults
|
390
|
+
if (colorKeywords instanceof Array) {
|
391
|
+
self.colorKeywords = colorKeywords;
|
392
|
+
} else if (typeof colorKeywords === 'string' && colorKeywords.length) {
|
393
|
+
self.colorKeywords = colorKeywords.split(',');
|
394
|
+
}
|
369
395
|
|
370
396
|
// set colour presets
|
371
|
-
if (
|
372
|
-
self.
|
397
|
+
if (colorPresets instanceof Array) {
|
398
|
+
self.colorPresets = colorPresets;
|
399
|
+
} else if (typeof colorPresets === 'string' && colorPresets.length) {
|
400
|
+
if (isValidJSON(colorPresets)) {
|
401
|
+
const { hue, hueSteps, lightSteps } = JSON.parse(colorPresets);
|
402
|
+
self.colorPresets = new ColorPalette(hue, hueSteps, lightSteps);
|
403
|
+
} else {
|
404
|
+
self.colorPresets = colorPresets.split(',').map((x) => x.trim());
|
405
|
+
}
|
373
406
|
}
|
374
407
|
|
375
408
|
// bind events
|
@@ -381,17 +414,18 @@ export default class ColorPicker {
|
|
381
414
|
self.pointerDown = self.pointerDown.bind(self);
|
382
415
|
self.pointerMove = self.pointerMove.bind(self);
|
383
416
|
self.pointerUp = self.pointerUp.bind(self);
|
417
|
+
self.update = self.update.bind(self);
|
384
418
|
self.handleScroll = self.handleScroll.bind(self);
|
385
419
|
self.handleFocusOut = self.handleFocusOut.bind(self);
|
386
420
|
self.changeHandler = self.changeHandler.bind(self);
|
387
421
|
self.handleDismiss = self.handleDismiss.bind(self);
|
388
|
-
self.
|
422
|
+
self.keyToggle = self.keyToggle.bind(self);
|
389
423
|
self.handleKnobs = self.handleKnobs.bind(self);
|
390
424
|
|
391
425
|
// generate markup
|
392
426
|
initCallback(self);
|
393
427
|
|
394
|
-
const
|
428
|
+
const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
|
395
429
|
// set main elements
|
396
430
|
/** @type {HTMLElement} */
|
397
431
|
// @ts-ignore
|
@@ -401,68 +435,24 @@ export default class ColorPicker {
|
|
401
435
|
self.menuToggle = querySelector('.menu-toggle', parent);
|
402
436
|
/** @type {HTMLElement} */
|
403
437
|
// @ts-ignore
|
404
|
-
self.
|
438
|
+
self.colorPicker = colorPicker;
|
405
439
|
/** @type {HTMLElement} */
|
406
440
|
// @ts-ignore
|
407
|
-
self.
|
408
|
-
/** @type {HTMLElement} */
|
409
|
-
// @ts-ignore
|
410
|
-
self.controls = querySelector('.color-controls', parent);
|
441
|
+
self.colorMenu = colorMenu;
|
411
442
|
/** @type {HTMLInputElement[]} */
|
412
443
|
// @ts-ignore
|
413
|
-
self.inputs = [...
|
444
|
+
self.inputs = [...getElementsByClassName('color-input', parent)];
|
445
|
+
const [controls] = getElementsByClassName('color-controls', parent);
|
446
|
+
self.controls = controls;
|
447
|
+
/** @type {(HTMLElement | Element)[]} */
|
448
|
+
self.controlKnobs = [...getElementsByClassName('knob', controls)];
|
414
449
|
/** @type {(HTMLElement)[]} */
|
415
450
|
// @ts-ignore
|
416
|
-
self.
|
417
|
-
/** @type {HTMLCanvasElement[]} */
|
418
|
-
// @ts-ignore
|
419
|
-
self.visuals = [...querySelectorAll('canvas', self.controls)];
|
420
|
-
/** @type {HTMLLabelElement[]} */
|
421
|
-
// @ts-ignore
|
422
|
-
self.knobLabels = [...querySelectorAll('.color-label', parent)];
|
423
|
-
/** @type {HTMLLabelElement} */
|
424
|
-
// @ts-ignore
|
425
|
-
self.appearance = querySelector('.color-appearance', parent);
|
451
|
+
self.visuals = [...getElementsByClassName('visual-control', controls)];
|
426
452
|
|
427
|
-
|
428
|
-
|
429
|
-
/** @type {number} */
|
430
|
-
self.width1 = v1.width;
|
431
|
-
/** @type {number} */
|
432
|
-
self.height1 = v1.height;
|
433
|
-
/** @type {number} */
|
434
|
-
self.width2 = v2.width;
|
435
|
-
/** @type {number} */
|
436
|
-
self.height2 = v2.height;
|
437
|
-
// set main controls
|
438
|
-
/** @type {*} */
|
439
|
-
self.ctx1 = v1.getContext('2d');
|
440
|
-
/** @type {*} */
|
441
|
-
self.ctx2 = v2.getContext('2d');
|
442
|
-
self.ctx1.rect(0, 0, self.width1, self.height1);
|
443
|
-
self.ctx2.rect(0, 0, self.width2, self.height2);
|
453
|
+
// update colour picker controls, inputs and visuals
|
454
|
+
self.update();
|
444
455
|
|
445
|
-
/** @type {number} */
|
446
|
-
self.width3 = 0;
|
447
|
-
/** @type {number} */
|
448
|
-
self.height3 = 0;
|
449
|
-
|
450
|
-
// set alpha control except hex
|
451
|
-
if (self.format !== 'hex') {
|
452
|
-
self.width3 = v3.width;
|
453
|
-
self.height3 = v3.height;
|
454
|
-
/** @type {*} */
|
455
|
-
this.ctx3 = v3.getContext('2d');
|
456
|
-
self.ctx3.rect(0, 0, self.width3, self.height3);
|
457
|
-
}
|
458
|
-
|
459
|
-
// update color picker controls, inputs and visuals
|
460
|
-
this.setControlPositions();
|
461
|
-
this.setColorAppearence();
|
462
|
-
// don't trigger change at initialization
|
463
|
-
this.updateInputs(true);
|
464
|
-
this.updateControls();
|
465
|
-
this.updateVisuals();
|
466
456
|
// add main events listeners
|
467
457
|
toggleEvents(self, true);
|
468
458
|
|
@@ -470,65 +460,52 @@ export default class ColorPicker {
|
|
470
460
|
Data.set(input, colorPickerString, self);
|
471
461
|
}
|
472
462
|
|
473
|
-
/** Returns the current
|
463
|
+
/** Returns the current colour value */
|
474
464
|
get value() { return this.input.value; }
|
475
465
|
|
476
466
|
/**
|
477
|
-
* Sets a new
|
478
|
-
* @param {string} v new
|
467
|
+
* Sets a new colour value.
|
468
|
+
* @param {string} v new colour value
|
479
469
|
*/
|
480
470
|
set value(v) { this.input.value = v; }
|
481
471
|
|
482
|
-
/** Check if the
|
483
|
-
get required() { return hasAttribute(this.input, 'required'); }
|
484
|
-
|
485
|
-
/**
|
486
|
-
* Returns the colour format.
|
487
|
-
* @returns {CP.ColorFormats | string}
|
488
|
-
*/
|
489
|
-
get format() { return getAttribute(this.input, 'format') || 'hex'; }
|
490
|
-
|
491
|
-
/** Returns the input name. */
|
492
|
-
get name() { return getAttribute(this.input, 'name'); }
|
493
|
-
|
494
|
-
/**
|
495
|
-
* Returns the label associated to the input.
|
496
|
-
* @returns {HTMLLabelElement?}
|
497
|
-
*/
|
498
|
-
// @ts-ignore
|
499
|
-
get label() { return querySelector(`[for="${this.input.id}"]`); }
|
500
|
-
|
501
|
-
/** Check if the color presets include any non-color. */
|
472
|
+
/** Check if the colour presets include any non-colour. */
|
502
473
|
get includeNonColor() {
|
503
|
-
return this.
|
504
|
-
&& this.
|
474
|
+
return this.colorKeywords instanceof Array
|
475
|
+
&& this.colorKeywords.some((x) => nonColors.includes(x));
|
505
476
|
}
|
506
477
|
|
507
|
-
/**
|
508
|
-
get
|
478
|
+
/** Check if the parent of the target is a `ColorPickerElement` instance. */
|
479
|
+
get isCE() { return this.parent.localName === colorPickerString; }
|
509
480
|
|
510
|
-
/** Returns
|
481
|
+
/** Returns hexadecimal value of the current colour. */
|
482
|
+
get hex() { return this.color.toHex(true); }
|
483
|
+
|
484
|
+
/** Returns the current colour value in {h,s,v,a} object format. */
|
511
485
|
get hsv() { return this.color.toHsv(); }
|
512
486
|
|
513
|
-
/** Returns the current
|
487
|
+
/** Returns the current colour value in {h,s,l,a} object format. */
|
514
488
|
get hsl() { return this.color.toHsl(); }
|
515
489
|
|
516
|
-
/** Returns the current
|
490
|
+
/** Returns the current colour value in {h,w,b,a} object format. */
|
491
|
+
get hwb() { return this.color.toHwb(); }
|
492
|
+
|
493
|
+
/** Returns the current colour value in {r,g,b,a} object format. */
|
517
494
|
get rgb() { return this.color.toRgb(); }
|
518
495
|
|
519
|
-
/** Returns the current
|
496
|
+
/** Returns the current colour brightness. */
|
520
497
|
get brightness() { return this.color.brightness; }
|
521
498
|
|
522
|
-
/** Returns the current
|
499
|
+
/** Returns the current colour luminance. */
|
523
500
|
get luminance() { return this.color.luminance; }
|
524
501
|
|
525
|
-
/** Checks if the current colour requires a light text
|
502
|
+
/** Checks if the current colour requires a light text colour. */
|
526
503
|
get isDark() {
|
527
|
-
const {
|
528
|
-
return brightness < 120 &&
|
504
|
+
const { color, brightness } = this;
|
505
|
+
return brightness < 120 && color.a > 0.33;
|
529
506
|
}
|
530
507
|
|
531
|
-
/** Checks if the current input value is a valid
|
508
|
+
/** Checks if the current input value is a valid colour. */
|
532
509
|
get isValid() {
|
533
510
|
const inputValue = this.input.value;
|
534
511
|
return inputValue !== '' && new Color(inputValue).isValid;
|
@@ -538,89 +515,79 @@ export default class ColorPicker {
|
|
538
515
|
updateVisuals() {
|
539
516
|
const self = this;
|
540
517
|
const {
|
541
|
-
|
542
|
-
width1, width2, width3,
|
543
|
-
height1, height2, height3,
|
544
|
-
ctx1, ctx2, ctx3,
|
518
|
+
format, controlPositions, visuals,
|
545
519
|
} = self;
|
546
|
-
const
|
520
|
+
const [v1, v2, v3] = visuals;
|
521
|
+
const { offsetWidth, offsetHeight } = v1;
|
522
|
+
const hue = format === 'hsl'
|
523
|
+
? controlPositions.c1x / offsetWidth
|
524
|
+
: controlPositions.c2y / offsetHeight;
|
525
|
+
// @ts-ignore - `hslToRgb` is assigned to `Color` as static method
|
526
|
+
const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
|
527
|
+
const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
|
528
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
529
|
+
const roundA = Math.round((alpha * 100)) / 100;
|
547
530
|
|
548
531
|
if (format !== 'hsl') {
|
549
|
-
const
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
ctx1.fillRect(0, 0, width1, height1);
|
564
|
-
|
565
|
-
const hueGrad = ctx2.createLinearGradient(0, 0, 0, height1);
|
566
|
-
hueGrad.addColorStop(0, 'rgba(255,0,0,1)');
|
567
|
-
hueGrad.addColorStop(0.17, 'rgba(255,255,0,1)');
|
568
|
-
hueGrad.addColorStop(0.34, 'rgba(0,255,0,1)');
|
569
|
-
hueGrad.addColorStop(0.51, 'rgba(0,255,255,1)');
|
570
|
-
hueGrad.addColorStop(0.68, 'rgba(0,0,255,1)');
|
571
|
-
hueGrad.addColorStop(0.85, 'rgba(255,0,255,1)');
|
572
|
-
hueGrad.addColorStop(1, 'rgba(255,0,0,1)');
|
573
|
-
ctx2.fillStyle = hueGrad;
|
574
|
-
ctx2.fillRect(0, 0, width2, height2);
|
532
|
+
const fill = new Color({
|
533
|
+
h: hue, s: 1, l: 0.5, a: alpha,
|
534
|
+
}).toRgbString();
|
535
|
+
const hueGradient = `linear-gradient(
|
536
|
+
rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
|
537
|
+
rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
|
538
|
+
rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
|
539
|
+
rgb(255,0,0) 100%)`;
|
540
|
+
setElementStyle(v1, {
|
541
|
+
background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
|
542
|
+
linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
|
543
|
+
${whiteGrad}`,
|
544
|
+
});
|
545
|
+
setElementStyle(v2, { background: hueGradient });
|
575
546
|
} else {
|
576
|
-
const
|
577
|
-
const
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
if (format !== 'hex') {
|
613
|
-
ctx3.clearRect(0, 0, width3, height3);
|
614
|
-
const alphaGrad = ctx3.createLinearGradient(0, 0, 0, height3);
|
615
|
-
alphaGrad.addColorStop(0, `rgba(${r},${g},${b},1)`);
|
616
|
-
alphaGrad.addColorStop(1, `rgba(${r},${g},${b},0)`);
|
617
|
-
ctx3.fillStyle = alphaGrad;
|
618
|
-
ctx3.fillRect(0, 0, width3, height3);
|
547
|
+
const saturation = Math.round((controlPositions.c2y / offsetHeight) * 100);
|
548
|
+
const fill0 = new Color({
|
549
|
+
r: 255, g: 0, b: 0, a: alpha,
|
550
|
+
}).saturate(-saturation).toRgbString();
|
551
|
+
const fill1 = new Color({
|
552
|
+
r: 255, g: 255, b: 0, a: alpha,
|
553
|
+
}).saturate(-saturation).toRgbString();
|
554
|
+
const fill2 = new Color({
|
555
|
+
r: 0, g: 255, b: 0, a: alpha,
|
556
|
+
}).saturate(-saturation).toRgbString();
|
557
|
+
const fill3 = new Color({
|
558
|
+
r: 0, g: 255, b: 255, a: alpha,
|
559
|
+
}).saturate(-saturation).toRgbString();
|
560
|
+
const fill4 = new Color({
|
561
|
+
r: 0, g: 0, b: 255, a: alpha,
|
562
|
+
}).saturate(-saturation).toRgbString();
|
563
|
+
const fill5 = new Color({
|
564
|
+
r: 255, g: 0, b: 255, a: alpha,
|
565
|
+
}).saturate(-saturation).toRgbString();
|
566
|
+
const fill6 = new Color({
|
567
|
+
r: 255, g: 0, b: 0, a: alpha,
|
568
|
+
}).saturate(-saturation).toRgbString();
|
569
|
+
const fillGradient = `linear-gradient(to right,
|
570
|
+
${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
|
571
|
+
${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
|
572
|
+
const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
|
573
|
+
linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
|
574
|
+
|
575
|
+
setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
|
576
|
+
const {
|
577
|
+
r: gr, g: gg, b: gb,
|
578
|
+
} = new Color({ r, g, b }).greyscale().toRgb();
|
579
|
+
|
580
|
+
setElementStyle(v2, {
|
581
|
+
background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
|
582
|
+
});
|
619
583
|
}
|
584
|
+
setElementStyle(v3, {
|
585
|
+
background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
|
586
|
+
});
|
620
587
|
}
|
621
588
|
|
622
589
|
/**
|
623
|
-
*
|
590
|
+
* The `ColorPicker` *focusout* event listener when open.
|
624
591
|
* @param {FocusEvent} e
|
625
592
|
* @this {ColorPicker}
|
626
593
|
*/
|
@@ -632,7 +599,7 @@ export default class ColorPicker {
|
|
632
599
|
}
|
633
600
|
|
634
601
|
/**
|
635
|
-
*
|
602
|
+
* The `ColorPicker` *keyup* event listener when open.
|
636
603
|
* @param {KeyboardEvent} e
|
637
604
|
* @this {ColorPicker}
|
638
605
|
*/
|
@@ -644,14 +611,13 @@ export default class ColorPicker {
|
|
644
611
|
}
|
645
612
|
|
646
613
|
/**
|
647
|
-
*
|
614
|
+
* The `ColorPicker` *scroll* event listener when open.
|
648
615
|
* @param {Event} e
|
649
616
|
* @this {ColorPicker}
|
650
617
|
*/
|
651
618
|
handleScroll(e) {
|
652
619
|
const self = this;
|
653
|
-
|
654
|
-
const { activeElement } = document;
|
620
|
+
const { activeElement } = getDocument(self.input);
|
655
621
|
|
656
622
|
if ((isMobile && self.dragElement)
|
657
623
|
|| (activeElement && self.controlKnobs.includes(activeElement))) {
|
@@ -663,22 +629,51 @@ export default class ColorPicker {
|
|
663
629
|
}
|
664
630
|
|
665
631
|
/**
|
666
|
-
*
|
632
|
+
* The `ColorPicker` keyboard event listener for menu navigation.
|
667
633
|
* @param {KeyboardEvent} e
|
668
634
|
* @this {ColorPicker}
|
669
635
|
*/
|
670
636
|
menuKeyHandler(e) {
|
671
637
|
const { target, code } = e;
|
672
|
-
|
673
|
-
|
638
|
+
// @ts-ignore
|
639
|
+
const { previousElementSibling, nextElementSibling, parentElement } = target;
|
640
|
+
const isColorOptionsMenu = parentElement && hasClass(parentElement, 'color-options');
|
641
|
+
const allSiblings = [...parentElement.children];
|
642
|
+
const columnsCount = isColorOptionsMenu
|
643
|
+
&& getElementStyle(parentElement, 'grid-template-columns').split(' ').length;
|
644
|
+
const currentIndex = allSiblings.indexOf(target);
|
645
|
+
const previousElement = currentIndex > -1
|
646
|
+
&& columnsCount && allSiblings[currentIndex - columnsCount];
|
647
|
+
const nextElement = currentIndex > -1
|
648
|
+
&& columnsCount && allSiblings[currentIndex + columnsCount];
|
649
|
+
|
650
|
+
if ([keyArrowDown, keyArrowUp, keySpace].includes(code)) {
|
651
|
+
// prevent scroll when navigating the menu via arrow keys / Space
|
674
652
|
e.preventDefault();
|
675
|
-
}
|
653
|
+
}
|
654
|
+
if (isColorOptionsMenu) {
|
655
|
+
if (previousElement && code === keyArrowUp) {
|
656
|
+
focus(previousElement);
|
657
|
+
} else if (nextElement && code === keyArrowDown) {
|
658
|
+
focus(nextElement);
|
659
|
+
} else if (previousElementSibling && code === keyArrowLeft) {
|
660
|
+
focus(previousElementSibling);
|
661
|
+
} else if (nextElementSibling && code === keyArrowRight) {
|
662
|
+
focus(nextElementSibling);
|
663
|
+
}
|
664
|
+
} else if (previousElementSibling && [keyArrowLeft, keyArrowUp].includes(code)) {
|
665
|
+
focus(previousElementSibling);
|
666
|
+
} else if (nextElementSibling && [keyArrowRight, keyArrowDown].includes(code)) {
|
667
|
+
focus(nextElementSibling);
|
668
|
+
}
|
669
|
+
|
670
|
+
if ([keyEnter, keySpace].includes(code)) {
|
676
671
|
this.menuClickHandler({ target });
|
677
672
|
}
|
678
673
|
}
|
679
674
|
|
680
675
|
/**
|
681
|
-
*
|
676
|
+
* The `ColorPicker` click event listener for the colour menu presets / defaults.
|
682
677
|
* @param {Partial<Event>} e
|
683
678
|
* @this {ColorPicker}
|
684
679
|
*/
|
@@ -686,16 +681,23 @@ export default class ColorPicker {
|
|
686
681
|
const self = this;
|
687
682
|
/** @type {*} */
|
688
683
|
const { target } = e;
|
689
|
-
const {
|
684
|
+
const { colorMenu } = self;
|
690
685
|
const newOption = (getAttribute(target, 'data-value') || '').trim();
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
686
|
+
// invalidate for targets other than color options
|
687
|
+
if (!newOption.length) return;
|
688
|
+
const currentActive = querySelector('li.active', colorMenu);
|
689
|
+
let newColor = nonColors.includes(newOption) ? 'white' : newOption;
|
690
|
+
newColor = newOption === 'transparent' ? 'rgba(0,0,0,0)' : newOption;
|
691
|
+
|
692
|
+
const {
|
693
|
+
r, g, b, a,
|
694
|
+
} = new Color(newColor);
|
695
|
+
|
696
|
+
ObjectAssign(self.color, {
|
697
|
+
r, g, b, a,
|
698
|
+
});
|
699
|
+
|
700
|
+
self.update();
|
699
701
|
|
700
702
|
if (currentActive) {
|
701
703
|
removeClass(currentActive, 'active');
|
@@ -708,29 +710,28 @@ export default class ColorPicker {
|
|
708
710
|
|
709
711
|
if (nonColors.includes(newOption)) {
|
710
712
|
self.value = newOption;
|
711
|
-
firePickerChange(self);
|
712
713
|
}
|
714
|
+
firePickerChange(self);
|
713
715
|
}
|
714
716
|
}
|
715
717
|
|
716
718
|
/**
|
717
|
-
*
|
719
|
+
* The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
|
718
720
|
* @param {TouchEvent} e
|
719
721
|
* @this {ColorPicker}
|
720
722
|
*/
|
721
723
|
pointerDown(e) {
|
722
724
|
const self = this;
|
725
|
+
/** @type {*} */
|
723
726
|
const {
|
724
|
-
// @ts-ignore
|
725
727
|
type, target, touches, pageX, pageY,
|
726
728
|
} = e;
|
727
|
-
const { visuals, controlKnobs
|
729
|
+
const { colorMenu, visuals, controlKnobs } = self;
|
728
730
|
const [v1, v2, v3] = visuals;
|
729
731
|
const [c1, c2, c3] = controlKnobs;
|
730
|
-
/** @type {
|
731
|
-
|
732
|
-
|
733
|
-
? target : querySelector('canvas', target.parentElement);
|
732
|
+
/** @type {HTMLElement} */
|
733
|
+
const visual = hasClass(target, 'visual-control')
|
734
|
+
? target : querySelector('.visual-control', target.parentElement);
|
734
735
|
const visualRect = getBoundingClientRect(visual);
|
735
736
|
const X = type === 'touchstart' ? touches[0].pageX : pageX;
|
736
737
|
const Y = type === 'touchstart' ? touches[0].pageY : pageY;
|
@@ -739,42 +740,53 @@ export default class ColorPicker {
|
|
739
740
|
|
740
741
|
if (target === v1 || target === c1) {
|
741
742
|
self.dragElement = visual;
|
742
|
-
self.changeControl1(
|
743
|
+
self.changeControl1(offsetX, offsetY);
|
743
744
|
} else if (target === v2 || target === c2) {
|
744
745
|
self.dragElement = visual;
|
745
|
-
self.changeControl2(
|
746
|
-
} else if (
|
746
|
+
self.changeControl2(offsetY);
|
747
|
+
} else if (target === v3 || target === c3) {
|
747
748
|
self.dragElement = visual;
|
748
|
-
self.changeAlpha(
|
749
|
+
self.changeAlpha(offsetY);
|
750
|
+
}
|
751
|
+
|
752
|
+
if (colorMenu) {
|
753
|
+
const currentActive = querySelector('li.active', colorMenu);
|
754
|
+
if (currentActive) {
|
755
|
+
removeClass(currentActive, 'active');
|
756
|
+
removeAttribute(currentActive, ariaSelected);
|
757
|
+
}
|
749
758
|
}
|
750
759
|
e.preventDefault();
|
751
760
|
}
|
752
761
|
|
753
762
|
/**
|
754
|
-
*
|
763
|
+
* The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
|
755
764
|
* @param {TouchEvent} e
|
756
765
|
* @this {ColorPicker}
|
757
766
|
*/
|
758
767
|
pointerUp({ target }) {
|
759
768
|
const self = this;
|
760
|
-
const
|
769
|
+
const { parent } = self;
|
770
|
+
const doc = getDocument(parent);
|
771
|
+
const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
|
772
|
+
const selection = doc.getSelection();
|
761
773
|
// @ts-ignore
|
762
774
|
if (!self.dragElement && !selection.toString().length
|
763
775
|
// @ts-ignore
|
764
|
-
&& !
|
765
|
-
self.hide();
|
776
|
+
&& !parent.contains(target)) {
|
777
|
+
self.hide(currentOpen);
|
766
778
|
}
|
767
779
|
|
768
780
|
self.dragElement = null;
|
769
781
|
}
|
770
782
|
|
771
783
|
/**
|
772
|
-
*
|
784
|
+
* The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
|
773
785
|
* @param {TouchEvent} e
|
774
786
|
*/
|
775
787
|
pointerMove(e) {
|
776
788
|
const self = this;
|
777
|
-
const { dragElement, visuals
|
789
|
+
const { dragElement, visuals } = self;
|
778
790
|
const [v1, v2, v3] = visuals;
|
779
791
|
const {
|
780
792
|
// @ts-ignore
|
@@ -790,20 +802,20 @@ export default class ColorPicker {
|
|
790
802
|
const offsetY = Y - window.pageYOffset - controlRect.top;
|
791
803
|
|
792
804
|
if (dragElement === v1) {
|
793
|
-
self.changeControl1(
|
805
|
+
self.changeControl1(offsetX, offsetY);
|
794
806
|
}
|
795
807
|
|
796
808
|
if (dragElement === v2) {
|
797
|
-
self.changeControl2(
|
809
|
+
self.changeControl2(offsetY);
|
798
810
|
}
|
799
811
|
|
800
|
-
if (dragElement === v3
|
801
|
-
self.changeAlpha(
|
812
|
+
if (dragElement === v3) {
|
813
|
+
self.changeAlpha(offsetY);
|
802
814
|
}
|
803
815
|
}
|
804
816
|
|
805
817
|
/**
|
806
|
-
*
|
818
|
+
* The `ColorPicker` *keydown* event listener for control knobs.
|
807
819
|
* @param {KeyboardEvent} e
|
808
820
|
*/
|
809
821
|
handleKnobs(e) {
|
@@ -814,10 +826,10 @@ export default class ColorPicker {
|
|
814
826
|
if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
|
815
827
|
e.preventDefault();
|
816
828
|
|
817
|
-
const { activeElement } = document;
|
818
829
|
const { controlKnobs } = self;
|
819
|
-
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
820
830
|
const [c1, c2, c3] = controlKnobs;
|
831
|
+
const { activeElement } = getDocument(c1);
|
832
|
+
const currentKnob = controlKnobs.find((x) => x === activeElement);
|
821
833
|
|
822
834
|
if (currentKnob) {
|
823
835
|
let offsetX = 0;
|
@@ -831,37 +843,36 @@ export default class ColorPicker {
|
|
831
843
|
|
832
844
|
offsetX = self.controlPositions.c1x;
|
833
845
|
offsetY = self.controlPositions.c1y;
|
834
|
-
self.changeControl1(
|
846
|
+
self.changeControl1(offsetX, offsetY);
|
835
847
|
} else if (target === c2) {
|
836
848
|
self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
|
837
849
|
offsetY = self.controlPositions.c2y;
|
838
|
-
self.changeControl2(
|
850
|
+
self.changeControl2(offsetY);
|
839
851
|
} else if (target === c3) {
|
840
852
|
self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code) ? +1 : -1;
|
841
853
|
offsetY = self.controlPositions.c3y;
|
842
|
-
self.changeAlpha(
|
854
|
+
self.changeAlpha(offsetY);
|
843
855
|
}
|
844
|
-
|
845
|
-
self.setColorAppearence();
|
846
|
-
self.updateInputs();
|
847
|
-
self.updateControls();
|
848
|
-
self.updateVisuals();
|
849
856
|
self.handleScroll(e);
|
850
857
|
}
|
851
858
|
}
|
852
859
|
|
853
|
-
/**
|
860
|
+
/** The event listener of the colour form inputs. */
|
854
861
|
changeHandler() {
|
855
862
|
const self = this;
|
856
863
|
let colorSource;
|
857
|
-
/** @type {HTMLInputElement} */
|
858
|
-
// @ts-ignore
|
859
|
-
const { activeElement } = document;
|
860
864
|
const {
|
861
|
-
inputs, format, value: currentValue, input,
|
865
|
+
inputs, format, value: currentValue, input, controlPositions, visuals,
|
862
866
|
} = self;
|
863
|
-
|
867
|
+
/** @type {*} */
|
868
|
+
const { activeElement } = getDocument(input);
|
869
|
+
const { offsetHeight } = visuals[0];
|
870
|
+
const [i1,,, i4] = inputs;
|
871
|
+
const [v1, v2, v3, v4] = format === 'rgb'
|
872
|
+
? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
|
873
|
+
: inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
|
864
874
|
const isNonColorValue = self.includeNonColor && nonColors.includes(currentValue);
|
875
|
+
const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
|
865
876
|
|
866
877
|
if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
|
867
878
|
if (activeElement === input) {
|
@@ -873,14 +884,28 @@ export default class ColorPicker {
|
|
873
884
|
} else if (format === 'hex') {
|
874
885
|
colorSource = i1.value;
|
875
886
|
} else if (format === 'hsl') {
|
876
|
-
colorSource =
|
887
|
+
colorSource = {
|
888
|
+
h: v1, s: v2, l: v3, a: alpha,
|
889
|
+
};
|
890
|
+
} else if (format === 'hwb') {
|
891
|
+
colorSource = {
|
892
|
+
h: v1, w: v2, b: v3, a: alpha,
|
893
|
+
};
|
877
894
|
} else {
|
878
|
-
colorSource =
|
895
|
+
colorSource = {
|
896
|
+
r: v1, g: v2, b: v3, a: alpha,
|
897
|
+
};
|
879
898
|
}
|
880
899
|
|
881
|
-
|
900
|
+
const {
|
901
|
+
r, g, b, a,
|
902
|
+
} = new Color(colorSource);
|
903
|
+
|
904
|
+
ObjectAssign(self.color, {
|
905
|
+
r, g, b, a,
|
906
|
+
});
|
882
907
|
self.setControlPositions();
|
883
|
-
self.
|
908
|
+
self.updateAppearance();
|
884
909
|
self.updateInputs();
|
885
910
|
self.updateControls();
|
886
911
|
self.updateVisuals();
|
@@ -897,49 +922,57 @@ export default class ColorPicker {
|
|
897
922
|
* * `lightness` and `saturation` for HEX/RGB;
|
898
923
|
* * `lightness` and `hue` for HSL.
|
899
924
|
*
|
900
|
-
* @param {
|
925
|
+
* @param {number} X the X component of the offset
|
926
|
+
* @param {number} Y the Y component of the offset
|
901
927
|
*/
|
902
|
-
changeControl1(
|
928
|
+
changeControl1(X, Y) {
|
903
929
|
const self = this;
|
904
930
|
let [offsetX, offsetY] = [0, 0];
|
905
|
-
const { offsetX: X, offsetY: Y } = offsets;
|
906
931
|
const {
|
907
|
-
format, controlPositions,
|
908
|
-
height1, height2, height3, width1,
|
932
|
+
format, controlPositions, visuals,
|
909
933
|
} = self;
|
934
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
910
935
|
|
911
|
-
if (X >
|
912
|
-
|
913
|
-
} else if (X >= 0) {
|
914
|
-
offsetX = X;
|
915
|
-
}
|
936
|
+
if (X > offsetWidth) offsetX = offsetWidth;
|
937
|
+
else if (X >= 0) offsetX = X;
|
916
938
|
|
917
|
-
if (Y >
|
918
|
-
|
919
|
-
} else if (Y >= 0) {
|
920
|
-
offsetY = Y;
|
921
|
-
}
|
939
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
940
|
+
else if (Y >= 0) offsetY = Y;
|
922
941
|
|
923
|
-
const hue = format
|
924
|
-
?
|
925
|
-
:
|
942
|
+
const hue = format === 'hsl'
|
943
|
+
? offsetX / offsetWidth
|
944
|
+
: controlPositions.c2y / offsetHeight;
|
926
945
|
|
927
|
-
const saturation = format
|
928
|
-
?
|
929
|
-
:
|
946
|
+
const saturation = format === 'hsl'
|
947
|
+
? 1 - controlPositions.c2y / offsetHeight
|
948
|
+
: offsetX / offsetWidth;
|
930
949
|
|
931
|
-
const lightness =
|
932
|
-
const alpha =
|
933
|
-
|
950
|
+
const lightness = 1 - offsetY / offsetHeight;
|
951
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
952
|
+
|
953
|
+
const colorObject = format === 'hsl'
|
954
|
+
? {
|
955
|
+
h: hue, s: saturation, l: lightness, a: alpha,
|
956
|
+
}
|
957
|
+
: {
|
958
|
+
h: hue, s: saturation, v: lightness, a: alpha,
|
959
|
+
};
|
934
960
|
|
935
961
|
// new color
|
936
|
-
|
962
|
+
const {
|
963
|
+
r, g, b, a,
|
964
|
+
} = new Color(colorObject);
|
965
|
+
|
966
|
+
ObjectAssign(self.color, {
|
967
|
+
r, g, b, a,
|
968
|
+
});
|
969
|
+
|
937
970
|
// new positions
|
938
971
|
self.controlPositions.c1x = offsetX;
|
939
972
|
self.controlPositions.c1y = offsetY;
|
940
973
|
|
941
974
|
// update color picker
|
942
|
-
self.
|
975
|
+
self.updateAppearance();
|
943
976
|
self.updateInputs();
|
944
977
|
self.updateControls();
|
945
978
|
self.updateVisuals();
|
@@ -947,37 +980,52 @@ export default class ColorPicker {
|
|
947
980
|
|
948
981
|
/**
|
949
982
|
* Updates `ColorPicker` second control:
|
950
|
-
* * `hue` for HEX/RGB;
|
983
|
+
* * `hue` for HEX/RGB/HWB;
|
951
984
|
* * `saturation` for HSL.
|
952
985
|
*
|
953
|
-
* @param {
|
986
|
+
* @param {number} Y the Y offset
|
954
987
|
*/
|
955
|
-
changeControl2(
|
988
|
+
changeControl2(Y) {
|
956
989
|
const self = this;
|
957
|
-
const { offsetY: Y } = offset;
|
958
990
|
const {
|
959
|
-
format,
|
991
|
+
format, controlPositions, visuals,
|
960
992
|
} = self;
|
961
|
-
|
993
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
962
994
|
|
963
|
-
|
964
|
-
offsetY = height2;
|
965
|
-
} else if (Y >= 0) {
|
966
|
-
offsetY = Y;
|
967
|
-
}
|
995
|
+
let offsetY = 0;
|
968
996
|
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
const
|
973
|
-
|
997
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
998
|
+
else if (Y >= 0) offsetY = Y;
|
999
|
+
|
1000
|
+
const hue = format === 'hsl'
|
1001
|
+
? controlPositions.c1x / offsetWidth
|
1002
|
+
: offsetY / offsetHeight;
|
1003
|
+
const saturation = format === 'hsl'
|
1004
|
+
? 1 - offsetY / offsetHeight
|
1005
|
+
: controlPositions.c1x / offsetWidth;
|
1006
|
+
const lightness = 1 - controlPositions.c1y / offsetHeight;
|
1007
|
+
const alpha = 1 - controlPositions.c3y / offsetHeight;
|
1008
|
+
const colorObject = format === 'hsl'
|
1009
|
+
? {
|
1010
|
+
h: hue, s: saturation, l: lightness, a: alpha,
|
1011
|
+
}
|
1012
|
+
: {
|
1013
|
+
h: hue, s: saturation, v: lightness, a: alpha,
|
1014
|
+
};
|
974
1015
|
|
975
1016
|
// new color
|
976
|
-
|
1017
|
+
const {
|
1018
|
+
r, g, b, a,
|
1019
|
+
} = new Color(colorObject);
|
1020
|
+
|
1021
|
+
ObjectAssign(self.color, {
|
1022
|
+
r, g, b, a,
|
1023
|
+
});
|
1024
|
+
|
977
1025
|
// new position
|
978
1026
|
self.controlPositions.c2y = offsetY;
|
979
1027
|
// update color picker
|
980
|
-
self.
|
1028
|
+
self.updateAppearance();
|
981
1029
|
self.updateInputs();
|
982
1030
|
self.updateControls();
|
983
1031
|
self.updateVisuals();
|
@@ -985,92 +1033,105 @@ export default class ColorPicker {
|
|
985
1033
|
|
986
1034
|
/**
|
987
1035
|
* Updates `ColorPicker` last control,
|
988
|
-
* the `alpha` channel
|
1036
|
+
* the `alpha` channel.
|
989
1037
|
*
|
990
|
-
* @param {
|
1038
|
+
* @param {number} Y
|
991
1039
|
*/
|
992
|
-
changeAlpha(
|
1040
|
+
changeAlpha(Y) {
|
993
1041
|
const self = this;
|
994
|
-
const {
|
995
|
-
const {
|
1042
|
+
const { visuals } = self;
|
1043
|
+
const { offsetHeight } = visuals[0];
|
996
1044
|
let offsetY = 0;
|
997
1045
|
|
998
|
-
if (Y >
|
999
|
-
|
1000
|
-
} else if (Y >= 0) {
|
1001
|
-
offsetY = Y;
|
1002
|
-
}
|
1046
|
+
if (Y > offsetHeight) offsetY = offsetHeight;
|
1047
|
+
else if (Y >= 0) offsetY = Y;
|
1003
1048
|
|
1004
1049
|
// update color alpha
|
1005
|
-
const alpha =
|
1006
|
-
self.color.setAlpha(alpha
|
1050
|
+
const alpha = 1 - offsetY / offsetHeight;
|
1051
|
+
self.color.setAlpha(alpha);
|
1007
1052
|
// update position
|
1008
1053
|
self.controlPositions.c3y = offsetY;
|
1009
1054
|
// update color picker
|
1055
|
+
self.updateAppearance();
|
1010
1056
|
self.updateInputs();
|
1011
1057
|
self.updateControls();
|
1012
|
-
// alpha?
|
1013
1058
|
self.updateVisuals();
|
1014
1059
|
}
|
1015
1060
|
|
1016
|
-
/**
|
1061
|
+
/**
|
1062
|
+
* Updates `ColorPicker` control positions on:
|
1063
|
+
* * initialization
|
1064
|
+
* * window resize
|
1065
|
+
*/
|
1066
|
+
update() {
|
1067
|
+
const self = this;
|
1068
|
+
self.updateDropdownPosition();
|
1069
|
+
self.updateAppearance();
|
1070
|
+
self.setControlPositions();
|
1071
|
+
self.updateInputs(true);
|
1072
|
+
self.updateControls();
|
1073
|
+
self.updateVisuals();
|
1074
|
+
}
|
1075
|
+
|
1076
|
+
/** Updates the open dropdown position on *scroll* event. */
|
1017
1077
|
updateDropdownPosition() {
|
1018
1078
|
const self = this;
|
1019
1079
|
const { input, colorPicker, colorMenu } = self;
|
1020
1080
|
const elRect = getBoundingClientRect(input);
|
1081
|
+
const { top, bottom } = elRect;
|
1021
1082
|
const { offsetHeight: elHeight } = input;
|
1022
|
-
const windowHeight =
|
1023
|
-
const isPicker =
|
1083
|
+
const windowHeight = getDocumentElement(input).clientHeight;
|
1084
|
+
const isPicker = hasClass(colorPicker, 'show');
|
1024
1085
|
const dropdown = isPicker ? colorPicker : colorMenu;
|
1086
|
+
if (!dropdown) return;
|
1025
1087
|
const { offsetHeight: dropHeight } = dropdown;
|
1026
|
-
const distanceBottom = windowHeight -
|
1027
|
-
const distanceTop =
|
1028
|
-
const bottomExceed =
|
1029
|
-
const topExceed =
|
1030
|
-
|
1031
|
-
if (hasClass(dropdown, '
|
1032
|
-
removeClass(dropdown, '
|
1033
|
-
addClass(dropdown, '
|
1034
|
-
}
|
1035
|
-
|
1036
|
-
|
1037
|
-
addClass(dropdown, 'show');
|
1088
|
+
const distanceBottom = windowHeight - bottom;
|
1089
|
+
const distanceTop = top;
|
1090
|
+
const bottomExceed = top + dropHeight + elHeight > windowHeight; // show
|
1091
|
+
const topExceed = top - dropHeight < 0; // show-top
|
1092
|
+
|
1093
|
+
if ((hasClass(dropdown, 'bottom') || !topExceed) && distanceBottom < distanceTop && bottomExceed) {
|
1094
|
+
removeClass(dropdown, 'bottom');
|
1095
|
+
addClass(dropdown, 'top');
|
1096
|
+
} else {
|
1097
|
+
removeClass(dropdown, 'top');
|
1098
|
+
addClass(dropdown, 'bottom');
|
1038
1099
|
}
|
1039
1100
|
}
|
1040
1101
|
|
1041
|
-
/**
|
1102
|
+
/** Updates control knobs' positions. */
|
1042
1103
|
setControlPositions() {
|
1043
1104
|
const self = this;
|
1044
1105
|
const {
|
1045
|
-
|
1106
|
+
format, visuals, color, hsl, hsv,
|
1046
1107
|
} = self;
|
1108
|
+
const { offsetHeight, offsetWidth } = visuals[0];
|
1109
|
+
const alpha = color.a;
|
1047
1110
|
const hue = hsl.h;
|
1111
|
+
|
1048
1112
|
const saturation = format !== 'hsl' ? hsv.s : hsl.s;
|
1049
1113
|
const lightness = format !== 'hsl' ? hsv.v : hsl.l;
|
1050
|
-
const alpha = hsv.a;
|
1051
1114
|
|
1052
|
-
self.controlPositions.c1x = format !== 'hsl' ? saturation *
|
1053
|
-
self.controlPositions.c1y = (1 - lightness) *
|
1054
|
-
self.controlPositions.c2y = format !== 'hsl' ?
|
1055
|
-
|
1056
|
-
if (format !== 'hex') {
|
1057
|
-
self.controlPositions.c3y = (1 - alpha) * height3;
|
1058
|
-
}
|
1115
|
+
self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
|
1116
|
+
self.controlPositions.c1y = (1 - lightness) * offsetHeight;
|
1117
|
+
self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
|
1118
|
+
self.controlPositions.c3y = (1 - alpha) * offsetHeight;
|
1059
1119
|
}
|
1060
1120
|
|
1061
|
-
/** Update the visual appearance label. */
|
1062
|
-
|
1121
|
+
/** Update the visual appearance label and control knob labels. */
|
1122
|
+
updateAppearance() {
|
1063
1123
|
const self = this;
|
1064
1124
|
const {
|
1065
|
-
componentLabels, colorLabels,
|
1125
|
+
componentLabels, colorLabels, color, parent,
|
1126
|
+
hsl, hsv, hex, format, controlKnobs,
|
1066
1127
|
} = self;
|
1067
1128
|
const {
|
1068
|
-
|
1129
|
+
appearanceLabel, hexLabel, valueLabel,
|
1069
1130
|
} = componentLabels;
|
1070
|
-
|
1071
|
-
const [
|
1072
|
-
const hue = Math.round(hsl.h);
|
1073
|
-
const alpha =
|
1131
|
+
const { r, g, b } = color.toRgb();
|
1132
|
+
const [knob1, knob2, knob3] = controlKnobs;
|
1133
|
+
const hue = Math.round(hsl.h * 360);
|
1134
|
+
const alpha = color.a;
|
1074
1135
|
const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
|
1075
1136
|
const saturation = Math.round(saturationSource * 100);
|
1076
1137
|
const lightness = Math.round(hsl.l * 100);
|
@@ -1109,99 +1170,111 @@ export default class ColorPicker {
|
|
1109
1170
|
colorName = colorLabels.pink;
|
1110
1171
|
}
|
1111
1172
|
|
1173
|
+
let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
|
1174
|
+
|
1112
1175
|
if (format === 'hsl') {
|
1113
|
-
|
1114
|
-
|
1176
|
+
colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
|
1177
|
+
setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
1178
|
+
setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
|
1179
|
+
setAttribute(knob1, ariaValueNow, `${hue}`);
|
1180
|
+
setAttribute(knob2, ariaValueText, `${saturation}%`);
|
1181
|
+
setAttribute(knob2, ariaValueNow, `${saturation}`);
|
1182
|
+
} else if (format === 'hwb') {
|
1183
|
+
const { hwb } = self;
|
1184
|
+
const whiteness = Math.round(hwb.w * 100);
|
1185
|
+
const blackness = Math.round(hwb.b * 100);
|
1186
|
+
colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
|
1187
|
+
setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
1188
|
+
setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
|
1189
|
+
setAttribute(knob1, ariaValueNow, `${whiteness}`);
|
1190
|
+
setAttribute(knob2, ariaValueText, `${hue}%`);
|
1191
|
+
setAttribute(knob2, ariaValueNow, `${hue}`);
|
1115
1192
|
} else {
|
1116
|
-
|
1117
|
-
|
1193
|
+
colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
|
1194
|
+
setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
|
1195
|
+
setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
|
1196
|
+
setAttribute(knob1, ariaValueNow, `${lightness}`);
|
1197
|
+
setAttribute(knob2, ariaValueText, `${hue}°`);
|
1198
|
+
setAttribute(knob2, ariaValueNow, `${hue}`);
|
1118
1199
|
}
|
1119
1200
|
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
}
|
1201
|
+
const alphaValue = Math.round(alpha * 100);
|
1202
|
+
setAttribute(knob3, ariaValueText, `${alphaValue}%`);
|
1203
|
+
setAttribute(knob3, ariaValueNow, `${alphaValue}`);
|
1124
1204
|
|
1125
|
-
// update
|
1126
|
-
|
1127
|
-
|
1128
|
-
? `${hexLabel} ${hex.split('').join(' ')}.`
|
1129
|
-
: self.value.toUpperCase();
|
1205
|
+
// update the input backgroundColor
|
1206
|
+
const newColor = color.toString();
|
1207
|
+
setElementStyle(self.input, { backgroundColor: newColor });
|
1130
1208
|
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1209
|
+
// toggle dark/light classes will also style the placeholder
|
1210
|
+
// dark sets color white, light sets color black
|
1211
|
+
// isDark ? '#000' : '#fff'
|
1212
|
+
if (!self.isDark) {
|
1213
|
+
if (hasClass(parent, 'txt-dark')) removeClass(parent, 'txt-dark');
|
1214
|
+
if (!hasClass(parent, 'txt-light')) addClass(parent, 'txt-light');
|
1215
|
+
} else {
|
1216
|
+
if (hasClass(parent, 'txt-light')) removeClass(parent, 'txt-light');
|
1217
|
+
if (!hasClass(parent, 'txt-dark')) addClass(parent, 'txt-dark');
|
1138
1218
|
}
|
1139
1219
|
}
|
1140
1220
|
|
1141
|
-
/** Updates the control knobs positions. */
|
1221
|
+
/** Updates the control knobs actual positions. */
|
1142
1222
|
updateControls() {
|
1143
|
-
const {
|
1223
|
+
const { controlKnobs, controlPositions } = this;
|
1144
1224
|
const [control1, control2, control3] = controlKnobs;
|
1145
|
-
control1
|
1146
|
-
control2
|
1147
|
-
|
1148
|
-
if (format !== 'hex') {
|
1149
|
-
control3.style.transform = `translate3d(0,${controlPositions.c3y - 3}px,0)`;
|
1150
|
-
}
|
1225
|
+
setElementStyle(control1, { transform: `translate3d(${controlPositions.c1x - 4}px,${controlPositions.c1y - 4}px,0)` });
|
1226
|
+
setElementStyle(control2, { transform: `translate3d(0,${controlPositions.c2y - 4}px,0)` });
|
1227
|
+
setElementStyle(control3, { transform: `translate3d(0,${controlPositions.c3y - 4}px,0)` });
|
1151
1228
|
}
|
1152
1229
|
|
1153
1230
|
/**
|
1154
|
-
*
|
1231
|
+
* Updates all color form inputs.
|
1155
1232
|
* @param {boolean=} isPrevented when `true`, the component original event is prevented
|
1156
1233
|
*/
|
1157
1234
|
updateInputs(isPrevented) {
|
1158
1235
|
const self = this;
|
1159
1236
|
const {
|
1160
|
-
value: oldColor,
|
1237
|
+
value: oldColor, format, inputs, color, hsl,
|
1161
1238
|
} = self;
|
1162
1239
|
const [i1, i2, i3, i4] = inputs;
|
1163
|
-
|
1164
|
-
const
|
1165
|
-
const hue = Math.round(hsl.h);
|
1166
|
-
const saturation = Math.round(hsl.s * 100);
|
1167
|
-
const lightSource = format === 'hsl' ? hsl.l : hsv.v;
|
1168
|
-
const lightness = Math.round(lightSource * 100);
|
1240
|
+
const alpha = Math.round(color.a * 100);
|
1241
|
+
const hue = Math.round(hsl.h * 360);
|
1169
1242
|
let newColor;
|
1170
1243
|
|
1171
1244
|
if (format === 'hex') {
|
1172
|
-
newColor = self.color.toHexString();
|
1245
|
+
newColor = self.color.toHexString(true);
|
1173
1246
|
i1.value = self.hex;
|
1174
1247
|
} else if (format === 'hsl') {
|
1248
|
+
const lightness = Math.round(hsl.l * 100);
|
1249
|
+
const saturation = Math.round(hsl.s * 100);
|
1175
1250
|
newColor = self.color.toHslString();
|
1176
1251
|
i1.value = `${hue}`;
|
1177
1252
|
i2.value = `${saturation}`;
|
1178
1253
|
i3.value = `${lightness}`;
|
1179
1254
|
i4.value = `${alpha}`;
|
1255
|
+
} else if (format === 'hwb') {
|
1256
|
+
const { w, b } = self.hwb;
|
1257
|
+
const whiteness = Math.round(w * 100);
|
1258
|
+
const blackness = Math.round(b * 100);
|
1259
|
+
|
1260
|
+
newColor = self.color.toHwbString();
|
1261
|
+
i1.value = `${hue}`;
|
1262
|
+
i2.value = `${whiteness}`;
|
1263
|
+
i3.value = `${blackness}`;
|
1264
|
+
i4.value = `${alpha}`;
|
1180
1265
|
} else if (format === 'rgb') {
|
1266
|
+
const { r, g, b } = self.rgb;
|
1267
|
+
|
1181
1268
|
newColor = self.color.toRgbString();
|
1182
|
-
i1.value = `${
|
1183
|
-
i2.value = `${
|
1184
|
-
i3.value = `${
|
1269
|
+
i1.value = `${r}`;
|
1270
|
+
i2.value = `${g}`;
|
1271
|
+
i3.value = `${b}`;
|
1185
1272
|
i4.value = `${alpha}`;
|
1186
1273
|
}
|
1187
1274
|
|
1188
1275
|
// update the color value
|
1189
1276
|
self.value = `${newColor}`;
|
1190
1277
|
|
1191
|
-
// update the input backgroundColor
|
1192
|
-
ObjectAssign(input.style, { backgroundColor: newColor });
|
1193
|
-
|
1194
|
-
// toggle dark/light classes will also style the placeholder
|
1195
|
-
// dark sets color white, light sets color black
|
1196
|
-
// isDark ? '#000' : '#fff'
|
1197
|
-
if (!self.isDark) {
|
1198
|
-
if (hasClass(parent, 'dark')) removeClass(parent, 'dark');
|
1199
|
-
if (!hasClass(parent, 'light')) addClass(parent, 'light');
|
1200
|
-
} else {
|
1201
|
-
if (hasClass(parent, 'light')) removeClass(parent, 'light');
|
1202
|
-
if (!hasClass(parent, 'dark')) addClass(parent, 'dark');
|
1203
|
-
}
|
1204
|
-
|
1205
1278
|
// don't trigger the custom event unless it's really changed
|
1206
1279
|
if (!isPrevented && newColor !== oldColor) {
|
1207
1280
|
firePickerChange(self);
|
@@ -1209,14 +1282,15 @@ export default class ColorPicker {
|
|
1209
1282
|
}
|
1210
1283
|
|
1211
1284
|
/**
|
1212
|
-
*
|
1285
|
+
* The `Space` & `Enter` keys specific event listener.
|
1286
|
+
* Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
|
1213
1287
|
* @param {KeyboardEvent} e
|
1214
1288
|
* @this {ColorPicker}
|
1215
1289
|
*/
|
1216
|
-
|
1290
|
+
keyToggle(e) {
|
1217
1291
|
const self = this;
|
1218
1292
|
const { menuToggle } = self;
|
1219
|
-
const { activeElement } =
|
1293
|
+
const { activeElement } = getDocument(menuToggle);
|
1220
1294
|
const { code } = e;
|
1221
1295
|
|
1222
1296
|
if ([keyEnter, keySpace].includes(code)) {
|
@@ -1239,80 +1313,94 @@ export default class ColorPicker {
|
|
1239
1313
|
togglePicker(e) {
|
1240
1314
|
e.preventDefault();
|
1241
1315
|
const self = this;
|
1242
|
-
const
|
1316
|
+
const { colorPicker } = self;
|
1243
1317
|
|
1244
|
-
if (self.isOpen &&
|
1318
|
+
if (self.isOpen && hasClass(colorPicker, 'show')) {
|
1245
1319
|
self.hide(true);
|
1246
1320
|
} else {
|
1247
|
-
self
|
1321
|
+
showDropdown(self, colorPicker);
|
1248
1322
|
}
|
1249
1323
|
}
|
1250
1324
|
|
1251
1325
|
/** Shows the `ColorPicker` dropdown. */
|
1252
1326
|
showPicker() {
|
1253
1327
|
const self = this;
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1328
|
+
const { colorPicker } = self;
|
1329
|
+
|
1330
|
+
if (!hasClass(colorPicker, 'show')) {
|
1331
|
+
showDropdown(self, colorPicker);
|
1332
|
+
}
|
1259
1333
|
}
|
1260
1334
|
|
1261
1335
|
/** Toggles the visibility of the `ColorPicker` presets menu. */
|
1262
1336
|
toggleMenu() {
|
1263
1337
|
const self = this;
|
1264
|
-
const
|
1338
|
+
const { colorMenu } = self;
|
1265
1339
|
|
1266
|
-
if (self.isOpen &&
|
1340
|
+
if (self.isOpen && hasClass(colorMenu, 'show')) {
|
1267
1341
|
self.hide(true);
|
1268
1342
|
} else {
|
1269
|
-
|
1343
|
+
showDropdown(self, colorMenu);
|
1270
1344
|
}
|
1271
1345
|
}
|
1272
1346
|
|
1273
|
-
/**
|
1347
|
+
/** Shows the `ColorPicker` dropdown or the presets menu. */
|
1274
1348
|
show() {
|
1275
1349
|
const self = this;
|
1350
|
+
const { menuToggle } = self;
|
1276
1351
|
if (!self.isOpen) {
|
1277
|
-
addClass(self.parent, 'open');
|
1278
1352
|
toggleEventsOnShown(self, true);
|
1279
1353
|
self.updateDropdownPosition();
|
1280
1354
|
self.isOpen = true;
|
1355
|
+
setAttribute(self.input, 'tabindex', '0');
|
1356
|
+
if (menuToggle) {
|
1357
|
+
setAttribute(menuToggle, 'tabindex', '0');
|
1358
|
+
}
|
1281
1359
|
}
|
1282
1360
|
}
|
1283
1361
|
|
1284
1362
|
/**
|
1285
|
-
* Hides the currently
|
1363
|
+
* Hides the currently open `ColorPicker` dropdown.
|
1286
1364
|
* @param {boolean=} focusPrevented
|
1287
1365
|
*/
|
1288
1366
|
hide(focusPrevented) {
|
1289
1367
|
const self = this;
|
1290
1368
|
if (self.isOpen) {
|
1291
|
-
const {
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
if (
|
1300
|
-
|
1301
|
-
setAttribute(
|
1369
|
+
const {
|
1370
|
+
pickerToggle, menuToggle, colorPicker, colorMenu, parent, input,
|
1371
|
+
} = self;
|
1372
|
+
const openPicker = hasClass(colorPicker, 'show');
|
1373
|
+
const openDropdown = openPicker ? colorPicker : colorMenu;
|
1374
|
+
const relatedBtn = openPicker ? pickerToggle : menuToggle;
|
1375
|
+
const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
|
1376
|
+
|
1377
|
+
if (openDropdown) {
|
1378
|
+
removeClass(openDropdown, 'show');
|
1379
|
+
setAttribute(relatedBtn, ariaExpanded, 'false');
|
1380
|
+
setTimeout(() => {
|
1381
|
+
removePosition(openDropdown);
|
1382
|
+
if (!querySelector('.show', parent)) {
|
1383
|
+
removeClass(parent, 'open');
|
1384
|
+
toggleEventsOnShown(self);
|
1385
|
+
self.isOpen = false;
|
1386
|
+
}
|
1387
|
+
}, animationDuration);
|
1302
1388
|
}
|
1303
1389
|
|
1304
1390
|
if (!self.isValid) {
|
1305
1391
|
self.value = self.color.toString();
|
1306
1392
|
}
|
1307
|
-
|
1308
|
-
self.isOpen = false;
|
1309
|
-
|
1310
1393
|
if (!focusPrevented) {
|
1311
|
-
|
1394
|
+
focus(pickerToggle);
|
1395
|
+
}
|
1396
|
+
setAttribute(input, 'tabindex', '-1');
|
1397
|
+
if (menuToggle) {
|
1398
|
+
setAttribute(menuToggle, 'tabindex', '-1');
|
1312
1399
|
}
|
1313
1400
|
}
|
1314
1401
|
}
|
1315
1402
|
|
1403
|
+
/** Removes `ColorPicker` from target `<input>`. */
|
1316
1404
|
dispose() {
|
1317
1405
|
const self = this;
|
1318
1406
|
const { input, parent } = self;
|
@@ -1321,12 +1409,15 @@ export default class ColorPicker {
|
|
1321
1409
|
[...parent.children].forEach((el) => {
|
1322
1410
|
if (el !== input) el.remove();
|
1323
1411
|
});
|
1412
|
+
setElementStyle(input, { backgroundColor: '' });
|
1413
|
+
['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
|
1324
1414
|
Data.remove(input, colorPickerString);
|
1325
1415
|
}
|
1326
1416
|
}
|
1327
1417
|
|
1328
1418
|
ObjectAssign(ColorPicker, {
|
1329
1419
|
Color,
|
1420
|
+
Version,
|
1330
1421
|
getInstance: getColorPickerInstance,
|
1331
1422
|
init: initColorPicker,
|
1332
1423
|
selector: colorPickerSelector,
|