@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.
@@ -1,4 +1,4 @@
1
- /* @bornova/colorpicker v1.0.0 */
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
- const ctx = document.createElement("canvas").getContext("2d");
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.floor(hue * 60);
108
+ hue = Math.round(hue * 60);
96
109
  return {
97
- h: hue < 0 ? hue + 360 : 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
- let match, rgba;
106
- ctx.fillStyle = "#000";
107
- ctx.fillStyle = str;
108
- match = regex.exec(ctx.fillStyle);
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
- rgba = {
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
- } else {
117
- match = ctx.fillStyle.replace("#", "").match(/.{2}/g).map(h => parseInt(h, 16));
118
- rgba = {
119
- r: match[0],
120
- g: match[1],
121
- b: match[2],
122
- a: 1
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
- return rgba;
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 | 0).toString(16).padStart(2, "0");
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, keyboardNav, colorAreaDims = {};
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
- let hasInstance = false;
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
- container = options.parent instanceof HTMLElement ? options.parent : document.querySelector(options.parent);
201
- if (container) {
202
- container.appendChild(picker);
203
- settings.parent = options.parent;
204
- if (container === document.body) {
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
- if (options.theme) {
218
- settings.theme = options.theme;
219
- }
220
- picker.className = `clr-picker clr-${settings.theme} clr-${settings.themeMode}`;
221
- if (settings.inline) {
222
- updatePickerPosition();
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
- if (options.el && options.wrap) {
242
- wrapFields(options.el);
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
- if (typeof options.clearButton === "object" && options.clearButton.label) {
299
- settings.clearLabel = options.clearButton.label;
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.innerHTML = settings.clearLabel;
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.innerHTML = settings.closeLabel;
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
- openLabel.innerHTML = settings.a11y.open;
343
- swatchLabel.innerHTML = settings.a11y.swatch;
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 (Object.keys(instances).length === 0) {
368
- hasInstance = false;
369
- if (selector === currentInstanceId) {
370
- resetVirtualInstance();
371
- }
424
+ if (selector === currentInstanceId) {
425
+ resetVirtualInstance();
372
426
  }
373
427
  }
374
428
  function attachVirtualInstance(element) {
375
- if (hasInstance) {
376
- const unsupportedOptions = [ "el", "wrap", "rtl", "inline", "defaultColor", "a11y" ];
377
- for (const selector of Object.keys(instances)) {
378
- const options = instances[selector];
379
- if (element.matches(selector)) {
380
- currentInstanceId = selector;
381
- defaultInstance = {};
382
- unsupportedOptions.forEach(option => delete options[option]);
383
- for (const option of Object.keys(options)) {
384
- defaultInstance[option] = Array.isArray(settings[option]) ? [ ...settings[option] ] : settings[option];
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
- addListener(field, "click", openPicker);
404
- addListener(field, "input", updateColorPreview);
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
- colorValue.setSelectionRange(currentEl.selectionStart, currentEl.selectionEnd);
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 (keyboardNav || settings.swatchesOnly) {
432
- getFocusableElements().shift().focus();
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 updatePickerPosition() {
439
- const parent = container;
440
- const scrollY = window.scrollY;
441
- const pickerWidth = picker.offsetWidth;
442
- const pickerHeight = picker.offsetHeight;
443
- const reposition = {
444
- left: false,
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
- if (parent) {
453
- parentStyle = window.getComputedStyle(parent);
454
- parentMarginTop = parseFloat(parentStyle.marginTop);
455
- parentBorderTop = parseFloat(parentStyle.borderTopWidth);
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
- colorAreaDims = {
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.parentNode;
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
- if (hasInstance) {
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
- const touch = event.changedTouches?.[0];
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
- document.querySelector(`.clr-format [value="${format}"]`).checked = true;
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" aria-label="${settings.a11y.input}">\n <div id="clr-color-area" class="clr-gradient" role="application" aria-label="${settings.a11y.instruction}">\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" aria-label="${settings.a11y.hueSlider}">\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" aria-label="${settings.a11y.alphaSlider}">\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>${settings.a11y.format}</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 <span></span>\n </fieldset>\n </div>\n <div id="clr-swatches" class="clr-swatches"></div>\n <button type="button" id="clr-clear" class="clr-clear" aria-label="${settings.a11y.clear}">${settings.clearLabel}</button>\n <div id="clr-color-preview" class="clr-preview">\n <button type="button" id="clr-close" class="clr-close" aria-label="${settings.a11y.close}">${settings.closeLabel}</button>\n </div>\n <span id="clr-open-label" hidden>${settings.a11y.open}</span>\n <span id="clr-swatch-label" hidden>${settings.a11y.swatch}</span>\n `;
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, "mousedown", event => {
710
- picker.classList.remove("clr-keyboard-nav");
801
+ addListener(picker, "pointerdown", event => {
711
802
  event.stopPropagation();
712
803
  });
713
- addListener(colorArea, "mousedown", () => {
714
- addListener(document, "mousemove", moveMarker);
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
- const color = value === "" ? value : setColorFromStr(value);
736
- pickColor(color);
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, "mouseup", () => {
760
- document.removeEventListener("mousemove", moveMarker);
761
- });
762
- addListener(document, "touchend", () => {
763
- document.removeEventListener("touchmove", moveMarker);
840
+ addListener(document, "pointerup", () => {
841
+ document.removeEventListener("pointermove", moveMarker);
764
842
  });
765
- addListener(document, "mousedown", () => {
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
- closePicker(true);
851
+ if (currentEl) {
852
+ closePicker(true);
853
+ }
777
854
  return;
778
855
  } else if (key === "Enter" && target.tagName !== "BUTTON") {
779
- closePicker();
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
- const firstFocusable = focusables.shift();
788
- const lastFocusable = focusables.pop();
789
- if (shiftKey && target === firstFocusable) {
790
- lastFocusable.focus();
791
- event.preventDefault();
792
- } else if (!shiftKey && target === lastFocusable) {
793
- firstFocusable.focus();
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
- if (hasInstance) {
800
- resetVirtualInstance();
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
  });