import { Color , RGBAColor , HSVAColor } from './color' ; import { isNumber , isString } from './types' ; import { clamp , Position , getMousePosition } from './utils' ; type ColorPickerOptions = { window ? : Window ; el ? : HTMLElement | string ; background ? : string | number ; widthUnits ? : string ; heightUnits ? : string ; width ? : number ; height ? : number ; color ? : string | number ; } ; export class ColorPicker { private _window : Window ; private _document : Document ; private _widthUnits : string = 'px' ; private _heightUnits : string = 'px' ; private _huePosition : number = 0 ; private _hueHeight : number = 0 ; private _maxHue : number = 0 ; public _inputIsNumber : boolean = false ; private _saturationWidth : number = 0 ; private _isChoosing : boolean = false ; private _callbacks : Function [ ] = [ ] ; public width : number = 0 ; public height : number = 0 ; public hue : number = 0 ; public position : Position = { x : 0 , y : 0 } ; public color : Color = new Color ( 0 ) ; public backgroundColor : Color = new Color ( 0 ) ; public hueColor : Color = new Color ( 0 ) ; public $el : HTMLElement ; public $saturation : HTMLElement ; public $hue : HTMLElement ; public $sbSelector : HTMLElement ; public $hSelector : HTMLElement ; constructor ( options = { } ) { this . _window = options . window || window ; this . _document = this . _window . document ; this . $el = this . _document . createElement ( 'div' ) ; this . $el . className = 'Scp' ; this . $el . innerHTML = `
      <div class="Scp-saturation">
        <div class="Scp-brightness"></div>
        <div class="Scp-sbSelector"></div>
      </div>
      <div class="Scp-hue">
        <div class="Scp-hSelector"></div>
      </div>
    ` ; this . $saturation = this . $el . querySelector ( '.Scp-saturation' ) ; this . $hue = this . $el . querySelector ( '.Scp-hue' ) ; this . $sbSelector = this . $el . querySelector ( '.Scp-sbSelector' ) ; this . $hSelector = this . $el . querySelector ( '.Scp-hSelector' ) ; this . $saturation . addEventListener ( 'mousedown' , this . _onSaturationMouseDown ) ; this . $saturation . addEventListener ( 'touchstart' , this . _onSaturationMouseDown ) ; this . $hue . addEventListener ( 'mousedown' , this . _onHueMouseDown ) ; this . $hue . addEventListener ( 'touchstart' , this . _onHueMouseDown ) ; if ( options . el ) { this . appendTo ( options . el ) ; } if ( options . background ) { this . setBackgroundColor ( options . background ) ; } if ( options . widthUnits ) { this . _widthUnits = options . widthUnits ; } if ( options . heightUnits ) { this . _heightUnits = options . heightUnits ; } this . setSize ( options . width || 175 , options . height || 150 ) ; this . setColor ( options . color ) ; } param { HTMLElement } el return { ColorPicker } public appendTo ( el ) { if ( isString ( el ) ) { document . querySelector ( el as string ) . appendChild ( this . $el ) ; } else { ; ( el as HTMLElement ) . appendChild ( this . $el ) ; } return this ; } /**
     * Removes picker from its parent and kill all listeners.
     * Call this method for proper destroy.
     */ public remove ( ) { this . _callbacks = [ ] ; this . _onSaturationMouseUp ( ) ; this . _onHueMouseUp ( ) ; this . $saturation . removeEventListener ( 'mousedown' , this . _onSaturationMouseDown ) ; this . $saturation . removeEventListener ( 'touchstart' , this . _onSaturationMouseDown ) ; this . $hue . removeEventListener ( 'mousedown' , this . _onHueMouseDown ) ; this . $hue . removeEventListener ( 'touchstart' , this . _onHueMouseDown ) ; if ( this . $el . parentNode ) { this . $el . parentNode . removeChild ( this . $el ) ; } } param { String | Number } color return { ColorPicker } public setColor ( color ) { this . _inputIsNumber = isNumber ( color ) ; this . color . fromHex ( color ) ; const { h , s , v } = this . color . hsv ; if ( ! isNaN ( h ) ) { this . hue = h ; } this . _moveSelectorTo ( this . _saturationWidth * s , ( 1 - v ) * this . _hueHeight ) ; this . _moveHueTo ( ( 1 - this . hue ) * this . _hueHeight ) ; this . _updateHue ( ) ; return this ; } param { Number } width param { Number } height return { ColorPicker } public setSize ( width , height ) { this . width = width ; this . height = height ; this . $el . style . width = this . width + this . _widthUnits ; this . $el . style . height = this . height + this . _heightUnits ; this . _saturationWidth = this . width - 25 ; this . $saturation . style . width = this . _saturationWidth + 'px' ; this . _hueHeight = this . height ; this . _maxHue = this . _hueHeight - 2 ; return this ; } param { String | Number } color return { ColorPicker } public setBackgroundColor ( color ) { this . backgroundColor . fromHex ( color ) ; this . $el . style . padding = '5px' ; this . $el . style . background = this . backgroundColor . hexString ; return this ; } /**
     * Removes background of the picker if previously set. It's no use
     * calling this method if you didn't set the background option on start
     * or if you didn't call setBackgroundColor previously.
     */ public setNoBackground ( ) { this . $el . style . padding = '0px' ; this . $el . style . background = 'none' ; return this ; } param { Function } callback return { ColorPicker } public onChange ( callback ) { if ( this . _callbacks . indexOf ( callback ) < 0 ) { this . _callbacks . push ( callback ) ; callback ( this . getHexString ( ) ) ; } return this ; } /**
     * Is true when mouse is down and user is currently choosing a color.
     */ public get isChoosing ( ) : boolean { return this . _isChoosing ; } return { Number | String } public getColor ( ) { if ( this . _inputIsNumber ) { return this . getHexNumber ( ) ; } return this . getHexString ( ) ; } return { String } public getHexString ( ) { return this . color . hexString . toUpperCase ( ) ; } return { Number } public getHexNumber ( ) { return this . color . hex ; } return { Object } public getRGB ( ) { return this . color . rgb ; } return { Object } public getHSV ( ) { return this . color . hsv ; } return { Boolean } public isDark ( ) { return this . color . isDark ; } return { Boolean } public isLight ( ) { return this . color . isLight ; } private _moveSelectorTo ( x , y ) { this . position . x = clamp ( x , 0 , this . _saturationWidth ) ; this . position . y = clamp ( y , 0 , this . _hueHeight ) ; this . $sbSelector . style . transform = `translate(${ this . position . x }px, ${ this . position . y }px)` ; } private _updateColorFromPosition ( ) { this . color . fromHsv ( { h : this . hue , s : this . position . x / this . _saturationWidth , v : 1 - this . position . y / this . _hueHeight } ) ; this . _updateColor ( ) ; } private _moveHueTo ( y ) { this . _huePosition = clamp ( y , 0 , this . _maxHue ) ; this . $hSelector . style . transform = `translateY(${ this . _huePosition }px)` ; } private _updateHueFromPosition ( ) { const hsvColor = this . getHSV ( ) ; this . hue = 1 - this . _huePosition / this . _maxHue ; this . color . fromHsv ( { h : this . hue , s : hsvColor . s , v : hsvColor . v } ) ; this . _updateHue ( ) ; } private _updateHue ( ) { this . hueColor . fromHsv ( { h : this . hue , s : 1 , v : 1 } ) ; this . $saturation . style . background = `linear-gradient(to right, #fff, ${ this . hueColor . hexString })` ; this . _updateColor ( ) ; } private _updateColor ( ) { this . $sbSelector . style . background = this . getHexString ( ) ; this . $sbSelector . style . borderColor = this . isDark ( ) ? '#fff' : '#000' ; this . _triggerChange ( ) ; } private _triggerChange ( ) { this . _callbacks . forEach ( callback => callback ( this . getHexString ( ) ) ) ; } private _onSaturationMouseDown = ( e ) : void => { const sbOffset = this . $saturation . getBoundingClientRect ( ) ; const { x , y } = getMousePosition ( e ) ; this . _isChoosing = true ; this . _moveSelectorTo ( x - sbOffset . left , y - sbOffset . top ) ; this . _updateColorFromPosition ( ) ; this . _window . addEventListener ( 'mouseup' , this . _onSaturationMouseUp ) ; this . _window . addEventListener ( 'touchend' , this . _onSaturationMouseUp ) ; this . _window . addEventListener ( 'mousemove' , this . _onSaturationMouseMove ) ; this . _window . addEventListener ( 'touchmove' , this . _onSaturationMouseMove ) ; e . preventDefault ( ) ; } ; private _onSaturationMouseMove = ( e ) : void => { const sbOffset = this . $saturation . getBoundingClientRect ( ) ; const { x , y } = getMousePosition ( e ) ; this . _moveSelectorTo ( x - sbOffset . left , y - sbOffset . top ) ; this . _updateColorFromPosition ( ) ; } ; private _onSaturationMouseUp = ( ) => { this . _isChoosing = false ; this . _window . removeEventListener ( 'mouseup' , this . _onSaturationMouseUp ) ; this . _window . removeEventListener ( 'touchend' , this . _onSaturationMouseUp ) ; this . _window . removeEventListener ( 'mousemove' , this . _onSaturationMouseMove ) ; this . _window . removeEventListener ( 'touchmove' , this . _onSaturationMouseMove ) ; } ; private _onHueMouseDown = ( e ) : void => { const hOffset = this . $hue . getBoundingClientRect ( ) ; const { y } = getMousePosition ( e ) ; this . _isChoosing = true ; this . _moveHueTo ( y - hOffset . top ) ; this . _updateHueFromPosition ( ) ; this . _window . addEventListener ( 'mouseup' , this . _onHueMouseUp ) ; this . _window . addEventListener ( 'touchend' , this . _onHueMouseUp ) ; this . _window . addEventListener ( 'mousemove' , this . _onHueMouseMove ) ; this . _window . addEventListener ( 'touchmove' , this . _onHueMouseMove ) ; e . preventDefault ( ) ; } ; private _onHueMouseMove = e => { const hOffset = this . $hue . getBoundingClientRect ( ) ; const { y } = getMousePosition ( e ) ; this . _moveHueTo ( y - hOffset . top ) ; this . _updateHueFromPosition ( ) ; } ; private _onHueMouseUp = ( ) => { this . _isChoosing = false ; this . _window . removeEventListener ( 'mouseup' , this . _onHueMouseUp ) ; this . _window . removeEventListener ( 'touchend' , this . _onHueMouseUp ) ; this . _window . removeEventListener ( 'mousemove' , this . _onHueMouseMove ) ; this . _window . removeEventListener ( 'touchmove' , this . _onHueMouseMove ) ; } ; }