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