import { NgModule, Component, ElementRef, Input, Output, AfterViewInit, AfterViewChecked, OnDestroy, EventEmitter, forwardRef, Renderer2, ViewChild, ChangeDetectorRef } from '@angular/core'; import { trigger, state, style, transition, animate } from '@angular/animations'; import { CommonModule } from '@angular/common'; import { DomHandler } from '../dom/domhandler'; import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; export const COLORPICKER_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ColorPicker), multi: true }; @Component({ selector: 'p-colorPicker', template: `
`, animations: [ trigger('panelState', [ state('hidden', style({ opacity: 0 })), state('visible', style({ opacity: 1 })), transition('visible => hidden', animate('400ms ease-in')), transition('hidden => visible', animate('400ms ease-out')) ]) ], providers: [DomHandler,COLORPICKER_VALUE_ACCESSOR] }) export class ColorPicker implements ControlValueAccessor, AfterViewInit, AfterViewChecked, OnDestroy{ @Input() style: any; @Input() styleClass: string; @Input() inline: boolean; @Input() format: string = 'hex'; @Input() appendTo: string; @Input() disabled: boolean; @Input() tabindex: string; @Input() inputId: string; @Output() onChange: EventEmitter = new EventEmitter(); @ViewChild('panel') panelViewChild: ElementRef; @ViewChild('colorSelector') colorSelectorViewChild: ElementRef; @ViewChild('colorHandle') colorHandleViewChild: ElementRef; @ViewChild('hue') hueViewChild: ElementRef; @ViewChild('hueHandle') hueHandleViewChild: ElementRef; @ViewChild('input') inputViewChild: ElementRef; value: any; inputBgColor: string; shown: boolean; panelVisible: boolean; defaultColor: string = 'ff0000'; onModelChange: Function = () => {}; onModelTouched: Function = () => {}; documentClickListener: Function; documentMousemoveListener: Function; documentMouseupListener: Function; documentHueMoveListener: Function; selfClick: boolean; colorDragging: boolean; hueDragging: boolean; constructor(public el: ElementRef, public domHandler: DomHandler, public renderer: Renderer2, public cd: ChangeDetectorRef) {} ngAfterViewInit() { if (this.appendTo) { if (this.appendTo === 'body') document.body.appendChild(this.panelViewChild.nativeElement); else this.domHandler.appendChild(this.panelViewChild.nativeElement, this.appendTo); } } ngAfterViewChecked() { if(this.shown) { this.onShow(); this.shown = false; } } onHueMousedown(event: MouseEvent) { if(this.disabled) { return; } this.bindDocumentMousemoveListener(); this.bindDocumentMouseupListener(); this.hueDragging = true; this.pickHue(event); } pickHue(event: MouseEvent) { let top: number = this.hueViewChild.nativeElement.getBoundingClientRect().top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); this.value = this.validateHSB({ h: Math.floor(360 * (150 - Math.max(0, Math.min(150, (event.pageY - top)))) / 150), s: this.value.s, b: this.value.b }); this.updateColorSelector(); this.updateUI(); this.updateModel(); this.onChange.emit({originalEvent: event, value: this.getValueToUpdate()}); } onColorMousedown(event: MouseEvent) { if(this.disabled) { return; } this.bindDocumentMousemoveListener(); this.bindDocumentMouseupListener(); this.colorDragging = true; this.pickColor(event); } pickColor(event: MouseEvent) { let rect = this.colorSelectorViewChild.nativeElement.getBoundingClientRect(); let top = rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); let left = rect.left + document.body.scrollLeft; let saturation = Math.floor(100 * (Math.max(0, Math.min(150, (event.pageX - left)))) / 150); let brightness = Math.floor(100 * (150 - Math.max(0, Math.min(150, (event.pageY - top)))) / 150); this.value = this.validateHSB({ h: this.value.h, s: saturation, b: brightness }); this.updateUI(); this.updateModel(); this.onChange.emit({originalEvent: event, value: this.getValueToUpdate()}); } getValueToUpdate() { let val: any; switch(this.format) { case 'hex': val = '#' + this.HSBtoHEX(this.value); break; case 'rgb': val = this.HSBtoRGB(this.value); break; case 'hsb': val = this.value; break; } return val; } updateModel(): void { this.onModelChange(this.getValueToUpdate()); } writeValue(value: any): void { if(value) { switch(this.format) { case 'hex': this.value = this.HEXtoHSB(value); break; case 'rgb': this.value = this.RGBtoHSB(value); break; case 'hsb': this.value = value; break; } } else { this.value = this.HEXtoHSB(this.defaultColor); } this.updateColorSelector(); this.updateUI(); } updateColorSelector() { this.colorSelectorViewChild.nativeElement.style.backgroundColor = '#' + this.HSBtoHEX(this.value); } updateUI() { this.colorHandleViewChild.nativeElement.style.left = Math.floor(150 * this.value.s / 100) + 'px'; this.colorHandleViewChild.nativeElement.style.top = Math.floor(150 * (100 - this.value.b) / 100) + 'px'; this.hueHandleViewChild.nativeElement.style.top = Math.floor(150 - (150 * this.value.h / 360)) + 'px'; this.inputBgColor = '#' + this.HSBtoHEX(this.value); } onInputFocus() { this.onModelTouched(); } show() { this.panelViewChild.nativeElement.style.zIndex = String(++DomHandler.zindex); this.panelVisible = true; this.shown = true; } hide() { this.panelVisible = false; this.unbindDocumentClickListener(); } onShow() { this.alignPanel(); this.bindDocumentClickListener(); } alignPanel() { if(this.appendTo) this.domHandler.absolutePosition(this.panelViewChild.nativeElement, this.inputViewChild.nativeElement); else this.domHandler.relativePosition(this.panelViewChild.nativeElement, this.inputViewChild.nativeElement); } onInputClick() { this.selfClick = true; this.togglePanel(); } togglePanel() { if(!this.panelVisible) this.show(); else this.hide(); } onInputKeydown(event: KeyboardEvent) { switch(event.which) { //space case 32: this.togglePanel(); event.preventDefault(); break; //escape and tab case 27: case 9: this.hide(); break; } } onPanelClick() { this.selfClick = true; } registerOnChange(fn: Function): void { this.onModelChange = fn; } registerOnTouched(fn: Function): void { this.onModelTouched = fn; } setDisabledState(val: boolean): void { this.disabled = val; } bindDocumentClickListener() { if(!this.documentClickListener) { this.documentClickListener = this.renderer.listen('document', 'click', () => { if(!this.selfClick) { this.panelVisible = false; this.unbindDocumentClickListener(); } this.selfClick = false; this.cd.markForCheck(); }); } } unbindDocumentClickListener() { if(this.documentClickListener) { this.documentClickListener(); this.documentClickListener = null; } } bindDocumentMousemoveListener() { if(!this.documentMousemoveListener) { this.documentMousemoveListener = this.renderer.listen('document', 'mousemove', (event: MouseEvent) => { if(this.colorDragging) { this.pickColor(event); } if(this.hueDragging) { this.pickHue(event); } }); } } unbindDocumentMousemoveListener() { if(this.documentMousemoveListener) { this.documentMousemoveListener(); this.documentMousemoveListener = null; } } bindDocumentMouseupListener() { if(!this.documentMouseupListener) { this.documentMouseupListener = this.renderer.listen('document', 'mouseup', () => { this.colorDragging = false; this.hueDragging = false; this.unbindDocumentMousemoveListener(); this.unbindDocumentMouseupListener(); }); } } unbindDocumentMouseupListener() { if(this.documentMouseupListener) { this.documentMouseupListener(); this.documentMouseupListener = null; } } validateHSB(hsb) { return { h: Math.min(360, Math.max(0, hsb.h)), s: Math.min(100, Math.max(0, hsb.s)), b: Math.min(100, Math.max(0, hsb.b)) }; } validateRGB(rgb) { return { r: Math.min(255, Math.max(0, rgb.r)), g: Math.min(255, Math.max(0, rgb.g)), b: Math.min(255, Math.max(0, rgb.b)) }; } validateHEX(hex) { var len = 6 - hex.length; if (len > 0) { var o = []; for (var i=0; i -1) ? hex.substring(1) : hex), 16); return {r: hexValue >> 16, g: (hexValue & 0x00FF00) >> 8, b: (hexValue & 0x0000FF)}; } HEXtoHSB(hex) { return this.RGBtoHSB(this.HEXtoRGB(hex)); } RGBtoHSB(rgb) { var hsb = { h: 0, s: 0, b: 0 }; var min = Math.min(rgb.r, rgb.g, rgb.b); var max = Math.max(rgb.r, rgb.g, rgb.b); var delta = max - min; hsb.b = max; if (max != 0) { } hsb.s = max != 0 ? 255 * delta / max : 0; if (hsb.s != 0) { if (rgb.r == max) { hsb.h = (rgb.g - rgb.b) / delta; } else if (rgb.g == max) { hsb.h = 2 + (rgb.b - rgb.r) / delta; } else { hsb.h = 4 + (rgb.r - rgb.g) / delta; } } else { hsb.h = -1; } hsb.h *= 60; if (hsb.h < 0) { hsb.h += 360; } hsb.s *= 100/255; hsb.b *= 100/255; return hsb; } HSBtoRGB(hsb) { var rgb = { r: null, g: null, b: null }; var h = Math.round(hsb.h); var s = Math.round(hsb.s*255/100); var v = Math.round(hsb.b*255/100); if(s == 0) { rgb = { r: v, g: v, b: v } } else { var t1 = v; var t2 = (255-s)*v/255; var t3 = (t1-t2)*(h%60)/60; if(h==360) h = 0; if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} else {rgb.r=0; rgb.g=0; rgb.b=0} } return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; } RGBtoHEX(rgb) { var hex = [ rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16) ]; for(var key in hex) { if(hex[key].length == 1) { hex[key] = '0' + hex[key]; } } return hex.join(''); } HSBtoHEX(hsb) { return this.RGBtoHEX(this.HSBtoRGB(hsb)); } ngOnDestroy() { this.unbindDocumentClickListener(); if (this.appendTo) { this.el.nativeElement.appendChild(this.panelViewChild.nativeElement); } } } @NgModule({ imports: [CommonModule], exports: [ColorPicker], declarations: [ColorPicker] }) export class ColorPickerModule { }