forked from tsai/budibase
23 changed files with 2 additions and 2293 deletions
@ -1,116 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount } from "svelte" |
|
||||
// import { HsvPicker } from "svelte-color-picker" |
|
||||
|
|
||||
// export let initialValue = "#ffffff" |
|
||||
export let onChange = color => {} |
|
||||
export let open = false |
|
||||
let value = "#ffffff" |
|
||||
|
|
||||
let _justMounted = true //see onColorChange |
|
||||
let pickerHeight = 275 |
|
||||
let colorPreview |
|
||||
let pickerTopPosition = null |
|
||||
|
|
||||
function rbgaToHexa({ r, g, b, a }) { |
|
||||
r = r.toString(16) |
|
||||
g = g.toString(16) |
|
||||
b = b.toString(16) |
|
||||
a = Math.round(a * 255).toString(16) |
|
||||
|
|
||||
if (r.length == 1) r = "0" + r |
|
||||
if (g.length == 1) g = "0" + g |
|
||||
if (b.length == 1) b = "0" + b |
|
||||
if (a.length == 1) a = "0" + a |
|
||||
|
|
||||
return "#" + r + g + b + a |
|
||||
} |
|
||||
|
|
||||
function onColourChange(rgba) { |
|
||||
value = rbgaToHexa(rgba.detail) |
|
||||
|
|
||||
//Hack: so that color change doesn't fire onMount |
|
||||
if (!_justMounted) { |
|
||||
// onChange(value) |
|
||||
} |
|
||||
_justMounted = false |
|
||||
} |
|
||||
|
|
||||
function toggleColorpicker(isOpen) { |
|
||||
if (isOpen) { |
|
||||
const { |
|
||||
y: previewYPosition, |
|
||||
height: previewHeight, |
|
||||
} = colorPreview.getBoundingClientRect() |
|
||||
|
|
||||
let wiggleRoom = window.innerHeight - previewYPosition |
|
||||
let displayTop = wiggleRoom < pickerHeight |
|
||||
|
|
||||
if (displayTop) { |
|
||||
pickerTopPosition = previewYPosition - (pickerHeight - window.scrollY) |
|
||||
} else { |
|
||||
pickerTopPosition = null |
|
||||
} |
|
||||
} |
|
||||
open = isOpen |
|
||||
} |
|
||||
|
|
||||
$: style = open ? "display: block;" : "display: none;" |
|
||||
$: pickerStyle = pickerTopPosition ? `top: ${pickerTopPosition}px;` : "" |
|
||||
</script> |
|
||||
|
|
||||
<div |
|
||||
bind:this={colorPreview} |
|
||||
on:click={() => toggleColorpicker(true)} |
|
||||
class="color-preview" |
|
||||
style={`background: ${value}`} /> |
|
||||
|
|
||||
<div class="colorpicker" {style}> |
|
||||
<div class="overlay" on:click|self={() => toggleColorpicker(false)} /> |
|
||||
<div class="cp" style={pickerStyle}> |
|
||||
<!-- <HsvPicker on:colorChange={onColourChange} startColor={value} /> --> |
|
||||
</div> |
|
||||
</div> |
|
||||
<!-- |
|
||||
OLD LOCAL STORAGE OPTIONS. INCLUDING FOR ADDING LATER |
|
||||
function getRecentColors() { |
|
||||
let colorStore = localStorage.getItem("bb:recentColors") |
|
||||
if (!!colorStore) { |
|
||||
swatches = JSON.parse(colorStore) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function setRecentColor(color) { |
|
||||
if (swatches.length >= 15) { |
|
||||
swatches.splice(0, 1) |
|
||||
picker.removeSwatch(0) |
|
||||
} |
|
||||
if (!swatches.includes(color)) { |
|
||||
swatches = [...swatches, color] |
|
||||
picker.addSwatch(color) |
|
||||
localStorage.setItem("bb:recentColors", JSON.stringify(swatches)) |
|
||||
} |
|
||||
} --> |
|
||||
|
|
||||
<style> |
|
||||
.overlay { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
bottom: 0; |
|
||||
/* background: rgba(5, 5, 5, 0.25); */ |
|
||||
} |
|
||||
|
|
||||
.cp { |
|
||||
position: absolute; |
|
||||
right: 25px; |
|
||||
} |
|
||||
.color-preview { |
|
||||
height: 30px; |
|
||||
width: 100%; |
|
||||
margin: 5px; |
|
||||
cursor: pointer; |
|
||||
border: 1px solid gainsboro; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,23 +0,0 @@ |
|||||
export default function(node) { |
|
||||
function handleMouseDown() { |
|
||||
window.addEventListener("mousemove", handleMouseMove) |
|
||||
window.addEventListener("mouseup", handleMouseUp) |
|
||||
node.dispatchEvent(new CustomEvent("dragstart")) |
|
||||
} |
|
||||
|
|
||||
function handleMouseMove(event) { |
|
||||
node.dispatchEvent( |
|
||||
new CustomEvent("drag", { |
|
||||
detail: { mouseX: event.clientX, mouseY: event.clientY }, |
|
||||
}) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
function handleMouseUp() { |
|
||||
window.removeEventListener("mousedown", handleMouseDown) |
|
||||
window.removeEventListener("mousemove", handleMouseMove) |
|
||||
node.dispatchEvent(new CustomEvent("dragend")) |
|
||||
} |
|
||||
|
|
||||
node.addEventListener("mousedown", handleMouseDown) |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
export { default as drag } from "./drag.js" |
|
||||
export { default as keyevents } from "./key-events.js" |
|
||||
@ -1,41 +0,0 @@ |
|||||
//events: Array<{trigger: fn}>
|
|
||||
export default function(node, events = []) { |
|
||||
const ev = Object.entries(events) |
|
||||
let fns = [] |
|
||||
|
|
||||
for (let [trigger, fn] of ev) { |
|
||||
let f = addEvent(trigger, fn) |
|
||||
fns = [...fns, f] |
|
||||
} |
|
||||
|
|
||||
function _scaffold(trigger, fn) { |
|
||||
return () => { |
|
||||
let trig = parseInt(trigger) |
|
||||
if (trig) { |
|
||||
if (event.keyCode === trig) { |
|
||||
fn(event) |
|
||||
} |
|
||||
} else { |
|
||||
if (event.key === trigger) { |
|
||||
fn(event) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function addEvent(trigger, fn) { |
|
||||
let f = _scaffold(trigger, fn) |
|
||||
node.addEventListener("keydown", f) |
|
||||
return f |
|
||||
} |
|
||||
|
|
||||
function removeEvents() { |
|
||||
fns.forEach(f => node.removeEventListener("keypress", f)) |
|
||||
} |
|
||||
|
|
||||
return { |
|
||||
destroy() { |
|
||||
removeEvents() |
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
@ -1,31 +0,0 @@ |
|||||
<script> |
|
||||
import FlatButton from "./FlatButton.svelte" |
|
||||
import { createEventDispatcher } from "svelte" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
|
|
||||
export let format = "hex" |
|
||||
|
|
||||
let colorFormats = ["hex", "rgb", "hsl"] |
|
||||
</script> |
|
||||
|
|
||||
<div class="flatbutton-group"> |
|
||||
{#each colorFormats as text} |
|
||||
<FlatButton |
|
||||
selected={format === text} |
|
||||
{text} |
|
||||
on:click={() => dispatch('click', text)} /> |
|
||||
{/each} |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.flatbutton-group { |
|
||||
font-weight: 500; |
|
||||
display: flex; |
|
||||
flex-flow: row nowrap; |
|
||||
justify-content: center; |
|
||||
width: 170px; |
|
||||
height: 30px; |
|
||||
align-self: center; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,26 +0,0 @@ |
|||||
<script> |
|
||||
import { buildStyle } from "../helpers.js" |
|
||||
import { fade } from "svelte/transition" |
|
||||
|
|
||||
export let backgroundSize = "10px" |
|
||||
export let borderRadius = "" |
|
||||
export let height = "" |
|
||||
export let width = "" |
|
||||
export let margin = "" |
|
||||
|
|
||||
$: style = buildStyle({ backgroundSize, borderRadius, height, width, margin }) |
|
||||
</script> |
|
||||
|
|
||||
<div in:fade {style}> |
|
||||
<slot /> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
div { |
|
||||
background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>'); |
|
||||
height: fit-content; |
|
||||
width: fit-content; |
|
||||
width: -moz-fit-content; |
|
||||
height: -moz-fit-content; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,349 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount, createEventDispatcher } from "svelte" |
|
||||
import { fade } from "svelte/transition" |
|
||||
import Swatch from "./Swatch.svelte" |
|
||||
import CheckedBackground from "./CheckedBackground.svelte" |
|
||||
import { buildStyle } from "../helpers.js" |
|
||||
import { |
|
||||
getColorFormat, |
|
||||
convertToHSVA, |
|
||||
convertHsvaToFormat, |
|
||||
} from "../utils.js" |
|
||||
import Slider from "./Slider.svelte" |
|
||||
import Palette from "./Palette.svelte" |
|
||||
import ButtonGroup from "./ButtonGroup.svelte" |
|
||||
import Input from "./Input.svelte" |
|
||||
import Portal from "./Portal.svelte" |
|
||||
import { keyevents } from "../actions" |
|
||||
|
|
||||
export let value = "#3ec1d3ff" |
|
||||
export let open = false |
|
||||
export let swatches = [] |
|
||||
|
|
||||
export let disableSwatches = false |
|
||||
export let format = "hexa" |
|
||||
export let style = "" |
|
||||
export let pickerHeight = 0 |
|
||||
export let pickerWidth = 0 |
|
||||
|
|
||||
let colorPicker = null |
|
||||
let adder = null |
|
||||
let swatchesSetFromLocalStore = false |
|
||||
|
|
||||
let h = 0 |
|
||||
let s = 0 |
|
||||
let v = 0 |
|
||||
let a = 0 |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
|
|
||||
onMount(() => { |
|
||||
if (!swatches.length > 0) { |
|
||||
//Don't use locally stored recent colors if swatches have been passed as props |
|
||||
swatchesSetFromLocalStore = true |
|
||||
swatches = getRecentColors() || [] |
|
||||
} |
|
||||
|
|
||||
if (swatches.length > 12) { |
|
||||
console.warn( |
|
||||
`Colorpicker - ${swatches.length} swatches were provided. Only the first 12 swatches will be displayed.` |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
if (colorPicker) { |
|
||||
colorPicker.focus() |
|
||||
} |
|
||||
|
|
||||
if (format) { |
|
||||
convertAndSetHSVA() |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
function getRecentColors() { |
|
||||
let colorStore = localStorage.getItem("cp:recent-colors") |
|
||||
if (colorStore) { |
|
||||
return JSON.parse(colorStore) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function handleEscape() { |
|
||||
if (open) { |
|
||||
open = false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function setRecentColors(color) { |
|
||||
const s = swatchesSetFromLocalStore |
|
||||
? swatches |
|
||||
: [...getRecentColors(), color] |
|
||||
localStorage.setItem("cp:recent-colors", JSON.stringify(s)) |
|
||||
} |
|
||||
|
|
||||
function convertAndSetHSVA() { |
|
||||
let hsva = convertToHSVA(value, format) |
|
||||
setHSVA(hsva) |
|
||||
} |
|
||||
|
|
||||
function setHSVA([hue, sat, val, alpha]) { |
|
||||
h = hue |
|
||||
s = sat |
|
||||
v = val |
|
||||
a = alpha |
|
||||
} |
|
||||
|
|
||||
//fired by choosing a color from the palette |
|
||||
function setSaturationAndValue({ detail }) { |
|
||||
s = detail.s |
|
||||
v = detail.v |
|
||||
value = convertHsvaToFormat([h, s, v, a], format) |
|
||||
dispatchValue() |
|
||||
} |
|
||||
|
|
||||
function setHue({ color, isDrag }) { |
|
||||
h = color |
|
||||
value = convertHsvaToFormat([h, s, v, a], format) |
|
||||
if (!isDrag) { |
|
||||
dispatchValue() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function setAlpha({ color, isDrag }) { |
|
||||
a = color === "1.00" ? "1" : color |
|
||||
value = convertHsvaToFormat([h, s, v, a], format) |
|
||||
if (!isDrag) { |
|
||||
dispatchValue() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function dispatchValue() { |
|
||||
dispatch("change", value) |
|
||||
} |
|
||||
|
|
||||
function changeFormatAndConvert(f) { |
|
||||
format = f.detail |
|
||||
value = convertHsvaToFormat([h, s, v, a], format) |
|
||||
} |
|
||||
|
|
||||
function handleColorInput(text) { |
|
||||
format = getColorFormat(text) |
|
||||
if (format) { |
|
||||
value = text |
|
||||
convertAndSetHSVA() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function dispatchInputChange() { |
|
||||
if (format) { |
|
||||
dispatchValue() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function addSwatch() { |
|
||||
if (format) { |
|
||||
if (swatches.length === 12) { |
|
||||
swatches.splice(0, 1) |
|
||||
} |
|
||||
|
|
||||
if (!swatches.includes(value)) { |
|
||||
swatches = [...swatches, value] |
|
||||
setRecentColors(value) |
|
||||
} |
|
||||
|
|
||||
dispatch("addswatch", value) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function removeSwatch(idx) { |
|
||||
let [removedSwatch] = swatches.splice(idx, 1) |
|
||||
swatches = swatches |
|
||||
dispatch("removeswatch", removedSwatch) |
|
||||
if (swatchesSetFromLocalStore) { |
|
||||
//as could be a swatch not present in local storage |
|
||||
setRecentColors() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function applySwatch(color) { |
|
||||
if (value !== color) { |
|
||||
format = getColorFormat(color) |
|
||||
if (format) { |
|
||||
value = color |
|
||||
convertAndSetHSVA() |
|
||||
dispatchValue() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : "" |
|
||||
$: selectedColorStyle = buildStyle({ background: value, border }) |
|
||||
$: hasSwatches = swatches.length > 0 |
|
||||
</script> |
|
||||
|
|
||||
<Portal> |
|
||||
<div |
|
||||
class="colorpicker-container" |
|
||||
use:keyevents={{ Escape: handleEscape }} |
|
||||
transition:fade |
|
||||
bind:this={colorPicker} |
|
||||
{style} |
|
||||
tabindex="0" |
|
||||
bind:clientHeight={pickerHeight} |
|
||||
bind:clientWidth={pickerWidth}> |
|
||||
|
|
||||
<div class="palette-panel"> |
|
||||
<Palette on:change={setSaturationAndValue} {h} {s} {v} {a} /> |
|
||||
</div> |
|
||||
|
|
||||
<div class="control-panel"> |
|
||||
<div class="alpha-hue-panel"> |
|
||||
<div> |
|
||||
<CheckedBackground borderRadius="50%" backgroundSize="8px"> |
|
||||
<div |
|
||||
class="selected-color" |
|
||||
title={value} |
|
||||
style={selectedColorStyle} /> |
|
||||
</CheckedBackground> |
|
||||
</div> |
|
||||
<div> |
|
||||
<Slider |
|
||||
type="hue" |
|
||||
value={h} |
|
||||
on:change={hue => setHue(hue.detail)} |
|
||||
on:dragend={dispatchValue} /> |
|
||||
|
|
||||
<CheckedBackground borderRadius="10px" backgroundSize="7px"> |
|
||||
<Slider |
|
||||
type="alpha" |
|
||||
value={a} |
|
||||
on:change={(alpha, isDrag) => setAlpha(alpha.detail, isDrag)} |
|
||||
on:dragend={dispatchValue} /> |
|
||||
</CheckedBackground> |
|
||||
|
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{#if !disableSwatches} |
|
||||
<div transition:fade class="swatch-panel"> |
|
||||
{#if hasSwatches} |
|
||||
{#each swatches as color, idx} |
|
||||
{#if idx < 12} |
|
||||
<Swatch |
|
||||
{color} |
|
||||
on:click={() => applySwatch(color)} |
|
||||
on:removeswatch={() => removeSwatch(idx)} /> |
|
||||
{/if} |
|
||||
{/each} |
|
||||
{/if} |
|
||||
{#if swatches.length < 12} |
|
||||
<div |
|
||||
tabindex="0" |
|
||||
title="Add Swatch" |
|
||||
use:keyevents={{ Enter: addSwatch }} |
|
||||
bind:this={adder} |
|
||||
transition:fade |
|
||||
class="adder" |
|
||||
class:shrink={hasSwatches} |
|
||||
on:click={addSwatch}> |
|
||||
<span>+</span> |
|
||||
</div> |
|
||||
{/if} |
|
||||
</div> |
|
||||
{/if} |
|
||||
|
|
||||
<div class="format-input-panel"> |
|
||||
<ButtonGroup {format} on:click={changeFormatAndConvert} /> |
|
||||
<Input |
|
||||
{value} |
|
||||
on:input={event => handleColorInput(event.target.value)} |
|
||||
on:change={dispatchInputChange} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
</div> |
|
||||
</Portal> |
|
||||
|
|
||||
<style> |
|
||||
.colorpicker-container { |
|
||||
position: absolute; |
|
||||
outline: none; |
|
||||
z-index: 3; |
|
||||
display: flex; |
|
||||
font-size: 11px; |
|
||||
font-weight: 400; |
|
||||
transition: top 0.1s, left 0.1s; |
|
||||
flex-direction: column; |
|
||||
margin: 5px 0px; |
|
||||
height: auto; |
|
||||
width: 220px; |
|
||||
background: #ffffff; |
|
||||
border-radius: 2px; |
|
||||
box-shadow: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1), |
|
||||
0 0 1em 0 rgba(0, 0, 0, 0.03); |
|
||||
} |
|
||||
|
|
||||
.palette-panel { |
|
||||
flex: 1; |
|
||||
} |
|
||||
|
|
||||
.control-panel { |
|
||||
flex: 1; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
padding: 8px; |
|
||||
background: white; |
|
||||
border: 1px solid #d2d2d2; |
|
||||
color: #777373; |
|
||||
} |
|
||||
|
|
||||
.alpha-hue-panel { |
|
||||
display: grid; |
|
||||
grid-template-columns: 25px 1fr; |
|
||||
grid-gap: 15px; |
|
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
.selected-color { |
|
||||
width: 30px; |
|
||||
height: 30px; |
|
||||
border-radius: 50%; |
|
||||
} |
|
||||
|
|
||||
.swatch-panel { |
|
||||
flex: 0 0 15px; |
|
||||
display: flex; |
|
||||
flex-flow: row wrap; |
|
||||
justify-content: flex-start; |
|
||||
padding: 0 5px; |
|
||||
max-height: 56px; |
|
||||
} |
|
||||
|
|
||||
.adder { |
|
||||
flex: 1; |
|
||||
height: 20px; |
|
||||
display: flex; |
|
||||
transition: flex 0.3s; |
|
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
background: #f1f3f4; |
|
||||
cursor: pointer; |
|
||||
border: 1px solid #d4d4d4; |
|
||||
border-radius: 8px; |
|
||||
margin-left: 5px; |
|
||||
margin-top: 3px; |
|
||||
font-weight: 500; |
|
||||
outline-color: #003cb0; |
|
||||
outline-width: thin; |
|
||||
} |
|
||||
|
|
||||
.shrink { |
|
||||
flex: 0 0 20px; |
|
||||
} |
|
||||
|
|
||||
.format-input-panel { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
justify-content: center; |
|
||||
padding-top: 3px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,177 +0,0 @@ |
|||||
<script> |
|
||||
import Colorpicker from "./Colorpicker.svelte" |
|
||||
import CheckedBackground from "./CheckedBackground.svelte" |
|
||||
import { createEventDispatcher, beforeUpdate, onMount } from "svelte" |
|
||||
|
|
||||
import { buildStyle, debounce } from "../helpers.js" |
|
||||
import { fade } from "svelte/transition" |
|
||||
import { getColorFormat } from "../utils.js" |
|
||||
|
|
||||
export let value = "#3ec1d3ff" |
|
||||
export let swatches = [] |
|
||||
export let disableSwatches = false |
|
||||
export let open = false |
|
||||
export let width = "25px" |
|
||||
export let height = "25px" |
|
||||
|
|
||||
let format = "hexa" |
|
||||
let dimensions = { top: 0, bottom: 0, right: 0, left: 0 } |
|
||||
let positionY = "top" |
|
||||
let positionX = "left" |
|
||||
let colorPreview = null |
|
||||
|
|
||||
let previewHeight = null |
|
||||
let previewWidth = null |
|
||||
let pickerWidth = 0 |
|
||||
let pickerHeight = 0 |
|
||||
|
|
||||
let errorMsg = null |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
const dispatchValue = value => |
|
||||
debounce(() => dispatch("change", value), 300, true) |
|
||||
|
|
||||
beforeUpdate(() => { |
|
||||
format = getColorFormat(value) |
|
||||
if (!format) { |
|
||||
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value` |
|
||||
console.error(errorMsg) |
|
||||
} else { |
|
||||
errorMsg = null |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
function openColorpicker(event) { |
|
||||
if (colorPreview) { |
|
||||
open = true |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function onColorChange(color) { |
|
||||
value = color.detail |
|
||||
dispatchValue(value) |
|
||||
} |
|
||||
|
|
||||
function calculateDimensions() { |
|
||||
if (open) { |
|
||||
const { |
|
||||
top: spaceAbove, |
|
||||
width, |
|
||||
bottom, |
|
||||
right, |
|
||||
left, |
|
||||
} = colorPreview.getBoundingClientRect() |
|
||||
|
|
||||
const spaceBelow = window.innerHeight - bottom |
|
||||
const previewCenter = previewWidth / 2 |
|
||||
|
|
||||
let y, x |
|
||||
|
|
||||
if (spaceAbove > spaceBelow) { |
|
||||
positionY = "bottom" |
|
||||
y = window.innerHeight - spaceAbove |
|
||||
} else { |
|
||||
positionY = "top" |
|
||||
y = bottom |
|
||||
} |
|
||||
|
|
||||
// Centre picker by default |
|
||||
x = left + previewCenter - 220 / 2 |
|
||||
|
|
||||
const spaceRight = window.innerWidth - right |
|
||||
|
|
||||
//Position picker left or right if space not available for centering |
|
||||
if (left < 110 && spaceRight > 220) { |
|
||||
positionX = "left" |
|
||||
x = right |
|
||||
} else if (spaceRight < 100 && left > 220) { |
|
||||
positionX = "right" |
|
||||
x = document.body.clientWidth - left |
|
||||
} |
|
||||
|
|
||||
dimensions = { [positionY]: y.toFixed(1), [positionX]: x.toFixed(1) } |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
$: if (open && colorPreview) { |
|
||||
calculateDimensions() |
|
||||
} |
|
||||
|
|
||||
$: previewStyle = buildStyle({ width, height, background: value }) |
|
||||
$: errorPreviewStyle = buildStyle({ width, height }) |
|
||||
$: pickerStyle = buildStyle({ |
|
||||
[positionY]: `${dimensions[positionY]}px`, |
|
||||
[positionX]: `${dimensions[positionX]}px`, |
|
||||
}) |
|
||||
</script> |
|
||||
|
|
||||
<svelte:window on:resize={debounce(calculateDimensions, 200)} /> |
|
||||
|
|
||||
<div class="color-preview-container"> |
|
||||
{#if !errorMsg} |
|
||||
<CheckedBackground borderRadius="3px" backgroundSize="8px"> |
|
||||
<div |
|
||||
bind:this={colorPreview} |
|
||||
bind:clientHeight={previewHeight} |
|
||||
bind:clientWidth={previewWidth} |
|
||||
title={value} |
|
||||
class="color-preview" |
|
||||
style={previewStyle} |
|
||||
on:click={openColorpicker} /> |
|
||||
</CheckedBackground> |
|
||||
|
|
||||
{#if open} |
|
||||
<Colorpicker |
|
||||
style={pickerStyle} |
|
||||
on:change={onColorChange} |
|
||||
on:addswatch |
|
||||
on:removeswatch |
|
||||
bind:format |
|
||||
bind:value |
|
||||
bind:pickerHeight |
|
||||
bind:pickerWidth |
|
||||
bind:open |
|
||||
{swatches} |
|
||||
{disableSwatches} /> |
|
||||
<div on:click|self={() => (open = false)} class="overlay" /> |
|
||||
{/if} |
|
||||
{:else} |
|
||||
<div |
|
||||
class="color-preview preview-error" |
|
||||
title="Invalid Color" |
|
||||
style={errorPreviewStyle}> |
|
||||
<span>×</span> |
|
||||
</div> |
|
||||
{/if} |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.color-preview-container { |
|
||||
display: flex; |
|
||||
flex-flow: row nowrap; |
|
||||
height: fit-content; |
|
||||
} |
|
||||
|
|
||||
.color-preview { |
|
||||
cursor: pointer; |
|
||||
border-radius: 3px; |
|
||||
border: 1px solid #dedada; |
|
||||
} |
|
||||
|
|
||||
.preview-error { |
|
||||
background: #cccccc; |
|
||||
color: #808080; |
|
||||
text-align: center; |
|
||||
font-size: 18px; |
|
||||
cursor: not-allowed; |
|
||||
} |
|
||||
|
|
||||
.overlay { |
|
||||
position: fixed; |
|
||||
top: 0; |
|
||||
bottom: 0; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
z-index: 2; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,44 +0,0 @@ |
|||||
<script> |
|
||||
import { createEventDispatcher } from "svelte" |
|
||||
import { keyevents } from "../actions" |
|
||||
|
|
||||
export let text = "" |
|
||||
export let selected = false |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
</script> |
|
||||
|
|
||||
<div |
|
||||
class="flatbutton" |
|
||||
tabindex="0" |
|
||||
use:keyevents={{ Enter: () => dispatch('click') }} |
|
||||
class:selected |
|
||||
on:click> |
|
||||
{text} |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.flatbutton { |
|
||||
cursor: pointer; |
|
||||
border: 1px solid #d4d4d4; |
|
||||
border-radius: 8px; |
|
||||
text-transform: uppercase; |
|
||||
margin: 5px; |
|
||||
transition: all 0.3s; |
|
||||
font-size: 10px; |
|
||||
flex: 1; |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
background: #f1f3f4; |
|
||||
outline-color: #003cb0; |
|
||||
outline-width: thin; |
|
||||
} |
|
||||
|
|
||||
.selected { |
|
||||
color: #ffffff; |
|
||||
background-color: #003cb0; |
|
||||
border: none; |
|
||||
outline: none; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,29 +0,0 @@ |
|||||
<script> |
|
||||
export let value = "" |
|
||||
</script> |
|
||||
|
|
||||
<div> |
|
||||
<input on:input on:change type="text" {value} maxlength="25" /> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
div { |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
margin: 5px 0px; |
|
||||
} |
|
||||
|
|
||||
input { |
|
||||
width: 175px; |
|
||||
font-size: 14px; |
|
||||
background: #f1f3f4; |
|
||||
border-radius: 8px; |
|
||||
height: 20px; |
|
||||
outline-color: #003cb0; |
|
||||
color: inherit; |
|
||||
text-align: center; |
|
||||
border: 1px solid #dadada; |
|
||||
font-weight: 550; |
|
||||
font-family: sans-serif; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,81 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount, createEventDispatcher } from "svelte" |
|
||||
import { drag } from "../actions" |
|
||||
import CheckedBackground from "./CheckedBackground.svelte" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
|
|
||||
export let h = 0 |
|
||||
export let s = 0 |
|
||||
export let v = 0 |
|
||||
export let a = 1 |
|
||||
|
|
||||
let palette |
|
||||
|
|
||||
let paletteHeight, |
|
||||
paletteWidth = 0 |
|
||||
|
|
||||
function handePaletteChange({ mouseX, mouseY }) { |
|
||||
const { left, top } = palette.getBoundingClientRect() |
|
||||
let x = mouseX - left |
|
||||
let y = mouseY - top |
|
||||
if (x > 0 && y > 0 && x < paletteWidth && y < paletteHeight) { |
|
||||
let s = (x / paletteWidth) * 100 |
|
||||
let v = 100 - (y / paletteHeight) * 100 |
|
||||
dispatch("change", { s, v }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
$: pickerX = (s * paletteWidth) / 100 |
|
||||
$: pickerY = paletteHeight * ((100 - v) / 100) |
|
||||
|
|
||||
$: paletteGradient = `linear-gradient(to top, rgba(0, 0, 0, 1), transparent), |
|
||||
linear-gradient(to left, hsla(${h}, 100%, 50%, ${a}), rgba(255, 255, 255, ${a})) |
|
||||
` |
|
||||
$: style = `background: ${paletteGradient};` |
|
||||
|
|
||||
$: pickerStyle = `transform: translate(${pickerX - 8}px, ${pickerY - 8}px);` |
|
||||
</script> |
|
||||
|
|
||||
<CheckedBackground width="100%"> |
|
||||
<div |
|
||||
bind:this={palette} |
|
||||
bind:clientHeight={paletteHeight} |
|
||||
bind:clientWidth={paletteWidth} |
|
||||
on:click={event => handePaletteChange({ |
|
||||
mouseX: event.clientX, |
|
||||
mouseY: event.clientY, |
|
||||
})} |
|
||||
class="palette" |
|
||||
{style}> |
|
||||
<div |
|
||||
use:drag |
|
||||
on:drag={event => handePaletteChange(event.detail)} |
|
||||
class="picker" |
|
||||
style={pickerStyle} /> |
|
||||
</div> |
|
||||
</CheckedBackground> |
|
||||
|
|
||||
<style> |
|
||||
.palette { |
|
||||
position: relative; |
|
||||
width: 100%; |
|
||||
height: 140px; |
|
||||
cursor: crosshair; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
.picker { |
|
||||
position: absolute; |
|
||||
cursor: grab; |
|
||||
width: 10px; |
|
||||
height: 10px; |
|
||||
background: transparent; |
|
||||
border: 2px solid white; |
|
||||
border-radius: 50%; |
|
||||
} |
|
||||
|
|
||||
.picker:active { |
|
||||
cursor: grabbing; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,37 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount } from "svelte" |
|
||||
|
|
||||
export let target = document.body |
|
||||
|
|
||||
let targetEl |
|
||||
let portal |
|
||||
let componentInstance |
|
||||
|
|
||||
onMount(() => { |
|
||||
if (typeof target === "string") { |
|
||||
targetEl = document.querySelector(target) |
|
||||
// Force exit |
|
||||
if (targetEl === null) { |
|
||||
return () => {} |
|
||||
} |
|
||||
} else if (target instanceof HTMLElement) { |
|
||||
targetEl = target |
|
||||
} else { |
|
||||
throw new TypeError( |
|
||||
`Unknown target type: ${typeof target}. Allowed types: String (CSS selector), HTMLElement.` |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
portal = document.createElement("div") |
|
||||
targetEl.appendChild(portal) |
|
||||
portal.appendChild(componentInstance) |
|
||||
|
|
||||
return () => { |
|
||||
targetEl.removeChild(portal) |
|
||||
} |
|
||||
}) |
|
||||
</script> |
|
||||
|
|
||||
<div bind:this={componentInstance}> |
|
||||
<slot /> |
|
||||
</div> |
|
||||
@ -1,118 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount, createEventDispatcher } from "svelte" |
|
||||
import { drag, keyevents } from "../actions" |
|
||||
|
|
||||
export let value = 1 |
|
||||
export let type = "hue" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
|
|
||||
let slider |
|
||||
let sliderWidth = 0 |
|
||||
let upperLimit = type === "hue" ? 360 : 1 |
|
||||
let incrementFactor = type === "hue" ? 1 : 0.01 |
|
||||
|
|
||||
const isWithinLimit = value => value >= 0 && value <= upperLimit |
|
||||
|
|
||||
function onSliderChange({ mouseX }, isDrag = false) { |
|
||||
const { left, width } = slider.getBoundingClientRect() |
|
||||
|
|
||||
let clickPosition = mouseX - left |
|
||||
|
|
||||
let percentageClick = (clickPosition / sliderWidth).toFixed(2) |
|
||||
|
|
||||
if (percentageClick >= 0 && percentageClick <= 1) { |
|
||||
let value = type === "hue" ? 360 * percentageClick : percentageClick |
|
||||
dispatch("change", { color: value, isDrag }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function handleLeftKey() { |
|
||||
let v = value - incrementFactor |
|
||||
if (isWithinLimit(v)) { |
|
||||
value = v |
|
||||
dispatch("change", { color: value }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function handleRightKey() { |
|
||||
let v = value + incrementFactor |
|
||||
if (isWithinLimit(v)) { |
|
||||
value = v |
|
||||
dispatch("change", { color: value }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function handleDragEnd() { |
|
||||
dispatch("dragend") |
|
||||
} |
|
||||
|
|
||||
$: thumbPosition = |
|
||||
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value |
|
||||
|
|
||||
$: style = `transform: translateX(${thumbPosition - 6}px);` |
|
||||
</script> |
|
||||
|
|
||||
<div |
|
||||
tabindex="0" |
|
||||
bind:this={slider} |
|
||||
use:keyevents={{ 37: handleLeftKey, 39: handleRightKey }} |
|
||||
bind:clientWidth={sliderWidth} |
|
||||
on:click={event => onSliderChange({ mouseX: event.clientX })} |
|
||||
class="color-format-slider" |
|
||||
class:hue={type === 'hue'} |
|
||||
class:alpha={type === 'alpha'}> |
|
||||
<div |
|
||||
use:drag |
|
||||
on:drag={e => onSliderChange(e.detail, true)} |
|
||||
on:dragend={handleDragEnd} |
|
||||
class="slider-thumb" |
|
||||
{style} /> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.color-format-slider { |
|
||||
position: relative; |
|
||||
align-self: center; |
|
||||
height: 8px; |
|
||||
width: 150px; |
|
||||
border-radius: 10px; |
|
||||
margin: 10px 0px; |
|
||||
border: 1px solid #e8e8ef; |
|
||||
cursor: pointer; |
|
||||
outline-color: #003cb0; |
|
||||
outline-width: thin; |
|
||||
} |
|
||||
|
|
||||
.hue { |
|
||||
background: linear-gradient( |
|
||||
to right, |
|
||||
hsl(0, 100%, 50%), |
|
||||
hsl(60, 100%, 50%), |
|
||||
hsl(120, 100%, 50%), |
|
||||
hsl(180, 100%, 50%), |
|
||||
hsl(240, 100%, 50%), |
|
||||
hsl(300, 100%, 50%), |
|
||||
hsl(360, 100%, 50%) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
.alpha { |
|
||||
background: linear-gradient(to right, transparent, rgb(0 0 0)); |
|
||||
} |
|
||||
|
|
||||
.slider-thumb { |
|
||||
position: absolute; |
|
||||
bottom: -3px; |
|
||||
height: 12px; |
|
||||
width: 12px; |
|
||||
border: 1px solid #777676; |
|
||||
border-radius: 50%; |
|
||||
background-color: #ffffff; |
|
||||
cursor: grab; |
|
||||
} |
|
||||
|
|
||||
.slider-thumb:active { |
|
||||
cursor: grabbing; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,68 +0,0 @@ |
|||||
<script> |
|
||||
import { createEventDispatcher } from "svelte" |
|
||||
import { fade } from "svelte/transition" |
|
||||
import CheckedBackground from "./CheckedBackground.svelte" |
|
||||
import { keyevents } from "../actions" |
|
||||
|
|
||||
export let hovered = false |
|
||||
export let color = "#fff" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
</script> |
|
||||
|
|
||||
<div class="space"> |
|
||||
<CheckedBackground borderRadius="6px"> |
|
||||
<div |
|
||||
tabindex="0" |
|
||||
use:keyevents={{ Enter: () => dispatch('click') }} |
|
||||
in:fade |
|
||||
title={color} |
|
||||
class="swatch" |
|
||||
style={`background: ${color};`} |
|
||||
on:mouseover={() => (hovered = true)} |
|
||||
on:mouseleave={() => (hovered = false)} |
|
||||
on:click|self> |
|
||||
{#if hovered} |
|
||||
<span in:fade on:click={() => dispatch('removeswatch')} /> |
|
||||
{/if} |
|
||||
</div> |
|
||||
</CheckedBackground> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.swatch { |
|
||||
position: relative; |
|
||||
cursor: pointer; |
|
||||
border-radius: 6px; |
|
||||
border: 1px solid #dedada; |
|
||||
height: 20px; |
|
||||
width: 20px; |
|
||||
outline-color: #003cb0; |
|
||||
outline-width: thin; |
|
||||
} |
|
||||
|
|
||||
.space { |
|
||||
padding: 3px 5px; |
|
||||
} |
|
||||
|
|
||||
span { |
|
||||
cursor: pointer; |
|
||||
position: absolute; |
|
||||
top: -5px; |
|
||||
right: -4px; |
|
||||
background: #800000; |
|
||||
color: white; |
|
||||
font-size: 12px; |
|
||||
border-radius: 50%; |
|
||||
text-align: center; |
|
||||
width: 13px; |
|
||||
height: 13px; |
|
||||
} |
|
||||
|
|
||||
span:after { |
|
||||
content: "\00d7"; |
|
||||
position: relative; |
|
||||
left: 0; |
|
||||
bottom: 3px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,24 +0,0 @@ |
|||||
export function 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()}`) |
|
||||
} |
|
||||
|
|
||||
export const debounce = (fn, milliseconds, callImmediately) => { |
|
||||
const debouncedFn = () => { |
|
||||
setTimeout(fn, milliseconds) |
|
||||
} |
|
||||
if (callImmediately) { |
|
||||
debouncedFn() |
|
||||
} |
|
||||
return debouncedFn |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
import Colorpreview from "./components/Colorpreview.svelte" |
|
||||
export default Colorpreview |
|
||||
@ -1,281 +0,0 @@ |
|||||
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 => { |
|
||||
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 isValidRgb = str => { |
|
||||
const hasValidStructure = /^(?:rgba\(|rgb\()(?:[0-9,\s]|\.(?=\d))*\)$/gi.test( |
|
||||
str |
|
||||
) |
|
||||
if (hasValidStructure) { |
|
||||
return testRgbaValues(str.toLowerCase()) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const findNonNumericChars = /[a-z()\s]/gi |
|
||||
|
|
||||
export const getNumericValues = str => |
|
||||
str |
|
||||
.replace(findNonNumericChars, "") |
|
||||
.split(",") |
|
||||
.map(v => (v !== "" ? v : undefined)) |
|
||||
|
|
||||
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 |
|
||||
} |
|
||||
|
|
||||
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) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export const convertHsvaToFormat = (hsva, format) => { |
|
||||
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 getAndConvertRgba = color => { |
|
||||
let rgba = getNumericValues(color) |
|
||||
return rgbaToHSVA(rgba) |
|
||||
} |
|
||||
|
|
||||
export const getAndConvertHsla = color => { |
|
||||
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 rgbaToHSVA = rgba => { |
|
||||
const [r, g, b, a = 1] = rgba |
|
||||
let hsv = _rgbToHSV([r, g, b]) |
|
||||
return [...hsv, a].map(x => parseFloat(x)) |
|
||||
} |
|
||||
|
|
||||
export const hslaToHSVA = ([h, s, l, a = 1]) => { |
|
||||
let sat = s.replace(/%/, "") |
|
||||
let lum = l.replace(/%/, "") |
|
||||
let hsv = _hslToHSV([h, sat, lum]) |
|
||||
return [...hsv, a].map(x => parseFloat(x)) |
|
||||
} |
|
||||
|
|
||||
export const hsvaToHexa = (hsva, asString = false) => { |
|
||||
const [r, g, b, a] = hsvaToRgba(hsva) |
|
||||
const padSingle = hex => (hex.length === 1 ? `0${hex}` : hex) |
|
||||
|
|
||||
let hexa = [r, g, b].map(v => { |
|
||||
let hex = Math.round(v).toString(16) |
|
||||
return padSingle(hex) |
|
||||
}) |
|
||||
|
|
||||
let alpha = padSingle(Math.round(a * 255).toString(16)) |
|
||||
hexa = [...hexa, alpha] |
|
||||
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]) => { |
|
||||
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) |
|
||||
|
|
||||
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 |
|
||||
} |
|
||||
|
|
||||
//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 |
|
||||
|
|
||||
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 |
|
||||
|
|
||||
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)) |
|
||||
@ -1,106 +0,0 @@ |
|||||
import { getColorFormat, convertToHSVA, convertHsvaToFormat } from "./utils" |
|
||||
|
|
||||
describe("convertToHSVA - convert to hsva from format", () => { |
|
||||
test("convert from hexa", () => { |
|
||||
expect(convertToHSVA("#f222d382", "hex")).toEqual([309, 86, 95, 0.51]) |
|
||||
}) |
|
||||
|
|
||||
test("convert from hex", () => { |
|
||||
expect(convertToHSVA("#f222d3", "hex")).toEqual([309, 86, 95, 1]) |
|
||||
}) |
|
||||
|
|
||||
test("convert from rgba", () => { |
|
||||
expect(convertToHSVA("rgba(242, 34, 211, 1)", "rgb")).toEqual([ |
|
||||
309, |
|
||||
86, |
|
||||
95, |
|
||||
1, |
|
||||
]) |
|
||||
}) |
|
||||
|
|
||||
test("convert from rgb", () => { |
|
||||
expect(convertToHSVA("rgb(150, 80, 255)", "rgb")).toEqual([264, 69, 100, 1]) |
|
||||
}) |
|
||||
|
|
||||
test("convert from from hsl", () => { |
|
||||
expect(convertToHSVA("hsl(264, 100%, 65.7%)", "hsl")).toEqual([ |
|
||||
264, |
|
||||
68.6, |
|
||||
100, |
|
||||
1, |
|
||||
]) |
|
||||
}) |
|
||||
|
|
||||
test("convert from from hsla", () => { |
|
||||
expect(convertToHSVA("hsla(264, 100%, 65.7%, 0.51)", "hsl")).toEqual([ |
|
||||
264, |
|
||||
68.6, |
|
||||
100, |
|
||||
0.51, |
|
||||
]) |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
describe("convertHsvaToFormat - convert from hsva to format", () => { |
|
||||
test("Convert to hexa", () => { |
|
||||
expect(convertHsvaToFormat([264, 68.63, 100, 0.5], "hex")).toBe("#9650ff80") |
|
||||
}) |
|
||||
|
|
||||
test("Convert to rgba", () => { |
|
||||
expect(convertHsvaToFormat([264, 68.63, 100, 0.75], "rgb")).toBe( |
|
||||
"rgba(150,80,255,0.75)" |
|
||||
) |
|
||||
}) |
|
||||
|
|
||||
test("Convert to hsla", () => { |
|
||||
expect(convertHsvaToFormat([264, 68.63, 100, 1], "hsl")).toBe( |
|
||||
"hsla(264,100%,66%,1)" |
|
||||
) |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
describe("Get Color Format", () => { |
|
||||
test("Testing valid hex string", () => { |
|
||||
expect(getColorFormat("#FFF")).toBe("hex") |
|
||||
}) |
|
||||
|
|
||||
test("Testing invalid hex string", () => { |
|
||||
expect(getColorFormat("#FFZ")).toBeUndefined() |
|
||||
}) |
|
||||
|
|
||||
test("Testing valid hex with alpha", () => { |
|
||||
expect(getColorFormat("#FF00BB80")).toBe("hex") |
|
||||
}) |
|
||||
|
|
||||
test("Test valid rgb value", () => { |
|
||||
expect(getColorFormat("RGB(255, 20, 50)")).toBe("rgb") |
|
||||
}) |
|
||||
|
|
||||
test("Testing invalid rgb value", () => { |
|
||||
expect(getColorFormat("rgb(255, 0)")).toBeUndefined() |
|
||||
}) |
|
||||
|
|
||||
test("Testing rgb value with alpha", () => { |
|
||||
expect(getColorFormat("rgba(255, 0, 50, 0.5)")).toBe("rgb") |
|
||||
}) |
|
||||
|
|
||||
test("Testing rgb value with incorrectly provided alpha", () => { |
|
||||
expect(getColorFormat("rgb(255, 0, 50, 0.5)")).toBeUndefined() |
|
||||
}) |
|
||||
|
|
||||
test("Testing invalid hsl value", () => { |
|
||||
expect(getColorFormat("hsla(255, 0)")).toBeUndefined() |
|
||||
}) |
|
||||
|
|
||||
test("Testing hsla value with alpha", () => { |
|
||||
expect(getColorFormat("hsla(150, 60%, 50%, 0.5)")).toBe("hsl") |
|
||||
}) |
|
||||
|
|
||||
test("Testing hsl value with incorrectly provided alpha", () => { |
|
||||
expect(getColorFormat("hsl(150, 0, 50, 0.5)")).toBeUndefined() |
|
||||
}) |
|
||||
|
|
||||
test("Testing out of bounds hsl", () => { |
|
||||
expect(getColorFormat("hsl(375, 0, 50)")).toBeUndefined() |
|
||||
}) |
|
||||
}) |
|
||||
File diff suppressed because it is too large
Loading…
Reference in new issue