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