/*! * iro.js v5.5.2 * 2016-2021 James Daniel * Licensed under MPL 2.0 * github.com/jaames/iro.js */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.iro = factory()); }(this, function () { 'use strict'; var n,u,t,i,r,o,f={},e=[],c=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;function s(n,l){for(var u in l){ n[u]=l[u]; }return n}function a(n){var l=n.parentNode;l&&l.removeChild(n);}function h(n,l,u){var t,i,r,o,f=arguments;if(l=s({},l),arguments.length>3){ for(u=[u],t=3;t -1; var num = parseFloat(str); return isPercentage ? max / 100 * num : num; } /** * @desc Parse hex str to an int * @param str - hex string to parse */ function parseHexInt(str) { return parseInt(str, 16); } /** * @desc Convert nunber into to 2-digit hex * @param int - number to convert */ function intToHex(_int) { return _int.toString(16).padStart(2, '0'); } var IroColor = /*#__PURE__*/ function () { /** * @constructor Color object * @param value - initial color value */ function IroColor(value, onChange) { // The default Color value this.$ = { h: 0, s: 0, v: 0, a: 1 }; if (value) { this.set(value); } // The watch callback function for this Color will be stored here this.onChange = onChange; this.initialValue = _extends({}, this.$); // copy initial value } /** * @desc Set the Color from any valid value * @param value - new color value */ var _proto = IroColor.prototype; _proto.set = function set(value) { if (typeof value === 'string') { if (/^(?:#?|0x?)[0-9a-fA-F]{3,8}$/.test(value)) { this.hexString = value; } else if (/^rgba?/.test(value)) { this.rgbString = value; } else if (/^hsla?/.test(value)) { this.hslString = value; } } else if (typeof value === 'object') { if (value instanceof IroColor) { this.hsva = value.hsva; } else if ('r' in value && 'g' in value && 'b' in value) { this.rgb = value; } else if ('h' in value && 's' in value && 'v' in value) { this.hsv = value; } else if ('h' in value && 's' in value && 'l' in value) { this.hsl = value; } else if ('kelvin' in value) { this.kelvin = value.kelvin; } } else { throw new Error('Invalid color value'); } } /** * @desc Shortcut to set a specific channel value * @param format - hsv | hsl | rgb * @param channel - individual channel to set, for example if model = hsl, chanel = h | s | l * @param value - new value for the channel */ ; _proto.setChannel = function setChannel(format, channel, value) { var _extends2; this[format] = _extends({}, this[format], (_extends2 = {}, _extends2[channel] = value, _extends2)); } /** * @desc Reset color back to its initial value */ ; _proto.reset = function reset() { this.hsva = this.initialValue; } /** * @desc make new Color instance with the same value as this one */ ; _proto.clone = function clone() { return new IroColor(this); } /** * @desc remove color onChange */ ; _proto.unbind = function unbind() { this.onChange = undefined; } /** * @desc Convert hsv object to rgb * @param hsv - hsv color object */ ; IroColor.hsvToRgb = function hsvToRgb(hsv) { var h = hsv.h / 60; var s = hsv.s / 100; var v = hsv.v / 100; var i = floor(h); var f = h - i; var p = v * (1 - s); var q = v * (1 - f * s); var t = v * (1 - (1 - f) * s); var mod = i % 6; var r = [v, q, p, p, t, v][mod]; var g = [t, v, v, q, p, p][mod]; var b = [p, p, t, v, v, q][mod]; return { r: clamp(r * 255, 0, 255), g: clamp(g * 255, 0, 255), b: clamp(b * 255, 0, 255) }; } /** * @desc Convert rgb object to hsv * @param rgb - rgb object */ ; IroColor.rgbToHsv = function rgbToHsv(rgb) { var r = rgb.r / 255; var g = rgb.g / 255; var b = rgb.b / 255; var max = Math.max(r, g, b); var min = Math.min(r, g, b); var delta = max - min; var hue = 0; var value = max; var saturation = max === 0 ? 0 : delta / max; switch (max) { case min: hue = 0; // achromatic break; case r: hue = (g - b) / delta + (g < b ? 6 : 0); break; case g: hue = (b - r) / delta + 2; break; case b: hue = (r - g) / delta + 4; break; } return { h: hue * 60 % 360, s: clamp(saturation * 100, 0, 100), v: clamp(value * 100, 0, 100) }; } /** * @desc Convert hsv object to hsl * @param hsv - hsv object */ ; IroColor.hsvToHsl = function hsvToHsl(hsv) { var s = hsv.s / 100; var v = hsv.v / 100; var l = (2 - s) * v; var divisor = l <= 1 ? l : 2 - l; // Avoid division by zero when lightness is close to zero var saturation = divisor < 1e-9 ? 0 : s * v / divisor; return { h: hsv.h, s: clamp(saturation * 100, 0, 100), l: clamp(l * 50, 0, 100) }; } /** * @desc Convert hsl object to hsv * @param hsl - hsl object */ ; IroColor.hslToHsv = function hslToHsv(hsl) { var l = hsl.l * 2; var s = hsl.s * (l <= 100 ? l : 200 - l) / 100; // Avoid division by zero when l + s is near 0 var saturation = l + s < 1e-9 ? 0 : 2 * s / (l + s); return { h: hsl.h, s: clamp(saturation * 100, 0, 100), v: clamp((l + s) / 2, 0, 100) }; } /** * @desc Convert a kelvin temperature to an approx, RGB value * @param kelvin - kelvin temperature */ ; IroColor.kelvinToRgb = function kelvinToRgb(kelvin) { var temp = kelvin / 100; var r, g, b; if (temp < 66) { r = 255; g = -155.25485562709179 - 0.44596950469579133 * (g = temp - 2) + 104.49216199393888 * log(g); b = temp < 20 ? 0 : -254.76935184120902 + 0.8274096064007395 * (b = temp - 10) + 115.67994401066147 * log(b); } else { r = 351.97690566805693 + 0.114206453784165 * (r = temp - 55) - 40.25366309332127 * log(r); g = 325.4494125711974 + 0.07943456536662342 * (g = temp - 50) - 28.0852963507957 * log(g); b = 255; } return { r: clamp(floor(r), 0, 255), g: clamp(floor(g), 0, 255), b: clamp(floor(b), 0, 255) }; } /** * @desc Convert an RGB color to an approximate kelvin temperature * @param kelvin - kelvin temperature */ ; IroColor.rgbToKelvin = function rgbToKelvin(rgb) { var r = rgb.r, b = rgb.b; var eps = 0.4; var minTemp = KELVIN_MIN; var maxTemp = KELVIN_MAX; var temp; while (maxTemp - minTemp > eps) { temp = (maxTemp + minTemp) * 0.5; var _rgb = IroColor.kelvinToRgb(temp); if (_rgb.b / _rgb.r >= b / r) { maxTemp = temp; } else { minTemp = temp; } } return temp; }; _createClass(IroColor, [{ key: "hsv", get: function get() { // value is cloned to allow changes to be made to the values before passing them back var value = this.$; return { h: value.h, s: value.s, v: value.v }; }, set: function set(newValue) { var oldValue = this.$; newValue = _extends({}, oldValue, newValue); // If this Color is being watched for changes we need to compare the new and old values to check the difference // Otherwise we can just be lazy if (this.onChange) { // Compute changed values var changes = { h: false, v: false, s: false, a: false }; for (var key in oldValue) { changes[key] = newValue[key] != oldValue[key]; } this.$ = newValue; // If the value has changed, call hook callback if (changes.h || changes.s || changes.v || changes.a) { this.onChange(this, changes); } } else { this.$ = newValue; } } }, { key: "hsva", get: function get() { return _extends({}, this.$); }, set: function set(value) { this.hsv = value; } }, { key: "hue", get: function get() { return this.$.h; }, set: function set(value) { this.hsv = { h: value }; } }, { key: "saturation", get: function get() { return this.$.s; }, set: function set(value) { this.hsv = { s: value }; } }, { key: "value", get: function get() { return this.$.v; }, set: function set(value) { this.hsv = { v: value }; } }, { key: "alpha", get: function get() { return this.$.a; }, set: function set(value) { this.hsv = _extends({}, this.hsv, { a: value }); } }, { key: "kelvin", get: function get() { return IroColor.rgbToKelvin(this.rgb); }, set: function set(value) { this.rgb = IroColor.kelvinToRgb(value); } }, { key: "red", get: function get() { var rgb = this.rgb; return rgb.r; }, set: function set(value) { this.rgb = _extends({}, this.rgb, { r: value }); } }, { key: "green", get: function get() { var rgb = this.rgb; return rgb.g; }, set: function set(value) { this.rgb = _extends({}, this.rgb, { g: value }); } }, { key: "blue", get: function get() { var rgb = this.rgb; return rgb.b; }, set: function set(value) { this.rgb = _extends({}, this.rgb, { b: value }); } }, { key: "rgb", get: function get() { var _IroColor$hsvToRgb = IroColor.hsvToRgb(this.$), r = _IroColor$hsvToRgb.r, g = _IroColor$hsvToRgb.g, b = _IroColor$hsvToRgb.b; return { r: round(r), g: round(g), b: round(b) }; }, set: function set(value) { this.hsv = _extends({}, IroColor.rgbToHsv(value), { a: value.a === undefined ? 1 : value.a }); } }, { key: "rgba", get: function get() { return _extends({}, this.rgb, { a: this.alpha }); }, set: function set(value) { this.rgb = value; } }, { key: "hsl", get: function get() { var _IroColor$hsvToHsl = IroColor.hsvToHsl(this.$), h = _IroColor$hsvToHsl.h, s = _IroColor$hsvToHsl.s, l = _IroColor$hsvToHsl.l; return { h: round(h), s: round(s), l: round(l) }; }, set: function set(value) { this.hsv = _extends({}, IroColor.hslToHsv(value), { a: value.a === undefined ? 1 : value.a }); } }, { key: "hsla", get: function get() { return _extends({}, this.hsl, { a: this.alpha }); }, set: function set(value) { this.hsl = value; } }, { key: "rgbString", get: function get() { var rgb = this.rgb; return "rgb(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ")"; }, set: function set(value) { var match; var r, g, b, a = 1; if (match = REGEX_FUNCTIONAL_RGB.exec(value)) { r = parseUnit(match[1], 255); g = parseUnit(match[2], 255); b = parseUnit(match[3], 255); } else if (match = REGEX_FUNCTIONAL_RGBA.exec(value)) { r = parseUnit(match[1], 255); g = parseUnit(match[2], 255); b = parseUnit(match[3], 255); a = parseUnit(match[4], 1); } if (match) { this.rgb = { r: r, g: g, b: b, a: a }; } else { throw new Error('Invalid rgb string'); } } }, { key: "rgbaString", get: function get() { var rgba = this.rgba; return "rgba(" + rgba.r + ", " + rgba.g + ", " + rgba.b + ", " + rgba.a + ")"; }, set: function set(value) { this.rgbString = value; } }, { key: "hexString", get: function get() { var rgb = this.rgb; return "#" + intToHex(rgb.r) + intToHex(rgb.g) + intToHex(rgb.b); }, set: function set(value) { var match; var r, g, b, a = 255; if (match = REGEX_HEX_3.exec(value)) { r = parseHexInt(match[1]) * 17; g = parseHexInt(match[2]) * 17; b = parseHexInt(match[3]) * 17; } else if (match = REGEX_HEX_4.exec(value)) { r = parseHexInt(match[1]) * 17; g = parseHexInt(match[2]) * 17; b = parseHexInt(match[3]) * 17; a = parseHexInt(match[4]) * 17; } else if (match = REGEX_HEX_6.exec(value)) { r = parseHexInt(match[1]); g = parseHexInt(match[2]); b = parseHexInt(match[3]); } else if (match = REGEX_HEX_8.exec(value)) { r = parseHexInt(match[1]); g = parseHexInt(match[2]); b = parseHexInt(match[3]); a = parseHexInt(match[4]); } if (match) { this.rgb = { r: r, g: g, b: b, a: a / 255 }; } else { throw new Error('Invalid hex string'); } } }, { key: "hex8String", get: function get() { var rgba = this.rgba; return "#" + intToHex(rgba.r) + intToHex(rgba.g) + intToHex(rgba.b) + intToHex(floor(rgba.a * 255)); }, set: function set(value) { this.hexString = value; } }, { key: "hslString", get: function get() { var hsl = this.hsl; return "hsl(" + hsl.h + ", " + hsl.s + "%, " + hsl.l + "%)"; }, set: function set(value) { var match; var h, s, l, a = 1; if (match = REGEX_FUNCTIONAL_HSL.exec(value)) { h = parseUnit(match[1], 360); s = parseUnit(match[2], 100); l = parseUnit(match[3], 100); } else if (match = REGEX_FUNCTIONAL_HSLA.exec(value)) { h = parseUnit(match[1], 360); s = parseUnit(match[2], 100); l = parseUnit(match[3], 100); a = parseUnit(match[4], 1); } if (match) { this.hsl = { h: h, s: s, l: l, a: a }; } else { throw new Error('Invalid hsl string'); } } }, { key: "hslaString", get: function get() { var hsla = this.hsla; return "hsla(" + hsla.h + ", " + hsla.s + "%, " + hsla.l + "%, " + hsla.a + ")"; }, set: function set(value) { this.hslString = value; } }]); return IroColor; }(); var sliderDefaultOptions = { sliderShape: 'bar', sliderType: 'value', minTemperature: 2200, maxTemperature: 11000 }; /** * @desc Get the bounding dimensions of the slider * @param props - slider props */ function getSliderDimensions(props) { var _sliderSize; var width = props.width, sliderSize = props.sliderSize, borderWidth = props.borderWidth, handleRadius = props.handleRadius, padding = props.padding, sliderShape = props.sliderShape; var ishorizontal = props.layoutDirection === 'horizontal'; // automatically calculate sliderSize if its not defined sliderSize = (_sliderSize = sliderSize) != null ? _sliderSize : padding * 2 + handleRadius * 2; if (sliderShape === 'circle') { return { handleStart: props.padding + props.handleRadius, handleRange: width - padding * 2 - handleRadius * 2, width: width, height: width, cx: width / 2, cy: width / 2, radius: width / 2 - borderWidth / 2 }; } else { return { handleStart: sliderSize / 2, handleRange: width - sliderSize, radius: sliderSize / 2, x: 0, y: 0, width: ishorizontal ? sliderSize : width, height: ishorizontal ? width : sliderSize }; } } /** * @desc Get the current slider value for a given color, as a percentage * @param props - slider props * @param color */ function getCurrentSliderValue(props, color) { var hsva = color.hsva; var rgb = color.rgb; switch (props.sliderType) { case 'red': return rgb.r / 2.55; case 'green': return rgb.g / 2.55; case 'blue': return rgb.b / 2.55; case 'alpha': return hsva.a * 100; case 'kelvin': var minTemperature = props.minTemperature, maxTemperature = props.maxTemperature; var temperatureRange = maxTemperature - minTemperature; var percent = (color.kelvin - minTemperature) / temperatureRange * 100; // clmap percentage return Math.max(0, Math.min(percent, 100)); case 'hue': return hsva.h /= 3.6; case 'saturation': return hsva.s; case 'value': default: return hsva.v; } } /** * @desc Get the current slider value from user input * @param props - slider props * @param x - global input x position * @param y - global input y position */ function getSliderValueFromInput(props, x, y) { var _getSliderDimensions = getSliderDimensions(props), handleRange = _getSliderDimensions.handleRange, handleStart = _getSliderDimensions.handleStart; var handlePos; if (props.layoutDirection === 'horizontal') { handlePos = -1 * y + handleRange + handleStart; } else { handlePos = x - handleStart; } // clamp handle position handlePos = Math.max(Math.min(handlePos, handleRange), 0); var percent = Math.round(100 / handleRange * handlePos); switch (props.sliderType) { case 'kelvin': var minTemperature = props.minTemperature, maxTemperature = props.maxTemperature; var temperatureRange = maxTemperature - minTemperature; return minTemperature + temperatureRange * (percent / 100); case 'alpha': return percent / 100; case 'hue': return percent * 3.6; case 'red': case 'blue': case 'green': return percent * 2.55; default: return percent; } } /** * @desc Get the current handle position for a given color * @param props - slider props * @param color */ function getSliderHandlePosition(props, color) { var _getSliderDimensions2 = getSliderDimensions(props), width = _getSliderDimensions2.width, height = _getSliderDimensions2.height, handleRange = _getSliderDimensions2.handleRange, handleStart = _getSliderDimensions2.handleStart; var ishorizontal = props.layoutDirection === 'horizontal'; var sliderValue = getCurrentSliderValue(props, color); var midPoint = ishorizontal ? width / 2 : height / 2; var handlePos = handleStart + sliderValue / 100 * handleRange; if (ishorizontal) { handlePos = -1 * handlePos + handleRange + handleStart * 2; } return { x: ishorizontal ? midPoint : handlePos, y: ishorizontal ? handlePos : midPoint }; } /** * @desc Get the gradient stops for a slider * @param props - slider props * @param color */ function getSliderGradient(props, color) { var hsv = color.hsv; var rgb = color.rgb; switch (props.sliderType) { case 'red': return [[0, "rgb(" + 0 + "," + rgb.g + "," + rgb.b + ")"], [100, "rgb(" + 255 + "," + rgb.g + "," + rgb.b + ")"]]; case 'green': return [[0, "rgb(" + rgb.r + "," + 0 + "," + rgb.b + ")"], [100, "rgb(" + rgb.r + "," + 255 + "," + rgb.b + ")"]]; case 'blue': return [[0, "rgb(" + rgb.r + "," + rgb.g + "," + 0 + ")"], [100, "rgb(" + rgb.r + "," + rgb.g + "," + 255 + ")"]]; case 'alpha': return [[0, "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + ",0)"], [100, "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"]]; case 'kelvin': var stops = []; var min = props.minTemperature; var max = props.maxTemperature; var numStops = 8; var range = max - min; for (var kelvin = min, stop = 0; kelvin < max; kelvin += range / numStops, stop += 1) { var _IroColor$kelvinToRgb = IroColor.kelvinToRgb(kelvin), r = _IroColor$kelvinToRgb.r, g = _IroColor$kelvinToRgb.g, b = _IroColor$kelvinToRgb.b; stops.push([100 / numStops * stop, "rgb(" + r + "," + g + "," + b + ")"]); } return stops; case 'hue': return [[0, '#f00'], [16.666, '#ff0'], [33.333, '#0f0'], [50, '#0ff'], [66.666, '#00f'], [83.333, '#f0f'], [100, '#f00']]; case 'saturation': var noSat = IroColor.hsvToHsl({ h: hsv.h, s: 0, v: hsv.v }); var fullSat = IroColor.hsvToHsl({ h: hsv.h, s: 100, v: hsv.v }); return [[0, "hsl(" + noSat.h + "," + noSat.s + "%," + noSat.l + "%)"], [100, "hsl(" + fullSat.h + "," + fullSat.s + "%," + fullSat.l + "%)"]]; case 'value': default: var hsl = IroColor.hsvToHsl({ h: hsv.h, s: hsv.s, v: 100 }); return [[0, '#000'], [100, "hsl(" + hsl.h + "," + hsl.s + "%," + hsl.l + "%)"]]; } } var TAU = Math.PI * 2; // javascript's modulo operator doesn't produce positive numbers with negative input // https://dev.to/maurobringolf/a-neat-trick-to-compute-modulo-of-negative-numbers-111e var mod = function mod(a, n) { return (a % n + n) % n; }; // distance between points (x, y) and (0, 0) var dist = function dist(x, y) { return Math.sqrt(x * x + y * y); }; /** * @param props - wheel props * @internal */ function getHandleRange(props) { return props.width / 2 - props.padding - props.handleRadius - props.borderWidth; } /** * Returns true if point (x, y) lands inside the wheel * @param props - wheel props * @param x * @param y */ function isInputInsideWheel(props, x, y) { var _getWheelDimensions = getWheelDimensions(props), cx = _getWheelDimensions.cx, cy = _getWheelDimensions.cy; var r = props.width / 2; return dist(cx - x, cy - y) < r; } /** * @desc Get the point as the center of the wheel * @param props - wheel props */ function getWheelDimensions(props) { var r = props.width / 2; return { width: props.width, radius: r - props.borderWidth, cx: r, cy: r }; } /** * @desc Translate an angle according to wheelAngle and wheelDirection * @param props - wheel props * @param angle - input angle */ function translateWheelAngle(props, angle, invert) { var wheelAngle = props.wheelAngle; var wheelDirection = props.wheelDirection; // inverted and clockwisee if (invert && wheelDirection === 'clockwise') { angle = wheelAngle + angle; } // clockwise (input handling) else if (wheelDirection === 'clockwise') { angle = 360 - wheelAngle + angle; } // inverted and anticlockwise else if (invert && wheelDirection === 'anticlockwise') { angle = wheelAngle + 180 - angle; } // anticlockwise (input handling) else if (wheelDirection === 'anticlockwise') { angle = wheelAngle - angle; } return mod(angle, 360); } /** * @desc Get the current handle position for a given color * @param props - wheel props * @param color */ function getWheelHandlePosition(props, color) { var hsv = color.hsv; var _getWheelDimensions2 = getWheelDimensions(props), cx = _getWheelDimensions2.cx, cy = _getWheelDimensions2.cy; var handleRange = getHandleRange(props); var handleAngle = (180 + translateWheelAngle(props, hsv.h, true)) * (TAU / 360); var handleDist = hsv.s / 100 * handleRange; var direction = props.wheelDirection === 'clockwise' ? -1 : 1; return { x: cx + handleDist * Math.cos(handleAngle) * direction, y: cy + handleDist * Math.sin(handleAngle) * direction }; } /** * @desc Get the current wheel value from user input * @param props - wheel props * @param x - global input x position * @param y - global input y position */ function getWheelValueFromInput(props, x, y) { var _getWheelDimensions3 = getWheelDimensions(props), cx = _getWheelDimensions3.cx, cy = _getWheelDimensions3.cy; var handleRange = getHandleRange(props); x = cx - x; y = cy - y; // Calculate the hue by converting the angle to radians var hue = translateWheelAngle(props, Math.atan2(-y, -x) * (360 / TAU)); // Find the point's distance from the center of the wheel // This is used to show the saturation level var handleDist = Math.min(dist(x, y), handleRange); return { h: Math.round(hue), s: Math.round(100 / handleRange * handleDist) }; } /** * @desc Get the bounding dimensions of the box * @param props - box props */ function getBoxDimensions(props) { var width = props.width, boxHeight = props.boxHeight, padding = props.padding, handleRadius = props.handleRadius; return { width: width, height: boxHeight != null ? boxHeight : width, radius: padding + handleRadius }; } /** * @desc Get the current box value from user input * @param props - box props * @param x - global input x position * @param y - global input y position */ function getBoxValueFromInput(props, x, y) { var _getBoxDimensions = getBoxDimensions(props), width = _getBoxDimensions.width, height = _getBoxDimensions.height, radius = _getBoxDimensions.radius; var handleStart = radius; var handleRangeX = width - radius * 2; var handleRangeY = height - radius * 2; var percentX = (x - handleStart) / handleRangeX * 100; var percentY = (y - handleStart) / handleRangeY * 100; return { s: Math.max(0, Math.min(percentX, 100)), v: Math.max(0, Math.min(100 - percentY, 100)) }; } /** * @desc Get the current box handle position for a given color * @param props - box props * @param color */ function getBoxHandlePosition(props, color) { var _getBoxDimensions2 = getBoxDimensions(props), width = _getBoxDimensions2.width, height = _getBoxDimensions2.height, radius = _getBoxDimensions2.radius; var hsv = color.hsv; var handleStart = radius; var handleRangeX = width - radius * 2; var handleRangeY = height - radius * 2; return { x: handleStart + hsv.s / 100 * handleRangeX, y: handleStart + (handleRangeY - hsv.v / 100 * handleRangeY) }; } /** * @desc Get the gradient stops for a box * @param props - box props * @param color */ function getBoxGradients(props, color) { var hue = color.hue; return [// saturation gradient [[0, '#fff'], [100, "hsl(" + hue + ",100%,50%)"]], // lightness gradient [[0, 'rgba(0,0,0,0)'], [100, '#000']]]; } // Keep track of html elements for resolveSvgUrl // getElementsByTagName returns a live HTMLCollection, which stays in sync with the DOM tree // So it only needs to be called once var BASE_ELEMENTS; /** * @desc Resolve an SVG reference URL * This is required to work around how Safari and iOS webviews handle gradient URLS under certain conditions * If a page is using a client-side routing library which makes use of the HTML tag, * Safari won't be able to render SVG gradients properly (as they are referenced by URLs) * More info on the problem: * https://stackoverflow.com/questions/19742805/angular-and-svg-filters/19753427#19753427 * https://github.com/jaames/iro.js/issues/18 * https://github.com/jaames/iro.js/issues/45 * https://github.com/jaames/iro.js/pull/89 * @props url - SVG reference URL */ function resolveSvgUrl(url) { if (!BASE_ELEMENTS) { BASE_ELEMENTS = document.getElementsByTagName('base'); } // Sniff useragent string to check if the user is running Safari var ua = window.navigator.userAgent; var isSafari = /^((?!chrome|android).)*safari/i.test(ua); var isIos = /iPhone|iPod|iPad/i.test(ua); var location = window.location; return (isSafari || isIos) && BASE_ELEMENTS.length > 0 ? location.protocol + "//" + location.host + location.pathname + location.search + url : url; } /** * @desc Given a specifc (x, y) position, test if there's a handle there and return its index, else return null. * This is used for components like the box and wheel which support multiple handles when multicolor is active * @props x - point x position * @props y - point y position * @props handlePositions - array of {x, y} coords for each handle */ function getHandleAtPoint(props, x, y, handlePositions) { for (var i = 0; i < handlePositions.length; i++) { var dX = handlePositions[i].x - x; var dY = handlePositions[i].y - y; var dist = Math.sqrt(dX * dX + dY * dY); if (dist < props.handleRadius) { return i; } } return null; } function cssBorderStyles(props) { return { boxSizing: 'border-box', border: props.borderWidth + "px solid " + props.borderColor }; } function cssGradient(type, direction, stops) { return type + "-gradient(" + direction + ", " + stops.map(function (_ref) { var o = _ref[0], col = _ref[1]; return col + " " + o + "%"; }).join(',') + ")"; } function cssValue(value) { if (typeof value === 'string') { return value; } return value + "px"; } var iroColorPickerOptionDefaults = { width: 300, height: 300, color: '#fff', colors: [], padding: 6, layoutDirection: 'vertical', borderColor: '#fff', borderWidth: 0, handleRadius: 8, activeHandleRadius: null, handleSvg: null, handleProps: { x: 0, y: 0 }, wheelLightness: true, wheelAngle: 0, wheelDirection: 'anticlockwise', sliderSize: null, sliderMargin: 12, boxHeight: null }; var SECONDARY_EVENTS = ["mousemove" /* MouseMove */, "touchmove" /* TouchMove */, "mouseup" /* MouseUp */, "touchend" /* TouchEnd */]; // Base component class for iro UI components // This extends the Preact component class to allow them to react to mouse/touch input events by themselves var IroComponentWrapper = /*@__PURE__*/(function (Component) { function IroComponentWrapper(props) { Component.call(this, props); // Generate unique ID for the component // This can be used to generate unique IDs for gradients, etc this.uid = (Math.random() + 1).toString(36).substring(5); } if ( Component ) IroComponentWrapper.__proto__ = Component; IroComponentWrapper.prototype = Object.create( Component && Component.prototype ); IroComponentWrapper.prototype.constructor = IroComponentWrapper; IroComponentWrapper.prototype.render = function render (props) { var eventHandler = this.handleEvent.bind(this); var rootProps = { onMouseDown: eventHandler, // https://github.com/jaames/iro.js/issues/126 // https://github.com/preactjs/preact/issues/2113#issuecomment-553408767 ontouchstart: eventHandler, }; var isHorizontal = props.layoutDirection === 'horizontal'; var margin = props.margin === null ? props.sliderMargin : props.margin; var rootStyles = { overflow: 'visible', display: isHorizontal ? 'inline-block' : 'block' }; // first component shouldn't have any margin if (props.index > 0) { rootStyles[isHorizontal ? 'marginLeft' : 'marginTop'] = margin; } return (h(d, null, props.children(this.uid, rootProps, rootStyles))); }; // More info on handleEvent: // https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38 // TL;DR this lets us have a single point of entry for multiple events, and we can avoid callback/binding hell IroComponentWrapper.prototype.handleEvent = function handleEvent (e) { var this$1 = this; var inputHandler = this.props.onInput; // Get the screen position of the component var bounds = this.base.getBoundingClientRect(); // Prefect default browser action e.preventDefault(); // Detect if the event is a touch event by checking if it has the `touches` property // If it is a touch event, use the first touch input var point = e.touches ? e.changedTouches[0] : e; var x = point.clientX - bounds.left; var y = point.clientY - bounds.top; switch (e.type) { case "mousedown" /* MouseDown */: case "touchstart" /* TouchStart */: var result = inputHandler(x, y, 0 /* Start */); if (result !== false) { SECONDARY_EVENTS.forEach(function (event) { document.addEventListener(event, this$1, { passive: false }); }); } break; case "mousemove" /* MouseMove */: case "touchmove" /* TouchMove */: inputHandler(x, y, 1 /* Move */); break; case "mouseup" /* MouseUp */: case "touchend" /* TouchEnd */: inputHandler(x, y, 2 /* End */); SECONDARY_EVENTS.forEach(function (event) { document.removeEventListener(event, this$1, { passive: false }); }); break; } }; return IroComponentWrapper; }(m)); function IroHandle(props) { var radius = props.r; var url = props.url; var cx = radius; var cy = radius; return (h("svg", { className: ("IroHandle IroHandle--" + (props.index) + " " + (props.isActive ? 'IroHandle--isActive' : '')), style: { '-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0);', transform: ("translate(" + (cssValue(props.x)) + ", " + (cssValue(props.y)) + ")"), willChange: 'transform', top: cssValue(-radius), left: cssValue(-radius), width: cssValue(radius * 2), height: cssValue(radius * 2), position: 'absolute', overflow: 'visible' } }, url && (h("use", Object.assign({ xlinkHref: resolveSvgUrl(url) }, props.props))), !url && (h("circle", { cx: cx, cy: cy, r: radius, fill: "none", "stroke-width": 2, stroke: "#000" })), !url && (h("circle", { cx: cx, cy: cy, r: radius - 2, fill: props.fill, "stroke-width": 2, stroke: "#fff" })))); } IroHandle.defaultProps = { fill: 'none', x: 0, y: 0, r: 8, url: null, props: { x: 0, y: 0 } }; function IroSlider(props) { var activeIndex = props.activeIndex; var activeColor = (activeIndex !== undefined && activeIndex < props.colors.length) ? props.colors[activeIndex] : props.color; var ref = getSliderDimensions(props); var width = ref.width; var height = ref.height; var radius = ref.radius; var handlePos = getSliderHandlePosition(props, activeColor); var gradient = getSliderGradient(props, activeColor); function handleInput(x, y, type) { var value = getSliderValueFromInput(props, x, y); props.parent.inputActive = true; activeColor[props.sliderType] = value; props.onInput(type, props.id); } return (h(IroComponentWrapper, Object.assign({}, props, { onInput: handleInput }), function (uid, rootProps, rootStyles) { return (h("div", Object.assign({}, rootProps, { className: "IroSlider", style: Object.assign({}, {position: 'relative', width: cssValue(width), height: cssValue(height), borderRadius: cssValue(radius), // checkered bg to represent alpha background: "conic-gradient(#ccc 25%, #fff 0 50%, #ccc 0 75%, #fff 0)", backgroundSize: '8px 8px'}, rootStyles) }), h("div", { className: "IroSliderGradient", style: Object.assign({}, {position: 'absolute', top: 0, left: 0, width: "100%", height: "100%", borderRadius: cssValue(radius), background: cssGradient('linear', props.layoutDirection === 'horizontal' ? 'to top' : 'to right', gradient)}, cssBorderStyles(props)) }), h(IroHandle, { isActive: true, index: activeColor.index, r: props.handleRadius, url: props.handleSvg, props: props.handleProps, x: handlePos.x, y: handlePos.y }))); })); } IroSlider.defaultProps = Object.assign({}, sliderDefaultOptions); function IroBox(props) { var ref = getBoxDimensions(props); var width = ref.width; var height = ref.height; var radius = ref.radius; var colors = props.colors; var colorPicker = props.parent; var activeIndex = props.activeIndex; var activeColor = (activeIndex !== undefined && activeIndex < props.colors.length) ? props.colors[activeIndex] : props.color; var gradients = getBoxGradients(props, activeColor); var handlePositions = colors.map(function (color) { return getBoxHandlePosition(props, color); }); function handleInput(x, y, inputType) { if (inputType === 0 /* Start */) { // getHandleAtPoint() returns the index for the handle if the point 'hits' it, or null otherwise var activeHandle = getHandleAtPoint(props, x, y, handlePositions); // If the input hit a handle, set it as the active handle, but don't update the color if (activeHandle !== null) { colorPicker.setActiveColor(activeHandle); } // If the input didn't hit a handle, set the currently active handle to that position else { colorPicker.inputActive = true; activeColor.hsv = getBoxValueFromInput(props, x, y); props.onInput(inputType, props.id); } } // move is fired when the user has started dragging else if (inputType === 1 /* Move */) { colorPicker.inputActive = true; activeColor.hsv = getBoxValueFromInput(props, x, y); } // let the color picker fire input:start, input:move or input:end events props.onInput(inputType, props.id); } return (h(IroComponentWrapper, Object.assign({}, props, { onInput: handleInput }), function (uid, rootProps, rootStyles) { return (h("div", Object.assign({}, rootProps, { className: "IroBox", style: Object.assign({}, {width: cssValue(width), height: cssValue(height), position: 'relative'}, rootStyles) }), h("div", { className: "IroBox", style: Object.assign({}, {width: '100%', height: '100%', borderRadius: cssValue(radius)}, cssBorderStyles(props), {background: cssGradient('linear', 'to bottom', gradients[1]) + ',' + cssGradient('linear', 'to right', gradients[0])}) }), colors.filter(function (color) { return color !== activeColor; }).map(function (color) { return (h(IroHandle, { isActive: false, index: color.index, fill: color.hslString, r: props.handleRadius, url: props.handleSvg, props: props.handleProps, x: handlePositions[color.index].x, y: handlePositions[color.index].y })); }), h(IroHandle, { isActive: true, index: activeColor.index, fill: activeColor.hslString, r: props.activeHandleRadius || props.handleRadius, url: props.handleSvg, props: props.handleProps, x: handlePositions[activeColor.index].x, y: handlePositions[activeColor.index].y }))); })); } var HUE_GRADIENT_CLOCKWISE = 'conic-gradient(red, yellow, lime, aqua, blue, magenta, red)'; var HUE_GRADIENT_ANTICLOCKWISE = 'conic-gradient(red, magenta, blue, aqua, lime, yellow, red)'; function IroWheel(props) { var ref = getWheelDimensions(props); var width = ref.width; var colors = props.colors; var borderWidth = props.borderWidth; var colorPicker = props.parent; var activeColor = props.color; var hsv = activeColor.hsv; var handlePositions = colors.map(function (color) { return getWheelHandlePosition(props, color); }); var circleStyles = { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', borderRadius: '50%', boxSizing: 'border-box' }; function handleInput(x, y, inputType) { if (inputType === 0 /* Start */) { // input hitbox is a square, // so we want to ignore any initial clicks outside the circular shape of the wheel if (!isInputInsideWheel(props, x, y)) { // returning false will cease all event handling for this interaction return false; } // getHandleAtPoint() returns the index for the handle if the point 'hits' it, or null otherwise var activeHandle = getHandleAtPoint(props, x, y, handlePositions); // If the input hit a handle, set it as the active handle, but don't update the color if (activeHandle !== null) { colorPicker.setActiveColor(activeHandle); } // If the input didn't hit a handle, set the currently active handle to that position else { colorPicker.inputActive = true; activeColor.hsv = getWheelValueFromInput(props, x, y); props.onInput(inputType, props.id); } } // move is fired when the user has started dragging else if (inputType === 1 /* Move */) { colorPicker.inputActive = true; activeColor.hsv = getWheelValueFromInput(props, x, y); } // let the color picker fire input:start, input:move or input:end events props.onInput(inputType, props.id); } return (h(IroComponentWrapper, Object.assign({}, props, { onInput: handleInput }), function (uid, rootProps, rootStyles) { return (h("div", Object.assign({}, rootProps, { className: "IroWheel", style: Object.assign({}, {width: cssValue(width), height: cssValue(width), position: 'relative'}, rootStyles) }), h("div", { className: "IroWheelHue", style: Object.assign({}, circleStyles, {transform: ("rotateZ(" + (props.wheelAngle + 90) + "deg)"), background: props.wheelDirection === 'clockwise' ? HUE_GRADIENT_CLOCKWISE : HUE_GRADIENT_ANTICLOCKWISE}) }), h("div", { className: "IroWheelSaturation", style: Object.assign({}, circleStyles, {background: 'radial-gradient(circle closest-side, #fff, transparent)'}) }), props.wheelLightness && (h("div", { className: "IroWheelLightness", style: Object.assign({}, circleStyles, {background: '#000', opacity: 1 - hsv.v / 100}) })), h("div", { className: "IroWheelBorder", style: Object.assign({}, circleStyles, cssBorderStyles(props)) }), colors.filter(function (color) { return color !== activeColor; }).map(function (color) { return (h(IroHandle, { isActive: false, index: color.index, fill: color.hslString, r: props.handleRadius, url: props.handleSvg, props: props.handleProps, x: handlePositions[color.index].x, y: handlePositions[color.index].y })); }), h(IroHandle, { isActive: true, index: activeColor.index, fill: activeColor.hslString, r: props.activeHandleRadius || props.handleRadius, url: props.handleSvg, props: props.handleProps, x: handlePositions[activeColor.index].x, y: handlePositions[activeColor.index].y }))); })); } function createWidget(WidgetComponent) { var widgetFactory = function (parent, props) { var widget; // will become an instance of the widget component class var widgetRoot = document.createElement('div'); // Render widget into a temp DOM node I(h(WidgetComponent, Object.assign({}, {ref: function (ref) { return widget = ref; }}, props)), widgetRoot); function mountWidget() { var container = parent instanceof Element ? parent : document.querySelector(parent); container.appendChild(widget.base); widget.onMount(container); } // Mount it into the DOM when the page document is ready if (document.readyState !== 'loading') { mountWidget(); } else { document.addEventListener('DOMContentLoaded', mountWidget); } return widget; }; // Allow the widget factory to inherit component prototype + static class methods // This makes it easier for plugin authors to extend the base widget component widgetFactory.prototype = WidgetComponent.prototype; Object.assign(widgetFactory, WidgetComponent); // Add reference to base component too widgetFactory.__component = WidgetComponent; return widgetFactory; } var IroColorPicker = /*@__PURE__*/(function (Component) { function IroColorPicker(props) { var this$1 = this; Component.call(this, props); this.colors = []; this.inputActive = false; this.events = {}; this.activeEvents = {}; this.deferredEvents = {}; this.id = props.id; var colors = props.colors.length > 0 ? props.colors : [props.color]; colors.forEach(function (colorValue) { return this$1.addColor(colorValue); }); this.setActiveColor(0); // Pass all the props into the component's state, // Except we want to add the color object and make sure that refs aren't passed down to children this.state = Object.assign({}, props, {color: this.color, colors: this.colors, layout: props.layout}); } if ( Component ) IroColorPicker.__proto__ = Component; IroColorPicker.prototype = Object.create( Component && Component.prototype ); IroColorPicker.prototype.constructor = IroColorPicker; // Plubic multicolor API /** * @desc Add a color to the color picker * @param color new color to add * @param index optional color index */ IroColorPicker.prototype.addColor = function addColor (color, index) { if ( index === void 0 ) index = this.colors.length; // Create a new iro.Color // Also bind it to onColorChange, so whenever the color changes it updates the color picker var newColor = new IroColor(color, this.onColorChange.bind(this)); // Insert color @ the given index this.colors.splice(index, 0, newColor); // Reindex colors this.colors.forEach(function (color, index) { return color.index = index; }); // Update picker state if necessary if (this.state) { this.setState({ colors: this.colors }); } // Fire color init event this.deferredEmit('color:init', newColor); }; /** * @desc Remove a color from the color picker * @param index color index */ IroColorPicker.prototype.removeColor = function removeColor (index) { var color = this.colors.splice(index, 1)[0]; // Destroy the color object -- this unbinds it from the color picker color.unbind(); // Reindex colors this.colors.forEach(function (color, index) { return color.index = index; }); // Update picker state if necessary if (this.state) { this.setState({ colors: this.colors }); } // If the active color was removed, default active color to 0 if (color.index === this.color.index) { this.setActiveColor(0); } // Fire color remove event this.emit('color:remove', color); }; /** * @desc Set the currently active color * @param index color index */ IroColorPicker.prototype.setActiveColor = function setActiveColor (index) { this.color = this.colors[index]; if (this.state) { this.setState({ color: this.color }); } // Fire color switch event this.emit('color:setActive', this.color); }; /** * @desc Replace all of the current colorPicker colors * @param newColorValues list of new colors to add */ IroColorPicker.prototype.setColors = function setColors (newColorValues, activeColorIndex) { var this$1 = this; if ( activeColorIndex === void 0 ) activeColorIndex = 0; // Unbind color events this.colors.forEach(function (color) { return color.unbind(); }); // Destroy old colors this.colors = []; // Add new colors newColorValues.forEach(function (colorValue) { return this$1.addColor(colorValue); }); // Reset active color this.setActiveColor(activeColorIndex); this.emit('color:setAll', this.colors); }; // Public ColorPicker events API /** * @desc Set a callback function for an event * @param eventList event(s) to listen to * @param callback - Function called when the event is fired */ IroColorPicker.prototype.on = function on (eventList, callback) { var this$1 = this; var events = this.events; // eventList can be an eventType string or an array of eventType strings (!Array.isArray(eventList) ? [eventList] : eventList).forEach(function (eventType) { // Add event callback (events[eventType] || (events[eventType] = [])).push(callback); // Call deferred events // These are events that can be stored until a listener for them is added if (this$1.deferredEvents[eventType]) { // Deffered events store an array of arguments from when the event was called this$1.deferredEvents[eventType].forEach(function (args) { callback.apply(null, args); }); // Clear deferred events this$1.deferredEvents[eventType] = []; } }); }; /** * @desc Remove a callback function for an event added with on() * @param eventList - event(s) to listen to * @param callback - original callback function to remove */ IroColorPicker.prototype.off = function off (eventList, callback) { var this$1 = this; (!Array.isArray(eventList) ? [eventList] : eventList).forEach(function (eventType) { var callbackList = this$1.events[eventType]; // this.emitHook('event:off', eventType, callback); if (callbackList) { callbackList.splice(callbackList.indexOf(callback), 1); } }); }; /** * @desc Emit an event * @param eventType event to emit */ IroColorPicker.prototype.emit = function emit (eventType) { var this$1 = this; var args = [], len = arguments.length - 1; while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; var activeEvents = this.activeEvents; var isEventActive = activeEvents.hasOwnProperty(eventType) ? activeEvents[eventType] : false; // Prevent event callbacks from firing if the event is already active // This stops infinite loops if something in an event callback causes the same event to be fired again // (e.g. setting the color inside a color:change callback) if (!isEventActive) { activeEvents[eventType] = true; var callbackList = this.events[eventType] || []; callbackList.forEach(function (fn) { return fn.apply(this$1, args); }); activeEvents[eventType] = false; } }; /** * @desc Emit an event now, or save it for when the relevent event listener is added * @param eventType - The name of the event to emit */ IroColorPicker.prototype.deferredEmit = function deferredEmit (eventType) { var ref; var args = [], len = arguments.length - 1; while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; var deferredEvents = this.deferredEvents; (ref = this).emit.apply(ref, [ eventType ].concat( args )); (deferredEvents[eventType] || (deferredEvents[eventType] = [])).push(args); }; // Public utility methods IroColorPicker.prototype.setOptions = function setOptions (newOptions) { this.setState(newOptions); }; /** * @desc Resize the color picker * @param width - new width */ IroColorPicker.prototype.resize = function resize (width) { this.setOptions({ width: width }); }; /** * @desc Reset the color picker to the initial color provided in the color picker options */ IroColorPicker.prototype.reset = function reset () { this.colors.forEach(function (color) { return color.reset(); }); this.setState({ colors: this.colors }); }; /** * @desc Called by the createWidget wrapper when the element is mounted into the page * @param container - the container element for this ColorPicker instance */ IroColorPicker.prototype.onMount = function onMount (container) { this.el = container; this.deferredEmit('mount', this); }; // Internal methods /** * @desc React to a color update * @param color - current color * @param changes - shows which h,s,v,a color channels changed */ IroColorPicker.prototype.onColorChange = function onColorChange (color, changes) { this.setState({ color: this.color }); if (this.inputActive) { this.inputActive = false; this.emit('input:change', color, changes); } this.emit('color:change', color, changes); }; /** * @desc Handle input from a UI control element * @param type - event type */ IroColorPicker.prototype.emitInputEvent = function emitInputEvent (type, originId) { if (type === 0 /* Start */) { this.emit('input:start', this.color, originId); } else if (type === 1 /* Move */) { this.emit('input:move', this.color, originId); } else if (type === 2 /* End */) { this.emit('input:end', this.color, originId); } }; IroColorPicker.prototype.render = function render (props, state) { var this$1 = this; var layout = state.layout; // use layout shorthands if (!Array.isArray(layout)) { switch (layout) { // TODO: implement some? default: layout = [ { component: IroWheel }, { component: IroSlider } ]; } // add transparency slider to the layout if (state.transparency) { layout.push({ component: IroSlider, options: { sliderType: 'alpha' } }); } } return (h("div", { class: "IroColorPicker", id: state.id, style: { display: state.display } }, layout.map(function (ref, componentIndex) { var UiComponent = ref.component; var options = ref.options; return (h(UiComponent, Object.assign({}, state, options, { ref: undefined, onInput: this$1.emitInputEvent.bind(this$1), parent: this$1, index: componentIndex }))); }))); }; return IroColorPicker; }(m)); IroColorPicker.defaultProps = Object.assign({}, iroColorPickerOptionDefaults, {colors: [], display: 'block', id: null, layout: 'default', margin: null}); var IroColorPickerWidget = createWidget(IroColorPicker); var iro; (function (iro) { iro.version = "5.5.2"; // replaced by @rollup/plugin-replace; see rollup.config.js iro.Color = IroColor; iro.ColorPicker = IroColorPickerWidget; var ui; (function (ui) { ui.h = h; ui.ComponentBase = IroComponentWrapper; ui.Handle = IroHandle; ui.Slider = IroSlider; ui.Wheel = IroWheel; ui.Box = IroBox; })(ui = iro.ui || (iro.ui = {})); })(iro || (iro = {})); var iro$1 = iro; return iro$1; }));