@bornova/colorpicker 1.0.1 → 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/dist/browser/colorpicker.js +232 -233
- package/dist/browser/colorpicker.min.js +2 -2
- package/dist/browser/colorpicker.min.js.map +1 -1
- package/dist/colorpicker.css +35 -21
- package/dist/esm/colorpicker.js +234 -233
- 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 +15 -10
package/dist/esm/colorpicker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @bornova/colorpicker v1.0
|
|
1
|
+
/* @bornova/colorpicker v1.1.0 */
|
|
2
2
|
const settings = {
|
|
3
3
|
el: "[data-colorpicker]",
|
|
4
4
|
parent: "body",
|
|
@@ -41,7 +41,12 @@ let ctx;
|
|
|
41
41
|
function getCtx() {
|
|
42
42
|
if (!ctx && typeof document !== "undefined") {
|
|
43
43
|
try {
|
|
44
|
-
|
|
44
|
+
const canvas = document.createElement("canvas");
|
|
45
|
+
canvas.width = 1;
|
|
46
|
+
canvas.height = 1;
|
|
47
|
+
ctx = canvas.getContext("2d", {
|
|
48
|
+
willReadFrequently: true
|
|
49
|
+
});
|
|
45
50
|
} catch {}
|
|
46
51
|
}
|
|
47
52
|
return ctx;
|
|
@@ -103,9 +108,9 @@ function RGBAtoHSVA(rgba) {
|
|
|
103
108
|
hue = 4 + (red - green) / chroma;
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
|
-
hue = Math.
|
|
111
|
+
hue = Math.round(hue * 60);
|
|
107
112
|
return {
|
|
108
|
-
h: hue
|
|
113
|
+
h: (hue + 360) % 360,
|
|
109
114
|
s: Math.round(saturation * 100),
|
|
110
115
|
v: Math.round(value * 100),
|
|
111
116
|
a: rgba.a
|
|
@@ -113,73 +118,62 @@ function RGBAtoHSVA(rgba) {
|
|
|
113
118
|
}
|
|
114
119
|
|
|
115
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
|
+
}
|
|
116
129
|
const regex = /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i;
|
|
117
130
|
const canvasCtx = getCtx();
|
|
118
|
-
let
|
|
131
|
+
let normalized = str.trim();
|
|
119
132
|
if (canvasCtx) {
|
|
120
133
|
canvasCtx.fillStyle = "#000";
|
|
121
134
|
canvasCtx.fillStyle = str;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
135
|
+
normalized = canvasCtx.fillStyle;
|
|
136
|
+
}
|
|
137
|
+
const match = regex.exec(normalized);
|
|
138
|
+
if (match) {
|
|
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))))
|
|
144
|
+
};
|
|
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("");
|
|
138
150
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
156
|
+
};
|
|
157
|
+
}
|
|
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))
|
|
147
168
|
};
|
|
148
|
-
}
|
|
149
|
-
const hex = str.replace("#", "");
|
|
150
|
-
if (hex.length === 3 || hex.length === 4) {
|
|
151
|
-
const r = parseInt(hex[0] + hex[0], 16);
|
|
152
|
-
const g = parseInt(hex[1] + hex[1], 16);
|
|
153
|
-
const b = parseInt(hex[2] + hex[2], 16);
|
|
154
|
-
const a = hex.length === 4 ? Number((parseInt(hex[3] + hex[3], 16) / 255).toFixed(2)) : 1;
|
|
155
|
-
rgba = {
|
|
156
|
-
r: r,
|
|
157
|
-
g: g,
|
|
158
|
-
b: b,
|
|
159
|
-
a: a
|
|
160
|
-
};
|
|
161
|
-
} else if (hex.length === 6 || hex.length === 8) {
|
|
162
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
163
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
164
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
165
|
-
const a = hex.length === 8 ? Number((parseInt(hex.slice(6, 8), 16) / 255).toFixed(2)) : 1;
|
|
166
|
-
rgba = {
|
|
167
|
-
r: r,
|
|
168
|
-
g: g,
|
|
169
|
-
b: b,
|
|
170
|
-
a: a
|
|
171
|
-
};
|
|
172
|
-
} else {
|
|
173
|
-
rgba = {
|
|
174
|
-
r: 0,
|
|
175
|
-
g: 0,
|
|
176
|
-
b: 0,
|
|
177
|
-
a: 1
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
}
|
|
169
|
+
} catch {}
|
|
181
170
|
}
|
|
182
|
-
return
|
|
171
|
+
return {
|
|
172
|
+
r: 0,
|
|
173
|
+
g: 0,
|
|
174
|
+
b: 0,
|
|
175
|
+
a: 1
|
|
176
|
+
};
|
|
183
177
|
}
|
|
184
178
|
|
|
185
179
|
function RGBAToHex(rgba) {
|
|
@@ -208,7 +202,7 @@ function getEl(id) {
|
|
|
208
202
|
function addListener(context, type, selector, fn) {
|
|
209
203
|
if (typeof selector === "string") {
|
|
210
204
|
context.addEventListener(type, event => {
|
|
211
|
-
if (event.target.matches(selector)) {
|
|
205
|
+
if (event.target instanceof Element && event.target.matches(selector)) {
|
|
212
206
|
fn.call(event.target, event);
|
|
213
207
|
}
|
|
214
208
|
});
|
|
@@ -237,7 +231,7 @@ const currentColor = {
|
|
|
237
231
|
a: 1
|
|
238
232
|
};
|
|
239
233
|
|
|
240
|
-
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 = {};
|
|
241
235
|
|
|
242
236
|
const instances = {};
|
|
243
237
|
|
|
@@ -245,7 +239,9 @@ let currentInstanceId = "";
|
|
|
245
239
|
|
|
246
240
|
let defaultInstance = {};
|
|
247
241
|
|
|
248
|
-
|
|
242
|
+
const boundSelectors = new Set;
|
|
243
|
+
|
|
244
|
+
const boundElements = new WeakSet;
|
|
249
245
|
|
|
250
246
|
const MARKER_MOVEMENTS = {
|
|
251
247
|
ArrowUp: [ 0, -1 ],
|
|
@@ -255,28 +251,28 @@ const MARKER_MOVEMENTS = {
|
|
|
255
251
|
};
|
|
256
252
|
|
|
257
253
|
function configure(options) {
|
|
258
|
-
if (typeof options !== "object") {
|
|
254
|
+
if (!options || typeof options !== "object") {
|
|
259
255
|
return;
|
|
260
256
|
}
|
|
261
257
|
for (const key of Object.keys(options)) {
|
|
262
258
|
switch (key) {
|
|
263
259
|
case "el":
|
|
264
260
|
bindFields(options.el);
|
|
265
|
-
if (options.wrap !== false) {
|
|
261
|
+
if (options.wrap !== false && (options.wrap || settings.wrap)) {
|
|
266
262
|
wrapFields(options.el);
|
|
267
263
|
}
|
|
268
264
|
break;
|
|
269
265
|
|
|
270
266
|
case "parent":
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
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;
|
|
277
273
|
}
|
|
274
|
+
break;
|
|
278
275
|
}
|
|
279
|
-
break;
|
|
280
276
|
|
|
281
277
|
case "themeMode":
|
|
282
278
|
settings.themeMode = options.themeMode;
|
|
@@ -285,14 +281,22 @@ function configure(options) {
|
|
|
285
281
|
}
|
|
286
282
|
|
|
287
283
|
case "theme":
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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;
|
|
294
299
|
}
|
|
295
|
-
break;
|
|
296
300
|
|
|
297
301
|
case "rtl":
|
|
298
302
|
settings.rtl = !!options.rtl;
|
|
@@ -309,8 +313,9 @@ function configure(options) {
|
|
|
309
313
|
}
|
|
310
314
|
|
|
311
315
|
case "wrap":
|
|
312
|
-
|
|
313
|
-
|
|
316
|
+
settings.wrap = !!options.wrap;
|
|
317
|
+
if (!("el" in options) && options.wrap) {
|
|
318
|
+
wrapFields(settings.el);
|
|
314
319
|
}
|
|
315
320
|
break;
|
|
316
321
|
|
|
@@ -356,29 +361,16 @@ function configure(options) {
|
|
|
356
361
|
case "inline":
|
|
357
362
|
settings.inline = !!options.inline;
|
|
358
363
|
picker.setAttribute("data-inline", settings.inline);
|
|
359
|
-
if (settings.inline) {
|
|
360
|
-
const defaultColor = options.defaultColor || settings.defaultColor;
|
|
361
|
-
currentFormat = getColorFormatFromStr(defaultColor);
|
|
362
|
-
updatePickerPosition();
|
|
363
|
-
setColorFromStr(defaultColor);
|
|
364
|
-
}
|
|
365
364
|
break;
|
|
366
365
|
|
|
367
366
|
case "clearButton":
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
clearButton.innerHTML = settings.clearLabel;
|
|
372
|
-
}
|
|
373
|
-
const show = typeof options.clearButton === "object" ? options.clearButton.show : options.clearButton;
|
|
374
|
-
settings.clearButton = !!show;
|
|
375
|
-
clearButton.style.display = settings.clearButton ? "block" : "none";
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
367
|
+
settings.clearButton = !!options.clearButton;
|
|
368
|
+
clearButton.style.display = settings.clearButton ? "block" : "none";
|
|
369
|
+
break;
|
|
378
370
|
|
|
379
371
|
case "clearLabel":
|
|
380
372
|
settings.clearLabel = options.clearLabel;
|
|
381
|
-
clearButton.
|
|
373
|
+
clearButton.textContent = settings.clearLabel;
|
|
382
374
|
break;
|
|
383
375
|
|
|
384
376
|
case "closeButton":
|
|
@@ -392,14 +384,14 @@ function configure(options) {
|
|
|
392
384
|
|
|
393
385
|
case "closeLabel":
|
|
394
386
|
settings.closeLabel = options.closeLabel;
|
|
395
|
-
closeButton.
|
|
387
|
+
closeButton.textContent = settings.closeLabel;
|
|
396
388
|
break;
|
|
397
389
|
|
|
398
390
|
case "a11y":
|
|
399
391
|
{
|
|
400
392
|
const labels = options.a11y;
|
|
401
393
|
let update = false;
|
|
402
|
-
if (typeof labels === "object") {
|
|
394
|
+
if (labels && typeof labels === "object") {
|
|
403
395
|
for (const label of Object.keys(labels)) {
|
|
404
396
|
if (labels[label] && settings.a11y[label]) {
|
|
405
397
|
settings.a11y[label] = labels[label];
|
|
@@ -410,14 +402,19 @@ function configure(options) {
|
|
|
410
402
|
if (update) {
|
|
411
403
|
const openLabel = getEl("clr-open-label");
|
|
412
404
|
const swatchLabel = getEl("clr-swatch-label");
|
|
413
|
-
|
|
414
|
-
|
|
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
|
+
}
|
|
415
411
|
closeButton.setAttribute("aria-label", settings.a11y.close);
|
|
416
412
|
clearButton.setAttribute("aria-label", settings.a11y.clear);
|
|
417
413
|
hueSlider.setAttribute("aria-label", settings.a11y.hueSlider);
|
|
418
414
|
alphaSlider.setAttribute("aria-label", settings.a11y.alphaSlider);
|
|
419
415
|
colorValue.setAttribute("aria-label", settings.a11y.input);
|
|
420
416
|
colorArea.setAttribute("aria-label", settings.a11y.instruction);
|
|
417
|
+
updateMarkerA11yLabel(currentColor.s, currentColor.v);
|
|
421
418
|
}
|
|
422
419
|
break;
|
|
423
420
|
}
|
|
@@ -426,46 +423,52 @@ function configure(options) {
|
|
|
426
423
|
settings[key] = options[key];
|
|
427
424
|
}
|
|
428
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
|
+
}
|
|
429
435
|
}
|
|
430
436
|
|
|
431
437
|
function setVirtualInstance(selector, options) {
|
|
432
|
-
if (typeof selector === "string" && typeof options === "object") {
|
|
438
|
+
if (typeof selector === "string" && options && typeof options === "object") {
|
|
433
439
|
instances[selector] = options;
|
|
434
|
-
hasInstance = true;
|
|
435
440
|
}
|
|
436
441
|
}
|
|
437
442
|
|
|
438
443
|
function removeVirtualInstance(selector) {
|
|
439
444
|
delete instances[selector];
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
if (selector === currentInstanceId) {
|
|
443
|
-
resetVirtualInstance();
|
|
444
|
-
}
|
|
445
|
+
if (selector === currentInstanceId) {
|
|
446
|
+
resetVirtualInstance();
|
|
445
447
|
}
|
|
446
448
|
}
|
|
447
449
|
|
|
448
450
|
function attachVirtualInstance(element) {
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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;
|
|
465
468
|
}
|
|
466
|
-
configure(clonedOptions);
|
|
467
|
-
break;
|
|
468
469
|
}
|
|
470
|
+
configure(clonedOptions);
|
|
471
|
+
break;
|
|
469
472
|
}
|
|
470
473
|
}
|
|
471
474
|
}
|
|
@@ -479,13 +482,17 @@ function resetVirtualInstance() {
|
|
|
479
482
|
}
|
|
480
483
|
|
|
481
484
|
function bindFields(selector) {
|
|
482
|
-
const fields = selector instanceof HTMLElement ? [ selector ] : selector;
|
|
485
|
+
const fields = selector instanceof HTMLElement ? [ selector ] : selector instanceof NodeList ? [ ...selector ] : selector;
|
|
483
486
|
if (Array.isArray(fields)) {
|
|
484
487
|
fields.forEach(field => {
|
|
485
|
-
|
|
486
|
-
|
|
488
|
+
if (field instanceof HTMLElement && !boundElements.has(field)) {
|
|
489
|
+
boundElements.add(field);
|
|
490
|
+
addListener(field, "click", openPicker);
|
|
491
|
+
addListener(field, "input", updateColorPreview);
|
|
492
|
+
}
|
|
487
493
|
});
|
|
488
|
-
} else {
|
|
494
|
+
} else if (typeof fields === "string" && !boundSelectors.has(fields)) {
|
|
495
|
+
boundSelectors.add(fields);
|
|
489
496
|
addListener(document, "click", fields, openPicker);
|
|
490
497
|
addListener(document, "input", fields, updateColorPreview);
|
|
491
498
|
}
|
|
@@ -495,6 +502,9 @@ function openPicker(event) {
|
|
|
495
502
|
if (settings.inline) {
|
|
496
503
|
return;
|
|
497
504
|
}
|
|
505
|
+
if (currentEl && currentEl !== event.target) {
|
|
506
|
+
closePicker();
|
|
507
|
+
}
|
|
498
508
|
attachVirtualInstance(event.target);
|
|
499
509
|
currentEl = event.target;
|
|
500
510
|
oldColor = currentEl.value;
|
|
@@ -502,6 +512,7 @@ function openPicker(event) {
|
|
|
502
512
|
picker.classList.add("clr-open");
|
|
503
513
|
updatePickerPosition();
|
|
504
514
|
setColorFromStr(oldColor);
|
|
515
|
+
initialFormatColor = colorValue.value;
|
|
505
516
|
if (settings.focusInput || settings.selectInput) {
|
|
506
517
|
colorValue.focus({
|
|
507
518
|
preventScroll: true
|
|
@@ -517,7 +528,7 @@ function openPicker(event) {
|
|
|
517
528
|
if (settings.selectInput) {
|
|
518
529
|
colorValue.select();
|
|
519
530
|
}
|
|
520
|
-
if (
|
|
531
|
+
if (settings.swatchesOnly) {
|
|
521
532
|
const firstFocusable = getFocusableElements().shift();
|
|
522
533
|
if (firstFocusable) {
|
|
523
534
|
firstFocusable.focus();
|
|
@@ -528,32 +539,41 @@ function openPicker(event) {
|
|
|
528
539
|
}));
|
|
529
540
|
}
|
|
530
541
|
|
|
531
|
-
function
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
top: false
|
|
539
|
-
};
|
|
540
|
-
let parentStyle, parentMarginTop, parentBorderTop;
|
|
541
|
-
let offset = {
|
|
542
|
-
x: 0,
|
|
543
|
-
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
|
|
544
549
|
};
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
offset.y += parentBorderTop + scrollY;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function updatePickerPosition() {
|
|
553
|
+
if (!picker || !currentEl && !settings.inline) {
|
|
554
|
+
return;
|
|
551
555
|
}
|
|
552
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
|
+
};
|
|
553
566
|
const coords = currentEl.getBoundingClientRect();
|
|
554
|
-
let left = coords.x;
|
|
567
|
+
let left = scrollX + coords.x;
|
|
555
568
|
let top = scrollY + coords.y + coords.height + settings.margin;
|
|
556
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;
|
|
557
577
|
left -= offset.x;
|
|
558
578
|
top -= offset.y;
|
|
559
579
|
if (left + pickerWidth > parent.clientWidth) {
|
|
@@ -566,9 +586,10 @@ function updatePickerPosition() {
|
|
|
566
586
|
reposition.top = true;
|
|
567
587
|
}
|
|
568
588
|
}
|
|
589
|
+
left += parent.scrollLeft;
|
|
569
590
|
top += parent.scrollTop;
|
|
570
591
|
} else {
|
|
571
|
-
if (left + pickerWidth > document.documentElement.clientWidth) {
|
|
592
|
+
if (left - scrollX + pickerWidth > document.documentElement.clientWidth) {
|
|
572
593
|
left += coords.width - pickerWidth;
|
|
573
594
|
reposition.left = true;
|
|
574
595
|
}
|
|
@@ -583,25 +604,18 @@ function updatePickerPosition() {
|
|
|
583
604
|
picker.classList.toggle("clr-top", reposition.top);
|
|
584
605
|
picker.style.left = `${left}px`;
|
|
585
606
|
picker.style.top = `${top}px`;
|
|
586
|
-
offset.x += picker.offsetLeft;
|
|
587
|
-
offset.y += picker.offsetTop;
|
|
588
607
|
}
|
|
589
|
-
|
|
590
|
-
width: colorArea.offsetWidth,
|
|
591
|
-
height: colorArea.offsetHeight,
|
|
592
|
-
x: colorArea.offsetLeft + offset.x,
|
|
593
|
-
y: colorArea.offsetTop + offset.y
|
|
594
|
-
};
|
|
608
|
+
updateColorAreaDims();
|
|
595
609
|
}
|
|
596
610
|
|
|
597
611
|
function wrapFields(selector) {
|
|
598
|
-
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) ] : [];
|
|
599
613
|
fields.forEach(wrapColorField);
|
|
600
614
|
}
|
|
601
615
|
|
|
602
616
|
function wrapColorField(field) {
|
|
603
|
-
const parentNode = field
|
|
604
|
-
if (!parentNode.classList.contains("clr-field")) {
|
|
617
|
+
const parentNode = field?.parentNode;
|
|
618
|
+
if (parentNode && parentNode.classList && !parentNode.classList.contains("clr-field")) {
|
|
605
619
|
const wrapper = document.createElement("div");
|
|
606
620
|
let classes = "clr-field";
|
|
607
621
|
if (settings.rtl || field.classList.contains("clr-rtl")) {
|
|
@@ -617,7 +631,7 @@ function wrapColorField(field) {
|
|
|
617
631
|
|
|
618
632
|
function updateColorPreview(event) {
|
|
619
633
|
const parent = event.target.parentNode;
|
|
620
|
-
if (parent.classList.contains("clr-field")) {
|
|
634
|
+
if (parent && parent.classList && parent.classList.contains("clr-field")) {
|
|
621
635
|
parent.style.color = event.target.value;
|
|
622
636
|
}
|
|
623
637
|
}
|
|
@@ -635,13 +649,14 @@ function closePicker(revert) {
|
|
|
635
649
|
}
|
|
636
650
|
} else {
|
|
637
651
|
const value = colorValue.value;
|
|
638
|
-
if (value
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
pickColor(colorValue.value);
|
|
652
|
+
if (value !== initialFormatColor) {
|
|
653
|
+
if (value !== "") {
|
|
654
|
+
setColorFromStr(value);
|
|
655
|
+
}
|
|
656
|
+
pickColor(value === "" ? "" : colorValue.value);
|
|
643
657
|
}
|
|
644
658
|
}
|
|
659
|
+
currentEl = undefined;
|
|
645
660
|
setTimeout(() => {
|
|
646
661
|
if (oldColor !== prevEl.value) {
|
|
647
662
|
prevEl.dispatchEvent(new Event("change", {
|
|
@@ -650,9 +665,7 @@ function closePicker(revert) {
|
|
|
650
665
|
}
|
|
651
666
|
});
|
|
652
667
|
picker.classList.remove("clr-open");
|
|
653
|
-
|
|
654
|
-
resetVirtualInstance();
|
|
655
|
-
}
|
|
668
|
+
resetVirtualInstance();
|
|
656
669
|
prevEl.dispatchEvent(new Event("close", {
|
|
657
670
|
bubbles: false
|
|
658
671
|
}));
|
|
@@ -661,7 +674,6 @@ function closePicker(revert) {
|
|
|
661
674
|
preventScroll: true
|
|
662
675
|
});
|
|
663
676
|
}
|
|
664
|
-
currentEl = undefined;
|
|
665
677
|
}
|
|
666
678
|
}
|
|
667
679
|
|
|
@@ -680,12 +692,13 @@ function setColorFromStr(str) {
|
|
|
680
692
|
}
|
|
681
693
|
|
|
682
694
|
function getColorFormatFromStr(str) {
|
|
683
|
-
const fmt = str.slice(0, 3).toLowerCase();
|
|
695
|
+
const fmt = str.trim().slice(0, 3).toLowerCase();
|
|
684
696
|
return fmt === "rgb" || fmt === "hsl" ? fmt : "hex";
|
|
685
697
|
}
|
|
686
698
|
|
|
687
699
|
function pickColor(color) {
|
|
688
700
|
color = color ?? colorValue.value;
|
|
701
|
+
initialFormatColor = colorValue.value;
|
|
689
702
|
if (currentEl) {
|
|
690
703
|
currentEl.value = color;
|
|
691
704
|
currentEl.dispatchEvent(new Event("input", {
|
|
@@ -721,13 +734,7 @@ function updateMarkerA11yLabel(saturation, value) {
|
|
|
721
734
|
}
|
|
722
735
|
|
|
723
736
|
function moveMarker(event) {
|
|
724
|
-
|
|
725
|
-
let x = (touch?.pageX ?? event.pageX) - colorAreaDims.x;
|
|
726
|
-
let y = (touch?.pageY ?? event.pageY) - colorAreaDims.y;
|
|
727
|
-
if (container) {
|
|
728
|
-
y += container.scrollTop;
|
|
729
|
-
}
|
|
730
|
-
setMarkerPosition(x, y);
|
|
737
|
+
setMarkerPosition(event.pageX - colorAreaDims.x, event.pageY - colorAreaDims.y);
|
|
731
738
|
event.preventDefault();
|
|
732
739
|
event.stopPropagation();
|
|
733
740
|
}
|
|
@@ -744,7 +751,9 @@ function setMarkerPosition(x, y) {
|
|
|
744
751
|
colorMarker.style.left = `${x}px`;
|
|
745
752
|
colorMarker.style.top = `${y}px`;
|
|
746
753
|
setColorAtPosition(x, y);
|
|
747
|
-
colorMarker.focus(
|
|
754
|
+
colorMarker.focus({
|
|
755
|
+
preventScroll: true
|
|
756
|
+
});
|
|
748
757
|
}
|
|
749
758
|
|
|
750
759
|
function updateColor(rgba = {}, hsva = {}) {
|
|
@@ -756,12 +765,6 @@ function updateColor(rgba = {}, hsva = {}) {
|
|
|
756
765
|
alphaMarker.parentNode.style.color = opaqueHex;
|
|
757
766
|
alphaMarker.style.color = hex;
|
|
758
767
|
colorPreview.style.color = hex;
|
|
759
|
-
colorArea.style.display = "none";
|
|
760
|
-
colorArea.offsetHeight;
|
|
761
|
-
colorArea.style.display = "";
|
|
762
|
-
alphaMarker.nextElementSibling.style.display = "none";
|
|
763
|
-
alphaMarker.nextElementSibling.offsetHeight;
|
|
764
|
-
alphaMarker.nextElementSibling.style.display = "";
|
|
765
768
|
if (format === "mixed") {
|
|
766
769
|
format = currentColor.a === 1 ? "hex" : "rgb";
|
|
767
770
|
} else if (format === "auto") {
|
|
@@ -780,7 +783,10 @@ function updateColor(rgba = {}, hsva = {}) {
|
|
|
780
783
|
colorValue.value = HSLAToStr(HSVAtoHSLA(currentColor));
|
|
781
784
|
break;
|
|
782
785
|
}
|
|
783
|
-
|
|
786
|
+
const formatRadio = picker.querySelector(`.clr-format [value="${format}"]`);
|
|
787
|
+
if (formatRadio) {
|
|
788
|
+
formatRadio.checked = true;
|
|
789
|
+
}
|
|
784
790
|
}
|
|
785
791
|
|
|
786
792
|
function setHue() {
|
|
@@ -802,15 +808,16 @@ function setAlpha() {
|
|
|
802
808
|
}
|
|
803
809
|
|
|
804
810
|
function getFocusableElements() {
|
|
805
|
-
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));
|
|
806
812
|
}
|
|
807
813
|
|
|
808
814
|
function init() {
|
|
809
815
|
container = undefined;
|
|
810
816
|
picker = document.createElement("div");
|
|
811
817
|
picker.setAttribute("id", "clr-picker");
|
|
818
|
+
picker.setAttribute("role", "dialog");
|
|
812
819
|
picker.className = "clr-picker";
|
|
813
|
-
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 `;
|
|
814
821
|
document.body.appendChild(picker);
|
|
815
822
|
colorArea = getEl("clr-color-area");
|
|
816
823
|
colorMarker = getEl("clr-color-marker");
|
|
@@ -822,36 +829,36 @@ function init() {
|
|
|
822
829
|
hueMarker = getEl("clr-hue-marker");
|
|
823
830
|
alphaSlider = getEl("clr-alpha-slider");
|
|
824
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;
|
|
825
843
|
bindFields(settings.el);
|
|
826
844
|
wrapFields(settings.el);
|
|
827
|
-
addListener(picker, "
|
|
828
|
-
picker.classList.remove("clr-keyboard-nav");
|
|
845
|
+
addListener(picker, "pointerdown", event => {
|
|
829
846
|
event.stopPropagation();
|
|
830
847
|
});
|
|
831
|
-
addListener(colorArea, "
|
|
832
|
-
|
|
848
|
+
addListener(colorArea, "pointerdown", () => {
|
|
849
|
+
updateColorAreaDims();
|
|
850
|
+
addListener(document, "pointermove", moveMarker);
|
|
833
851
|
});
|
|
834
852
|
addListener(colorArea, "contextmenu", event => {
|
|
835
853
|
event.preventDefault();
|
|
836
854
|
});
|
|
837
|
-
addListener(colorArea, "touchstart", () => {
|
|
838
|
-
document.addEventListener("touchmove", moveMarker, {
|
|
839
|
-
passive: false
|
|
840
|
-
});
|
|
841
|
-
});
|
|
842
|
-
addListener(colorMarker, "mousedown", () => {
|
|
843
|
-
addListener(document, "mousemove", moveMarker);
|
|
844
|
-
});
|
|
845
|
-
addListener(colorMarker, "touchstart", () => {
|
|
846
|
-
document.addEventListener("touchmove", moveMarker, {
|
|
847
|
-
passive: false
|
|
848
|
-
});
|
|
849
|
-
});
|
|
850
855
|
addListener(colorValue, "change", () => {
|
|
851
856
|
const value = colorValue.value;
|
|
852
857
|
if (currentEl || settings.inline) {
|
|
853
|
-
|
|
854
|
-
|
|
858
|
+
if (value !== "") {
|
|
859
|
+
setColorFromStr(value);
|
|
860
|
+
}
|
|
861
|
+
pickColor(value === "" ? "" : colorValue.value);
|
|
855
862
|
}
|
|
856
863
|
});
|
|
857
864
|
addListener(clearButton, "click", () => {
|
|
@@ -874,33 +881,29 @@ function init() {
|
|
|
874
881
|
closePicker();
|
|
875
882
|
}
|
|
876
883
|
});
|
|
877
|
-
addListener(document, "
|
|
878
|
-
document.removeEventListener("
|
|
879
|
-
});
|
|
880
|
-
addListener(document, "touchend", () => {
|
|
881
|
-
document.removeEventListener("touchmove", moveMarker);
|
|
884
|
+
addListener(document, "pointerup", () => {
|
|
885
|
+
document.removeEventListener("pointermove", moveMarker);
|
|
882
886
|
});
|
|
883
|
-
addListener(document, "
|
|
884
|
-
keyboardNav = false;
|
|
885
|
-
picker.classList.remove("clr-keyboard-nav");
|
|
887
|
+
addListener(document, "pointerdown", () => {
|
|
886
888
|
closePicker();
|
|
887
889
|
});
|
|
888
890
|
addListener(document, "keydown", event => {
|
|
889
891
|
const key = event.key;
|
|
890
892
|
const target = event.target;
|
|
891
893
|
const shiftKey = event.shiftKey;
|
|
892
|
-
const navKeys = [ "Tab", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight" ];
|
|
893
894
|
if (key === "Escape") {
|
|
894
|
-
|
|
895
|
+
if (currentEl) {
|
|
896
|
+
closePicker(true);
|
|
897
|
+
}
|
|
895
898
|
return;
|
|
896
899
|
} else if (key === "Enter" && target.tagName !== "BUTTON") {
|
|
897
|
-
|
|
900
|
+
if (currentEl) {
|
|
901
|
+
event.preventDefault();
|
|
902
|
+
closePicker();
|
|
903
|
+
}
|
|
898
904
|
return;
|
|
899
|
-
} else if (navKeys.includes(key)) {
|
|
900
|
-
keyboardNav = true;
|
|
901
|
-
picker.classList.add("clr-keyboard-nav");
|
|
902
905
|
}
|
|
903
|
-
if (key === "Tab" && target.matches(".clr-picker *")) {
|
|
906
|
+
if (key === "Tab" && target instanceof Element && target.matches(".clr-picker *")) {
|
|
904
907
|
const focusables = getFocusableElements();
|
|
905
908
|
if (focusables.length > 1) {
|
|
906
909
|
const firstFocusable = focusables[0];
|
|
@@ -918,10 +921,8 @@ function init() {
|
|
|
918
921
|
}
|
|
919
922
|
});
|
|
920
923
|
addListener(document, "click", ".clr-field button", event => {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
event.target.nextElementSibling.dispatchEvent(new Event("click", {
|
|
924
|
+
resetVirtualInstance();
|
|
925
|
+
event.target.nextElementSibling?.dispatchEvent(new Event("click", {
|
|
925
926
|
bubbles: true
|
|
926
927
|
}));
|
|
927
928
|
});
|