~bibledit/bibledit/ubuntu-cloud

« back to all changes in this revision

Viewing changes to coloris/coloris.js

  • Committer: Teus Benschop
  • Date: 2022-08-30 18:42:32 UTC
  • Revision ID: teusjannette@gmail.com-20220830184232-a5bf5fkj14cqdx01
new upstream version

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 /*!
 
2
 * Copyright (c) 2021 Momo Bassit.
 
3
 * Licensed under the MIT License (MIT)
 
4
 * https://github.com/mdbassit/Coloris
 
5
 */
 
6
 
 
7
(function (window, document, Math) {
 
8
  var ctx = document.createElement('canvas').getContext('2d');
 
9
  var currentColor = { r: 0, g: 0, b: 0, h: 0, s: 0, v: 0, a: 1 };
 
10
  var picker, colorArea, colorAreaDims, colorMarker, colorPreview, colorValue, clearButton,
 
11
  hueSlider, hueMarker, alphaSlider, alphaMarker, currentEl, currentFormat, oldColor;
 
12
 
 
13
  // Default settings
 
14
  var settings = {
 
15
    el: '[data-coloris]',
 
16
    parent: null,
 
17
    theme: 'default',
 
18
    themeMode: 'light',
 
19
    wrap: true,
 
20
    margin: 2,
 
21
    format: 'hex',
 
22
    formatToggle: false,
 
23
    swatches: [],
 
24
    swatchesOnly: false,
 
25
    alpha: true,
 
26
    focusInput: true,
 
27
    autoClose: false,
 
28
    clearButton: {
 
29
      show: false,
 
30
      label: 'Clear' },
 
31
 
 
32
    a11y: {
 
33
      open: 'Open color picker',
 
34
      close: 'Close color picker',
 
35
      marker: 'Saturation: {s}. Brightness: {v}.',
 
36
      hueSlider: 'Hue slider',
 
37
      alphaSlider: 'Opacity slider',
 
38
      input: 'Color value field',
 
39
      format: 'Color format',
 
40
      swatch: 'Color swatch',
 
41
      instruction: 'Saturation and brightness selector. Use up, down, left and right arrow keys to select.' } };
 
42
 
 
43
 
 
44
 
 
45
  /**
 
46
   * Configure the color picker.
 
47
   * @param {object} options Configuration options.
 
48
   */
 
49
  function configure(options) {
 
50
    if (typeof options !== 'object') {
 
51
      return;
 
52
    }
 
53
 
 
54
    for (var key in options) {
 
55
      switch (key) {
 
56
        case 'el':
 
57
          bindFields(options.el);
 
58
          if (options.wrap !== false) {
 
59
            wrapFields(options.el);
 
60
          }
 
61
          break;
 
62
        case 'parent':
 
63
          settings.parent = document.querySelector(options.parent);
 
64
          if (settings.parent) {
 
65
            settings.parent.appendChild(picker);
 
66
          }
 
67
          break;
 
68
        case 'themeMode':
 
69
          settings.themeMode = options.themeMode;
 
70
          if (options.themeMode === 'auto' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
 
71
            settings.themeMode = 'dark';
 
72
          }
 
73
        // The lack of a break statement is intentional
 
74
        case 'theme':
 
75
          if (options.theme) {
 
76
            settings.theme = options.theme;
 
77
          }
 
78
          picker.className = "clr-picker clr-" + settings.theme + " clr-" + settings.themeMode;
 
79
          break;
 
80
        case 'margin':
 
81
          options.margin *= 1;
 
82
          settings.margin = !isNaN(options.margin) ? options.margin : settings.margin;
 
83
          break;
 
84
        case 'wrap':
 
85
          if (options.el && options.wrap) {
 
86
            wrapFields(options.el);
 
87
          }
 
88
          break;
 
89
        case 'formatToggle':
 
90
          getEl('clr-format').style.display = options.formatToggle ? 'block' : 'none';
 
91
          if (options.formatToggle) {
 
92
            settings.format = 'auto';
 
93
          }
 
94
          break;
 
95
        case 'swatches':
 
96
          if (Array.isArray(options.swatches)) {(function () {
 
97
              var swatches = [];
 
98
 
 
99
              options.swatches.forEach(function (swatch, i) {
 
100
                swatches.push("<button id=\"clr-swatch-" + i + "\" aria-labelledby=\"clr-swatch-label clr-swatch-" + i + "\" style=\"color: " + swatch + ";\">" + swatch + "</button>");
 
101
              });
 
102
 
 
103
              getEl('clr-swatches').innerHTML = swatches.length ? "<div>" + swatches.join('') + "</div>" : '';})();
 
104
          }
 
105
          break;
 
106
        case 'swatchesOnly':
 
107
          settings.swatchesOnly = !!options.swatchesOnly;
 
108
          picker.setAttribute('data-minimal', settings.swatchesOnly);
 
109
 
 
110
          if (settings.swatchesOnly) {
 
111
            settings.autoClose = true;
 
112
          }
 
113
          break;
 
114
        case 'alpha':
 
115
          settings.alpha = !!options.alpha;
 
116
          picker.setAttribute('data-alpha', settings.alpha);
 
117
          break;
 
118
        case 'clearButton':
 
119
          var display = 'none';
 
120
 
 
121
          if (options.clearButton.show) {
 
122
            display = 'block';
 
123
          }
 
124
 
 
125
          if (options.clearButton.label) {
 
126
            clearButton.innerHTML = options.clearButton.label;
 
127
          }
 
128
 
 
129
          clearButton.style.display = display;
 
130
          break;
 
131
        case 'a11y':
 
132
          var labels = options.a11y;
 
133
          var update = false;
 
134
 
 
135
          if (typeof labels === 'object') {
 
136
            for (var label in labels) {
 
137
              if (labels[label] && settings.a11y[label]) {
 
138
                settings.a11y[label] = labels[label];
 
139
                update = true;
 
140
              }
 
141
            }
 
142
          }
 
143
 
 
144
          if (update) {
 
145
            var openLabel = getEl('clr-open-label');
 
146
            var swatchLabel = getEl('clr-swatch-label');
 
147
 
 
148
            openLabel.innerHTML = settings.a11y.open;
 
149
            swatchLabel.innerHTML = settings.a11y.swatch;
 
150
            colorPreview.setAttribute('aria-label', settings.a11y.close);
 
151
            hueSlider.setAttribute('aria-label', settings.a11y.hueSlider);
 
152
            alphaSlider.setAttribute('aria-label', settings.a11y.alphaSlider);
 
153
            colorValue.setAttribute('aria-label', settings.a11y.input);
 
154
            colorArea.setAttribute('aria-label', settings.a11y.instruction);
 
155
          }
 
156
        default:
 
157
          settings[key] = options[key];}
 
158
 
 
159
    }
 
160
  }
 
161
 
 
162
  /**
 
163
   * Bind the color picker to input fields that match the selector.
 
164
   * @param {string} selector One or more selectors pointing to input fields.
 
165
   */
 
166
  function bindFields(selector) {
 
167
    // Show the color picker on click on the input fields that match the selector
 
168
    addListener(document, 'click', selector, function (event) {
 
169
      var parent = settings.parent;
 
170
      var coords = event.target.getBoundingClientRect();
 
171
      var scrollY = window.scrollY;
 
172
      var reposition = { left: false, top: false };
 
173
      var offset = { x: 0, y: 0 };
 
174
      var left = coords.x;
 
175
      var top = scrollY + coords.y + coords.height + settings.margin;
 
176
 
 
177
      currentEl = event.target;
 
178
      oldColor = currentEl.value;
 
179
      currentFormat = getColorFormatFromStr(oldColor);
 
180
      picker.classList.add('clr-open');
 
181
 
 
182
      var pickerWidth = picker.offsetWidth;
 
183
      var pickerHeight = picker.offsetHeight;
 
184
 
 
185
      // If the color picker is inside a custom container
 
186
      // set the position relative to it
 
187
      if (parent) {
 
188
        var style = window.getComputedStyle(parent);
 
189
        var marginTop = parseFloat(style.marginTop);
 
190
        var borderTop = parseFloat(style.borderTopWidth);
 
191
 
 
192
        offset = parent.getBoundingClientRect();
 
193
        offset.y += borderTop + scrollY;
 
194
        left -= offset.x;
 
195
        top -= offset.y;
 
196
 
 
197
        if (left + pickerWidth > parent.clientWidth) {
 
198
          left += coords.width - pickerWidth;
 
199
          reposition.left = true;
 
200
        }
 
201
 
 
202
        if (top + pickerHeight > parent.clientHeight - marginTop) {
 
203
          top -= coords.height + pickerHeight + settings.margin * 2;
 
204
          reposition.top = true;
 
205
        }
 
206
 
 
207
        top += parent.scrollTop;
 
208
 
 
209
        // Otherwise set the position relative to the whole document
 
210
      } else {
 
211
        if (left + pickerWidth > document.documentElement.clientWidth) {
 
212
          left += coords.width - pickerWidth;
 
213
          reposition.left = true;
 
214
        }
 
215
 
 
216
        if (top + pickerHeight - scrollY > document.documentElement.clientHeight) {
 
217
          top = scrollY + coords.y - pickerHeight - settings.margin;
 
218
          reposition.top = true;
 
219
        }
 
220
      }
 
221
 
 
222
      picker.classList.toggle('clr-left', reposition.left);
 
223
      picker.classList.toggle('clr-top', reposition.top);
 
224
      picker.style.left = left + "px";
 
225
      picker.style.top = top + "px";
 
226
      colorAreaDims = {
 
227
        width: colorArea.offsetWidth,
 
228
        height: colorArea.offsetHeight,
 
229
        x: picker.offsetLeft + colorArea.offsetLeft + offset.x,
 
230
        y: picker.offsetTop + colorArea.offsetTop + offset.y };
 
231
 
 
232
 
 
233
      setColorFromStr(oldColor);
 
234
 
 
235
      if (settings.focusInput) {
 
236
        colorValue.focus({ preventScroll: true });
 
237
      }
 
238
    });
 
239
 
 
240
    // Update the color preview of the input fields that match the selector
 
241
    addListener(document, 'input', selector, function (event) {
 
242
      var parent = event.target.parentNode;
 
243
 
 
244
      // Only update the preview if the field has been previously wrapped
 
245
      if (parent.classList.contains('clr-field')) {
 
246
        parent.style.color = event.target.value;
 
247
      }
 
248
    });
 
249
  }
 
250
 
 
251
  /**
 
252
   * Wrap the linked input fields in a div that adds a color preview.
 
253
   * @param {string} selector One or more selectors pointing to input fields.
 
254
   */
 
255
  function wrapFields(selector) {
 
256
    document.querySelectorAll(selector).forEach(function (field) {
 
257
      var parentNode = field.parentNode;
 
258
 
 
259
      if (!parentNode.classList.contains('clr-field')) {
 
260
        var wrapper = document.createElement('div');
 
261
 
 
262
        wrapper.innerHTML = "<button aria-labelledby=\"clr-open-label\"></button>";
 
263
        parentNode.insertBefore(wrapper, field);
 
264
        wrapper.setAttribute('class', 'clr-field');
 
265
        wrapper.style.color = field.value;
 
266
        wrapper.appendChild(field);
 
267
      }
 
268
    });
 
269
  }
 
270
 
 
271
  /**
 
272
   * Close the color picker.
 
273
   * @param {boolean} [revert] If true, revert the color to the original value.
 
274
   */
 
275
  function closePicker(revert) {
 
276
    if (currentEl) {
 
277
      // Revert the color to the original value if needed
 
278
      if (revert && oldColor !== currentEl.value) {
 
279
        currentEl.value = oldColor;
 
280
 
 
281
        // Trigger an "input" event to force update the thumbnail next to the input field
 
282
        currentEl.dispatchEvent(new Event('input', { bubbles: true }));
 
283
      }
 
284
 
 
285
      if (oldColor !== currentEl.value) {
 
286
        currentEl.dispatchEvent(new Event('change', { bubbles: true }));
 
287
      }
 
288
 
 
289
      picker.classList.remove('clr-open');
 
290
 
 
291
      if (settings.focusInput) {
 
292
        currentEl.focus({ preventScroll: true });
 
293
      }
 
294
 
 
295
      currentEl = null;
 
296
    }
 
297
  }
 
298
 
 
299
  /**
 
300
   * Set the active color from a string.
 
301
   * @param {string} str String representing a color.
 
302
   */
 
303
  function setColorFromStr(str) {
 
304
    var rgba = strToRGBA(str);
 
305
    var hsva = RGBAtoHSVA(rgba);
 
306
 
 
307
    updateMarkerA11yLabel(hsva.s, hsva.v);
 
308
    updateColor(rgba, hsva);
 
309
 
 
310
    // Update the UI
 
311
    hueSlider.value = hsva.h;
 
312
    picker.style.color = "hsl(" + hsva.h + ", 100%, 50%)";
 
313
    hueMarker.style.left = hsva.h / 360 * 100 + "%";
 
314
 
 
315
    colorMarker.style.left = colorAreaDims.width * hsva.s / 100 + "px";
 
316
    colorMarker.style.top = colorAreaDims.height - colorAreaDims.height * hsva.v / 100 + "px";
 
317
 
 
318
    alphaSlider.value = hsva.a * 100;
 
319
    alphaMarker.style.left = hsva.a * 100 + "%";
 
320
  }
 
321
 
 
322
  /**
 
323
   * Guess the color format from a string.
 
324
   * @param {string} str String representing a color.
 
325
   * @return {string} The color format.
 
326
   */
 
327
  function getColorFormatFromStr(str) {
 
328
    var format = str.substring(0, 3).toLowerCase();
 
329
 
 
330
    if (format === 'rgb' || format === 'hsl') {
 
331
      return format;
 
332
    }
 
333
 
 
334
    return 'hex';
 
335
  }
 
336
 
 
337
  /**
 
338
   * Copy the active color to the linked input field.
 
339
   * @param {number} [color] Color value to override the active color.
 
340
   */
 
341
  function pickColor(color) {
 
342
    if (currentEl) {
 
343
      currentEl.value = color !== undefined ? color : colorValue.value;
 
344
      currentEl.dispatchEvent(new Event('input', { bubbles: true }));
 
345
    }
 
346
  }
 
347
 
 
348
  /**
 
349
   * Set the active color based on a specific point in the color gradient.
 
350
   * @param {number} x Left position.
 
351
   * @param {number} y Top position.
 
352
   */
 
353
  function setColorAtPosition(x, y) {
 
354
    var hsva = {
 
355
      h: hueSlider.value * 1,
 
356
      s: x / colorAreaDims.width * 100,
 
357
      v: 100 - y / colorAreaDims.height * 100,
 
358
      a: alphaSlider.value / 100 };
 
359
 
 
360
    var rgba = HSVAtoRGBA(hsva);
 
361
 
 
362
    updateMarkerA11yLabel(hsva.s, hsva.v);
 
363
    updateColor(rgba, hsva);
 
364
    pickColor();
 
365
  }
 
366
 
 
367
  /**
 
368
   * Update the color marker's accessibility label.
 
369
   * @param {number} saturation
 
370
   * @param {number} value
 
371
   */
 
372
  function updateMarkerA11yLabel(saturation, value) {
 
373
    var label = settings.a11y.marker;
 
374
 
 
375
    saturation = saturation.toFixed(1) * 1;
 
376
    value = value.toFixed(1) * 1;
 
377
    label = label.replace('{s}', saturation);
 
378
    label = label.replace('{v}', value);
 
379
    colorMarker.setAttribute('aria-label', label);
 
380
  }
 
381
 
 
382
  //
 
383
  /**
 
384
   * Get the pageX and pageY positions of the pointer.
 
385
   * @param {object} event The MouseEvent or TouchEvent object.
 
386
   * @return {object} The pageX and pageY positions.
 
387
   */
 
388
  function getPointerPosition(event) {
 
389
    return {
 
390
      pageX: event.changedTouches ? event.changedTouches[0].pageX : event.pageX,
 
391
      pageY: event.changedTouches ? event.changedTouches[0].pageY : event.pageY };
 
392
 
 
393
  }
 
394
 
 
395
  /**
 
396
   * Move the color marker when dragged.
 
397
   * @param {object} event The MouseEvent object.
 
398
   */
 
399
  function moveMarker(event) {
 
400
    var pointer = getPointerPosition(event);
 
401
    var x = pointer.pageX - colorAreaDims.x;
 
402
    var y = pointer.pageY - colorAreaDims.y;
 
403
 
 
404
    if (settings.parent) {
 
405
      y += settings.parent.scrollTop;
 
406
    }
 
407
 
 
408
    x = x < 0 ? 0 : x > colorAreaDims.width ? colorAreaDims.width : x;
 
409
    y = y < 0 ? 0 : y > colorAreaDims.height ? colorAreaDims.height : y;
 
410
 
 
411
    colorMarker.style.left = x + "px";
 
412
    colorMarker.style.top = y + "px";
 
413
 
 
414
    setColorAtPosition(x, y);
 
415
 
 
416
    // Prevent scrolling while dragging the marker
 
417
    event.preventDefault();
 
418
    event.stopPropagation();
 
419
  }
 
420
 
 
421
  /**
 
422
   * Move the color marker when the arrow keys are pressed.
 
423
   * @param {number} offsetX The horizontal amount to move.
 
424
   * * @param {number} offsetY The vertical amount to move.
 
425
   */
 
426
  function moveMarkerOnKeydown(offsetX, offsetY) {
 
427
    var x = colorMarker.style.left.replace('px', '') * 1 + offsetX;
 
428
    var y = colorMarker.style.top.replace('px', '') * 1 + offsetY;
 
429
 
 
430
    colorMarker.style.left = x + "px";
 
431
    colorMarker.style.top = y + "px";
 
432
 
 
433
    setColorAtPosition(x, y);
 
434
  }
 
435
 
 
436
  /**
 
437
   * Update the color picker's input field and preview thumb.
 
438
   * @param {Object} rgba Red, green, blue and alpha values.
 
439
   * @param {Object} [hsva] Hue, saturation, value and alpha values.
 
440
   */
 
441
  function updateColor(rgba, hsva) {if (rgba === void 0) {rgba = {};}if (hsva === void 0) {hsva = {};}
 
442
    var format = settings.format;
 
443
 
 
444
    for (var key in rgba) {
 
445
      currentColor[key] = rgba[key];
 
446
    }
 
447
 
 
448
    for (var _key in hsva) {
 
449
      currentColor[_key] = hsva[_key];
 
450
    }
 
451
 
 
452
    var hex = RGBAToHex(currentColor);
 
453
    var opaqueHex = hex.substring(0, 7);
 
454
 
 
455
    colorMarker.style.color = opaqueHex;
 
456
    alphaMarker.parentNode.style.color = opaqueHex;
 
457
    alphaMarker.style.color = hex;
 
458
    colorPreview.style.color = hex;
 
459
 
 
460
    // Force repaint the color and alpha gradients as a workaround for a Google Chrome bug
 
461
    colorArea.style.display = 'none';
 
462
    colorArea.offsetHeight;
 
463
    colorArea.style.display = '';
 
464
    alphaMarker.nextElementSibling.style.display = 'none';
 
465
    alphaMarker.nextElementSibling.offsetHeight;
 
466
    alphaMarker.nextElementSibling.style.display = '';
 
467
 
 
468
    if (format === 'mixed') {
 
469
      format = currentColor.a === 1 ? 'hex' : 'rgb';
 
470
    } else if (format === 'auto') {
 
471
      format = currentFormat;
 
472
    }
 
473
 
 
474
    switch (format) {
 
475
      case 'hex':
 
476
        colorValue.value = hex;
 
477
        break;
 
478
      case 'rgb':
 
479
        colorValue.value = RGBAToStr(currentColor);
 
480
        break;
 
481
      case 'hsl':
 
482
        colorValue.value = HSLAToStr(HSVAtoHSLA(currentColor));
 
483
        break;}
 
484
 
 
485
 
 
486
    // Select the current format in the format switcher
 
487
    document.querySelector(".clr-format [value=\"" + format + "\"]").checked = true;
 
488
  }
 
489
 
 
490
  /**
 
491
   * Set the hue when its slider is moved.
 
492
   */
 
493
  function setHue() {
 
494
    var hue = hueSlider.value * 1;
 
495
    var x = colorMarker.style.left.replace('px', '') * 1;
 
496
    var y = colorMarker.style.top.replace('px', '') * 1;
 
497
 
 
498
    picker.style.color = "hsl(" + hue + ", 100%, 50%)";
 
499
    hueMarker.style.left = hue / 360 * 100 + "%";
 
500
 
 
501
    setColorAtPosition(x, y);
 
502
  }
 
503
 
 
504
  /**
 
505
   * Set the alpha when its slider is moved.
 
506
   */
 
507
  function setAlpha() {
 
508
    var alpha = alphaSlider.value / 100;
 
509
 
 
510
    alphaMarker.style.left = alpha * 100 + "%";
 
511
    updateColor({ a: alpha });
 
512
    pickColor();
 
513
  }
 
514
 
 
515
  /**
 
516
   * Convert HSVA to RGBA.
 
517
   * @param {object} hsva Hue, saturation, value and alpha values.
 
518
   * @return {object} Red, green, blue and alpha values.
 
519
   */
 
520
  function HSVAtoRGBA(hsva) {
 
521
    var saturation = hsva.s / 100;
 
522
    var value = hsva.v / 100;
 
523
    var chroma = saturation * value;
 
524
    var hueBy60 = hsva.h / 60;
 
525
    var x = chroma * (1 - Math.abs(hueBy60 % 2 - 1));
 
526
    var m = value - chroma;
 
527
 
 
528
    chroma = chroma + m;
 
529
    x = x + m;
 
530
 
 
531
    var index = Math.floor(hueBy60) % 6;
 
532
    var red = [chroma, x, m, m, x, chroma][index];
 
533
    var green = [x, chroma, chroma, x, m, m][index];
 
534
    var blue = [m, m, x, chroma, chroma, x][index];
 
535
 
 
536
    return {
 
537
      r: Math.round(red * 255),
 
538
      g: Math.round(green * 255),
 
539
      b: Math.round(blue * 255),
 
540
      a: hsva.a };
 
541
 
 
542
  }
 
543
 
 
544
  /**
 
545
   * Convert HSVA to HSLA.
 
546
   * @param {object} hsva Hue, saturation, value and alpha values.
 
547
   * @return {object} Hue, saturation, lightness and alpha values.
 
548
   */
 
549
  function HSVAtoHSLA(hsva) {
 
550
    var value = hsva.v / 100;
 
551
    var lightness = value * (1 - hsva.s / 100 / 2);
 
552
    var saturation;
 
553
 
 
554
    if (lightness > 0 && lightness < 1) {
 
555
      saturation = Math.round((value - lightness) / Math.min(lightness, 1 - lightness) * 100);
 
556
    }
 
557
 
 
558
    return {
 
559
      h: hsva.h,
 
560
      s: saturation || 0,
 
561
      l: Math.round(lightness * 100),
 
562
      a: hsva.a };
 
563
 
 
564
  }
 
565
 
 
566
  /**
 
567
   * Convert RGBA to HSVA.
 
568
   * @param {object} rgba Red, green, blue and alpha values.
 
569
   * @return {object} Hue, saturation, value and alpha values.
 
570
   */
 
571
  function RGBAtoHSVA(rgba) {
 
572
    var red = rgba.r / 255;
 
573
    var green = rgba.g / 255;
 
574
    var blue = rgba.b / 255;
 
575
    var xmax = Math.max(red, green, blue);
 
576
    var xmin = Math.min(red, green, blue);
 
577
    var chroma = xmax - xmin;
 
578
    var value = xmax;
 
579
    var hue = 0;
 
580
    var saturation = 0;
 
581
 
 
582
    if (chroma) {
 
583
      if (xmax === red) {hue = (green - blue) / chroma;}
 
584
      if (xmax === green) {hue = 2 + (blue - red) / chroma;}
 
585
      if (xmax === blue) {hue = 4 + (red - green) / chroma;}
 
586
      if (xmax) {saturation = chroma / xmax;}
 
587
    }
 
588
 
 
589
    hue = Math.floor(hue * 60);
 
590
 
 
591
    return {
 
592
      h: hue < 0 ? hue + 360 : hue,
 
593
      s: Math.round(saturation * 100),
 
594
      v: Math.round(value * 100),
 
595
      a: rgba.a };
 
596
 
 
597
  }
 
598
 
 
599
  /**
 
600
   * Parse a string to RGBA.
 
601
   * @param {string} str String representing a color.
 
602
   * @return {object} Red, green, blue and alpha values.
 
603
   */
 
604
  function strToRGBA(str) {
 
605
    var regex = /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i;
 
606
    var match, rgba;
 
607
 
 
608
    // Default to black for invalid color strings
 
609
    ctx.fillStyle = '#000';
 
610
 
 
611
    // Use canvas to convert the string to a valid color string
 
612
    ctx.fillStyle = str;
 
613
    match = regex.exec(ctx.fillStyle);
 
614
 
 
615
    if (match) {
 
616
      rgba = {
 
617
        r: match[3] * 1,
 
618
        g: match[4] * 1,
 
619
        b: match[5] * 1,
 
620
        a: match[6] * 1 };
 
621
 
 
622
 
 
623
      // Workaround to mitigate a Chromium bug where the alpha value is rounded incorrectly
 
624
      rgba.a = +rgba.a.toFixed(2);
 
625
 
 
626
    } else {
 
627
      match = ctx.fillStyle.replace('#', '').match(/.{2}/g).map(function (h) {return parseInt(h, 16);});
 
628
      rgba = {
 
629
        r: match[0],
 
630
        g: match[1],
 
631
        b: match[2],
 
632
        a: 1 };
 
633
 
 
634
    }
 
635
 
 
636
    return rgba;
 
637
  }
 
638
 
 
639
  /**
 
640
   * Convert RGBA to Hex.
 
641
   * @param {object} rgba Red, green, blue and alpha values.
 
642
   * @return {string} Hex color string.
 
643
   */
 
644
  function RGBAToHex(rgba) {
 
645
    var R = rgba.r.toString(16);
 
646
    var G = rgba.g.toString(16);
 
647
    var B = rgba.b.toString(16);
 
648
    var A = '';
 
649
 
 
650
    if (rgba.r < 16) {
 
651
      R = '0' + R;
 
652
    }
 
653
 
 
654
    if (rgba.g < 16) {
 
655
      G = '0' + G;
 
656
    }
 
657
 
 
658
    if (rgba.b < 16) {
 
659
      B = '0' + B;
 
660
    }
 
661
 
 
662
    if (settings.alpha && rgba.a < 1) {
 
663
      var alpha = rgba.a * 255 | 0;
 
664
      A = alpha.toString(16);
 
665
 
 
666
      if (alpha < 16) {
 
667
        A = '0' + A;
 
668
      }
 
669
    }
 
670
 
 
671
    return '#' + R + G + B + A;
 
672
  }
 
673
 
 
674
  /**
 
675
   * Convert RGBA values to a CSS rgb/rgba string.
 
676
   * @param {object} rgba Red, green, blue and alpha values.
 
677
   * @return {string} CSS color string.
 
678
   */
 
679
  function RGBAToStr(rgba) {
 
680
    if (!settings.alpha || rgba.a === 1) {
 
681
      return "rgb(" + rgba.r + ", " + rgba.g + ", " + rgba.b + ")";
 
682
    } else {
 
683
      return "rgba(" + rgba.r + ", " + rgba.g + ", " + rgba.b + ", " + rgba.a + ")";
 
684
    }
 
685
  }
 
686
 
 
687
  /**
 
688
   * Convert HSLA values to a CSS hsl/hsla string.
 
689
   * @param {object} hsla Hue, saturation, lightness and alpha values.
 
690
   * @return {string} CSS color string.
 
691
   */
 
692
  function HSLAToStr(hsla) {
 
693
    if (!settings.alpha || hsla.a === 1) {
 
694
      return "hsl(" + hsla.h + ", " + hsla.s + "%, " + hsla.l + "%)";
 
695
    } else {
 
696
      return "hsla(" + hsla.h + ", " + hsla.s + "%, " + hsla.l + "%, " + hsla.a + ")";
 
697
    }
 
698
  }
 
699
 
 
700
  /**
 
701
   * Init the color picker.
 
702
   */
 
703
  function init() {
 
704
    // Render the UI
 
705
    picker = document.createElement('div');
 
706
    picker.setAttribute('id', 'clr-picker');
 
707
    picker.className = 'clr-picker';
 
708
    picker.innerHTML =
 
709
    "<input id=\"clr-color-value\" class=\"clr-color\" type=\"text\" value=\"\" spellcheck=\"false\" aria-label=\"" + settings.a11y.input + "\">" + ("<div id=\"clr-color-area\" class=\"clr-gradient\" role=\"application\" aria-label=\"" +
 
710
    settings.a11y.instruction + "\">") +
 
711
    '<div id="clr-color-marker" class="clr-marker" tabindex="0"></div>' +
 
712
    '</div>' +
 
713
    '<div class="clr-hue">' + ("<input id=\"clr-hue-slider\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" aria-label=\"" +
 
714
    settings.a11y.hueSlider + "\">") +
 
715
    '<div id="clr-hue-marker"></div>' +
 
716
    '</div>' +
 
717
    '<div class="clr-alpha">' + ("<input id=\"clr-alpha-slider\" type=\"range\" min=\"0\" max=\"100\" step=\"1\" aria-label=\"" +
 
718
    settings.a11y.alphaSlider + "\">") +
 
719
    '<div id="clr-alpha-marker"></div>' +
 
720
    '<span></span>' +
 
721
    '</div>' +
 
722
    '<div id="clr-format" class="clr-format">' +
 
723
    '<fieldset class="clr-segmented">' + ("<legend>" +
 
724
    settings.a11y.format + "</legend>") +
 
725
    '<input id="clr-f1" type="radio" name="clr-format" value="hex">' +
 
726
    '<label for="clr-f1">Hex</label>' +
 
727
    '<input id="clr-f2" type="radio" name="clr-format" value="rgb">' +
 
728
    '<label for="clr-f2">RGB</label>' +
 
729
    '<input id="clr-f3" type="radio" name="clr-format" value="hsl">' +
 
730
    '<label for="clr-f3">HSL</label>' +
 
731
    '<span></span>' +
 
732
    '</fieldset>' +
 
733
    '</div>' +
 
734
    '<div id="clr-swatches" class="clr-swatches"></div>' + ("<button id=\"clr-clear\" class=\"clr-clear\">" +
 
735
    settings.clearButton.label + "</button>") + ("<button id=\"clr-color-preview\" class=\"clr-preview\" aria-label=\"" +
 
736
    settings.a11y.close + "\"></button>") + ("<span id=\"clr-open-label\" hidden>" +
 
737
    settings.a11y.open + "</span>") + ("<span id=\"clr-swatch-label\" hidden>" +
 
738
    settings.a11y.swatch + "</span>");
 
739
 
 
740
    // Append the color picker to the DOM
 
741
    document.body.appendChild(picker);
 
742
 
 
743
    // Reference the UI elements
 
744
    colorArea = getEl('clr-color-area');
 
745
    colorMarker = getEl('clr-color-marker');
 
746
    clearButton = getEl('clr-clear');
 
747
    colorPreview = getEl('clr-color-preview');
 
748
    colorValue = getEl('clr-color-value');
 
749
    hueSlider = getEl('clr-hue-slider');
 
750
    hueMarker = getEl('clr-hue-marker');
 
751
    alphaSlider = getEl('clr-alpha-slider');
 
752
    alphaMarker = getEl('clr-alpha-marker');
 
753
 
 
754
    // Bind the picker to the default selector
 
755
    bindFields(settings.el);
 
756
    wrapFields(settings.el);
 
757
 
 
758
    addListener(picker, 'mousedown', function (event) {
 
759
      picker.classList.remove('clr-keyboard-nav');
 
760
      event.stopPropagation();
 
761
    });
 
762
 
 
763
    addListener(colorArea, 'mousedown', function (event) {
 
764
      addListener(document, 'mousemove', moveMarker);
 
765
    });
 
766
 
 
767
    addListener(colorArea, 'touchstart', function (event) {
 
768
      document.addEventListener('touchmove', moveMarker, { passive: false });
 
769
    });
 
770
 
 
771
    addListener(colorMarker, 'mousedown', function (event) {
 
772
      addListener(document, 'mousemove', moveMarker);
 
773
    });
 
774
 
 
775
    addListener(colorMarker, 'touchstart', function (event) {
 
776
      document.addEventListener('touchmove', moveMarker, { passive: false });
 
777
    });
 
778
 
 
779
    addListener(colorValue, 'change', function (event) {
 
780
      setColorFromStr(colorValue.value);
 
781
      pickColor();
 
782
    });
 
783
 
 
784
    addListener(clearButton, 'click', function (event) {
 
785
      pickColor('');
 
786
      closePicker();
 
787
    });
 
788
 
 
789
    addListener(colorPreview, 'click', function (event) {
 
790
      pickColor();
 
791
      closePicker();
 
792
    });
 
793
 
 
794
    addListener(document, 'click', '.clr-format input', function (event) {
 
795
      currentFormat = event.target.value;
 
796
      updateColor();
 
797
      pickColor();
 
798
    });
 
799
 
 
800
    addListener(picker, 'click', '.clr-swatches button', function (event) {
 
801
      setColorFromStr(event.target.textContent);
 
802
      pickColor();
 
803
 
 
804
      if (settings.autoClose) {
 
805
        closePicker();
 
806
      }
 
807
    });
 
808
 
 
809
    addListener(document, 'mouseup', function (event) {
 
810
      document.removeEventListener('mousemove', moveMarker);
 
811
    });
 
812
 
 
813
    addListener(document, 'touchend', function (event) {
 
814
      document.removeEventListener('touchmove', moveMarker);
 
815
    });
 
816
 
 
817
    addListener(document, 'mousedown', function (event) {
 
818
      picker.classList.remove('clr-keyboard-nav');
 
819
      closePicker();
 
820
    });
 
821
 
 
822
    addListener(document, 'keydown', function (event) {
 
823
      if (event.key === 'Escape') {
 
824
        closePicker(true);
 
825
      } else if (event.key === 'Tab') {
 
826
        picker.classList.add('clr-keyboard-nav');
 
827
      }
 
828
    });
 
829
 
 
830
    addListener(document, 'click', '.clr-field button', function (event) {
 
831
      event.target.nextElementSibling.dispatchEvent(new Event('click', { bubbles: true }));
 
832
    });
 
833
 
 
834
    addListener(colorMarker, 'keydown', function (event) {
 
835
      var movements = {
 
836
        ArrowUp: [0, -1],
 
837
        ArrowDown: [0, 1],
 
838
        ArrowLeft: [-1, 0],
 
839
        ArrowRight: [1, 0] };
 
840
 
 
841
 
 
842
      if (Object.keys(movements).indexOf(event.key) !== -1) {
 
843
        moveMarkerOnKeydown.apply(void 0, movements[event.key]);
 
844
        event.preventDefault();
 
845
      }
 
846
    });
 
847
 
 
848
    addListener(colorArea, 'click', moveMarker);
 
849
    addListener(hueSlider, 'input', setHue);
 
850
    addListener(alphaSlider, 'input', setAlpha);
 
851
  }
 
852
 
 
853
  /**
 
854
   * Shortcut for getElementById to optimize the minified JS.
 
855
   * @param {string} id The element id.
 
856
   * @return {object} The DOM element with the provided id.
 
857
   */
 
858
  function getEl(id) {
 
859
    return document.getElementById(id);
 
860
  }
 
861
 
 
862
  /**
 
863
   * Shortcut for addEventListener to optimize the minified JS.
 
864
   * @param {object} context The context to which the listener is attached.
 
865
   * @param {string} type Event type.
 
866
   * @param {(string|function)} selector Event target if delegation is used, event handler if not.
 
867
   * @param {function} [fn] Event handler if delegation is used.
 
868
   */
 
869
  function addListener(context, type, selector, fn) {
 
870
    var matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
 
871
 
 
872
    // Delegate event to the target of the selector
 
873
    if (typeof selector === 'string') {
 
874
      context.addEventListener(type, function (event) {
 
875
        if (matches.call(event.target, selector)) {
 
876
          fn.call(event.target, event);
 
877
        }
 
878
      });
 
879
 
 
880
      // If the selector is not a string then it's a function
 
881
      // in which case we need regular event listener
 
882
    } else {
 
883
      fn = selector;
 
884
      context.addEventListener(type, fn);
 
885
    }
 
886
  }
 
887
 
 
888
  /**
 
889
   * Call a function only when the DOM is ready.
 
890
   * @param {function} fn The function to call.
 
891
   * @param {array} [args] Arguments to pass to the function.
 
892
   */
 
893
  function DOMReady(fn, args) {
 
894
    args = args !== undefined ? args : [];
 
895
 
 
896
    if (document.readyState !== 'loading') {
 
897
      fn.apply(void 0, args);
 
898
    } else {
 
899
      document.addEventListener('DOMContentLoaded', function () {
 
900
        fn.apply(void 0, args);
 
901
      });
 
902
    }
 
903
  }
 
904
 
 
905
  // Polyfill for Nodelist.forEach
 
906
  if (NodeList !== undefined && NodeList.prototype && !NodeList.prototype.forEach) {
 
907
    NodeList.prototype.forEach = Array.prototype.forEach;
 
908
  }
 
909
 
 
910
  // Expose the color picker to the global scope
 
911
  window.Coloris = function () {
 
912
    var methods = {
 
913
      set: configure,
 
914
      wrap: wrapFields,
 
915
      close: closePicker };
 
916
 
 
917
 
 
918
    function Coloris(options) {
 
919
      DOMReady(function () {
 
920
        if (options) {
 
921
          if (typeof options === 'string') {
 
922
            bindFields(options);
 
923
          } else {
 
924
            configure(options);
 
925
          }
 
926
        }
 
927
      });
 
928
    }var _loop = function _loop(
 
929
 
 
930
    key) {
 
931
      Coloris[key] = function () {for (var _len = arguments.length, args = new Array(_len), _key2 = 0; _key2 < _len; _key2++) {args[_key2] = arguments[_key2];}
 
932
        DOMReady(methods[key], args);
 
933
      };};for (var key in methods) {_loop(key);
 
934
    }
 
935
 
 
936
    return Coloris;
 
937
  }();
 
938
 
 
939
  // Init the color picker when the DOM is ready
 
940
  DOMReady(init);
 
941
 
 
942
})(window, document, Math);
 
 
b'\\ No newline at end of file'