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