From ebc47a9b1b6a227d8912c24cd7c26afaac825367 Mon Sep 17 00:00:00 2001 From: Conor_Mack Date: Fri, 12 Jun 2020 10:48:20 +0100 Subject: [PATCH] Fully functional Colorpicker with Preview --- .../Colorpicker/ButtonGroup.svelte | 27 ++ .../Colorpicker/Colorpicker.svelte | 142 ++++-- .../Colorpicker/Colorpreview.svelte | 78 ++++ .../Colorpicker/FlatButton.svelte | 26 ++ .../userInterface/Colorpicker/Input.svelte | 28 ++ .../userInterface/Colorpicker/Palette.svelte | 1 + .../userInterface/Colorpicker/Slider.svelte | 22 +- .../userInterface/Colorpicker/helpers.js | 14 + .../userInterface/Colorpicker/index.js | 5 +- .../userInterface/Colorpicker/utils.js | 426 +++++++++--------- 10 files changed, 510 insertions(+), 259 deletions(-) create mode 100644 packages/builder/src/components/userInterface/Colorpicker/ButtonGroup.svelte create mode 100644 packages/builder/src/components/userInterface/Colorpicker/Colorpreview.svelte create mode 100644 packages/builder/src/components/userInterface/Colorpicker/FlatButton.svelte create mode 100644 packages/builder/src/components/userInterface/Colorpicker/Input.svelte create mode 100644 packages/builder/src/components/userInterface/Colorpicker/helpers.js diff --git a/packages/builder/src/components/userInterface/Colorpicker/ButtonGroup.svelte b/packages/builder/src/components/userInterface/Colorpicker/ButtonGroup.svelte new file mode 100644 index 0000000000..f9c402ac81 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/ButtonGroup.svelte @@ -0,0 +1,27 @@ + + + + +
+ {#each colorFormats as text} + onclick(text)} /> + {/each} +
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Colorpicker.svelte b/packages/builder/src/components/userInterface/Colorpicker/Colorpicker.svelte index 015114ded8..8ec53d9b86 100644 --- a/packages/builder/src/components/userInterface/Colorpicker/Colorpicker.svelte +++ b/packages/builder/src/components/userInterface/Colorpicker/Colorpicker.svelte @@ -1,10 +1,17 @@ @@ -94,7 +125,24 @@ - setHue(hue.detail)} /> +
+
+
+
+
+
+ setHue(hue.detail)} /> + setAlpha(alpha.detail)} /> +
+
+ +
+ + handleColorInput(event.target.value)} /> +
+
- setAlpha(alpha.detail)} />
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Colorpreview.svelte b/packages/builder/src/components/userInterface/Colorpicker/Colorpreview.svelte new file mode 100644 index 0000000000..329d4e4adc --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/Colorpreview.svelte @@ -0,0 +1,78 @@ + + +
+
+ {#if open} +
+ +
+ {/if} +
+{#if open} +
open = false} class="overlay">
+{/if} + + \ No newline at end of file diff --git a/packages/builder/src/components/userInterface/Colorpicker/FlatButton.svelte b/packages/builder/src/components/userInterface/Colorpicker/FlatButton.svelte new file mode 100644 index 0000000000..af1ea5e945 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/FlatButton.svelte @@ -0,0 +1,26 @@ + + + + +
{text}
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Input.svelte b/packages/builder/src/components/userInterface/Colorpicker/Input.svelte new file mode 100644 index 0000000000..04966c1f30 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/Input.svelte @@ -0,0 +1,28 @@ + + + + +
+ +
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Palette.svelte b/packages/builder/src/components/userInterface/Colorpicker/Palette.svelte index 5f344e74a7..01a614124c 100644 --- a/packages/builder/src/components/userInterface/Colorpicker/Palette.svelte +++ b/packages/builder/src/components/userInterface/Colorpicker/Palette.svelte @@ -42,6 +42,7 @@ width: 100%; height: 175px; cursor: crosshair; + overflow: hidden; } .picker { diff --git a/packages/builder/src/components/userInterface/Colorpicker/Slider.svelte b/packages/builder/src/components/userInterface/Colorpicker/Slider.svelte index 13254c29c8..ec8d99248d 100644 --- a/packages/builder/src/components/userInterface/Colorpicker/Slider.svelte +++ b/packages/builder/src/components/userInterface/Colorpicker/Slider.svelte @@ -2,7 +2,7 @@ import { onMount, createEventDispatcher } from "svelte"; import dragable from "./drag.js"; - export let value = 1 + export let value = 1; export let type = "hue"; const dispatch = createEventDispatcher(); @@ -11,20 +11,22 @@ let sliderWidth = 0; function handleClick(mouseX) { - const { left } = slider.getBoundingClientRect(); + const { left, width } = slider.getBoundingClientRect(); let clickPosition = mouseX - left; + + let percentageClick = (clickPosition / sliderWidth).toFixed(2) - if (clickPosition >= 0 && clickPosition <= sliderWidth) { - let percentageClick = clickPosition / sliderWidth; + if (percentageClick >= 0 && percentageClick <= 1) { let value = type === "hue" - ? 360 * percentageClick.toString() - : percentageClick.toString(); + ? 360 * percentageClick + : percentageClick; dispatch("change", value); } } - $: thumbPosition = type === "hue" ? sliderWidth * (value / 360) : sliderWidth * (value) + $: thumbPosition = + type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value; $: style = `transform: translateX(${thumbPosition - 6}px);`; @@ -34,10 +36,11 @@ position: relative; align-self: center; height: 8px; - width: 220px; + width: 185px; border-radius: 10px; margin: 10px 0px; border: 1px solid #e8e8ef; + cursor: pointer; } .hue { @@ -62,9 +65,10 @@ bottom: -3px; height: 12px; width: 12px; - border: 1px solid black; + border: 1px solid #777676; border-radius: 50%; background-color: #ffffff; + cursor:grab; } diff --git a/packages/builder/src/components/userInterface/Colorpicker/helpers.js b/packages/builder/src/components/userInterface/Colorpicker/helpers.js new file mode 100644 index 0000000000..69f94bf208 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/helpers.js @@ -0,0 +1,14 @@ +export const buildStyle = styles => { + let str = "" + for (let s in styles) { + if (styles[s]) { + let key = convertCamel(s) + str += `${key}: ${styles[s]}; ` + } + } + return str + } + + export const convertCamel = str => { + return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) + } \ No newline at end of file diff --git a/packages/builder/src/components/userInterface/Colorpicker/index.js b/packages/builder/src/components/userInterface/Colorpicker/index.js index ad8187a09b..b497601edf 100644 --- a/packages/builder/src/components/userInterface/Colorpicker/index.js +++ b/packages/builder/src/components/userInterface/Colorpicker/index.js @@ -1,2 +1,3 @@ -import Colorpicker from './Colorpicker.svelte'; -export default Colorpicker; +import Colorpreview from './Colorpreview.svelte'; + +export default Colorpreview; diff --git a/packages/builder/src/components/userInterface/Colorpicker/utils.js b/packages/builder/src/components/userInterface/Colorpicker/utils.js index b46ad6d202..518e94ec9f 100644 --- a/packages/builder/src/components/userInterface/Colorpicker/utils.js +++ b/packages/builder/src/components/userInterface/Colorpicker/utils.js @@ -1,244 +1,268 @@ -export const isValidHex = (str) => /^#(?:[A-F0-9]{3}$|[A-F0-9]{6}$|[A-F0-9]{8})$/gi.test(str) +export const isValidHex = str => + /^#(?:[A-F0-9]{3}$|[A-F0-9]{4}$|[A-F0-9]{6}$|[A-F0-9]{8})$/gi.test(str); -const getHexaValues = (hexString) => hexString.match(/[A-F0-9]{2}/gi); - -export const isValidRgb = (str) => { - const hasValidStructure = /^(?:rgba\(|rgb\()[0-9,.\s]*\)$/gi.test(str); - if(hasValidStructure) { - return testRgbaValues(str.toLowerCase()); - } -} - -const findNonNumericChars = /[a-z()%\s]/gi - -export const getNumericValues = (str) => str.replace(findNonNumericChars, '').split(',').map(c => Number(c)); - -export const testRgbaValues = (str) => { - const rgba = getNumericValues(str) - let [ r, g, b, a ] = rgba; - - let isValidLengthRange = (str.startsWith("rgb(") && rgba.length === 3) || (str.startsWith("rgba(") && rgba.length === 4); - let isValidColorRange = [ r, g, b ].every((v) => v >= 0 && v <= 255); - let isValidAlphaRange = str.startsWith("rgba(") ? a >= 0 && a <= 1 : true; - - return isValidLengthRange && isValidColorRange && isValidAlphaRange; +const getHexaValues = hexString => { + if (hexString.length <= 5) { + let hexArr = hexString.match(/[A-F0-9]/gi); + let t = hexArr.map(c => (c += c)); + return t; + } else { + return hexString.match(/[A-F0-9]{2}/gi); + } }; -export const isValidHsl = (str) => { - const hasValidStructure = /^(?:hsl\(|hsla\()[0-9,.\s]*\)$/gi.test(str) - if(hasValidStructure) { - return testHslaValues(str.toLowerCase()) - } -} +export const isValidRgb = str => { + const hasValidStructure = /^(?:rgba\(|rgb\()(?:[0-9,\s]|\.(?=\d))*\)$/gi.test(str); + if (hasValidStructure) { + return testRgbaValues(str.toLowerCase()); + } +}; -export const testHslaValues = (str) => { +const findNonNumericChars = /[a-z()\s]/gi; - const hsla = getNumericValues(str) - const [h, s, l, a] = hsla +export const getNumericValues = str => + str + .replace(findNonNumericChars, "") + .split(",") + .map(v => v !== "" ? v : undefined); - let isValidLengthRange = (str.startsWith("hsl(") && hsla.length === 3) || (str.startsWith("hsla(") && hsla.length === 4); - let isValidColorRange = (h >= 0 && h <= 360) && [s, l].every(v => v >= 0 && v <= 100) - let isValidAlphaRange = str.startsWith("hsla(") ? (a >= 0 && a <= 1) : true +export const testRgbaValues = str => { + const rgba = getNumericValues(str); + const [r, g, b, a] = rgba; + + let isValidLengthRange = + (str.startsWith("rgb(") && rgba.length === 3) || + (str.startsWith("rgba(") && rgba.length === 4); + let isValidColorRange = [r, g, b].every(v => v >= 0 && v <= 255); + let isValidAlphaRange = str.startsWith("rgba(") ? `${a}`.length <= 4 && a >= 0 && a <= 1 : true; - return isValidLengthRange && isValidColorRange && isValidAlphaRange; -} + return isValidLengthRange && isValidColorRange && isValidAlphaRange; +}; -export const getColorFormat = (color) => { - if(typeof color === "string") { - if(isValidHex(color)) { - return 'hex' - }else if(isValidRgb(color)) { - return 'rgb' - }else if(isValidHsl(color)) { - return 'hsl' - } - } -} +export const isValidHsl = str => { + const hasValidStructure = /^(?:hsl\(|hsla\()(?:[0-9,%\s]|\.(?=\d))*\)$/gi.test(str); + if (hasValidStructure) { + return testHslaValues(str.toLowerCase()); + } +}; + +export const testHslaValues = str => { + const hsla = getNumericValues(str); + const [h, s, l, a] = hsla; + const isUndefined = [h,s,l].some(v => v === undefined) + + if(isUndefined) return false; + + let isValidLengthRange = + (str.startsWith("hsl(") && hsla.length === 3) || + (str.startsWith("hsla(") && hsla.length === 4); + let isValidHue = h >= 0 && h <= 360 + let isValidSatLum = [s, l].every(v => v.endsWith('%') && parseInt(v) >= 0 && parseInt(v) <= 100 ) + let isValidAlphaRange = str.startsWith("hsla(") ? `${a}`.length <= 4 && a >= 0 && a <= 1 : true; + + return isValidLengthRange && isValidHue && isValidSatLum && isValidAlphaRange; +}; + +export const getColorFormat = color => { + if (typeof color === "string") { + if (isValidHex(color)) { + return "hex"; + } else if (isValidRgb(color)) { + return "rgb"; + } else if (isValidHsl(color)) { + return "hsl"; + } + } +}; export const convertToHSVA = (value, format) => { - switch(format) { - case "hex": - return getAndConvertHexa(value) - case "rgb": - return getAndConvertRgba(value) - case "hsl": - return getAndConvertHsla(value) - } -} + switch (format) { + case "hex": + return getAndConvertHexa(value); + case "rgb": + return getAndConvertRgba(value); + case "hsl": + return getAndConvertHsla(value); + } +}; export const convertHsvaToFormat = (hsva, format) => { - switch(format) { - case "hex": - return hsvaToHexa(hsva, true) - case "rgb": - return hsvaToRgba(hsva, true) - case "hsl": - return hsvaToHsla(hsva, true) - } -} + switch (format) { + case "hex": + return hsvaToHexa(hsva, true); + case "rgb": + return hsvaToRgba(hsva, true); + case "hsl": + return hsvaToHsla(hsva); + } +}; - -export const getAndConvertHexa = (color) => { - let [ rHex, gHex, bHex, aHex ] = getHexaValues(color); - return hexaToHSVA([ rHex, gHex, bHex ], aHex); -} +export const getAndConvertHexa = color => { + let [rHex, gHex, bHex, aHex] = getHexaValues(color); + return hexaToHSVA([rHex, gHex, bHex], aHex); +}; export const getAndConvertRgba = color => { - let rgba = getNumericValues(color); - return rgbaToHSVA(rgba); -} + let rgba = getNumericValues(color); + return rgbaToHSVA(rgba); +}; export const getAndConvertHsla = color => { - let hsla = getNumericValues(color); - return hslaToHSVA(hsla) -} - -export const getHSLA = ([hue, sat, val, a]) => { - const [ h, s, l ] = _hsvToHSL([hue, sat, val]); - return `hsla(${h}, ${s}, ${l}, ${a})`; + let hsla = getNumericValues(color); + return hslaToHSVA(hsla); }; -export const hexaToHSVA = (hex, alpha = 'FF') => { - const rgba = hex.map((v) => parseInt(v, 16)).concat(Number((parseInt(alpha, 16) / 255).toFixed(2))); - return rgbaToHSVA(rgba); + +export const hexaToHSVA = (hex, alpha = "FF") => { + const rgba = hex + .map(v => parseInt(v, 16)) + .concat(Number((parseInt(alpha, 16) / 255).toFixed(2))); + return rgbaToHSVA(rgba); }; -export const rgbaToHSVA = (rgba) => { - const [ r, g, b, a = 1 ] = rgba; - let hsv = _rgbToHSV([ r, g, b ]); - return [ ...hsv, a ]; +export const rgbaToHSVA = rgba => { + const [r, g, b, a = 1] = rgba; + let hsv = _rgbToHSV([r, g, b]); + return [...hsv, a]; }; export const hslaToHSVA = ([h, s, l, a = 1]) => { - let hsv = _hslToHSV([h, s, l]) - return [...hsv, a] -} - -export const hsvaToHexa = (hsva, asString = false) => { - const [ r, g, b, a ] = hsvaToRgba(hsva); - - const hexa = [ r, g, b ].map((v) => Math.round(v).toString(16)).concat(Math.round((a * 255)).toString(16)); - return asString ? `#${hexa.join('')}` : hexa + let hsv = _hslToHSV([h, s, l]); + return [...hsv, a]; }; -export const hsvaToRgba = ([h, s, v, a], asString = false) => { - let rgb = _hsvToRgb([ h, s, v ]).map(x => Math.round(x)); - let rgba = [ ...rgb, a ]; - return asString ? `rgba(${rgba.join(",")})` : rgba +export const hsvaToHexa = (hsva, asString = false) => { + const [r, g, b, a] = hsvaToRgba(hsva); + + const hexa = [r, g, b] + .map(v => { + let hex = Math.round(v).toString(16) + return hex.length === 1 ? `0${hex}` : hex + }) + .concat(Math.round(a * 255).toString(16)); + return asString ? `#${hexa.join("")}` : hexa; +}; + +export const hsvaToRgba = ([h, s, v, a = 1], asString = false) => { + let rgb = _hsvToRgb([h, s, v]).map(x => Math.round(x)); + let rgba = [...rgb, a < 1 ? _fixNum(a, 2) : a]; + return asString ? `rgba(${rgba.join(",")})` : rgba; }; export const hsvaToHsla = ([h, s, v, a = 1], asString = false) => { - let hsl = _hsvToHSL([h, s, v]) - let hsla = [...hsl, a] - return asString ? `hsla(${hsla.join(",")})` : hsla -} + let [hue, sat, lum] = _hsvToHSL([h, s, v]); + let hsla = [hue, sat + "%", lum + "%", a < 1 ? _fixNum(a, 2) : a]; + return `hsla(${hsla.join(",")})`; +}; -export const _hslToHSV = (hsl) => { - const h = hsl[0]; - let s = hsl[1] / 100; - let l = hsl[2] / 100; - let smin = s; - const lmin = Math.max(l, 0.01); +export const _hslToHSV = hsl => { + const h = hsl[0]; + let s = hsl[1] / 100; + let l = hsl[2] / 100; + let smin = s; + const lmin = Math.max(l, 0.01); - l *= 2; - s *= (l <= 1) ? l : 2 - l; - smin *= lmin <= 1 ? lmin : 2 - lmin; - const v = (l + s) / 2; - const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); + l *= 2; + s *= l <= 1 ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + const v = (l + s) / 2; + const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); - return [h, sv * 100, v * 100]; -} - -//Credit : https://github.com/Qix-/color-convert -export const _rgbToHSV = (rgb) => { - let rdif; - let gdif; - let bdif; - let h; - let s; - - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - const v = Math.max(r, g, b); - const diff = v - Math.min(r, g, b); - const diffc = function(c) { - return (v - c) / 6 / diff + 1 / 2; - }; - - if (diff === 0) { - h = 0; - s = 0; - } else { - s = diff / v; - rdif = diffc(r); - gdif = diffc(g); - bdif = diffc(b); - - if (r === v) { - h = bdif - gdif; - } else if (g === v) { - h = 1 / 3 + rdif - bdif; - } else if (b === v) { - h = 2 / 3 + gdif - rdif; - } - - if (h < 0) { - h += 1; - } else if (h > 1) { - h -= 1; - } - } - - const hsvResult = [ h * 360, s * 100, v * 100 ].map((v) => Math.round(v)); - return hsvResult; + return [h, sv * 100, v * 100]; }; //Credit : https://github.com/Qix-/color-convert -export const _hsvToRgb = (hsv) => { - const h = hsv[0] / 60; - const s = hsv[1] / 100; - let v = hsv[2] / 100; - const hi = Math.floor(h) % 6; +export const _rgbToHSV = rgb => { + let rdif; + let gdif; + let bdif; + let h; + let s; - const f = h - Math.floor(h); - const p = 255 * v * (1 - s); - const q = 255 * v * (1 - s * f); - const t = 255 * v * (1 - s * (1 - f)); - v *= 255; + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const v = Math.max(r, g, b); + const diff = v - Math.min(r, g, b); + const diffc = function(c) { + return (v - c) / 6 / diff + 1 / 2; + }; - switch (hi) { - case 0: - return [ v, t, p ]; - case 1: - return [ q, v, p ]; - case 2: - return [ p, v, t ]; - case 3: - return [ p, q, v ]; - case 4: - return [ t, p, v ]; - case 5: - return [ v, p, q ]; - } + if (diff === 0) { + h = 0; + s = 0; + } else { + s = diff / v; + rdif = diffc(r); + gdif = diffc(g); + bdif = diffc(b); + + if (r === v) { + h = bdif - gdif; + } else if (g === v) { + h = 1 / 3 + rdif - bdif; + } else if (b === v) { + h = 2 / 3 + gdif - rdif; + } + + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + } + + const hsvResult = [h * 360, s * 100, v * 100].map(v => Math.round(v)); + return hsvResult; }; //Credit : https://github.com/Qix-/color-convert -export const _hsvToHSL = (hsv) => { - - const h = hsv[0]; - const s = hsv[1] / 100; - const v = hsv[2] / 100; - const vmin = Math.max(v, 0.01); - let sl; - let l; +export const _hsvToRgb = hsv => { + const h = hsv[0] / 60; + const s = hsv[1] / 100; + let v = hsv[2] / 100; + const hi = Math.floor(h) % 6; - l = (2 - s) * v; - const lmin = (2 - s) * vmin; - sl = s * vmin; - sl /= lmin <= 1 ? lmin : 2 - lmin; - sl = sl || 0; - l /= 2; + const f = h - Math.floor(h); + const p = 255 * v * (1 - s); + const q = 255 * v * (1 - s * f); + const t = 255 * v * (1 - s * (1 - f)); + v *= 255; - return [ h, Number((sl * 100).toFixed(1)), Number((l * 100).toFixed(1)) ]; + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } }; + +//Credit : https://github.com/Qix-/color-convert +export const _hsvToHSL = hsv => { + const h = hsv[0]; + const s = hsv[1] / 100; + const v = hsv[2] / 100; + const vmin = Math.max(v, 0.01); + let sl; + let l; + + l = (2 - s) * v; + const lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= lmin <= 1 ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + + return [_fixNum(h, 0), _fixNum(sl * 100, 0), _fixNum(l * 100, 0)]; +}; + +export const _fixNum = (value, decimalPlaces) => + Number(parseFloat(value).toFixed(decimalPlaces));