@bornova/colorpicker 1.0.0 → 1.1.0
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 +21 -21
- package/README.md +276 -276
- package/dist/browser/colorpicker.js +269 -190
- package/dist/browser/colorpicker.min.js +2 -2
- package/dist/browser/colorpicker.min.js.map +1 -1
- package/dist/colorpicker.css +612 -598
- package/dist/esm/colorpicker.js +272 -190
- package/dist/types/colors.d.ts +42 -42
- package/dist/types/dom.d.ts +3 -3
- package/dist/types/index.d.ts +10 -9
- package/dist/types/picker.d.ts +11 -11
- package/dist/types/settings.d.ts +68 -71
- package/package.json +56 -51
package/dist/esm/colorpicker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @bornova/colorpicker v1.
|
|
1
|
+
/* @bornova/colorpicker v1.1.0 */
|
|
2
2
|
const settings = {
|
|
3
3
|
el: "[data-colorpicker]",
|
|
4
4
|
parent: "body",
|
|
@@ -36,7 +36,21 @@ const settings = {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
let ctx;
|
|
40
|
+
|
|
41
|
+
function getCtx() {
|
|
42
|
+
if (!ctx && typeof document !== "undefined") {
|
|
43
|
+
try {
|
|
44
|
+
const canvas = document.createElement("canvas");
|
|
45
|
+
canvas.width = 1;
|
|
46
|
+
canvas.height = 1;
|
|
47
|
+
ctx = canvas.getContext("2d", {
|
|
48
|
+
willReadFrequently: true
|
|
49
|
+
});
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
return ctx;
|
|
53
|
+
}
|
|
40
54
|
|
|
41
55
|
function HSVAtoRGBA(hsva) {
|
|
42
56
|
const saturation = hsva.s / 100;
|
|
@@ -94,9 +108,9 @@ function RGBAtoHSVA(rgba) {
|
|
|
94
108
|
hue = 4 + (red - green) / chroma;
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
|
-
hue = Math.
|
|
111
|
+
hue = Math.round(hue * 60);
|
|
98
112
|
return {
|
|
99
|
-
h: hue
|
|
113
|
+
h: (hue + 360) % 360,
|
|
100
114
|
s: Math.round(saturation * 100),
|
|
101
115
|
v: Math.round(value * 100),
|
|
102
116
|
a: rgba.a
|
|
@@ -104,28 +118,62 @@ function RGBAtoHSVA(rgba) {
|
|
|
104
118
|
}
|
|
105
119
|
|
|
106
120
|
function strToRGBA(str) {
|
|
121
|
+
if (typeof str !== "string" || !str.trim()) {
|
|
122
|
+
return {
|
|
123
|
+
r: 0,
|
|
124
|
+
g: 0,
|
|
125
|
+
b: 0,
|
|
126
|
+
a: 1
|
|
127
|
+
};
|
|
128
|
+
}
|
|
107
129
|
const regex = /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
const canvasCtx = getCtx();
|
|
131
|
+
let normalized = str.trim();
|
|
132
|
+
if (canvasCtx) {
|
|
133
|
+
canvasCtx.fillStyle = "#000";
|
|
134
|
+
canvasCtx.fillStyle = str;
|
|
135
|
+
normalized = canvasCtx.fillStyle;
|
|
136
|
+
}
|
|
137
|
+
const match = regex.exec(normalized);
|
|
112
138
|
if (match) {
|
|
113
|
-
|
|
114
|
-
r: Number(match[3]),
|
|
115
|
-
g: Number(match[4]),
|
|
116
|
-
b: Number(match[5]),
|
|
117
|
-
a: Number(match[6])
|
|
139
|
+
return {
|
|
140
|
+
r: Math.min(255, Math.max(0, Number(match[3]))),
|
|
141
|
+
g: Math.min(255, Math.max(0, Number(match[4]))),
|
|
142
|
+
b: Math.min(255, Math.max(0, Number(match[5]))),
|
|
143
|
+
a: match[6] === "" ? 1 : Math.min(1, Math.max(0, Number(Number(match[6]).toFixed(2))))
|
|
118
144
|
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
}
|
|
146
|
+
if (/^#?(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(normalized)) {
|
|
147
|
+
let hex = normalized.replace("#", "");
|
|
148
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
149
|
+
hex = hex.split("").map(c => c + c).join("");
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
153
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
154
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
155
|
+
a: hex.length === 8 ? Number((parseInt(hex.slice(6, 8), 16) / 255).toFixed(2)) : 1
|
|
126
156
|
};
|
|
127
157
|
}
|
|
128
|
-
|
|
158
|
+
if (canvasCtx) {
|
|
159
|
+
try {
|
|
160
|
+
canvasCtx.clearRect(0, 0, 1, 1);
|
|
161
|
+
canvasCtx.fillRect(0, 0, 1, 1);
|
|
162
|
+
const [r, g, b, a] = canvasCtx.getImageData(0, 0, 1, 1).data;
|
|
163
|
+
return {
|
|
164
|
+
r: r,
|
|
165
|
+
g: g,
|
|
166
|
+
b: b,
|
|
167
|
+
a: Number((a / 255).toFixed(2))
|
|
168
|
+
};
|
|
169
|
+
} catch {}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
r: 0,
|
|
173
|
+
g: 0,
|
|
174
|
+
b: 0,
|
|
175
|
+
a: 1
|
|
176
|
+
};
|
|
129
177
|
}
|
|
130
178
|
|
|
131
179
|
function RGBAToHex(rgba) {
|
|
@@ -134,7 +182,7 @@ function RGBAToHex(rgba) {
|
|
|
134
182
|
const B = rgba.b.toString(16).padStart(2, "0");
|
|
135
183
|
let A = "";
|
|
136
184
|
if (settings.alpha && (rgba.a < 1 || settings.forceAlpha)) {
|
|
137
|
-
A = (rgba.a * 255
|
|
185
|
+
A = Math.round(rgba.a * 255).toString(16).padStart(2, "0");
|
|
138
186
|
}
|
|
139
187
|
return "#" + R + G + B + A;
|
|
140
188
|
}
|
|
@@ -154,7 +202,7 @@ function getEl(id) {
|
|
|
154
202
|
function addListener(context, type, selector, fn) {
|
|
155
203
|
if (typeof selector === "string") {
|
|
156
204
|
context.addEventListener(type, event => {
|
|
157
|
-
if (event.target.matches(selector)) {
|
|
205
|
+
if (event.target instanceof Element && event.target.matches(selector)) {
|
|
158
206
|
fn.call(event.target, event);
|
|
159
207
|
}
|
|
160
208
|
});
|
|
@@ -183,7 +231,7 @@ const currentColor = {
|
|
|
183
231
|
a: 1
|
|
184
232
|
};
|
|
185
233
|
|
|
186
|
-
let container, picker, colorArea, colorMarker, colorPreview, colorValue, clearButton, closeButton, hueSlider, hueMarker, alphaSlider, alphaMarker, currentEl, currentFormat, oldColor,
|
|
234
|
+
let container, picker, colorArea, colorMarker, colorPreview, colorValue, clearButton, closeButton, hueSlider, hueMarker, alphaSlider, alphaMarker, currentEl, currentFormat, oldColor, initialFormatColor, colorAreaDims = {};
|
|
187
235
|
|
|
188
236
|
const instances = {};
|
|
189
237
|
|
|
@@ -191,7 +239,9 @@ let currentInstanceId = "";
|
|
|
191
239
|
|
|
192
240
|
let defaultInstance = {};
|
|
193
241
|
|
|
194
|
-
|
|
242
|
+
const boundSelectors = new Set;
|
|
243
|
+
|
|
244
|
+
const boundElements = new WeakSet;
|
|
195
245
|
|
|
196
246
|
const MARKER_MOVEMENTS = {
|
|
197
247
|
ArrowUp: [ 0, -1 ],
|
|
@@ -201,28 +251,28 @@ const MARKER_MOVEMENTS = {
|
|
|
201
251
|
};
|
|
202
252
|
|
|
203
253
|
function configure(options) {
|
|
204
|
-
if (typeof options !== "object") {
|
|
254
|
+
if (!options || typeof options !== "object") {
|
|
205
255
|
return;
|
|
206
256
|
}
|
|
207
257
|
for (const key of Object.keys(options)) {
|
|
208
258
|
switch (key) {
|
|
209
259
|
case "el":
|
|
210
260
|
bindFields(options.el);
|
|
211
|
-
if (options.wrap !== false) {
|
|
261
|
+
if (options.wrap !== false && (options.wrap || settings.wrap)) {
|
|
212
262
|
wrapFields(options.el);
|
|
213
263
|
}
|
|
214
264
|
break;
|
|
215
265
|
|
|
216
266
|
case "parent":
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
container = undefined;
|
|
267
|
+
{
|
|
268
|
+
const newContainer = options.parent instanceof HTMLElement ? options.parent : typeof options.parent === "string" ? document.querySelector(options.parent) : null;
|
|
269
|
+
if (newContainer) {
|
|
270
|
+
newContainer.appendChild(picker);
|
|
271
|
+
settings.parent = options.parent;
|
|
272
|
+
container = newContainer === document.body ? undefined : newContainer;
|
|
223
273
|
}
|
|
274
|
+
break;
|
|
224
275
|
}
|
|
225
|
-
break;
|
|
226
276
|
|
|
227
277
|
case "themeMode":
|
|
228
278
|
settings.themeMode = options.themeMode;
|
|
@@ -231,14 +281,22 @@ function configure(options) {
|
|
|
231
281
|
}
|
|
232
282
|
|
|
233
283
|
case "theme":
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
284
|
+
{
|
|
285
|
+
if (options.theme) {
|
|
286
|
+
settings.theme = options.theme;
|
|
287
|
+
}
|
|
288
|
+
const isOpen = picker.classList.contains("clr-open");
|
|
289
|
+
const isLeft = picker.classList.contains("clr-left");
|
|
290
|
+
const isTop = picker.classList.contains("clr-top");
|
|
291
|
+
picker.className = `clr-picker clr-${settings.theme} clr-${settings.themeMode}`;
|
|
292
|
+
picker.classList.toggle("clr-open", isOpen);
|
|
293
|
+
picker.classList.toggle("clr-left", isLeft);
|
|
294
|
+
picker.classList.toggle("clr-top", isTop);
|
|
295
|
+
if (settings.inline) {
|
|
296
|
+
updatePickerPosition();
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
240
299
|
}
|
|
241
|
-
break;
|
|
242
300
|
|
|
243
301
|
case "rtl":
|
|
244
302
|
settings.rtl = !!options.rtl;
|
|
@@ -255,8 +313,9 @@ function configure(options) {
|
|
|
255
313
|
}
|
|
256
314
|
|
|
257
315
|
case "wrap":
|
|
258
|
-
|
|
259
|
-
|
|
316
|
+
settings.wrap = !!options.wrap;
|
|
317
|
+
if (!("el" in options) && options.wrap) {
|
|
318
|
+
wrapFields(settings.el);
|
|
260
319
|
}
|
|
261
320
|
break;
|
|
262
321
|
|
|
@@ -302,29 +361,16 @@ function configure(options) {
|
|
|
302
361
|
case "inline":
|
|
303
362
|
settings.inline = !!options.inline;
|
|
304
363
|
picker.setAttribute("data-inline", settings.inline);
|
|
305
|
-
if (settings.inline) {
|
|
306
|
-
const defaultColor = options.defaultColor || settings.defaultColor;
|
|
307
|
-
currentFormat = getColorFormatFromStr(defaultColor);
|
|
308
|
-
updatePickerPosition();
|
|
309
|
-
setColorFromStr(defaultColor);
|
|
310
|
-
}
|
|
311
364
|
break;
|
|
312
365
|
|
|
313
366
|
case "clearButton":
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
clearButton.innerHTML = settings.clearLabel;
|
|
318
|
-
}
|
|
319
|
-
const show = typeof options.clearButton === "object" ? options.clearButton.show : options.clearButton;
|
|
320
|
-
settings.clearButton = !!show;
|
|
321
|
-
clearButton.style.display = settings.clearButton ? "block" : "none";
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
367
|
+
settings.clearButton = !!options.clearButton;
|
|
368
|
+
clearButton.style.display = settings.clearButton ? "block" : "none";
|
|
369
|
+
break;
|
|
324
370
|
|
|
325
371
|
case "clearLabel":
|
|
326
372
|
settings.clearLabel = options.clearLabel;
|
|
327
|
-
clearButton.
|
|
373
|
+
clearButton.textContent = settings.clearLabel;
|
|
328
374
|
break;
|
|
329
375
|
|
|
330
376
|
case "closeButton":
|
|
@@ -338,14 +384,14 @@ function configure(options) {
|
|
|
338
384
|
|
|
339
385
|
case "closeLabel":
|
|
340
386
|
settings.closeLabel = options.closeLabel;
|
|
341
|
-
closeButton.
|
|
387
|
+
closeButton.textContent = settings.closeLabel;
|
|
342
388
|
break;
|
|
343
389
|
|
|
344
390
|
case "a11y":
|
|
345
391
|
{
|
|
346
392
|
const labels = options.a11y;
|
|
347
393
|
let update = false;
|
|
348
|
-
if (typeof labels === "object") {
|
|
394
|
+
if (labels && typeof labels === "object") {
|
|
349
395
|
for (const label of Object.keys(labels)) {
|
|
350
396
|
if (labels[label] && settings.a11y[label]) {
|
|
351
397
|
settings.a11y[label] = labels[label];
|
|
@@ -356,14 +402,19 @@ function configure(options) {
|
|
|
356
402
|
if (update) {
|
|
357
403
|
const openLabel = getEl("clr-open-label");
|
|
358
404
|
const swatchLabel = getEl("clr-swatch-label");
|
|
359
|
-
|
|
360
|
-
|
|
405
|
+
const formatLegend = picker.querySelector(".clr-format legend");
|
|
406
|
+
openLabel.textContent = settings.a11y.open;
|
|
407
|
+
swatchLabel.textContent = settings.a11y.swatch;
|
|
408
|
+
if (formatLegend) {
|
|
409
|
+
formatLegend.textContent = settings.a11y.format;
|
|
410
|
+
}
|
|
361
411
|
closeButton.setAttribute("aria-label", settings.a11y.close);
|
|
362
412
|
clearButton.setAttribute("aria-label", settings.a11y.clear);
|
|
363
413
|
hueSlider.setAttribute("aria-label", settings.a11y.hueSlider);
|
|
364
414
|
alphaSlider.setAttribute("aria-label", settings.a11y.alphaSlider);
|
|
365
415
|
colorValue.setAttribute("aria-label", settings.a11y.input);
|
|
366
416
|
colorArea.setAttribute("aria-label", settings.a11y.instruction);
|
|
417
|
+
updateMarkerA11yLabel(currentColor.s, currentColor.v);
|
|
367
418
|
}
|
|
368
419
|
break;
|
|
369
420
|
}
|
|
@@ -372,40 +423,52 @@ function configure(options) {
|
|
|
372
423
|
settings[key] = options[key];
|
|
373
424
|
}
|
|
374
425
|
}
|
|
426
|
+
if (settings.formatToggle) {
|
|
427
|
+
settings.format = "auto";
|
|
428
|
+
}
|
|
429
|
+
if (settings.inline && ("inline" in options || "defaultColor" in options || "format" in options || "formatToggle" in options || "alpha" in options || "forceAlpha" in options)) {
|
|
430
|
+
const defaultColor = options.defaultColor || settings.defaultColor;
|
|
431
|
+
currentFormat = getColorFormatFromStr(defaultColor);
|
|
432
|
+
updatePickerPosition();
|
|
433
|
+
setColorFromStr(defaultColor);
|
|
434
|
+
}
|
|
375
435
|
}
|
|
376
436
|
|
|
377
437
|
function setVirtualInstance(selector, options) {
|
|
378
|
-
if (typeof selector === "string" && typeof options === "object") {
|
|
438
|
+
if (typeof selector === "string" && options && typeof options === "object") {
|
|
379
439
|
instances[selector] = options;
|
|
380
|
-
hasInstance = true;
|
|
381
440
|
}
|
|
382
441
|
}
|
|
383
442
|
|
|
384
443
|
function removeVirtualInstance(selector) {
|
|
385
444
|
delete instances[selector];
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
if (selector === currentInstanceId) {
|
|
389
|
-
resetVirtualInstance();
|
|
390
|
-
}
|
|
445
|
+
if (selector === currentInstanceId) {
|
|
446
|
+
resetVirtualInstance();
|
|
391
447
|
}
|
|
392
448
|
}
|
|
393
449
|
|
|
394
450
|
function attachVirtualInstance(element) {
|
|
395
|
-
if (
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
451
|
+
if (currentInstanceId) {
|
|
452
|
+
resetVirtualInstance();
|
|
453
|
+
}
|
|
454
|
+
const unsupportedOptions = [ "el", "wrap", "rtl", "inline", "defaultColor", "a11y" ];
|
|
455
|
+
for (const selector of Object.keys(instances)) {
|
|
456
|
+
const options = instances[selector];
|
|
457
|
+
if (element.matches(selector)) {
|
|
458
|
+
currentInstanceId = selector;
|
|
459
|
+
defaultInstance = {};
|
|
460
|
+
const clonedOptions = {
|
|
461
|
+
...options
|
|
462
|
+
};
|
|
463
|
+
unsupportedOptions.forEach(option => delete clonedOptions[option]);
|
|
464
|
+
for (const option of Object.keys(clonedOptions)) {
|
|
465
|
+
defaultInstance[option] = Array.isArray(settings[option]) ? [ ...settings[option] ] : settings[option];
|
|
466
|
+
if (option === "formatToggle" && !("format" in clonedOptions)) {
|
|
467
|
+
defaultInstance.format = settings.format;
|
|
405
468
|
}
|
|
406
|
-
configure(options);
|
|
407
|
-
break;
|
|
408
469
|
}
|
|
470
|
+
configure(clonedOptions);
|
|
471
|
+
break;
|
|
409
472
|
}
|
|
410
473
|
}
|
|
411
474
|
}
|
|
@@ -419,13 +482,17 @@ function resetVirtualInstance() {
|
|
|
419
482
|
}
|
|
420
483
|
|
|
421
484
|
function bindFields(selector) {
|
|
422
|
-
const fields = selector instanceof HTMLElement ? [ selector ] : selector;
|
|
485
|
+
const fields = selector instanceof HTMLElement ? [ selector ] : selector instanceof NodeList ? [ ...selector ] : selector;
|
|
423
486
|
if (Array.isArray(fields)) {
|
|
424
487
|
fields.forEach(field => {
|
|
425
|
-
|
|
426
|
-
|
|
488
|
+
if (field instanceof HTMLElement && !boundElements.has(field)) {
|
|
489
|
+
boundElements.add(field);
|
|
490
|
+
addListener(field, "click", openPicker);
|
|
491
|
+
addListener(field, "input", updateColorPreview);
|
|
492
|
+
}
|
|
427
493
|
});
|
|
428
|
-
} else {
|
|
494
|
+
} else if (typeof fields === "string" && !boundSelectors.has(fields)) {
|
|
495
|
+
boundSelectors.add(fields);
|
|
429
496
|
addListener(document, "click", fields, openPicker);
|
|
430
497
|
addListener(document, "input", fields, updateColorPreview);
|
|
431
498
|
}
|
|
@@ -435,6 +502,9 @@ function openPicker(event) {
|
|
|
435
502
|
if (settings.inline) {
|
|
436
503
|
return;
|
|
437
504
|
}
|
|
505
|
+
if (currentEl && currentEl !== event.target) {
|
|
506
|
+
closePicker();
|
|
507
|
+
}
|
|
438
508
|
attachVirtualInstance(event.target);
|
|
439
509
|
currentEl = event.target;
|
|
440
510
|
oldColor = currentEl.value;
|
|
@@ -442,49 +512,68 @@ function openPicker(event) {
|
|
|
442
512
|
picker.classList.add("clr-open");
|
|
443
513
|
updatePickerPosition();
|
|
444
514
|
setColorFromStr(oldColor);
|
|
515
|
+
initialFormatColor = colorValue.value;
|
|
445
516
|
if (settings.focusInput || settings.selectInput) {
|
|
446
517
|
colorValue.focus({
|
|
447
518
|
preventScroll: true
|
|
448
519
|
});
|
|
449
|
-
|
|
520
|
+
let selectionStart = 0;
|
|
521
|
+
let selectionEnd = 0;
|
|
522
|
+
try {
|
|
523
|
+
selectionStart = currentEl.selectionStart;
|
|
524
|
+
selectionEnd = currentEl.selectionEnd;
|
|
525
|
+
} catch {}
|
|
526
|
+
colorValue.setSelectionRange(selectionStart, selectionEnd);
|
|
450
527
|
}
|
|
451
528
|
if (settings.selectInput) {
|
|
452
529
|
colorValue.select();
|
|
453
530
|
}
|
|
454
|
-
if (
|
|
455
|
-
getFocusableElements().shift()
|
|
531
|
+
if (settings.swatchesOnly) {
|
|
532
|
+
const firstFocusable = getFocusableElements().shift();
|
|
533
|
+
if (firstFocusable) {
|
|
534
|
+
firstFocusable.focus();
|
|
535
|
+
}
|
|
456
536
|
}
|
|
457
537
|
currentEl.dispatchEvent(new Event("open", {
|
|
458
538
|
bubbles: false
|
|
459
539
|
}));
|
|
460
540
|
}
|
|
461
541
|
|
|
462
|
-
function
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
top: false
|
|
470
|
-
};
|
|
471
|
-
let parentStyle, parentMarginTop, parentBorderTop;
|
|
472
|
-
let offset = {
|
|
473
|
-
x: 0,
|
|
474
|
-
y: 0
|
|
542
|
+
function updateColorAreaDims() {
|
|
543
|
+
const rect = colorArea.getBoundingClientRect();
|
|
544
|
+
colorAreaDims = {
|
|
545
|
+
width: rect.width || colorArea.offsetWidth,
|
|
546
|
+
height: rect.height || colorArea.offsetHeight,
|
|
547
|
+
x: rect.x + window.scrollX,
|
|
548
|
+
y: rect.y + window.scrollY
|
|
475
549
|
};
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
offset.y += parentBorderTop + scrollY;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function updatePickerPosition() {
|
|
553
|
+
if (!picker || !currentEl && !settings.inline) {
|
|
554
|
+
return;
|
|
482
555
|
}
|
|
483
556
|
if (!settings.inline) {
|
|
557
|
+
const parent = container;
|
|
558
|
+
const scrollX = window.scrollX;
|
|
559
|
+
const scrollY = window.scrollY;
|
|
560
|
+
const pickerWidth = picker.offsetWidth;
|
|
561
|
+
const pickerHeight = picker.offsetHeight;
|
|
562
|
+
const reposition = {
|
|
563
|
+
left: false,
|
|
564
|
+
top: false
|
|
565
|
+
};
|
|
484
566
|
const coords = currentEl.getBoundingClientRect();
|
|
485
|
-
let left = coords.x;
|
|
567
|
+
let left = scrollX + coords.x;
|
|
486
568
|
let top = scrollY + coords.y + coords.height + settings.margin;
|
|
487
569
|
if (parent) {
|
|
570
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
571
|
+
const parentMarginTop = parseFloat(parentStyle.marginTop);
|
|
572
|
+
const parentBorderTop = parseFloat(parentStyle.borderTopWidth);
|
|
573
|
+
const parentBorderLeft = parseFloat(parentStyle.borderLeftWidth);
|
|
574
|
+
const offset = parent.getBoundingClientRect();
|
|
575
|
+
offset.x += parentBorderLeft + scrollX;
|
|
576
|
+
offset.y += parentBorderTop + scrollY;
|
|
488
577
|
left -= offset.x;
|
|
489
578
|
top -= offset.y;
|
|
490
579
|
if (left + pickerWidth > parent.clientWidth) {
|
|
@@ -497,9 +586,10 @@ function updatePickerPosition() {
|
|
|
497
586
|
reposition.top = true;
|
|
498
587
|
}
|
|
499
588
|
}
|
|
589
|
+
left += parent.scrollLeft;
|
|
500
590
|
top += parent.scrollTop;
|
|
501
591
|
} else {
|
|
502
|
-
if (left + pickerWidth > document.documentElement.clientWidth) {
|
|
592
|
+
if (left - scrollX + pickerWidth > document.documentElement.clientWidth) {
|
|
503
593
|
left += coords.width - pickerWidth;
|
|
504
594
|
reposition.left = true;
|
|
505
595
|
}
|
|
@@ -514,25 +604,18 @@ function updatePickerPosition() {
|
|
|
514
604
|
picker.classList.toggle("clr-top", reposition.top);
|
|
515
605
|
picker.style.left = `${left}px`;
|
|
516
606
|
picker.style.top = `${top}px`;
|
|
517
|
-
offset.x += picker.offsetLeft;
|
|
518
|
-
offset.y += picker.offsetTop;
|
|
519
607
|
}
|
|
520
|
-
|
|
521
|
-
width: colorArea.offsetWidth,
|
|
522
|
-
height: colorArea.offsetHeight,
|
|
523
|
-
x: colorArea.offsetLeft + offset.x,
|
|
524
|
-
y: colorArea.offsetTop + offset.y
|
|
525
|
-
};
|
|
608
|
+
updateColorAreaDims();
|
|
526
609
|
}
|
|
527
610
|
|
|
528
611
|
function wrapFields(selector) {
|
|
529
|
-
const fields = selector instanceof HTMLElement ? [ selector ] : Array.isArray(selector) ? selector : [ ...document.querySelectorAll(selector) ];
|
|
612
|
+
const fields = selector instanceof HTMLElement ? [ selector ] : Array.isArray(selector) || selector instanceof NodeList ? [ ...selector ] : typeof selector === "string" ? [ ...document.querySelectorAll(selector) ] : [];
|
|
530
613
|
fields.forEach(wrapColorField);
|
|
531
614
|
}
|
|
532
615
|
|
|
533
616
|
function wrapColorField(field) {
|
|
534
|
-
const parentNode = field
|
|
535
|
-
if (!parentNode.classList.contains("clr-field")) {
|
|
617
|
+
const parentNode = field?.parentNode;
|
|
618
|
+
if (parentNode && parentNode.classList && !parentNode.classList.contains("clr-field")) {
|
|
536
619
|
const wrapper = document.createElement("div");
|
|
537
620
|
let classes = "clr-field";
|
|
538
621
|
if (settings.rtl || field.classList.contains("clr-rtl")) {
|
|
@@ -548,7 +631,7 @@ function wrapColorField(field) {
|
|
|
548
631
|
|
|
549
632
|
function updateColorPreview(event) {
|
|
550
633
|
const parent = event.target.parentNode;
|
|
551
|
-
if (parent.classList.contains("clr-field")) {
|
|
634
|
+
if (parent && parent.classList && parent.classList.contains("clr-field")) {
|
|
552
635
|
parent.style.color = event.target.value;
|
|
553
636
|
}
|
|
554
637
|
}
|
|
@@ -564,7 +647,16 @@ function closePicker(revert) {
|
|
|
564
647
|
bubbles: true
|
|
565
648
|
}));
|
|
566
649
|
}
|
|
650
|
+
} else {
|
|
651
|
+
const value = colorValue.value;
|
|
652
|
+
if (value !== initialFormatColor) {
|
|
653
|
+
if (value !== "") {
|
|
654
|
+
setColorFromStr(value);
|
|
655
|
+
}
|
|
656
|
+
pickColor(value === "" ? "" : colorValue.value);
|
|
657
|
+
}
|
|
567
658
|
}
|
|
659
|
+
currentEl = undefined;
|
|
568
660
|
setTimeout(() => {
|
|
569
661
|
if (oldColor !== prevEl.value) {
|
|
570
662
|
prevEl.dispatchEvent(new Event("change", {
|
|
@@ -573,9 +665,7 @@ function closePicker(revert) {
|
|
|
573
665
|
}
|
|
574
666
|
});
|
|
575
667
|
picker.classList.remove("clr-open");
|
|
576
|
-
|
|
577
|
-
resetVirtualInstance();
|
|
578
|
-
}
|
|
668
|
+
resetVirtualInstance();
|
|
579
669
|
prevEl.dispatchEvent(new Event("close", {
|
|
580
670
|
bubbles: false
|
|
581
671
|
}));
|
|
@@ -584,7 +674,6 @@ function closePicker(revert) {
|
|
|
584
674
|
preventScroll: true
|
|
585
675
|
});
|
|
586
676
|
}
|
|
587
|
-
currentEl = undefined;
|
|
588
677
|
}
|
|
589
678
|
}
|
|
590
679
|
|
|
@@ -603,12 +692,13 @@ function setColorFromStr(str) {
|
|
|
603
692
|
}
|
|
604
693
|
|
|
605
694
|
function getColorFormatFromStr(str) {
|
|
606
|
-
const fmt = str.slice(0, 3).toLowerCase();
|
|
695
|
+
const fmt = str.trim().slice(0, 3).toLowerCase();
|
|
607
696
|
return fmt === "rgb" || fmt === "hsl" ? fmt : "hex";
|
|
608
697
|
}
|
|
609
698
|
|
|
610
699
|
function pickColor(color) {
|
|
611
700
|
color = color ?? colorValue.value;
|
|
701
|
+
initialFormatColor = colorValue.value;
|
|
612
702
|
if (currentEl) {
|
|
613
703
|
currentEl.value = color;
|
|
614
704
|
currentEl.dispatchEvent(new Event("input", {
|
|
@@ -644,13 +734,7 @@ function updateMarkerA11yLabel(saturation, value) {
|
|
|
644
734
|
}
|
|
645
735
|
|
|
646
736
|
function moveMarker(event) {
|
|
647
|
-
|
|
648
|
-
let x = (touch?.pageX ?? event.pageX) - colorAreaDims.x;
|
|
649
|
-
let y = (touch?.pageY ?? event.pageY) - colorAreaDims.y;
|
|
650
|
-
if (container) {
|
|
651
|
-
y += container.scrollTop;
|
|
652
|
-
}
|
|
653
|
-
setMarkerPosition(x, y);
|
|
737
|
+
setMarkerPosition(event.pageX - colorAreaDims.x, event.pageY - colorAreaDims.y);
|
|
654
738
|
event.preventDefault();
|
|
655
739
|
event.stopPropagation();
|
|
656
740
|
}
|
|
@@ -667,7 +751,9 @@ function setMarkerPosition(x, y) {
|
|
|
667
751
|
colorMarker.style.left = `${x}px`;
|
|
668
752
|
colorMarker.style.top = `${y}px`;
|
|
669
753
|
setColorAtPosition(x, y);
|
|
670
|
-
colorMarker.focus(
|
|
754
|
+
colorMarker.focus({
|
|
755
|
+
preventScroll: true
|
|
756
|
+
});
|
|
671
757
|
}
|
|
672
758
|
|
|
673
759
|
function updateColor(rgba = {}, hsva = {}) {
|
|
@@ -679,12 +765,6 @@ function updateColor(rgba = {}, hsva = {}) {
|
|
|
679
765
|
alphaMarker.parentNode.style.color = opaqueHex;
|
|
680
766
|
alphaMarker.style.color = hex;
|
|
681
767
|
colorPreview.style.color = hex;
|
|
682
|
-
colorArea.style.display = "none";
|
|
683
|
-
colorArea.offsetHeight;
|
|
684
|
-
colorArea.style.display = "";
|
|
685
|
-
alphaMarker.nextElementSibling.style.display = "none";
|
|
686
|
-
alphaMarker.nextElementSibling.offsetHeight;
|
|
687
|
-
alphaMarker.nextElementSibling.style.display = "";
|
|
688
768
|
if (format === "mixed") {
|
|
689
769
|
format = currentColor.a === 1 ? "hex" : "rgb";
|
|
690
770
|
} else if (format === "auto") {
|
|
@@ -703,7 +783,10 @@ function updateColor(rgba = {}, hsva = {}) {
|
|
|
703
783
|
colorValue.value = HSLAToStr(HSVAtoHSLA(currentColor));
|
|
704
784
|
break;
|
|
705
785
|
}
|
|
706
|
-
|
|
786
|
+
const formatRadio = picker.querySelector(`.clr-format [value="${format}"]`);
|
|
787
|
+
if (formatRadio) {
|
|
788
|
+
formatRadio.checked = true;
|
|
789
|
+
}
|
|
707
790
|
}
|
|
708
791
|
|
|
709
792
|
function setHue() {
|
|
@@ -725,15 +808,16 @@ function setAlpha() {
|
|
|
725
808
|
}
|
|
726
809
|
|
|
727
810
|
function getFocusableElements() {
|
|
728
|
-
return [ ...picker.querySelectorAll("input, button") ].filter(node => !!node.offsetWidth);
|
|
811
|
+
return [ ...picker.querySelectorAll("input, button, [tabindex]") ].filter(node => !!node.offsetWidth && (node.type !== "radio" || node.checked));
|
|
729
812
|
}
|
|
730
813
|
|
|
731
814
|
function init() {
|
|
732
815
|
container = undefined;
|
|
733
816
|
picker = document.createElement("div");
|
|
734
817
|
picker.setAttribute("id", "clr-picker");
|
|
818
|
+
picker.setAttribute("role", "dialog");
|
|
735
819
|
picker.className = "clr-picker";
|
|
736
|
-
picker.innerHTML = `\n <input id="clr-color-value" name="clr-color-value" class="clr-color" type="text" value="" spellcheck="false"
|
|
820
|
+
picker.innerHTML = `\n <input id="clr-color-value" name="clr-color-value" class="clr-color" type="text" value="" spellcheck="false">\n <div id="clr-color-area" class="clr-gradient" role="application">\n <div id="clr-color-marker" class="clr-marker" tabindex="0"></div>\n </div>\n <div class="clr-hue">\n <input id="clr-hue-slider" name="clr-hue-slider" type="range" min="0" max="360" step="1">\n <div id="clr-hue-marker"></div>\n </div>\n <div class="clr-alpha">\n <input id="clr-alpha-slider" name="clr-alpha-slider" type="range" min="0" max="100" step="1">\n <div id="clr-alpha-marker"></div>\n <span></span>\n </div>\n <div id="clr-format" class="clr-format">\n <fieldset class="clr-segmented">\n <legend></legend>\n <input id="clr-f1" type="radio" name="clr-format" value="hex">\n <label for="clr-f1">Hex</label>\n <input id="clr-f2" type="radio" name="clr-format" value="rgb">\n <label for="clr-f2">RGB</label>\n <input id="clr-f3" type="radio" name="clr-format" value="hsl">\n <label for="clr-f3">HSL</label>\n </fieldset>\n </div>\n <div id="clr-swatches" class="clr-swatches"></div>\n <button type="button" id="clr-clear" class="clr-clear"></button>\n <div id="clr-color-preview" class="clr-preview">\n <button type="button" id="clr-close" class="clr-close"></button>\n </div>\n <span id="clr-open-label" hidden></span>\n <span id="clr-swatch-label" hidden></span>\n `;
|
|
737
821
|
document.body.appendChild(picker);
|
|
738
822
|
colorArea = getEl("clr-color-area");
|
|
739
823
|
colorMarker = getEl("clr-color-marker");
|
|
@@ -745,36 +829,36 @@ function init() {
|
|
|
745
829
|
hueMarker = getEl("clr-hue-marker");
|
|
746
830
|
alphaSlider = getEl("clr-alpha-slider");
|
|
747
831
|
alphaMarker = getEl("clr-alpha-marker");
|
|
832
|
+
colorValue.setAttribute("aria-label", settings.a11y.input);
|
|
833
|
+
colorArea.setAttribute("aria-label", settings.a11y.instruction);
|
|
834
|
+
hueSlider.setAttribute("aria-label", settings.a11y.hueSlider);
|
|
835
|
+
alphaSlider.setAttribute("aria-label", settings.a11y.alphaSlider);
|
|
836
|
+
clearButton.setAttribute("aria-label", settings.a11y.clear);
|
|
837
|
+
clearButton.textContent = settings.clearLabel;
|
|
838
|
+
closeButton.setAttribute("aria-label", settings.a11y.close);
|
|
839
|
+
closeButton.textContent = settings.closeLabel;
|
|
840
|
+
picker.querySelector(".clr-format legend").textContent = settings.a11y.format;
|
|
841
|
+
getEl("clr-open-label").textContent = settings.a11y.open;
|
|
842
|
+
getEl("clr-swatch-label").textContent = settings.a11y.swatch;
|
|
748
843
|
bindFields(settings.el);
|
|
749
844
|
wrapFields(settings.el);
|
|
750
|
-
addListener(picker, "
|
|
751
|
-
picker.classList.remove("clr-keyboard-nav");
|
|
845
|
+
addListener(picker, "pointerdown", event => {
|
|
752
846
|
event.stopPropagation();
|
|
753
847
|
});
|
|
754
|
-
addListener(colorArea, "
|
|
755
|
-
|
|
848
|
+
addListener(colorArea, "pointerdown", () => {
|
|
849
|
+
updateColorAreaDims();
|
|
850
|
+
addListener(document, "pointermove", moveMarker);
|
|
756
851
|
});
|
|
757
852
|
addListener(colorArea, "contextmenu", event => {
|
|
758
853
|
event.preventDefault();
|
|
759
854
|
});
|
|
760
|
-
addListener(colorArea, "touchstart", () => {
|
|
761
|
-
document.addEventListener("touchmove", moveMarker, {
|
|
762
|
-
passive: false
|
|
763
|
-
});
|
|
764
|
-
});
|
|
765
|
-
addListener(colorMarker, "mousedown", () => {
|
|
766
|
-
addListener(document, "mousemove", moveMarker);
|
|
767
|
-
});
|
|
768
|
-
addListener(colorMarker, "touchstart", () => {
|
|
769
|
-
document.addEventListener("touchmove", moveMarker, {
|
|
770
|
-
passive: false
|
|
771
|
-
});
|
|
772
|
-
});
|
|
773
855
|
addListener(colorValue, "change", () => {
|
|
774
856
|
const value = colorValue.value;
|
|
775
857
|
if (currentEl || settings.inline) {
|
|
776
|
-
|
|
777
|
-
|
|
858
|
+
if (value !== "") {
|
|
859
|
+
setColorFromStr(value);
|
|
860
|
+
}
|
|
861
|
+
pickColor(value === "" ? "" : colorValue.value);
|
|
778
862
|
}
|
|
779
863
|
});
|
|
780
864
|
addListener(clearButton, "click", () => {
|
|
@@ -797,50 +881,48 @@ function init() {
|
|
|
797
881
|
closePicker();
|
|
798
882
|
}
|
|
799
883
|
});
|
|
800
|
-
addListener(document, "
|
|
801
|
-
document.removeEventListener("
|
|
802
|
-
});
|
|
803
|
-
addListener(document, "touchend", () => {
|
|
804
|
-
document.removeEventListener("touchmove", moveMarker);
|
|
884
|
+
addListener(document, "pointerup", () => {
|
|
885
|
+
document.removeEventListener("pointermove", moveMarker);
|
|
805
886
|
});
|
|
806
|
-
addListener(document, "
|
|
807
|
-
keyboardNav = false;
|
|
808
|
-
picker.classList.remove("clr-keyboard-nav");
|
|
887
|
+
addListener(document, "pointerdown", () => {
|
|
809
888
|
closePicker();
|
|
810
889
|
});
|
|
811
890
|
addListener(document, "keydown", event => {
|
|
812
891
|
const key = event.key;
|
|
813
892
|
const target = event.target;
|
|
814
893
|
const shiftKey = event.shiftKey;
|
|
815
|
-
const navKeys = [ "Tab", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight" ];
|
|
816
894
|
if (key === "Escape") {
|
|
817
|
-
|
|
895
|
+
if (currentEl) {
|
|
896
|
+
closePicker(true);
|
|
897
|
+
}
|
|
818
898
|
return;
|
|
819
899
|
} else if (key === "Enter" && target.tagName !== "BUTTON") {
|
|
820
|
-
|
|
900
|
+
if (currentEl) {
|
|
901
|
+
event.preventDefault();
|
|
902
|
+
closePicker();
|
|
903
|
+
}
|
|
821
904
|
return;
|
|
822
|
-
} else if (navKeys.includes(key)) {
|
|
823
|
-
keyboardNav = true;
|
|
824
|
-
picker.classList.add("clr-keyboard-nav");
|
|
825
905
|
}
|
|
826
|
-
if (key === "Tab" && target.matches(".clr-picker *")) {
|
|
906
|
+
if (key === "Tab" && target instanceof Element && target.matches(".clr-picker *")) {
|
|
827
907
|
const focusables = getFocusableElements();
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
908
|
+
if (focusables.length > 1) {
|
|
909
|
+
const firstFocusable = focusables[0];
|
|
910
|
+
const lastFocusable = focusables[focusables.length - 1];
|
|
911
|
+
if (shiftKey && target === firstFocusable) {
|
|
912
|
+
lastFocusable.focus();
|
|
913
|
+
event.preventDefault();
|
|
914
|
+
} else if (!shiftKey && target === lastFocusable) {
|
|
915
|
+
firstFocusable.focus();
|
|
916
|
+
event.preventDefault();
|
|
917
|
+
}
|
|
918
|
+
} else if (focusables.length === 1) {
|
|
835
919
|
event.preventDefault();
|
|
836
920
|
}
|
|
837
921
|
}
|
|
838
922
|
});
|
|
839
923
|
addListener(document, "click", ".clr-field button", event => {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
event.target.nextElementSibling.dispatchEvent(new Event("click", {
|
|
924
|
+
resetVirtualInstance();
|
|
925
|
+
event.target.nextElementSibling?.dispatchEvent(new Event("click", {
|
|
844
926
|
bubbles: true
|
|
845
927
|
}));
|
|
846
928
|
});
|