@thednp/color-picker 0.0.1-alpha1 → 0.0.1-alpha2
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/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,
|