├── README.md ├── actions ├── drag.js ├── index.js └── key-events.js ├── components ├── ButtonGroup.svelte ├── CheckedBackground.svelte ├── Colorpicker.svelte ├── Colorpreview.svelte ├── FlatButton.svelte ├── Input.svelte ├── Palette.svelte ├── Portal.svelte ├── Slider.svelte └── Swatch.svelte ├── helpers.js ├── img └── colorpicker.png ├── index.js ├── utils.js └── utils.test.js /README.md: -------------------------------------------------------------------------------- 1 | # Colorpicker 🎨 2 | A multi format Colorpicker built with Svelte. The Colorpicker accepts and sets hex(a), rgb(a) and hsl(a) colors. 3 | 4 | ![Image](./img/colorpicker.png) 5 | 6 | ### Features 7 | * Choose your color by clicking or dragging on a palette. 8 | * Set the hue and opacity of your chosen color using Slider controls. 9 | * Convert your selected color to your preferred color format (hexa, rgba, hsla). 10 | * Set your color by typing into an input. 11 | * A swatch panel for keeping track of recent colours or for passing favourite colors so they can be easily set again. 12 | * Handy keyboard events to tab through control panel, set colors and close the Colorpicker. 13 | * Colorpicker anchors to its preview element which displays the selected color and acts as a button to display the Colorpicker. 14 | * Viewport positional awareness - the colorpicker centers by default but will adapt and position itself appropriately based on available space. 15 | * Colorpicker renders outside the dom structure which prevents problems with overflow clipping in scrolling containers. 16 | 17 | ### Installation 18 | 19 | 1. Navigate to your project in the terminal. 20 | 2. Run `yarn add @budibase/colorpicker`. 21 | 3. Or use npm `npm i @budibase/colorpicker`. 22 | 23 | ### Usage 24 | ```javascript 25 | import Colorpicker from "@budibase/colorpicker" 26 | 27 | {}} 42 | //remove swatch - invoked when swatch has been removed 43 | on:removeswatch={removedSwatch => {})} 44 | //add swatch event - invoked when a swatch has been added 45 | on:addswatch={addedSwatch => {}} 46 | /> 47 | ``` 48 | ### Events 49 | The Colorpicker exposes the following events. In each case, the color will be provided as the first parameter to the bound function. 50 | 51 | **Change Event** 52 | `on:change={selectedColor: string => {}}` 53 | The on change event will be invoked whenever a color has been set in the Colorpicker via the palette, the hue slider, the alpha slider or by typing into the input. 54 | 55 | **Add Swatch** 56 | `on:addswatch={addedSwatch: string => {}}` 57 | The add swatch event will be invoked when a user adds a swatch by clicking the add button. 58 | 59 | **Remove Swatch** 60 | `on:removeswatch={removedSwatch: string => {}}` 61 | The remove swatch will be invoked when a user clicks the delete button that appears when hovering over a swatch. 62 | 63 | ### Swatches 64 | * By default, added swatches are saved to local storage so that they can be displayed across all Colorpickers in your application. 65 | * Only 12 or less swatches can be displayed in the Coloricker at any any one time. 66 | * If more than 12 have been passed the Colorpicker will display the first 12 and warn in the console. 67 | * An array of swatches can be passed to the Colorpicker to set up a dedicated panel of swatches. This will use provided swatches instead of locally stored swatches. Swatches added will still be saved to local storage. 68 | * You can disable swatch functionality by passing the `disableSwatches={true}` property to the Colorpicker. 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /actions/drag.js: -------------------------------------------------------------------------------- 1 | export default function(node) { 2 | function handleMouseDown() { 3 | window.addEventListener("mousemove", handleMouseMove) 4 | window.addEventListener("mouseup", handleMouseUp) 5 | node.dispatchEvent(new CustomEvent("dragstart")) 6 | } 7 | 8 | function handleMouseMove(event) { 9 | node.dispatchEvent( 10 | new CustomEvent("drag", { 11 | detail: { mouseX: event.clientX, mouseY: event.clientY }, 12 | }) 13 | ) 14 | } 15 | 16 | function handleMouseUp() { 17 | window.removeEventListener("mousedown", handleMouseDown) 18 | window.removeEventListener("mousemove", handleMouseMove) 19 | node.dispatchEvent(new CustomEvent("dragend")) 20 | } 21 | 22 | node.addEventListener("mousedown", handleMouseDown) 23 | } 24 | -------------------------------------------------------------------------------- /actions/index.js: -------------------------------------------------------------------------------- 1 | export { default as drag } from "./drag.js" 2 | export { default as keyevents } from "./key-events.js" 3 | -------------------------------------------------------------------------------- /actions/key-events.js: -------------------------------------------------------------------------------- 1 | //events: Array<{trigger: fn}> 2 | export default function(node, events = []) { 3 | const ev = Object.entries(events) 4 | let fns = [] 5 | 6 | for (let [trigger, fn] of ev) { 7 | let f = addEvent(trigger, fn) 8 | fns = [...fns, f] 9 | } 10 | 11 | function _scaffold(trigger, fn) { 12 | return () => { 13 | let trig = parseInt(trigger) 14 | if (trig) { 15 | if (event.keyCode === trig) { 16 | fn(event) 17 | } 18 | } else { 19 | if (event.key === trigger) { 20 | fn(event) 21 | } 22 | } 23 | } 24 | } 25 | 26 | function addEvent(trigger, fn) { 27 | let f = _scaffold(trigger, fn) 28 | node.addEventListener("keydown", f) 29 | return f 30 | } 31 | 32 | function removeEvents() { 33 | fns.forEach(f => node.removeEventListener("keypress", f)) 34 | } 35 | 36 | return { 37 | destroy() { 38 | removeEvents() 39 | }, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /components/ButtonGroup.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | {#each colorFormats as text} 14 | dispatch('click', text)} /> 18 | {/each} 19 |
20 | 21 | 32 | -------------------------------------------------------------------------------- /components/CheckedBackground.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 16 |
17 | 18 | 27 | -------------------------------------------------------------------------------- /components/Colorpicker.svelte: -------------------------------------------------------------------------------- 1 | 181 | 182 | 183 |
192 | 193 |
194 | 195 |
196 | 197 |
198 |
199 |
200 | 201 |
205 | 206 |
207 |
208 | setHue(hue.detail)} 212 | on:dragend={dispatchValue} /> 213 | 214 | 215 | setAlpha(alpha.detail, isDrag)} 219 | on:dragend={dispatchValue} /> 220 | 221 | 222 |
223 |
224 | 225 | {#if !disableSwatches} 226 |
227 | {#if hasSwatches} 228 | {#each swatches as color, idx} 229 | {#if idx < 12} 230 | applySwatch(color)} 233 | on:removeswatch={() => removeSwatch(idx)} /> 234 | {/if} 235 | {/each} 236 | {/if} 237 | {#if swatches.length < 12} 238 |
247 | + 248 |
249 | {/if} 250 |
251 | {/if} 252 | 253 |
254 | 255 | handleColorInput(event.target.value)} 258 | on:change={dispatchInputChange} /> 259 |
260 |
261 | 262 |
263 | 264 | 265 | 350 | -------------------------------------------------------------------------------- /components/Colorpreview.svelte: -------------------------------------------------------------------------------- 1 | 107 | 108 | 109 | 110 |
111 | {#if !errorMsg} 112 | 113 |
121 | 122 | 123 | {#if open} 124 | 136 |
(open = false)} class="overlay" /> 137 | {/if} 138 | {:else} 139 |
143 | × 144 |
145 | {/if} 146 |
147 | 148 | 178 | -------------------------------------------------------------------------------- /components/FlatButton.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
dispatch('click') }} 15 | class:selected 16 | on:click> 17 | {text} 18 |
19 | 20 | 45 | -------------------------------------------------------------------------------- /components/Input.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | 9 | 30 | -------------------------------------------------------------------------------- /components/Palette.svelte: -------------------------------------------------------------------------------- 1 | 39 | 40 | 41 |
handePaletteChange({ 46 | mouseX: event.clientX, 47 | mouseY: event.clientY, 48 | })} 49 | class="palette" 50 | {style}> 51 |
handePaletteChange(event.detail)} 54 | class="picker" 55 | style={pickerStyle} /> 56 |
57 | 58 | 59 | 82 | -------------------------------------------------------------------------------- /components/Portal.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 |
36 | 37 |
38 | -------------------------------------------------------------------------------- /components/Slider.svelte: -------------------------------------------------------------------------------- 1 | 55 | 56 |
onSliderChange({ mouseX: event.clientX })} 62 | class="color-format-slider" 63 | class:hue={type === 'hue'} 64 | class:alpha={type === 'alpha'}> 65 |
onSliderChange(e.detail, true)} 68 | on:dragend={handleDragEnd} 69 | class="slider-thumb" 70 | {style} /> 71 |
72 | 73 | 119 | -------------------------------------------------------------------------------- /components/Swatch.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 |
dispatch('click') }} 18 | in:fade 19 | title={color} 20 | class="swatch" 21 | style={`background: ${color};`} 22 | on:mouseover={() => (hovered = true)} 23 | on:mouseleave={() => (hovered = false)} 24 | on:click|self> 25 | {#if hovered} 26 | dispatch('removeswatch')} /> 27 | {/if} 28 |
29 |
30 |
31 | 32 | 69 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | export function buildStyle(styles) { 2 | let str = "" 3 | for (let s in styles) { 4 | if (styles[s]) { 5 | let key = convertCamel(s) 6 | str += `${key}: ${styles[s]}; ` 7 | } 8 | } 9 | return str 10 | } 11 | 12 | export const convertCamel = str => { 13 | return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) 14 | } 15 | 16 | export const debounce = (fn, milliseconds, callImmediately) => { 17 | const debouncedFn = () => { 18 | setTimeout(fn, milliseconds) 19 | } 20 | if (callImmediately) { 21 | debouncedFn() 22 | } 23 | return debouncedFn 24 | } 25 | -------------------------------------------------------------------------------- /img/colorpicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Budibase/Colorpicker/7649637c398649780c4ec4d19af0a26d9fa3b2bc/img/colorpicker.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Colorpreview from "./components/Colorpreview.svelte" 2 | export default Colorpreview 3 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | export const isValidHex = str => 2 | /^#(?:[A-F0-9]{3}$|[A-F0-9]{4}$|[A-F0-9]{6}$|[A-F0-9]{8})$/gi.test(str) 3 | 4 | const getHexaValues = hexString => { 5 | if (hexString.length <= 5) { 6 | let hexArr = hexString.match(/[A-F0-9]/gi) 7 | let t = hexArr.map(c => (c += c)) 8 | return t 9 | } else { 10 | return hexString.match(/[A-F0-9]{2}/gi) 11 | } 12 | } 13 | 14 | export const isValidRgb = str => { 15 | const hasValidStructure = /^(?:rgba\(|rgb\()(?:[0-9,\s]|\.(?=\d))*\)$/gi.test( 16 | str 17 | ) 18 | if (hasValidStructure) { 19 | return testRgbaValues(str.toLowerCase()) 20 | } 21 | } 22 | 23 | const findNonNumericChars = /[a-z()\s]/gi 24 | 25 | export const getNumericValues = str => 26 | str 27 | .replace(findNonNumericChars, "") 28 | .split(",") 29 | .map(v => (v !== "" ? v : undefined)) 30 | 31 | export const testRgbaValues = str => { 32 | const rgba = getNumericValues(str) 33 | const [r, g, b, a] = rgba 34 | 35 | let isValidLengthRange = 36 | (str.startsWith("rgb(") && rgba.length === 3) || 37 | (str.startsWith("rgba(") && rgba.length === 4) 38 | let isValidColorRange = [r, g, b].every(v => v >= 0 && v <= 255) 39 | let isValidAlphaRange = str.startsWith("rgba(") 40 | ? `${a}`.length <= 4 && a >= 0 && a <= 1 41 | : true 42 | 43 | return isValidLengthRange && isValidColorRange && isValidAlphaRange 44 | } 45 | 46 | export const isValidHsl = str => { 47 | const hasValidStructure = /^(?:hsl\(|hsla\()(?:[0-9,%\s]|\.(?=\d))*\)$/gi.test( 48 | str 49 | ) 50 | if (hasValidStructure) { 51 | return testHslaValues(str.toLowerCase()) 52 | } 53 | } 54 | 55 | export const testHslaValues = str => { 56 | const hsla = getNumericValues(str) 57 | const [h, s, l, a] = hsla 58 | const isUndefined = [h, s, l].some(v => v === undefined) 59 | 60 | if (isUndefined) return false 61 | 62 | let isValidLengthRange = 63 | (str.startsWith("hsl(") && hsla.length === 3) || 64 | (str.startsWith("hsla(") && hsla.length === 4) 65 | let isValidHue = h >= 0 && h <= 360 66 | let isValidSatLum = [s, l].every( 67 | v => v.endsWith("%") && parseInt(v) >= 0 && parseInt(v) <= 100 68 | ) 69 | let isValidAlphaRange = str.startsWith("hsla(") 70 | ? `${a}`.length <= 4 && a >= 0 && a <= 1 71 | : true 72 | 73 | return isValidLengthRange && isValidHue && isValidSatLum && isValidAlphaRange 74 | } 75 | 76 | export const getColorFormat = color => { 77 | if (typeof color === "string") { 78 | if (isValidHex(color)) { 79 | return "hex" 80 | } else if (isValidRgb(color)) { 81 | return "rgb" 82 | } else if (isValidHsl(color)) { 83 | return "hsl" 84 | } 85 | } 86 | } 87 | 88 | export const convertToHSVA = (value, format) => { 89 | switch (format) { 90 | case "hex": 91 | return getAndConvertHexa(value) 92 | case "rgb": 93 | return getAndConvertRgba(value) 94 | case "hsl": 95 | return getAndConvertHsla(value) 96 | } 97 | } 98 | 99 | export const convertHsvaToFormat = (hsva, format) => { 100 | switch (format) { 101 | case "hex": 102 | return hsvaToHexa(hsva, true) 103 | case "rgb": 104 | return hsvaToRgba(hsva, true) 105 | case "hsl": 106 | return hsvaToHsla(hsva) 107 | } 108 | } 109 | 110 | export const getAndConvertHexa = color => { 111 | let [rHex, gHex, bHex, aHex] = getHexaValues(color) 112 | return hexaToHSVA([rHex, gHex, bHex], aHex) 113 | } 114 | 115 | export const getAndConvertRgba = color => { 116 | let rgba = getNumericValues(color) 117 | return rgbaToHSVA(rgba) 118 | } 119 | 120 | export const getAndConvertHsla = color => { 121 | let hsla = getNumericValues(color) 122 | return hslaToHSVA(hsla) 123 | } 124 | 125 | export const hexaToHSVA = (hex, alpha = "FF") => { 126 | const rgba = hex 127 | .map(v => parseInt(v, 16)) 128 | .concat(Number((parseInt(alpha, 16) / 255).toFixed(2))) 129 | return rgbaToHSVA(rgba) 130 | } 131 | 132 | export const rgbaToHSVA = rgba => { 133 | const [r, g, b, a = 1] = rgba 134 | let hsv = _rgbToHSV([r, g, b]) 135 | return [...hsv, a].map(x => parseFloat(x)) 136 | } 137 | 138 | export const hslaToHSVA = ([h, s, l, a = 1]) => { 139 | let sat = s.replace(/%/, "") 140 | let lum = l.replace(/%/, "") 141 | let hsv = _hslToHSV([h, sat, lum]) 142 | return [...hsv, a].map(x => parseFloat(x)) 143 | } 144 | 145 | export const hsvaToHexa = (hsva, asString = false) => { 146 | const [r, g, b, a] = hsvaToRgba(hsva) 147 | const padSingle = hex => (hex.length === 1 ? `0${hex}` : hex) 148 | 149 | let hexa = [r, g, b].map(v => { 150 | let hex = Math.round(v).toString(16) 151 | return padSingle(hex) 152 | }) 153 | 154 | let alpha = padSingle(Math.round(a * 255).toString(16)) 155 | hexa = [...hexa, alpha] 156 | return asString ? `#${hexa.join("")}` : hexa 157 | } 158 | 159 | export const hsvaToRgba = ([h, s, v, a = 1], asString = false) => { 160 | let rgb = _hsvToRgb([h, s, v]).map(x => Math.round(x)) 161 | let rgba = [...rgb, a < 1 ? _fixNum(a, 2) : a] 162 | return asString ? `rgba(${rgba.join(",")})` : rgba 163 | } 164 | 165 | export const hsvaToHsla = ([h, s, v, a = 1]) => { 166 | let [hue, sat, lum] = _hsvToHSL([h, s, v]) 167 | let hsla = [hue, sat + "%", lum + "%", a < 1 ? _fixNum(a, 2) : a] 168 | return `hsla(${hsla.join(",")})` 169 | } 170 | 171 | export const _hslToHSV = hsl => { 172 | const h = hsl[0] 173 | let s = hsl[1] / 100 174 | let l = hsl[2] / 100 175 | let smin = s 176 | const lmin = Math.max(l, 0.01) 177 | 178 | l *= 2 179 | s *= l <= 1 ? l : 2 - l 180 | smin *= lmin <= 1 ? lmin : 2 - lmin 181 | const v = (l + s) / 2 182 | const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s) 183 | 184 | return [h, sv * 100, v * 100] 185 | } 186 | 187 | //Credit : https://github.com/Qix-/color-convert 188 | export const _rgbToHSV = rgb => { 189 | let rdif 190 | let gdif 191 | let bdif 192 | let h 193 | let s 194 | 195 | const r = rgb[0] / 255 196 | const g = rgb[1] / 255 197 | const b = rgb[2] / 255 198 | const v = Math.max(r, g, b) 199 | const diff = v - Math.min(r, g, b) 200 | const diffc = function(c) { 201 | return (v - c) / 6 / diff + 1 / 2 202 | } 203 | 204 | if (diff === 0) { 205 | h = 0 206 | s = 0 207 | } else { 208 | s = diff / v 209 | rdif = diffc(r) 210 | gdif = diffc(g) 211 | bdif = diffc(b) 212 | 213 | if (r === v) { 214 | h = bdif - gdif 215 | } else if (g === v) { 216 | h = 1 / 3 + rdif - bdif 217 | } else if (b === v) { 218 | h = 2 / 3 + gdif - rdif 219 | } 220 | 221 | if (h < 0) { 222 | h += 1 223 | } else if (h > 1) { 224 | h -= 1 225 | } 226 | } 227 | 228 | const hsvResult = [h * 360, s * 100, v * 100].map(v => Math.round(v)) 229 | return hsvResult 230 | } 231 | 232 | //Credit : https://github.com/Qix-/color-convert 233 | export const _hsvToRgb = hsv => { 234 | const h = hsv[0] / 60 235 | const s = hsv[1] / 100 236 | let v = hsv[2] / 100 237 | const hi = Math.floor(h) % 6 238 | 239 | const f = h - Math.floor(h) 240 | const p = 255 * v * (1 - s) 241 | const q = 255 * v * (1 - s * f) 242 | const t = 255 * v * (1 - s * (1 - f)) 243 | v *= 255 244 | 245 | switch (hi) { 246 | case 0: 247 | return [v, t, p] 248 | case 1: 249 | return [q, v, p] 250 | case 2: 251 | return [p, v, t] 252 | case 3: 253 | return [p, q, v] 254 | case 4: 255 | return [t, p, v] 256 | case 5: 257 | return [v, p, q] 258 | } 259 | } 260 | 261 | //Credit : https://github.com/Qix-/color-convert 262 | export const _hsvToHSL = hsv => { 263 | const h = hsv[0] 264 | const s = hsv[1] / 100 265 | const v = hsv[2] / 100 266 | const vmin = Math.max(v, 0.01) 267 | let sl 268 | let l 269 | 270 | l = (2 - s) * v 271 | const lmin = (2 - s) * vmin 272 | sl = s * vmin 273 | sl /= lmin <= 1 ? lmin : 2 - lmin 274 | sl = sl || 0 275 | l /= 2 276 | 277 | return [_fixNum(h, 0), _fixNum(sl * 100, 0), _fixNum(l * 100, 0)] 278 | } 279 | 280 | export const _fixNum = (value, decimalPlaces) => 281 | Number(parseFloat(value).toFixed(decimalPlaces)) 282 | -------------------------------------------------------------------------------- /utils.test.js: -------------------------------------------------------------------------------- 1 | import { getColorFormat, convertToHSVA, convertHsvaToFormat } from "./utils" 2 | 3 | describe("convertToHSVA - convert to hsva from format", () => { 4 | test("convert from hexa", () => { 5 | expect(convertToHSVA("#f222d382", "hex")).toEqual([309, 86, 95, 0.51]) 6 | }) 7 | 8 | test("convert from hex", () => { 9 | expect(convertToHSVA("#f222d3", "hex")).toEqual([309, 86, 95, 1]) 10 | }) 11 | 12 | test("convert from rgba", () => { 13 | expect(convertToHSVA("rgba(242, 34, 211, 1)", "rgb")).toEqual([ 14 | 309, 15 | 86, 16 | 95, 17 | 1, 18 | ]) 19 | }) 20 | 21 | test("convert from rgb", () => { 22 | expect(convertToHSVA("rgb(150, 80, 255)", "rgb")).toEqual([264, 69, 100, 1]) 23 | }) 24 | 25 | test("convert from from hsl", () => { 26 | expect(convertToHSVA("hsl(264, 100%, 65.7%)", "hsl")).toEqual([ 27 | 264, 28 | 68.6, 29 | 100, 30 | 1, 31 | ]) 32 | }) 33 | 34 | test("convert from from hsla", () => { 35 | expect(convertToHSVA("hsla(264, 100%, 65.7%, 0.51)", "hsl")).toEqual([ 36 | 264, 37 | 68.6, 38 | 100, 39 | 0.51, 40 | ]) 41 | }) 42 | }) 43 | 44 | describe("convertHsvaToFormat - convert from hsva to format", () => { 45 | test("Convert to hexa", () => { 46 | expect(convertHsvaToFormat([264, 68.63, 100, 0.5], "hex")).toBe("#9650ff80") 47 | }) 48 | 49 | test("Convert to rgba", () => { 50 | expect(convertHsvaToFormat([264, 68.63, 100, 0.75], "rgb")).toBe( 51 | "rgba(150,80,255,0.75)" 52 | ) 53 | }) 54 | 55 | test("Convert to hsla", () => { 56 | expect(convertHsvaToFormat([264, 68.63, 100, 1], "hsl")).toBe( 57 | "hsla(264,100%,66%,1)" 58 | ) 59 | }) 60 | }) 61 | 62 | describe("Get Color Format", () => { 63 | test("Testing valid hex string", () => { 64 | expect(getColorFormat("#FFF")).toBe("hex") 65 | }) 66 | 67 | test("Testing invalid hex string", () => { 68 | expect(getColorFormat("#FFZ")).toBeUndefined() 69 | }) 70 | 71 | test("Testing valid hex with alpha", () => { 72 | expect(getColorFormat("#FF00BB80")).toBe("hex") 73 | }) 74 | 75 | test("Test valid rgb value", () => { 76 | expect(getColorFormat("RGB(255, 20, 50)")).toBe("rgb") 77 | }) 78 | 79 | test("Testing invalid rgb value", () => { 80 | expect(getColorFormat("rgb(255, 0)")).toBeUndefined() 81 | }) 82 | 83 | test("Testing rgb value with alpha", () => { 84 | expect(getColorFormat("rgba(255, 0, 50, 0.5)")).toBe("rgb") 85 | }) 86 | 87 | test("Testing rgb value with incorrectly provided alpha", () => { 88 | expect(getColorFormat("rgb(255, 0, 50, 0.5)")).toBeUndefined() 89 | }) 90 | 91 | test("Testing invalid hsl value", () => { 92 | expect(getColorFormat("hsla(255, 0)")).toBeUndefined() 93 | }) 94 | 95 | test("Testing hsla value with alpha", () => { 96 | expect(getColorFormat("hsla(150, 60%, 50%, 0.5)")).toBe("hsl") 97 | }) 98 | 99 | test("Testing hsl value with incorrectly provided alpha", () => { 100 | expect(getColorFormat("hsl(150, 0, 50, 0.5)")).toBeUndefined() 101 | }) 102 | 103 | test("Testing out of bounds hsl", () => { 104 | expect(getColorFormat("hsl(375, 0, 50)")).toBeUndefined() 105 | }) 106 | }) 107 | --------------------------------------------------------------------------------