commit
58eb4cfbed
@ -0,0 +1,128 @@ |
||||
/* |
||||
* Copyright (c) 2010 Tim Baumann |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
* of this software and associated documentation files (the "Software"), to deal |
||||
* in the Software without restriction, including without limitation the rights |
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
* copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in |
||||
* all copies or substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
* THE SOFTWARE. |
||||
*/ |
||||
|
||||
// NOTE:
|
||||
// Extracted from ColorTriangle and
|
||||
// Converted to ES6 by MightyPork (2017)
|
||||
|
||||
/******************* |
||||
* Color conversion * |
||||
*******************/ |
||||
|
||||
const M = Math |
||||
const PI = M.PI |
||||
|
||||
exports.hue_to_rgb = function (v1, v2, h) { |
||||
if (h < 0) h += 1 |
||||
if (h > 1) h -= 1 |
||||
|
||||
if ((6 * h) < 1) return v1 + (v2 - v1) * 6 * h |
||||
if ((2 * h) < 1) return v2 |
||||
if ((3 * h) < 2) return v1 + (v2 - v1) * ((2 / 3) - h) * 6 |
||||
return v1 |
||||
} |
||||
|
||||
exports.hsl_to_rgb = function (h, s, l) { |
||||
h /= 2 * PI |
||||
let r, g, b |
||||
|
||||
if (s === 0) { |
||||
r = g = b = l |
||||
} else { |
||||
let var_1, var_2 |
||||
|
||||
if (l < 0.5) var_2 = l * (1 + s) |
||||
else var_2 = (l + s) - (s * l) |
||||
|
||||
var_1 = 2 * l - var_2 |
||||
|
||||
r = exports.hue_to_rgb(var_1, var_2, h + (1 / 3)) |
||||
g = exports.hue_to_rgb(var_1, var_2, h) |
||||
b = exports.hue_to_rgb(var_1, var_2, h - (1 / 3)) |
||||
} |
||||
return [r, g, b] |
||||
} |
||||
|
||||
exports.rgb_to_hsl = function (r, g, b) { |
||||
const min = M.min(r, g, b) |
||||
const max = M.max(r, g, b) |
||||
const d = max - min // delta
|
||||
|
||||
let h, s, l |
||||
|
||||
l = (max + min) / 2 |
||||
|
||||
if (d === 0) { |
||||
// gray
|
||||
h = s = 0 // HSL results from 0 to 1
|
||||
} else { |
||||
// chroma
|
||||
if (l < 0.5) s = d / (max + min) |
||||
else s = d / (2 - max - min) |
||||
|
||||
const d_r = (((max - r) / 6) + (d / 2)) / d |
||||
const d_g = (((max - g) / 6) + (d / 2)) / d |
||||
const d_b = (((max - b) / 6) + (d / 2)) / d // deltas
|
||||
|
||||
if (r === max) h = d_b - d_g |
||||
else if (g === max) h = (1 / 3) + d_r - d_b |
||||
else if (b === max) h = (2 / 3) + d_g - d_r |
||||
|
||||
if (h < 0) h += 1 |
||||
else if (h > 1) h -= 1 |
||||
} |
||||
h *= 2 * PI |
||||
return [h, s, l] |
||||
} |
||||
|
||||
exports.hex_to_rgb = function (hex) { |
||||
const groups = hex.match(/^#([A-Fa-f0-9]+)$/) |
||||
if (groups && groups[1].length % 3 === 0) { |
||||
hex = groups[1] |
||||
const bytes = hex.length / 3 |
||||
const max = Math.pow(16, bytes) - 1 |
||||
const r = parseInt(hex.slice(0 * bytes, 1 * bytes), 16) / max |
||||
const g = parseInt(hex.slice(1 * bytes, 2 * bytes), 16) / max |
||||
const b = parseInt(hex.slice(2 * bytes, 3 * bytes), 16) / max |
||||
return [r, g, b] |
||||
} |
||||
return [0, 0, 0] |
||||
} |
||||
|
||||
function pad (n) { |
||||
if (n.length === 1) n = '0' + n |
||||
return n |
||||
} |
||||
|
||||
exports.rgb255_to_hex = function (r, g, b) { |
||||
r = r.toString(16) |
||||
g = g.toString(16) |
||||
b = b.toString(16) |
||||
return `#${pad(r)}${pad(g)}${pad(b)}` |
||||
} |
||||
|
||||
exports.rgb_to_hex = function (r, g, b) { |
||||
r = Math.round(r * 255).toString(16) |
||||
g = Math.round(g * 255).toString(16) |
||||
b = Math.round(b * 255).toString(16) |
||||
return `#${pad(r)}${pad(g)}${pad(b)}` |
||||
} |
@ -0,0 +1,604 @@ |
||||
/* |
||||
* Copyright (c) 2010 Tim Baumann |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
* of this software and associated documentation files (the "Software"), to deal |
||||
* in the Software without restriction, including without limitation the rights |
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
* copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in |
||||
* all copies or substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
* THE SOFTWARE. |
||||
*/ |
||||
|
||||
// NOTE: Converted to ES6 by MightyPork (2017)
|
||||
|
||||
const { |
||||
rgb_to_hex, |
||||
hex_to_rgb, |
||||
hsl_to_rgb, |
||||
rgb_to_hsl |
||||
} = require('./color_utils') |
||||
|
||||
const win = window |
||||
const doc = document |
||||
const M = Math |
||||
const PI = M.PI |
||||
|
||||
function times (i, fn) { |
||||
for (let j = 0; j < i; j++) { |
||||
fn(j) |
||||
} |
||||
} |
||||
|
||||
function each (obj, fn) { |
||||
if (obj.length) { |
||||
times(obj.length, function (i) { |
||||
fn(obj[i], i) |
||||
}) |
||||
} else { |
||||
for (let key in obj) { |
||||
if (obj.hasOwnProperty(key)) { |
||||
fn(obj[key], key) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
function getOffsets (el) { |
||||
let left = 0 |
||||
let top = 0 |
||||
|
||||
while (el !== null) { |
||||
console.log(el) |
||||
console.log(el.offsetLeft, el.offsetTop) |
||||
left += el.offsetLeft |
||||
top += el.offsetTop |
||||
el = el.offsetParent |
||||
} |
||||
|
||||
return [left, top] |
||||
} |
||||
|
||||
module.exports = class ColorTriangle { |
||||
/**************** |
||||
* ColorTriangle * |
||||
****************/ |
||||
|
||||
// Constructor function:
|
||||
constructor (color, options) { |
||||
this.options = { |
||||
size: 150, |
||||
padding: 8, |
||||
triangleSize: 0.8, |
||||
wheelPointerColor1: '#444', |
||||
wheelPointerColor2: '#eee', |
||||
trianglePointerSize: 16, |
||||
// wheelPointerSize: 16,
|
||||
trianglePointerColor1: '#eee', |
||||
trianglePointerColor2: '#444', |
||||
background: 'transparent' |
||||
} |
||||
|
||||
this._setOptions(options) |
||||
this._calculateProperties() |
||||
|
||||
this._createContainer() |
||||
this._createTriangle() |
||||
this._createWheel() |
||||
this._createWheelPointer() |
||||
this._createTrianglePointer() |
||||
this._attachEvents() |
||||
|
||||
color = color || '#f00' |
||||
if (typeof color == 'string') { |
||||
this.setHEX(color) |
||||
} |
||||
} |
||||
|
||||
_calculateProperties () { |
||||
let opts = this.options |
||||
|
||||
this.padding = opts.padding |
||||
this.innerSize = opts.size - opts.padding * 2 |
||||
this.triangleSize = opts.triangleSize * this.innerSize |
||||
this.wheelThickness = (this.innerSize - this.triangleSize) / 2 |
||||
this.wheelPointerSize = opts.wheelPointerSize || this.wheelThickness |
||||
|
||||
this.wheelRadius = (this.innerSize) / 2 |
||||
this.triangleRadius = (this.triangleSize) / 2 |
||||
this.triangleSideLength = M.sqrt(3) * this.triangleRadius |
||||
} |
||||
|
||||
_calculatePositions () { |
||||
const r = this.triangleRadius |
||||
const hue = this.hue |
||||
const third = (2 / 3) * PI |
||||
const s = this.saturation |
||||
const l = this.lightness |
||||
|
||||
// Colored point
|
||||
const hx = this.hx = M.cos(hue) * r |
||||
const hy = this.hy = -M.sin(hue) * r |
||||
// Black point
|
||||
const sx = this.sx = M.cos(hue - third) * r |
||||
const sy = this.sy = -M.sin(hue - third) * r |
||||
// White point
|
||||
const vx = this.vx = M.cos(hue + third) * r |
||||
const vy = this.vy = -M.sin(hue + third) * r |
||||
// Current point
|
||||
const mx = (sx + vx) / 2 |
||||
const my = (sy + vy) / 2 |
||||
const a = (1 - 2 * M.abs(l - 0.5)) * s |
||||
this.x = sx + (vx - sx) * l + (hx - mx) * a |
||||
this.y = sy + (vy - sy) * l + (hy - my) * a |
||||
} |
||||
|
||||
_createContainer () { |
||||
let c = this.container = doc.createElement('div') |
||||
c.className = 'color-triangle' |
||||
|
||||
c.style.display = 'block' |
||||
c.style.padding = `${this.padding}px` |
||||
c.style.position = 'relative' |
||||
c.style.boxShadow = '0 1px 10px black' |
||||
c.style.borderRadius = '5px' |
||||
c.style.width = c.style.height = `${this.innerSize + 2 * this.padding}px` |
||||
c.style.background = this.options.background |
||||
} |
||||
|
||||
_createWheel () { |
||||
let c = this.wheel = doc.createElement('canvas') |
||||
c.width = c.height = this.innerSize |
||||
c.style.position = 'absolute' |
||||
c.style.margin = c.style.padding = '0' |
||||
c.style.left = c.style.top = `${this.padding}px` |
||||
|
||||
this._drawWheel(c.getContext('2d')) |
||||
this.container.appendChild(c) |
||||
} |
||||
|
||||
_drawWheel (ctx) { |
||||
let s, i |
||||
|
||||
ctx.save() |
||||
ctx.translate(this.wheelRadius, this.wheelRadius) |
||||
s = this.wheelRadius - this.triangleRadius |
||||
// Draw a circle for every color
|
||||
for (i = 0; i < 360; i++) { |
||||
ctx.rotate(PI / -180) // rotate one degree
|
||||
ctx.beginPath() |
||||
ctx.fillStyle = 'hsl(' + i + ', 100%, 50%)' |
||||
ctx.arc(this.wheelRadius - (s / 2), 0, s / 2, 0, PI * 2, true) |
||||
ctx.fill() |
||||
} |
||||
ctx.restore() |
||||
} |
||||
|
||||
_createTriangle () { |
||||
let c = this.triangle = doc.createElement('canvas') |
||||
|
||||
c.width = c.height = this.innerSize |
||||
c.style.position = 'absolute' |
||||
c.style.margin = c.style.padding = '0' |
||||
c.style.left = c.style.top = this.padding + 'px' |
||||
|
||||
this.triangleCtx = c.getContext('2d') |
||||
|
||||
this.container.appendChild(c) |
||||
} |
||||
|
||||
_drawTriangle () { |
||||
const hx = this.hx |
||||
const hy = this.hy |
||||
const sx = this.sx |
||||
const sy = this.sy |
||||
const vx = this.vx |
||||
const vy = this.vy |
||||
const size = this.innerSize |
||||
|
||||
let ctx = this.triangleCtx |
||||
|
||||
// clear
|
||||
ctx.clearRect(0, 0, size, size) |
||||
|
||||
ctx.save() |
||||
ctx.translate(this.wheelRadius, this.wheelRadius) |
||||
|
||||
// make a triangle
|
||||
ctx.beginPath() |
||||
ctx.moveTo(hx, hy) |
||||
ctx.lineTo(sx, sy) |
||||
ctx.lineTo(vx, vy) |
||||
ctx.closePath() |
||||
ctx.clip() |
||||
|
||||
ctx.fillStyle = '#000' |
||||
ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) |
||||
// => black triangle
|
||||
|
||||
// create gradient from hsl(hue, 1, 1) to transparent
|
||||
let grad0 = ctx.createLinearGradient(hx, hy, (sx + vx) / 2, (sy + vy) / 2) |
||||
const hsla = 'hsla(' + M.round(this.hue * (180 / PI)) + ', 100%, 50%, ' |
||||
grad0.addColorStop(0, hsla + '1)') |
||||
grad0.addColorStop(1, hsla + '0)') |
||||
ctx.fillStyle = grad0 |
||||
ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) |
||||
// => gradient: one side of the triangle is black, the opponent angle is $color
|
||||
|
||||
// create color gradient from white to transparent
|
||||
let grad1 = ctx.createLinearGradient(vx, vy, (hx + sx) / 2, (hy + sy) / 2) |
||||
grad1.addColorStop(0, '#fff') |
||||
grad1.addColorStop(1, 'rgba(255, 255, 255, 0)') |
||||
ctx.globalCompositeOperation = 'lighter' |
||||
ctx.fillStyle = grad1 |
||||
ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) |
||||
// => white angle
|
||||
|
||||
ctx.restore() |
||||
} |
||||
|
||||
// The two pointers
|
||||
_createWheelPointer () { |
||||
let c = this.wheelPointer = doc.createElement('canvas') |
||||
const size = this.wheelPointerSize |
||||
c.width = c.height = size |
||||
c.style.position = 'absolute' |
||||
c.style.margin = c.style.padding = '0' |
||||
this._drawPointer(c.getContext('2d'), size / 2, this.options.wheelPointerColor1, this.options.wheelPointerColor2) |
||||
this.container.appendChild(c) |
||||
} |
||||
|
||||
_moveWheelPointer () { |
||||
const r = this.wheelPointerSize / 2 |
||||
const s = this.wheelPointer.style |
||||
s.top = this.padding + this.wheelRadius - M.sin(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' |
||||
s.left = this.padding + this.wheelRadius + M.cos(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' |
||||
} |
||||
|
||||
_createTrianglePointer () { // create pointer in the triangle
|
||||
let c = this.trianglePointer = doc.createElement('canvas') |
||||
const size = this.options.trianglePointerSize |
||||
|
||||
c.width = c.height = size |
||||
c.style.position = 'absolute' |
||||
c.style.margin = c.style.padding = '0' |
||||
this._drawPointer(c.getContext('2d'), size / 2, this.options.trianglePointerColor1, this.options.trianglePointerColor2) |
||||
this.container.appendChild(c) |
||||
} |
||||
|
||||
_moveTrianglePointer (x, y) { |
||||
const s = this.trianglePointer.style |
||||
const r = this.options.trianglePointerSize / 2 |
||||
s.top = (this.y + this.wheelRadius + this.padding - r) + 'px' |
||||
s.left = (this.x + this.wheelRadius + this.padding - r) + 'px' |
||||
} |
||||
|
||||
_drawPointer (ctx, r, color1, color2) { |
||||
ctx.fillStyle = color2 |
||||
ctx.beginPath() |
||||
ctx.arc(r, r, r, 0, PI * 2, true) |
||||
ctx.fill() // => black circle
|
||||
ctx.fillStyle = color1 |
||||
ctx.beginPath() |
||||
ctx.arc(r, r, r - 2, 0, PI * 2, true) |
||||
ctx.fill() // => white circle with 1px black border
|
||||
ctx.fillStyle = color2 |
||||
ctx.beginPath() |
||||
ctx.arc(r, r, r / 4 + 2, 0, PI * 2, true) |
||||
ctx.fill() // => black circle with big white border and a small black border
|
||||
ctx.globalCompositeOperation = 'destination-out' |
||||
ctx.beginPath() |
||||
ctx.arc(r, r, r / 4, 0, PI * 2, true) |
||||
ctx.fill() // => transparent center
|
||||
} |
||||
|
||||
// The Element and the DOM
|
||||
inject (parent) { |
||||
parent.appendChild(this.container) |
||||
|
||||
// calculate canvas position on page
|
||||
const offsets = getOffsets(this.triangle) |
||||
this.offset = { |
||||
x: offsets[0], |
||||
y: offsets[1] |
||||
} |
||||
} |
||||
|
||||
dispose () { |
||||
let parent = this.container.parentNode |
||||
if (parent) { |
||||
parent.removeChild(this.container) |
||||
} |
||||
} |
||||
|
||||
getElement () { |
||||
return this.container |
||||
} |
||||
|
||||
// Color accessors
|
||||
getCSS () { |
||||
const h = Math.round(this.hue * (180 / PI)) |
||||
const s = Math.round(this.saturation * 100) |
||||
const l = Math.round(this.lightness * 100) |
||||
|
||||
return `hsl(${h}, ${s}%, ${l}%)` |
||||
} |
||||
|
||||
getHEX () { |
||||
return rgb_to_hex(...this.getRGB()) |
||||
} |
||||
|
||||
setHEX (hex) { |
||||
this.setRGB(...hex_to_rgb(hex)) |
||||
} |
||||
|
||||
getRGB () { |
||||
return hsl_to_rgb(...this.getHSL()) |
||||
} |
||||
|
||||
setRGB (r, g, b) { |
||||
this.setHSL(...rgb_to_hsl(r, g, b)) |
||||
} |
||||
|
||||
getHSL () { |
||||
return [this.hue, this.saturation, this.lightness] |
||||
} |
||||
|
||||
setHSL (h, s, l) { |
||||
this.hue = h |
||||
this.saturation = s |
||||
this.lightness = l |
||||
|
||||
this._initColor() |
||||
} |
||||
|
||||
_initColor () { |
||||
this._calculatePositions() |
||||
this._moveWheelPointer() |
||||
this._drawTriangle() |
||||
this._moveTrianglePointer() |
||||
} |
||||
|
||||
// Mouse event handling
|
||||
_attachEvents () { |
||||
this.down = null |
||||
|
||||
let mousedown = (evt) => { |
||||
evt.stopPropagation() |
||||
evt.preventDefault() |
||||
|
||||
doc.body.addEventListener('mousemove', mousemove, false) |
||||
doc.body.addEventListener('mouseup', mouseup, false) |
||||
this._map(evt.pageX, evt.pageY) |
||||
} |
||||
|
||||
let mousemove = (evt) => { |
||||
this._move(evt.pageX, evt.pageY) |
||||
} |
||||
|
||||
let mouseup = (evt) => { |
||||
if (this.down) { |
||||
this.down = null |
||||
this._fireEvent('dragend') |
||||
} |
||||
doc.body.removeEventListener('mousemove', mousemove, false) |
||||
doc.body.removeEventListener('mouseup', mouseup, false) |
||||
} |
||||
|
||||
this.container.addEventListener('mousedown', mousedown, false) |
||||
this.container.addEventListener('mousemove', mousemove, false) |
||||
} |
||||
|
||||
_map (x, y) { |
||||
const ox = x |
||||
const oy = y |
||||
|
||||
x -= this.offset.x + this.wheelRadius |
||||
y -= this.offset.y + this.wheelRadius |
||||
|
||||
const r = M.sqrt(x * x + y * y) // Pythagoras
|
||||
if (r > this.triangleRadius && r < this.wheelRadius) { |
||||
// Wheel
|
||||
this.down = 'wheel' |
||||
this._fireEvent('dragstart') |
||||
this._move(ox, oy) |
||||
} else if (r < this.triangleRadius) { |
||||
// Inner circle
|
||||
this.down = 'triangle' |
||||
this._fireEvent('dragstart') |
||||
this._move(ox, oy) |
||||
} |
||||
} |
||||
|
||||
_move (x, y) { |
||||
if (!this.down) { |
||||
return |
||||
} |
||||
|
||||
x -= this.offset.x + this.wheelRadius |
||||
y -= this.offset.y + this.wheelRadius |
||||
|
||||
let rad = M.atan2(-y, x) |
||||
if (rad < 0) { |
||||
rad += 2 * PI |
||||
} |
||||
|
||||
if (this.down === 'wheel') { |
||||
this.hue = rad |
||||
this._initColor() |
||||
this._fireEvent('drag') |
||||
} else if (this.down === 'triangle') { |
||||
// get radius and max radius
|
||||
let rad0 = (rad + 2 * PI - this.hue) % (2 * PI) |
||||
let rad1 = rad0 % ((2 / 3) * PI) - (PI / 3) |
||||
let a = 0.5 * this.triangleRadius |
||||
let b = M.tan(rad1) * a |
||||
let r = M.sqrt(x * x + y * y) // Pythagoras
|
||||
let maxR = M.sqrt(a * a + b * b) // Pythagoras
|
||||
|
||||
if (r > maxR) { |
||||
const dx = M.tan(rad1) * r |
||||
let rad2 = M.atan(dx / maxR) |
||||
if (rad2 > PI / 3) { |
||||
rad2 = PI / 3 |
||||
} else if (rad2 < -PI / 3) { |
||||
rad2 = -PI / 3 |
||||
} |
||||
rad += rad2 - rad1 |
||||
|
||||
rad0 = (rad + 2 * PI - this.hue) % (2 * PI) |
||||
rad1 = rad0 % ((2 / 3) * PI) - (PI / 3) |
||||
b = M.tan(rad1) * a |
||||
r = maxR = M.sqrt(a * a + b * b) // Pythagoras
|
||||
} |
||||
|
||||
x = M.round(M.cos(rad) * r) |
||||
y = M.round(-M.sin(rad) * r) |
||||
|
||||
const l = this.lightness = ((M.sin(rad0) * r) / this.triangleSideLength) + 0.5 |
||||
|
||||
const widthShare = 1 - (M.abs(l - 0.5) * 2) |
||||
let s = this.saturation = (((M.cos(rad0) * r) + (this.triangleRadius / 2)) / (1.5 * this.triangleRadius)) / widthShare |
||||
s = M.max(0, s) // cannot be lower than 0
|
||||
s = M.min(1, s) // cannot be greater than 1
|
||||
|
||||
this.lightness = l |
||||
this.saturation = s |
||||
|
||||
this.x = x |
||||
this.y = y |
||||
this._moveTrianglePointer() |
||||
|
||||
this._fireEvent('drag') |
||||
} |
||||
} |
||||
|
||||
/*************** |
||||
* Init helpers * |
||||
***************/ |
||||
|
||||
static initInput (input, options) { |
||||
options = options || {} |
||||
|
||||
let ct |
||||
let openColorTriangle = function () { |
||||
let hex = input.value |
||||
if (options.parseColor) hex = options.parseColor(hex) |
||||
if (!ct) { |
||||
options.size = options.size || input.offsetWidth |
||||
options.background = win.getComputedStyle(input, null).backgroundColor |
||||
options.margin = options.margin || 10 |
||||
options.event = options.event || 'dragend' |
||||
|
||||
ct = new ColorTriangle(hex, options) |
||||
ct.addEventListener(options.event, () => { |
||||
const hex = ct.getHEX() |
||||
input.value = options.uppercase ? hex.toUpperCase() : hex |
||||
fireChangeEvent() |
||||
}) |
||||
} else { |
||||
ct.setHEX(hex) |
||||
} |
||||
|
||||
let top = input.offsetTop |
||||
if (win.innerHeight - input.getBoundingClientRect().top > input.offsetHeight + options.margin + options.size) { |
||||
top += input.offsetHeight + options.margin // below
|
||||
} else { |
||||
top -= options.margin + options.size // above
|
||||
} |
||||
|
||||
let el = ct.getElement() |
||||
el.style.position = 'absolute' |
||||
el.style.left = input.offsetLeft + 'px' |
||||
el.style.top = top + 'px' |
||||
el.style.zIndex = '1338' // above everything
|
||||
|
||||
ct.inject(input.parentNode) |
||||
} |
||||
|
||||
let closeColorTriangle = () => { |
||||
if (ct) { |
||||
ct.dispose() |
||||
} |
||||
} |
||||
|
||||
let fireChangeEvent = () => { |
||||
let evt = doc.createEvent('HTMLEvents') |
||||
evt.initEvent('input', true, false) // bubbles = true, cancable = false
|
||||
input.dispatchEvent(evt) // fire event
|
||||
} |
||||
|
||||
input.addEventListener('focus', openColorTriangle, false) |
||||
input.addEventListener('blur', closeColorTriangle, false) |
||||
input.addEventListener('keyup', () => { |
||||
const val = input.value |
||||
if (val.match(/^#((?:[0-9A-Fa-f]{3})|(?:[0-9A-Fa-f]{6}))$/)) { |
||||
openColorTriangle() |
||||
fireChangeEvent() |
||||
} else { |
||||
closeColorTriangle() |
||||
} |
||||
}, false) |
||||
} |
||||
|
||||
/******************* |
||||
* Helper functions * |
||||
*******************/ |
||||
|
||||
_setOptions (opts) { |
||||
opts = opts || {} |
||||
let dflt = this.options |
||||
let options = this.options = {} |
||||
|
||||
each(dflt, function (val, key) { |
||||
options[key] = (opts.hasOwnProperty(key)) |
||||
? opts[key] |
||||
: val |
||||
}) |
||||
} |
||||
|
||||
addEventListener (type, fn) { |
||||
if (!this.events) { |
||||
this.events = {} |
||||
} |
||||
if (!this.events[type]) { |
||||
this.events[type] = [] |
||||
} |
||||
this.events[type].push(fn) |
||||
} |
||||
|
||||
removeEventListener (type, fn) { |
||||
if (this.events) { |
||||
let fns = this.events[type] |
||||
const l = fns.length |
||||
for (let i = 0; i < l; i += 1) { |
||||
if (fns[i] === fn) { |
||||
fns.splice(i, 1) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
_fireEvent (type) { |
||||
if (this.events) { |
||||
let evts = this.events[type] |
||||
if (evts) { |
||||
const args = Array.prototype.slice.call(arguments, 1) |
||||
each(evts, function (evt) { |
||||
evt(...args) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,105 @@ |
||||
const ColorTriangle = require('./lib/colortriangle') |
||||
const $ = require('./lib/chibi') |
||||
const themes = require('./term/themes') |
||||
const { qs } = require('./utils') |
||||
|
||||
function selectedTheme () { |
||||
return +$('#theme').val() |
||||
} |
||||
|
||||
exports.init = function () { |
||||
$('#theme').on('change', showColor) |
||||
|
||||
$('#default_fg').on('input', showColor) |
||||
$('#default_bg').on('input', showColor) |
||||
|
||||
let opts = { |
||||
padding: 10, |
||||
event: 'drag', |
||||
uppercase: true, |
||||
trianglePointerSize: 20, |
||||
// wheelPointerSize: 12,
|
||||
size: 200, |
||||
parseColor: (color) => { |
||||
return themes.toHex(color, selectedTheme()) |
||||
} |
||||
} |
||||
|
||||
ColorTriangle.initInput(qs('#default_fg'), opts) |
||||
ColorTriangle.initInput(qs('#default_bg'), opts) |
||||
|
||||
$('.colorprev.bg span').on('click', function () { |
||||
const bg = this.dataset.bg |
||||
if (typeof bg != 'undefined') $('#default_bg').val(bg) |
||||
showColor() |
||||
}) |
||||
|
||||
$('.colorprev.fg span').on('click', function () { |
||||
const fg = this.dataset.fg |
||||
if (typeof fg != 'undefined') $('#default_fg').val(fg) |
||||
showColor() |
||||
}) |
||||
|
||||
let $presets = $('#fgbg_presets') |
||||
for (let i = 0; i < themes.fgbgThemes.length; i++) { |
||||
const thm = themes.fgbgThemes[i] |
||||
const fg = thm[0] |
||||
const bg = thm[1] |
||||
const lbl = thm[2] |
||||
const tit = thm[3] |
||||
$presets.htmlAppend( |
||||
'<span class="preset" ' + |
||||
'data-xfg="' + fg + '" data-xbg="' + bg + '" ' + |
||||
'style="color:' + fg + ';background:' + bg + '" title="' + tit + '"> ' + lbl + ' </span>') |
||||
|
||||
if ((i + 1) % 5 === 0) $presets.htmlAppend('<br>') |
||||
} |
||||
|
||||
$('.preset').on('click', function () { |
||||
$('#default_fg').val(this.dataset.xfg) |
||||
$('#default_bg').val(this.dataset.xbg) |
||||
showColor() |
||||
}) |
||||
|
||||
showColor() |
||||
} |
||||
|
||||
function showColor () { |
||||
let ex = qs('.color-example') |
||||
let fg = $('#default_fg').val() |
||||
let bg = $('#default_bg').val() |
||||
|
||||
if (/^\d+$/.test(fg)) { |
||||
fg = +fg |
||||
} else if (!/^#[\da-f]{6}$/i.test(fg)) { |
||||
fg = 'black' |
||||
} |
||||
|
||||
if (/^\d+$/.test(bg)) { |
||||
bg = +bg |
||||
} else if (!/^#[\da-f]{6}$/i.test(bg)) { |
||||
bg = 'black' |
||||
} |
||||
|
||||
const themeN = selectedTheme() |
||||
ex.dataset.fg = fg |
||||
ex.dataset.bg = bg |
||||
|
||||
themes.themePreview(themeN) |
||||
|
||||
$('.colorprev.fg span').css('background', themes.toHex(bg, themeN)) |
||||
} |
||||
|
||||
exports.nextTheme = () => { |
||||
let sel = qs('#theme') |
||||
let i = sel.selectedIndex |
||||
sel.options[++i % sel.options.length].selected = true |
||||
showColor() |
||||
} |
||||
|
||||
exports.prevTheme = () => { |
||||
let sel = qs('#theme') |
||||
let i = sel.selectedIndex |
||||
sel.options[(sel.options.length + (--i)) % sel.options.length].selected = true |
||||
showColor() |
||||
} |
Loading…
Reference in new issue