diff --git a/dist/huebee/huebee.css b/dist/huebee/huebee.css new file mode 100644 index 00000000..f6ef1357 --- /dev/null +++ b/dist/huebee/huebee.css @@ -0,0 +1,83 @@ +/*! Huebee v2.1.0 +https://huebee.buzz +---------------------------------------------- */ + +.huebee { + position: absolute; + z-index: 1; + transform: translateY(0px); + transition: opacity 0.15s, transform 0.15s; +} + +.huebee.is-hidden { + opacity: 0; + transform: translateY(10px); +} + +.huebee.is-static-open { + position: relative; + z-index: auto; +} + +.huebee__container { + position: absolute; + left: 0; + top: 5px; + padding: 10px; + background: #EEE; + border-radius: 5px; + box-shadow: 0 5px 10px hsla(0, 0%, 0%, 0.3); +} + +.huebee.is-static-open .huebee__container { + position: relative; + display: inline-block; + left: auto; + top: auto; + box-shadow: none; +} + +.huebee__canvas { + display: block; + cursor: pointer; +} + +.huebee__cursor { + width: 15px; + height: 15px; + position: absolute; + left: 0px; + top: 0px; + box-sizing: content-box; + border: 3px solid white; + border-radius: 5px; + pointer-events: none; +} + +.huebee__cursor.is-hidden { opacity: 0; } + +.huebee__close-button { + display: block; + position: absolute; + width: 24px; + height: 24px; + top: -9px; + right: -9px; + border-radius: 12px; + background: #222; +} + +.huebee__close-button__x { + stroke: white; + stroke-width: 3; + stroke-linecap: round; +} + +.huebee__close-button:hover { + background: white; + cursor: pointer; +} + +.huebee__close-button:hover .huebee__close-button__x { + stroke: #222; +} diff --git a/dist/huebee/huebee.min.css b/dist/huebee/huebee.min.css new file mode 100644 index 00000000..3f826deb --- /dev/null +++ b/dist/huebee/huebee.min.css @@ -0,0 +1,4 @@ +/*! Huebee v2.1.0 +https://huebee.buzz +---------------------------------------------- */ +.huebee{position:absolute;z-index:1;transform:translateY(0);transition:opacity .15s,transform .15s}.huebee.is-hidden{opacity:0;transform:translateY(10px)}.huebee.is-static-open{position:relative;z-index:auto}.huebee__container{position:absolute;left:0;top:5px;padding:10px;background:#eee;border-radius:5px;box-shadow:0 5px 10px hsla(0,0%,0%,.3)}.huebee.is-static-open .huebee__container{position:relative;display:inline-block;left:auto;top:auto;box-shadow:none}.huebee__canvas{display:block;cursor:pointer}.huebee__cursor{width:15px;height:15px;position:absolute;left:0;top:0;box-sizing:content-box;border:3px solid #fff;border-radius:5px;pointer-events:none}.huebee__cursor.is-hidden{opacity:0}.huebee__close-button{display:block;position:absolute;width:24px;height:24px;top:-9px;right:-9px;border-radius:12px;background:#222}.huebee__close-button__x{stroke:#fff;stroke-width:3;stroke-linecap:round}.huebee__close-button:hover{background:#fff;cursor:pointer}.huebee__close-button:hover .huebee__close-button__x{stroke:#222} \ No newline at end of file diff --git a/dist/huebee/huebee.pkgd.js b/dist/huebee/huebee.pkgd.js new file mode 100644 index 00000000..704bd961 --- /dev/null +++ b/dist/huebee/huebee.pkgd.js @@ -0,0 +1,1125 @@ +/*! + * Huebee PACKAGED v2.1.0 + * 1-click color picker + * MIT license + * https://huebee.buzz + * Copyright 2020 Metafizzy + */ + +/** + * EvEmitter v1.1.0 + * Lil' event emitter + * MIT License + */ + +/* jshint unused: true, undef: true, strict: true */ + +( function( global, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, window */ + if ( typeof define == 'function' && define.amd ) { + // AMD - RequireJS + define( 'ev-emitter/ev-emitter',factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS - Browserify, Webpack + module.exports = factory(); + } else { + // Browser globals + global.EvEmitter = factory(); + } + +}( typeof window != 'undefined' ? window : this, function() { + +"use strict"; + +function EvEmitter() {} + +var proto = EvEmitter.prototype; + +proto.on = function( eventName, listener ) { + if ( !eventName || !listener ) { + return; + } + // set events hash + var events = this._events = this._events || {}; + // set listeners array + var listeners = events[ eventName ] = events[ eventName ] || []; + // only add once + if ( listeners.indexOf( listener ) == -1 ) { + listeners.push( listener ); + } + + return this; +}; + +proto.once = function( eventName, listener ) { + if ( !eventName || !listener ) { + return; + } + // add event + this.on( eventName, listener ); + // set once flag + // set onceEvents hash + var onceEvents = this._onceEvents = this._onceEvents || {}; + // set onceListeners object + var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; + // set flag + onceListeners[ listener ] = true; + + return this; +}; + +proto.off = function( eventName, listener ) { + var listeners = this._events && this._events[ eventName ]; + if ( !listeners || !listeners.length ) { + return; + } + var index = listeners.indexOf( listener ); + if ( index != -1 ) { + listeners.splice( index, 1 ); + } + + return this; +}; + +proto.emitEvent = function( eventName, args ) { + var listeners = this._events && this._events[ eventName ]; + if ( !listeners || !listeners.length ) { + return; + } + // copy over to avoid interference if .off() in listener + listeners = listeners.slice(0); + args = args || []; + // once stuff + var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; + + for ( var i=0; i < listeners.length; i++ ) { + var listener = listeners[i] + var isOnce = onceListeners && onceListeners[ listener ]; + if ( isOnce ) { + // remove listener + // remove before trigger to prevent recursion + this.off( eventName, listener ); + // unset once flag + delete onceListeners[ listener ]; + } + // trigger listener + listener.apply( this, args ); + } + + return this; +}; + +proto.allOff = function() { + delete this._events; + delete this._onceEvents; +}; + +return EvEmitter; + +})); +/*! + * Unipointer v2.3.0 + * base class for doing one thing with pointer event + * MIT license + */ + +/*jshint browser: true, undef: true, unused: true, strict: true */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /*global define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'unipointer/unipointer',[ + 'ev-emitter/ev-emitter' + ], function( EvEmitter ) { + return factory( window, EvEmitter ); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('ev-emitter') + ); + } else { + // browser global + window.Unipointer = factory( + window, + window.EvEmitter + ); + } + +}( window, function factory( window, EvEmitter ) { + +'use strict'; + +function noop() {} + +function Unipointer() {} + +// inherit EvEmitter +var proto = Unipointer.prototype = Object.create( EvEmitter.prototype ); + +proto.bindStartEvent = function( elem ) { + this._bindStartEvent( elem, true ); +}; + +proto.unbindStartEvent = function( elem ) { + this._bindStartEvent( elem, false ); +}; + +/** + * Add or remove start event + * @param {Boolean} isAdd - remove if falsey + */ +proto._bindStartEvent = function( elem, isAdd ) { + // munge isAdd, default to true + isAdd = isAdd === undefined ? true : isAdd; + var bindMethod = isAdd ? 'addEventListener' : 'removeEventListener'; + + // default to mouse events + var startEvent = 'mousedown'; + if ( window.PointerEvent ) { + // Pointer Events + startEvent = 'pointerdown'; + } else if ( 'ontouchstart' in window ) { + // Touch Events. iOS Safari + startEvent = 'touchstart'; + } + elem[ bindMethod ]( startEvent, this ); +}; + +// trigger handler methods for events +proto.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; + +// returns the touch that we're keeping track of +proto.getTouch = function( touches ) { + for ( var i=0; i < touches.length; i++ ) { + var touch = touches[i]; + if ( touch.identifier == this.pointerIdentifier ) { + return touch; + } + } +}; + +// ----- start event ----- // + +proto.onmousedown = function( event ) { + // dismiss clicks from right or middle buttons + var button = event.button; + if ( button && ( button !== 0 && button !== 1 ) ) { + return; + } + this._pointerDown( event, event ); +}; + +proto.ontouchstart = function( event ) { + this._pointerDown( event, event.changedTouches[0] ); +}; + +proto.onpointerdown = function( event ) { + this._pointerDown( event, event ); +}; + +/** + * pointer start + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto._pointerDown = function( event, pointer ) { + // dismiss right click and other pointers + // button = 0 is okay, 1-4 not + if ( event.button || this.isPointerDown ) { + return; + } + + this.isPointerDown = true; + // save pointer identifier to match up touch events + this.pointerIdentifier = pointer.pointerId !== undefined ? + // pointerId for pointer events, touch.indentifier for touch events + pointer.pointerId : pointer.identifier; + + this.pointerDown( event, pointer ); +}; + +proto.pointerDown = function( event, pointer ) { + this._bindPostStartEvents( event ); + this.emitEvent( 'pointerDown', [ event, pointer ] ); +}; + +// hash of events to be bound after start event +var postStartEvents = { + mousedown: [ 'mousemove', 'mouseup' ], + touchstart: [ 'touchmove', 'touchend', 'touchcancel' ], + pointerdown: [ 'pointermove', 'pointerup', 'pointercancel' ], +}; + +proto._bindPostStartEvents = function( event ) { + if ( !event ) { + return; + } + // get proper events to match start event + var events = postStartEvents[ event.type ]; + // bind events to node + events.forEach( function( eventName ) { + window.addEventListener( eventName, this ); + }, this ); + // save these arguments + this._boundPointerEvents = events; +}; + +proto._unbindPostStartEvents = function() { + // check for _boundEvents, in case dragEnd triggered twice (old IE8 bug) + if ( !this._boundPointerEvents ) { + return; + } + this._boundPointerEvents.forEach( function( eventName ) { + window.removeEventListener( eventName, this ); + }, this ); + + delete this._boundPointerEvents; +}; + +// ----- move event ----- // + +proto.onmousemove = function( event ) { + this._pointerMove( event, event ); +}; + +proto.onpointermove = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerMove( event, event ); + } +}; + +proto.ontouchmove = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerMove( event, touch ); + } +}; + +/** + * pointer move + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +proto._pointerMove = function( event, pointer ) { + this.pointerMove( event, pointer ); +}; + +// public +proto.pointerMove = function( event, pointer ) { + this.emitEvent( 'pointerMove', [ event, pointer ] ); +}; + +// ----- end event ----- // + + +proto.onmouseup = function( event ) { + this._pointerUp( event, event ); +}; + +proto.onpointerup = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerUp( event, event ); + } +}; + +proto.ontouchend = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerUp( event, touch ); + } +}; + +/** + * pointer up + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +proto._pointerUp = function( event, pointer ) { + this._pointerDone(); + this.pointerUp( event, pointer ); +}; + +// public +proto.pointerUp = function( event, pointer ) { + this.emitEvent( 'pointerUp', [ event, pointer ] ); +}; + +// ----- pointer done ----- // + +// triggered on pointer up & pointer cancel +proto._pointerDone = function() { + this._pointerReset(); + this._unbindPostStartEvents(); + this.pointerDone(); +}; + +proto._pointerReset = function() { + // reset properties + this.isPointerDown = false; + delete this.pointerIdentifier; +}; + +proto.pointerDone = noop; + +// ----- pointer cancel ----- // + +proto.onpointercancel = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerCancel( event, event ); + } +}; + +proto.ontouchcancel = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerCancel( event, touch ); + } +}; + +/** + * pointer cancel + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +proto._pointerCancel = function( event, pointer ) { + this._pointerDone(); + this.pointerCancel( event, pointer ); +}; + +// public +proto.pointerCancel = function( event, pointer ) { + this.emitEvent( 'pointerCancel', [ event, pointer ] ); +}; + +// ----- ----- // + +// utility function for getting x/y coords from event +Unipointer.getPointerPoint = function( pointer ) { + return { + x: pointer.pageX, + y: pointer.pageY + }; +}; + +// ----- ----- // + +return Unipointer; + +})); +/*! + * Huebee v2.1.0 + * 1-click color picker + * MIT license + * https://huebee.buzz + * Copyright 2020 Metafizzy + */ + +/* jshint browser: true, unused: true, undef: true */ + +( function( window, factory ) { + // universal module definition + if ( typeof define == 'function' && define.amd ) { + /* globals define */ // AMD + define( [ + 'ev-emitter/ev-emitter', + 'unipointer/unipointer', + ], function( EvEmitter, Unipointer ) { + return factory( window, EvEmitter, Unipointer ); + } ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('ev-emitter'), + require('unipointer') + ); + } else { + // browser global + window.Huebee = factory( + window, + window.EvEmitter, + window.Unipointer + ); + } + +}( window, function factory( window, EvEmitter, Unipointer ) { + +function Huebee( anchor, options ) { + // anchor + anchor = getQueryElement( anchor ); + if ( !anchor ) { + throw new Error( 'Bad element for Huebee: ' + anchor ); + } + this.anchor = anchor; + // options + this.options = {}; + this.option( Huebee.defaults ); + this.option( options ); + // kick things off + this.create(); +} + +Huebee.defaults = { + hues: 12, + hue0: 0, + shades: 5, + saturations: 3, + notation: 'shortHex', + setText: true, + setBGColor: true, +}; + +var proto = Huebee.prototype = Object.create( EvEmitter.prototype ); + +proto.option = function( options ) { + this.options = extend( this.options, options ); +}; + +// globally unique identifiers +var GUID = 0; +// internal store of all Colcade intances +var instances = {}; + +proto.create = function() { + // add guid for Colcade.data + var guid = this.guid = ++GUID; + this.anchor.huebeeGUID = guid; + instances[ guid ] = this; // associate via id + // properties + this.setBGElems = this.getSetElems( this.options.setBGColor ); + this.setTextElems = this.getSetElems( this.options.setText ); + // events + // HACK: this is getting ugly + this.outsideCloseIt = this.outsideClose.bind( this ); + this.onDocKeydown = this.docKeydown.bind( this ); + this.closeIt = this.close.bind( this ); + this.openIt = this.open.bind( this ); + this.onElemTransitionend = this.elemTransitionend.bind( this ); + // open events + this.isInputAnchor = this.anchor.nodeName == 'INPUT'; + if ( !this.options.staticOpen ) { + this.anchor.addEventListener( 'click', this.openIt ); + this.anchor.addEventListener( 'focus', this.openIt ); + } + // change event + if ( this.isInputAnchor ) { + this.anchor.addEventListener( 'input', this.inputInput.bind( this ) ); + } + // create element + var element = this.element = document.createElement('div'); + element.className = 'huebee '; + element.className += this.options.staticOpen ? 'is-static-open ' : + 'is-hidden '; + element.className += this.options.className || ''; + // create container + var container = this.container = document.createElement('div'); + container.className = 'huebee__container'; + // do not blur if padding clicked + function onContainerPointerStart( event ) { + if ( event.target == container ) { + event.preventDefault(); + } + } + container.addEventListener( 'mousedown', onContainerPointerStart ); + container.addEventListener( 'touchstart', onContainerPointerStart ); + // create canvas + this.createCanvas(); + // create cursor + this.cursor = document.createElement('div'); + this.cursor.className = 'huebee__cursor is-hidden'; + container.appendChild( this.cursor ); + // create close button + this.createCloseButton(); + + element.appendChild( container ); + // set relative position on parent + if ( !this.options.staticOpen ) { + var parentStyle = getComputedStyle( this.anchor.parentNode ); + if ( parentStyle.position != 'relative' && parentStyle.position != 'absolute' ) { + this.anchor.parentNode.style.position = 'relative'; + } + } + + // satY, y position where saturation grid starts + var customLength = this.getCustomLength(); + this.satY = customLength ? Math.ceil( customLength / this.options.hues ) + 1 : 0; + // colors + this.updateColors(); + this.setAnchorColor(); + if ( this.options.staticOpen ) { + this.open(); + } +}; + +proto.getSetElems = function( option ) { + if ( option === true ) { + return [ this.anchor ]; + } else if ( typeof option == 'string' ) { + return document.querySelectorAll( option ); + } +}; + +proto.getCustomLength = function() { + var customColors = this.options.customColors; + return customColors && customColors.length || 0; +}; + +proto.createCanvas = function() { + var canvas = this.canvas = document.createElement('canvas'); + canvas.className = 'huebee__canvas'; + this.ctx = canvas.getContext('2d'); + // canvas pointer events + var canvasPointer = this.canvasPointer = new Unipointer(); + canvasPointer._bindStartEvent( canvas ); + canvasPointer.on( 'pointerDown', this.canvasPointerDown.bind( this ) ); + canvasPointer.on( 'pointerMove', this.canvasPointerMove.bind( this ) ); + this.container.appendChild( canvas ); +}; + +var svgURI = 'http://www.w3.org/2000/svg'; + +proto.createCloseButton = function() { + if ( this.options.staticOpen ) { + return; + } + var svg = document.createElementNS( svgURI, 'svg' ); + svg.setAttribute( 'class', 'huebee__close-button' ); + svg.setAttribute( 'viewBox', '0 0 24 24' ); + svg.setAttribute( 'width', '24' ); + svg.setAttribute( 'height', '24' ); + var path = document.createElementNS( svgURI, 'path' ); + path.setAttribute( 'd', 'M 7,7 L 17,17 M 17,7 L 7,17' ); + path.setAttribute( 'class', 'huebee__close-button__x' ); + svg.appendChild( path ); + svg.addEventListener( 'click', this.closeIt ); + this.container.appendChild( svg ); +}; + +proto.updateColors = function() { + // hash of color, h, s, l according to x,y grid position + // [x,y] = { color, h, s, l } + this.swatches = {}; + // hash of gridX,gridY position according to color + // [#09F] = { x, y } + this.colorGrid = {}; + this.updateColorModer(); + + var shades = this.options.shades; + var sats = this.options.saturations; + var hues = this.options.hues; + + // render custom colors + if ( this.getCustomLength() ) { + var customI = 0; + this.options.customColors.forEach( function( color ) { + var x = customI % hues; + var y = Math.floor( customI/hues ); + var swatch = getSwatch( color ); + if ( swatch ) { + this.addSwatch( swatch, x, y ); + customI++; + } + }.bind( this ) ); + } + + // render saturation grids + var i; + for ( i = 0; i < sats; i++ ) { + var sat = 1 - i/sats; + var yOffset = shades * i + this.satY; + this.updateSaturationGrid( i, sat, yOffset ); + } + + // render grays + var grayCount = this.getGrayCount(); + for ( i = 0; i < grayCount; i++ ) { + var lum = 1 - i / ( shades + 1 ); + var color = this.colorModer( 0, 0, lum ); + var swatch = getSwatch( color ); + this.addSwatch( swatch, hues + 1, i ); + } +}; + +// get shades + black & white; else 0 +proto.getGrayCount = function() { + return this.options.shades ? this.options.shades + 2 : 0; +}; + +proto.updateSaturationGrid = function( i, sat, yOffset ) { + var shades = this.options.shades; + var hues = this.options.hues; + var hue0 = this.options.hue0; + for ( var row = 0; row < shades; row++ ) { + for ( var col = 0; col < hues; col++ ) { + var hue = Math.round( col * 360/hues + hue0 ) % 360; + var lum = 1 - ( row + 1 ) / ( shades + 1 ); + var color = this.colorModer( hue, sat, lum ); + var swatch = getSwatch( color ); + var gridY = row + yOffset; + this.addSwatch( swatch, col, gridY ); + } + } +}; + +proto.addSwatch = function( swatch, gridX, gridY ) { + // add swatch color to hash + this.swatches[ gridX + ',' + gridY ] = swatch; + // add color to colorGrid + this.colorGrid[ swatch.color.toUpperCase() ] = { + x: gridX, + y: gridY, + }; +}; + +var colorModers = { + hsl: function( h, s, l ) { + s = Math.round( s * 100 ); + l = Math.round( l * 100 ); + return 'hsl(' + h + ', ' + s + '%, ' + l + '%)'; + }, + hex: hsl2hex, + shortHex: function( h, s, l ) { + var hex = hsl2hex( h, s, l ); + return roundHex( hex ); + }, +}; + +proto.updateColorModer = function() { + this.colorModer = colorModers[ this.options.notation ] || colorModers.shortHex; +}; + +proto.renderColors = function() { + var gridSize = this.gridSize * 2; + for ( var position in this.swatches ) { + var swatch = this.swatches[ position ]; + var duple = position.split(','); + var gridX = duple[0]; + var gridY = duple[1]; + this.ctx.fillStyle = swatch.color; + this.ctx.fillRect( gridX * gridSize, gridY * gridSize, gridSize, gridSize ); + } +}; + +proto.setAnchorColor = function() { + if ( this.isInputAnchor ) { + this.setColor( this.anchor.value ); + } +}; + +// ----- events ----- // + +var docElem = document.documentElement; + +proto.open = function() { + /* jshint unused: false */ + if ( this.isOpen ) { + return; + } + var anchor = this.anchor; + var elem = this.element; + if ( !this.options.staticOpen ) { + elem.style.left = anchor.offsetLeft + 'px'; + elem.style.top = anchor.offsetTop + anchor.offsetHeight + 'px'; + } + this.bindOpenEvents( true ); + elem.removeEventListener( 'transitionend', this.onElemTransitionend ); + // add huebee to DOM + anchor.parentNode.insertBefore( elem, anchor.nextSibling ); + // measurements + var duration = getComputedStyle( elem ).transitionDuration; + this.hasTransition = duration && duration != 'none' && parseFloat( duration ); + + this.isOpen = true; + this.updateSizes(); + this.renderColors(); + this.setAnchorColor(); + + // trigger reflow for transition + /* eslint-disable-next-line no-unused-vars */ + var h = elem.offsetHeight; + elem.classList.remove('is-hidden'); +}; + +proto.bindOpenEvents = function( isAdd ) { + if ( this.options.staticOpen ) { + return; + } + var method = ( isAdd ? 'add' : 'remove' ) + 'EventListener'; + docElem[ method ]( 'mousedown', this.outsideCloseIt ); + docElem[ method ]( 'touchstart', this.outsideCloseIt ); + document[ method ]( 'focusin', this.outsideCloseIt ); + document[ method ]( 'keydown', this.onDocKeydown ); + this.anchor[ method ]( 'blur', this.closeIt ); +}; + +proto.updateSizes = function() { + var hues = this.options.hues; + var shades = this.options.shades; + var sats = this.options.saturations; + var grayCount = this.getGrayCount(); + var customLength = this.getCustomLength(); + + this.cursorBorder = parseInt( getComputedStyle( this.cursor ).borderTopWidth, 10 ); + this.gridSize = Math.round( this.cursor.offsetWidth - this.cursorBorder * 2 ); + this.canvasOffset = { + x: this.canvas.offsetLeft, + y: this.canvas.offsetTop, + }; + var cols, rows; + if ( customLength && !grayCount ) { + // custom colors only + cols = Math.min( customLength, hues ); + rows = Math.ceil( customLength/hues ); + } else { + cols = hues + 2; + rows = Math.max( shades * sats + this.satY, grayCount ); + } + var width = this.canvas.width = cols * this.gridSize * 2; + this.canvas.height = rows * this.gridSize * 2; + this.canvas.style.width = width/2 + 'px'; +}; + +// close if target is not anchor or element +proto.outsideClose = function( event ) { + var isAnchor = this.anchor.contains( event.target ); + var isElement = this.element.contains( event.target ); + if ( !isAnchor && !isElement ) { + this.close(); + } +}; + +var closeKeydowns = { + 13: true, // enter + 27: true, // esc +}; + +proto.docKeydown = function( event ) { + if ( closeKeydowns[ event.keyCode ] ) { + this.close(); + } +}; + +var supportsTransitions = typeof docElem.style.transform == 'string'; + +proto.close = function() { + if ( !this.isOpen ) { + return; + } + + if ( supportsTransitions && this.hasTransition ) { + this.element.addEventListener( 'transitionend', this.onElemTransitionend ); + } else { + this.remove(); + } + this.element.classList.add('is-hidden'); + + this.bindOpenEvents( false ); + this.isOpen = false; +}; + +proto.remove = function() { + var parent = this.element.parentNode; + if ( parent.contains( this.element ) ) { + parent.removeChild( this.element ); + } +}; + +proto.elemTransitionend = function( event ) { + if ( event.target != this.element ) { + return; + } + this.element.removeEventListener( 'transitionend', this.onElemTransitionend ); + this.remove(); +}; + +proto.inputInput = function() { + this.setColor( this.anchor.value ); +}; + +// ----- canvas pointer ----- // + +proto.canvasPointerDown = function( event, pointer ) { + event.preventDefault(); + this.updateOffset(); + this.canvasPointerChange( pointer ); +}; + +proto.updateOffset = function() { + var boundingRect = this.canvas.getBoundingClientRect(); + this.offset = { + x: boundingRect.left + window.pageXOffset, + y: boundingRect.top + window.pageYOffset, + }; +}; + +proto.canvasPointerMove = function( event, pointer ) { + this.canvasPointerChange( pointer ); +}; + +proto.canvasPointerChange = function( pointer ) { + var x = Math.round( pointer.pageX - this.offset.x ); + var y = Math.round( pointer.pageY - this.offset.y ); + var gridSize = this.gridSize; + var sx = Math.floor( x/gridSize ); + var sy = Math.floor( y/gridSize ); + + var swatch = this.swatches[ sx + ',' + sy ]; + this.setSwatch( swatch ); +}; + +// ----- select ----- // + +proto.setColor = function( color ) { + var swatch = getSwatch( color ); + this.setSwatch( swatch ); +}; + +proto.setSwatch = function( swatch ) { + var color = swatch && swatch.color; + if ( !swatch ) { + return; + } + var wasSameColor = color == this.color; + // color properties + this.color = color; + this.hue = swatch.hue; + this.sat = swatch.sat; + this.lum = swatch.lum; + // estimate if color can have dark or white text + var lightness = this.lum - Math.cos( ( this.hue + 70 )/180 * Math.PI ) * 0.15; + this.isLight = lightness > 0.5; + // cursor + var gridPosition = this.colorGrid[ color.toUpperCase() ]; + this.updateCursor( gridPosition ); + // set texts & backgrounds + this.setTexts(); + this.setBackgrounds(); + // event + if ( !wasSameColor ) { + this.emitEvent( 'change', [ color, swatch.hue, swatch.sat, swatch.lum ] ); + } +}; + +proto.setTexts = function() { + if ( !this.setTextElems ) { + return; + } + for ( var i = 0; i < this.setTextElems.length; i++ ) { + var elem = this.setTextElems[i]; + var property = elem.nodeName == 'INPUT' ? 'value' : 'textContent'; + elem[ property ] = this.color; + } +}; + +proto.setBackgrounds = function() { + if ( !this.setBGElems ) { + return; + } + var textColor = this.isLight ? '#222' : 'white'; + for ( var i = 0; i < this.setBGElems.length; i++ ) { + var elem = this.setBGElems[i]; + elem.style.backgroundColor = this.color; + elem.style.color = textColor; + } +}; + +proto.updateCursor = function( position ) { + if ( !this.isOpen ) { + return; + } + // show cursor if color is on the grid + var classMethod = position ? 'remove' : 'add'; + this.cursor.classList[ classMethod ]('is-hidden'); + + if ( !position ) { + return; + } + var gridSize = this.gridSize; + var offset = this.canvasOffset; + var border = this.cursorBorder; + this.cursor.style.left = position.x * gridSize + offset.x - border + 'px'; + this.cursor.style.top = position.y * gridSize + offset.y - border + 'px'; +}; + +// -------------------------- htmlInit -------------------------- // + +var console = window.console; + +function htmlInit() { + var elems = document.querySelectorAll('[data-huebee]'); + for ( var i = 0; i < elems.length; i++ ) { + var elem = elems[i]; + var attr = elem.getAttribute('data-huebee'); + var options; + try { + options = attr && JSON.parse( attr ); + } catch ( error ) { + // log error, do not initialize + if ( console ) { + console.error( 'Error parsing data-huebee on ' + elem.className + + ': ' + error ); + } + continue; + } + // initialize + new Huebee( elem, options ); + } +} + +var readyState = document.readyState; +if ( readyState == 'complete' || readyState == 'interactive' ) { + htmlInit(); +} else { + document.addEventListener( 'DOMContentLoaded', htmlInit ); +} + +// -------------------------- Huebee.data -------------------------- // + +Huebee.data = function( elem ) { + elem = getQueryElement( elem ); + var id = elem && elem.huebeeGUID; + return id && instances[ id ]; +}; + +// -------------------------- getSwatch -------------------------- // + +// proxy canvas used to check colors +var proxyCanvas = document.createElement('canvas'); +proxyCanvas.width = proxyCanvas.height = 1; +var proxyCtx = proxyCanvas.getContext('2d'); + +function getSwatch( color ) { + // check that color value is valid + proxyCtx.clearRect( 0, 0, 1, 1 ); + proxyCtx.fillStyle = '#010203'; // reset value + proxyCtx.fillStyle = color; + proxyCtx.fillRect( 0, 0, 1, 1 ); + var data = proxyCtx.getImageData( 0, 0, 1, 1 ).data; + // convert to array, imageData not array, #10 + data = [ data[0], data[1], data[2], data[3] ]; + if ( data.join(',') == '1,2,3,255' ) { + // invalid color + return; + } + // convert rgb to hsl + var hsl = rgb2hsl.apply( this, data ); + return { + color: color.trim(), + hue: hsl[0], + sat: hsl[1], + lum: hsl[2], + }; +} + +// -------------------------- utils -------------------------- // + +function extend( a, b ) { + for ( var prop in b ) { + a[ prop ] = b[ prop ]; + } + return a; +} + +function getQueryElement( elem ) { + if ( typeof elem == 'string' ) { + elem = document.querySelector( elem ); + } + return elem; +} + +function hsl2hex( h, s, l ) { + var rgb = hsl2rgb( h, s, l ); + return rgb2hex( rgb ); +} + +// thx jfsiii +// https://github.com/jfsiii/chromath/blob/master/src/static.js#L312 +/* eslint-disable max-statements-per-line */ +function hsl2rgb( h, s, l ) { + + var C = ( 1 - Math.abs( 2 * l - 1 ) ) * s; + var hp = h/60; + var X = C * ( 1 - Math.abs( hp % 2 - 1 ) ); + var rgb, m; + + switch ( Math.floor( hp ) ) { + case 0: rgb = [ C, X, 0 ]; break; + case 1: rgb = [ X, C, 0 ]; break; + case 2: rgb = [ 0, C, X ]; break; + case 3: rgb = [ 0, X, C ]; break; + case 4: rgb = [ X, 0, C ]; break; + case 5: rgb = [ C, 0, X ]; break; + default: rgb = [ 0, 0, 0 ]; + } + + m = l - ( C/2 ); + rgb = rgb.map( function( v ) { + return v + m; + } ); + + return rgb; +} + +function rgb2hsl( r, g, b ) { + r /= 255; g /= 255; b /= 255; + + var M = Math.max( r, g, b ); + var m = Math.min( r, g, b ); + var C = M - m; + var L = 0.5 * ( M + m ); + var S = C === 0 ? 0 : C / ( 1 - Math.abs( 2 * L - 1 ) ); + + var h; + if ( C === 0 ) { + h = 0; // spec'd as undefined, but usually set to 0 + } else if ( M === r ) { + h = ( ( g - b )/C ) % 6; + } else if ( M === g ) { + h = ( ( b - r )/C ) + 2; + } else if ( M === b ) { + h = ( ( r - g )/C ) + 4; + } + + var H = 60 * h; + + return [ H, parseFloat( S ), parseFloat( L ) ]; +} +/* eslint-enable max-statements-per-line */ + +function rgb2hex( rgb ) { + var hex = rgb.map( function( value ) { + value = Math.round( value * 255 ); + var hexNum = value.toString( 16 ).toUpperCase(); + // left pad 0 + hexNum = hexNum.length < 2 ? '0' + hexNum : hexNum; + return hexNum; + } ); + + return '#' + hex.join(''); +} + +// #123456 -> #135 +// grab first digit from hex +// not mathematically accurate, but makes for better palette +function roundHex( hex ) { + return '#' + hex[1] + hex[3] + hex[5]; +} + +// -------------------------- -------------------------- // + +return Huebee; + +} ) ); diff --git a/dist/huebee/huebee.pkgd.min.js b/dist/huebee/huebee.pkgd.min.js new file mode 100644 index 00000000..fdd8c3ac --- /dev/null +++ b/dist/huebee/huebee.pkgd.min.js @@ -0,0 +1,22 @@ +/*! + * Huebee PACKAGED v2.1.0 + * 1-click color picker + * MIT license + * https://huebee.buzz + * Copyright 2020 Metafizzy + */ +(function(t,e){if(typeof define=="function"&&define.amd){define("ev-emitter/ev-emitter",e)}else if(typeof module=="object"&&module.exports){module.exports=e()}else{t.EvEmitter=e()}})(typeof window!="undefined"?window:this,function(){"use strict";function t(){}var e=t.prototype;e.on=function(t,e){if(!t||!e){return}var i=this._events=this._events||{};var n=i[t]=i[t]||[];if(n.indexOf(e)==-1){n.push(e)}return this};e.once=function(t,e){if(!t||!e){return}this.on(t,e);var i=this._onceEvents=this._onceEvents||{};var n=i[t]=i[t]||{};n[e]=true;return this};e.off=function(t,e){var i=this._events&&this._events[t];if(!i||!i.length){return}var n=i.indexOf(e);if(n!=-1){i.splice(n,1)}return this};e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(!i||!i.length){return}i=i.slice(0);e=e||[];var n=this._onceEvents&&this._onceEvents[t];for(var o=0;o.5;var o=this.colorGrid[e.toUpperCase()];this.updateCursor(o);this.setTexts();this.setBackgrounds();if(!i){this.emitEvent("change",[e,t.hue,t.sat,t.lum])}};o.setTexts=function(){if(!this.setTextElems){return}for(var t=0;t