UNPKG

ngx-color-picker

Version:
1,326 lines (1,322 loc) 141 kB
import * as i0 from '@angular/core'; import { EventEmitter, HostListener, Output, Input, Directive, Injectable, PLATFORM_ID, ViewChild, Inject, ViewEncapsulation, Component, Injector } from '@angular/core'; import * as i2 from '@angular/common'; import { isPlatformBrowser, DOCUMENT, CommonModule } from '@angular/common'; var ColorFormats; (function (ColorFormats) { ColorFormats[ColorFormats["HEX"] = 0] = "HEX"; ColorFormats[ColorFormats["RGBA"] = 1] = "RGBA"; ColorFormats[ColorFormats["HSLA"] = 2] = "HSLA"; ColorFormats[ColorFormats["CMYK"] = 3] = "CMYK"; })(ColorFormats || (ColorFormats = {})); class Rgba { r; g; b; a; constructor(r, g, b, a) { this.r = r; this.g = g; this.b = b; this.a = a; } } class Hsva { h; s; v; a; constructor(h, s, v, a) { this.h = h; this.s = s; this.v = v; this.a = a; } } class Hsla { h; s; l; a; constructor(h, s, l, a) { this.h = h; this.s = s; this.l = l; this.a = a; } } class Cmyk { c; m; y; k; a; constructor(c, m, y, k, a = 1) { this.c = c; this.m = m; this.y = y; this.k = k; this.a = a; } } function calculateAutoPositioning(elBounds, triggerElBounds) { // Defaults let usePositionX = 'right'; let usePositionY = 'bottom'; // Calculate collisions const { height, width } = elBounds; const { top, left } = triggerElBounds; const bottom = top + triggerElBounds.height; const right = left + triggerElBounds.width; const collisionTop = top - height < 0; const collisionBottom = bottom + height > (window.innerHeight || document.documentElement.clientHeight); const collisionLeft = left - width < 0; const collisionRight = right + width > (window.innerWidth || document.documentElement.clientWidth); const collisionAll = collisionTop && collisionBottom && collisionLeft && collisionRight; // Generate X & Y position values if (collisionBottom) { usePositionY = 'top'; } if (collisionTop) { usePositionY = 'bottom'; } if (collisionLeft) { usePositionX = 'right'; } if (collisionRight) { usePositionX = 'left'; } // Choose the largest gap available if (collisionAll) { const postions = ['left', 'right', 'top', 'bottom']; return postions.reduce((prev, next) => elBounds[prev] > elBounds[next] ? prev : next); } if ((collisionLeft && collisionRight)) { if (collisionTop) { return 'bottom'; } if (collisionBottom) { return 'top'; } return top > bottom ? 'top' : 'bottom'; } if ((collisionTop && collisionBottom)) { if (collisionLeft) { return 'right'; } if (collisionRight) { return 'left'; } return left > right ? 'left' : 'right'; } return `${usePositionY}-${usePositionX}`; } function detectIE() { let ua = ''; if (typeof navigator !== 'undefined') { ua = navigator.userAgent.toLowerCase(); } const msie = ua.indexOf('msie '); if (msie > 0) { // IE 10 or older => return version number return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); } // Other browser return false; } class TextDirective { rg; text; newValue = new EventEmitter(); inputChange(event) { const value = event.target.value; if (this.rg === undefined) { this.newValue.emit(value); } else { const numeric = parseFloat(value); this.newValue.emit({ v: numeric, rg: this.rg }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: TextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.1", type: TextDirective, isStandalone: true, selector: "[text]", inputs: { rg: "rg", text: "text" }, outputs: { newValue: "newValue" }, host: { listeners: { "input": "inputChange($event)" } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: TextDirective, decorators: [{ type: Directive, args: [{ selector: '[text]' }] }], propDecorators: { rg: [{ type: Input }], text: [{ type: Input }], newValue: [{ type: Output }], inputChange: [{ type: HostListener, args: ['input', ['$event']] }] } }); class SliderDirective { elRef; listenerMove; listenerStop; rgX; rgY; slider; dragEnd = new EventEmitter(); dragStart = new EventEmitter(); newValue = new EventEmitter(); mouseDown(event) { this.start(event); } touchStart(event) { this.start(event); } constructor(elRef) { this.elRef = elRef; this.listenerMove = (event) => this.move(event); this.listenerStop = () => this.stop(); } move(event) { event.preventDefault(); this.setCursor(event); } start(event) { this.setCursor(event); event.stopPropagation(); document.addEventListener('mouseup', this.listenerStop); document.addEventListener('touchend', this.listenerStop); document.addEventListener('mousemove', this.listenerMove); document.addEventListener('touchmove', this.listenerMove); this.dragStart.emit(); } stop() { document.removeEventListener('mouseup', this.listenerStop); document.removeEventListener('touchend', this.listenerStop); document.removeEventListener('mousemove', this.listenerMove); document.removeEventListener('touchmove', this.listenerMove); this.dragEnd.emit(); } getX(event) { const position = this.elRef.nativeElement.getBoundingClientRect(); const pageX = (event.pageX !== undefined) ? event.pageX : event.touches[0].pageX; return pageX - position.left - window.pageXOffset; } getY(event) { const position = this.elRef.nativeElement.getBoundingClientRect(); const pageY = (event.pageY !== undefined) ? event.pageY : event.touches[0].pageY; return pageY - position.top - window.pageYOffset; } setCursor(event) { const width = this.elRef.nativeElement.offsetWidth; const height = this.elRef.nativeElement.offsetHeight; const x = Math.max(0, Math.min(this.getX(event), width)); const y = Math.max(0, Math.min(this.getY(event), height)); if (this.rgX !== undefined && this.rgY !== undefined) { this.newValue.emit({ s: x / width, v: (1 - y / height), rgX: this.rgX, rgY: this.rgY }); } else if (this.rgX === undefined && this.rgY !== undefined) { this.newValue.emit({ v: y / height, rgY: this.rgY }); } else if (this.rgX !== undefined && this.rgY === undefined) { this.newValue.emit({ v: x / width, rgX: this.rgX }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: SliderDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.1", type: SliderDirective, isStandalone: true, selector: "[slider]", inputs: { rgX: "rgX", rgY: "rgY", slider: "slider" }, outputs: { dragEnd: "dragEnd", dragStart: "dragStart", newValue: "newValue" }, host: { listeners: { "mousedown": "mouseDown($event)", "touchstart": "touchStart($event)" } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: SliderDirective, decorators: [{ type: Directive, args: [{ selector: '[slider]' }] }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { rgX: [{ type: Input }], rgY: [{ type: Input }], slider: [{ type: Input }], dragEnd: [{ type: Output }], dragStart: [{ type: Output }], newValue: [{ type: Output }], mouseDown: [{ type: HostListener, args: ['mousedown', ['$event']] }], touchStart: [{ type: HostListener, args: ['touchstart', ['$event']] }] } }); class SliderPosition { h; s; v; a; constructor(h, s, v, a) { this.h = h; this.s = s; this.v = v; this.a = a; } } class SliderDimension { h; s; v; a; constructor(h, s, v, a) { this.h = h; this.s = s; this.v = v; this.a = a; } } class ColorPickerService { active = null; setActive(active) { if (this.active && this.active !== active && this.active.cpDialogDisplay !== 'inline') { this.active.closeDialog(); } this.active = active; } hsva2hsla(hsva) { const h = hsva.h, s = hsva.s, v = hsva.v, a = hsva.a; if (v === 0) { return new Hsla(h, 0, 0, a); } else if (s === 0 && v === 1) { return new Hsla(h, 1, 1, a); } else { const l = v * (2 - s) / 2; return new Hsla(h, v * s / (1 - Math.abs(2 * l - 1)), l, a); } } hsla2hsva(hsla) { const h = Math.min(hsla.h, 1), s = Math.min(hsla.s, 1); const l = Math.min(hsla.l, 1), a = Math.min(hsla.a, 1); if (l === 0) { return new Hsva(h, 0, 0, a); } else { const v = l + s * (1 - Math.abs(2 * l - 1)) / 2; return new Hsva(h, 2 * (v - l) / v, v, a); } } hsvaToRgba(hsva) { let r, g, b; const h = hsva.h, s = hsva.s, v = hsva.v, a = hsva.a; const i = Math.floor(h * 6); const f = h * 6 - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; default: r = 0, g = 0, b = 0; } return new Rgba(r, g, b, a); } cmykToRgb(cmyk) { const r = (1 - cmyk.c) * (1 - cmyk.k); const g = (1 - cmyk.m) * (1 - cmyk.k); const b = (1 - cmyk.y) * (1 - cmyk.k); return new Rgba(r, g, b, cmyk.a); } rgbaToCmyk(rgba) { const k = 1 - Math.max(rgba.r, rgba.g, rgba.b); if (k === 1) { return new Cmyk(0, 0, 0, 1, rgba.a); } else { const c = (1 - rgba.r - k) / (1 - k); const m = (1 - rgba.g - k) / (1 - k); const y = (1 - rgba.b - k) / (1 - k); return new Cmyk(c, m, y, k, rgba.a); } } rgbaToHsva(rgba) { let h, s; const r = Math.min(rgba.r, 1), g = Math.min(rgba.g, 1); const b = Math.min(rgba.b, 1), a = Math.min(rgba.a, 1); const max = Math.max(r, g, b), min = Math.min(r, g, b); const v = max, d = max - min; s = (max === 0) ? 0 : d / max; if (max === min) { h = 0; } else { switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; default: h = 0; } h /= 6; } return new Hsva(h, s, v, a); } rgbaToHex(rgba, allowHex8) { /* eslint-disable no-bitwise */ let hex = '#' + ((1 << 24) | (rgba.r << 16) | (rgba.g << 8) | rgba.b).toString(16).substr(1); if (allowHex8) { hex += ((1 << 8) | Math.round(rgba.a * 255)).toString(16).substr(1); } /* eslint-enable no-bitwise */ return hex; } normalizeCMYK(cmyk) { return new Cmyk(cmyk.c / 100, cmyk.m / 100, cmyk.y / 100, cmyk.k / 100, cmyk.a); } denormalizeCMYK(cmyk) { return new Cmyk(Math.floor(cmyk.c * 100), Math.floor(cmyk.m * 100), Math.floor(cmyk.y * 100), Math.floor(cmyk.k * 100), cmyk.a); } denormalizeRGBA(rgba) { return new Rgba(Math.round(rgba.r * 255), Math.round(rgba.g * 255), Math.round(rgba.b * 255), rgba.a); } stringToHsva(colorString = '', allowHex8 = false) { let hsva = null; colorString = (colorString || '').toLowerCase(); const stringParsers = [ { re: /(rgb)a?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*%?,\s*(\d{1,3})\s*%?(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, parse: function (execResult) { return new Rgba(parseInt(execResult[2], 10) / 255, parseInt(execResult[3], 10) / 255, parseInt(execResult[4], 10) / 255, isNaN(parseFloat(execResult[5])) ? 1 : parseFloat(execResult[5])); } }, { re: /(hsl)a?\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, parse: function (execResult) { return new Hsla(parseInt(execResult[2], 10) / 360, parseInt(execResult[3], 10) / 100, parseInt(execResult[4], 10) / 100, isNaN(parseFloat(execResult[5])) ? 1 : parseFloat(execResult[5])); } } ]; if (allowHex8) { stringParsers.push({ re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})?$/, parse: function (execResult) { return new Rgba(parseInt(execResult[1], 16) / 255, parseInt(execResult[2], 16) / 255, parseInt(execResult[3], 16) / 255, parseInt(execResult[4] || 'FF', 16) / 255); } }); } else { stringParsers.push({ re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/, parse: function (execResult) { return new Rgba(parseInt(execResult[1], 16) / 255, parseInt(execResult[2], 16) / 255, parseInt(execResult[3], 16) / 255, 1); } }); } stringParsers.push({ re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/, parse: function (execResult) { return new Rgba(parseInt(execResult[1] + execResult[1], 16) / 255, parseInt(execResult[2] + execResult[2], 16) / 255, parseInt(execResult[3] + execResult[3], 16) / 255, 1); } }); for (const key in stringParsers) { if (stringParsers.hasOwnProperty(key)) { const parser = stringParsers[key]; const match = parser.re.exec(colorString), color = match && parser.parse(match); if (color) { if (color instanceof Rgba) { hsva = this.rgbaToHsva(color); } else if (color instanceof Hsla) { hsva = this.hsla2hsva(color); } return hsva; } } } return hsva; } outputFormat(hsva, outputFormat, alphaChannel) { if (outputFormat === 'auto') { outputFormat = hsva.a < 1 ? 'rgba' : 'hex'; } switch (outputFormat) { case 'hsla': const hsla = this.hsva2hsla(hsva); const hslaText = new Hsla(Math.round((hsla.h) * 360), Math.round(hsla.s * 100), Math.round(hsla.l * 100), Math.round(hsla.a * 100) / 100); if (hsva.a < 1 || alphaChannel === 'always') { return 'hsla(' + hslaText.h + ',' + hslaText.s + '%,' + hslaText.l + '%,' + hslaText.a + ')'; } else { return 'hsl(' + hslaText.h + ',' + hslaText.s + '%,' + hslaText.l + '%)'; } case 'rgba': const rgba = this.denormalizeRGBA(this.hsvaToRgba(hsva)); if (hsva.a < 1 || alphaChannel === 'always') { return 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + Math.round(rgba.a * 100) / 100 + ')'; } else { return 'rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')'; } default: const allowHex8 = (alphaChannel === 'always' || alphaChannel === 'forced'); return this.rgbaToHex(this.denormalizeRGBA(this.hsvaToRgba(hsva)), allowHex8); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ColorPickerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ColorPickerService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ColorPickerService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); // Do not store that on the class instance since the condition will be run // every time the class is created. const SUPPORTS_TOUCH = typeof window !== 'undefined' && 'ontouchstart' in window; class ColorPickerComponent { ngZone; elRef; cdRef; document; platformId; service; isIE10 = false; cmyk; hsva; width; height; cmykColor; outputColor; initialColor; fallbackColor; listenerResize; listenerMouseDown; directiveInstance; sliderH; sliderDimMax; directiveElementRef; dialogArrowSize = 10; dialogArrowOffset = 15; dialogInputFields = [ ColorFormats.HEX, ColorFormats.RGBA, ColorFormats.HSLA, ColorFormats.CMYK ]; useRootViewContainer = false; show; hidden; top; left; position; format; slider; hexText; hexAlpha; cmykText; hslaText; rgbaText; arrowTop; selectedColor; hueSliderColor; alphaSliderColor; cpWidth; cpHeight; cpColorMode; cpCmykEnabled; cpAlphaChannel; cpOutputFormat; cpDisableInput; cpDialogDisplay; cpIgnoredElements; cpSaveClickOutside; cpCloseClickOutside; cpPosition; cpUsePosition; cpPositionOffset; cpOKButton; cpOKButtonText; cpOKButtonClass; cpCancelButton; cpCancelButtonText; cpCancelButtonClass; cpEyeDropper; eyeDropperSupported; cpPresetLabel; cpPresetColors; cpPresetColorsClass; cpMaxPresetColorsLength; cpPresetEmptyMessage; cpPresetEmptyMessageClass; cpAddColorButton; cpAddColorButtonText; cpAddColorButtonClass; cpRemoveColorButtonClass; cpArrowPosition; cpTriggerElement; cpExtraTemplate; dialogElement; hueSlider; alphaSlider; handleEsc(event) { if (this.show && this.cpDialogDisplay === 'popup') { this.onCancelColor(event); } } handleEnter(event) { if (this.show && this.cpDialogDisplay === 'popup') { this.onAcceptColor(event); } } constructor(ngZone, elRef, cdRef, document, platformId, service) { this.ngZone = ngZone; this.elRef = elRef; this.cdRef = cdRef; this.document = document; this.platformId = platformId; this.service = service; this.eyeDropperSupported = isPlatformBrowser(this.platformId) && 'EyeDropper' in this.document.defaultView; } ngOnInit() { this.slider = new SliderPosition(0, 0, 0, 0); const hueWidth = this.hueSlider.nativeElement.offsetWidth || 140; const alphaWidth = this.alphaSlider.nativeElement.offsetWidth || 140; this.sliderDimMax = new SliderDimension(hueWidth, this.cpWidth, 130, alphaWidth); if (this.cpCmykEnabled) { this.format = ColorFormats.CMYK; } else if (this.cpOutputFormat === 'rgba') { this.format = ColorFormats.RGBA; } else if (this.cpOutputFormat === 'hsla') { this.format = ColorFormats.HSLA; } else { this.format = ColorFormats.HEX; } this.listenerMouseDown = (event) => { this.onMouseDown(event); }; this.listenerResize = () => { this.onResize(); }; this.openDialog(this.initialColor, false); } ngOnDestroy() { this.closeDialog(); } ngAfterViewInit() { if (this.cpWidth !== 230 || this.cpDialogDisplay === 'inline') { const hueWidth = this.hueSlider.nativeElement.offsetWidth || 140; const alphaWidth = this.alphaSlider.nativeElement.offsetWidth || 140; this.sliderDimMax = new SliderDimension(hueWidth, this.cpWidth, 130, alphaWidth); this.updateColorPicker(false); this.cdRef.detectChanges(); } } openDialog(color, emit = true) { this.service.setActive(this); if (!this.width) { this.cpWidth = this.directiveElementRef.nativeElement.offsetWidth; } if (!this.height) { this.height = 320; } this.setInitialColor(color); this.setColorFromString(color, emit); this.openColorPicker(); } closeDialog() { this.closeColorPicker(); } setupDialog(instance, elementRef, color, cpWidth, cpHeight, cpDialogDisplay, cpFallbackColor, cpColorMode, cpCmykEnabled, cpAlphaChannel, cpOutputFormat, cpDisableInput, cpIgnoredElements, cpSaveClickOutside, cpCloseClickOutside, cpUseRootViewContainer, cpPosition, cpPositionOffset, cpPositionRelativeToArrow, cpPresetLabel, cpPresetColors, cpPresetColorsClass, cpMaxPresetColorsLength, cpPresetEmptyMessage, cpPresetEmptyMessageClass, cpOKButton, cpOKButtonClass, cpOKButtonText, cpCancelButton, cpCancelButtonClass, cpCancelButtonText, cpAddColorButton, cpAddColorButtonClass, cpAddColorButtonText, cpRemoveColorButtonClass, cpEyeDropper, cpTriggerElement, cpExtraTemplate) { this.setInitialColor(color); this.setColorMode(cpColorMode); this.isIE10 = (detectIE() === 10); this.directiveInstance = instance; this.directiveElementRef = elementRef; this.cpDisableInput = cpDisableInput; this.cpCmykEnabled = cpCmykEnabled; this.cpAlphaChannel = cpAlphaChannel; this.cpOutputFormat = cpOutputFormat; this.cpDialogDisplay = cpDialogDisplay; this.cpIgnoredElements = cpIgnoredElements; this.cpSaveClickOutside = cpSaveClickOutside; this.cpCloseClickOutside = cpCloseClickOutside; this.useRootViewContainer = cpUseRootViewContainer; this.width = this.cpWidth = parseInt(cpWidth, 10); this.height = this.cpHeight = parseInt(cpHeight, 10); this.cpPosition = cpPosition; this.cpPositionOffset = parseInt(cpPositionOffset, 10); this.cpOKButton = cpOKButton; this.cpOKButtonText = cpOKButtonText; this.cpOKButtonClass = cpOKButtonClass; this.cpCancelButton = cpCancelButton; this.cpCancelButtonText = cpCancelButtonText; this.cpCancelButtonClass = cpCancelButtonClass; this.cpEyeDropper = cpEyeDropper; this.fallbackColor = cpFallbackColor || '#fff'; this.setPresetConfig(cpPresetLabel, cpPresetColors); this.cpPresetColorsClass = cpPresetColorsClass; this.cpMaxPresetColorsLength = cpMaxPresetColorsLength; this.cpPresetEmptyMessage = cpPresetEmptyMessage; this.cpPresetEmptyMessageClass = cpPresetEmptyMessageClass; this.cpAddColorButton = cpAddColorButton; this.cpAddColorButtonText = cpAddColorButtonText; this.cpAddColorButtonClass = cpAddColorButtonClass; this.cpRemoveColorButtonClass = cpRemoveColorButtonClass; this.cpTriggerElement = cpTriggerElement; this.cpExtraTemplate = cpExtraTemplate; if (!cpPositionRelativeToArrow) { this.dialogArrowOffset = 0; } if (cpDialogDisplay === 'inline') { this.dialogArrowSize = 0; this.dialogArrowOffset = 0; } if (cpOutputFormat === 'hex' && cpAlphaChannel !== 'always' && cpAlphaChannel !== 'forced') { this.cpAlphaChannel = 'disabled'; } } setColorMode(mode) { switch (mode.toString().toUpperCase()) { case '1': case 'C': case 'COLOR': this.cpColorMode = 1; break; case '2': case 'G': case 'GRAYSCALE': this.cpColorMode = 2; break; case '3': case 'P': case 'PRESETS': this.cpColorMode = 3; break; default: this.cpColorMode = 1; } } setInitialColor(color) { this.initialColor = color; } setPresetConfig(cpPresetLabel, cpPresetColors) { this.cpPresetLabel = cpPresetLabel; this.cpPresetColors = cpPresetColors; } setColorFromString(value, emit = true, update = true) { let hsva; if (this.cpAlphaChannel === 'always' || this.cpAlphaChannel === 'forced') { hsva = this.service.stringToHsva(value, true); if (!hsva && !this.hsva) { hsva = this.service.stringToHsva(value, false); } } else { hsva = this.service.stringToHsva(value, false); } if (!hsva && !this.hsva) { hsva = this.service.stringToHsva(this.fallbackColor, false); } if (hsva) { this.hsva = hsva; this.sliderH = this.hsva.h; if (this.cpOutputFormat === 'hex' && this.cpAlphaChannel === 'disabled') { this.hsva.a = 1; } this.updateColorPicker(emit, update); } } onResize() { if (this.position === 'fixed') { this.setDialogPosition(); } else if (this.cpDialogDisplay !== 'inline') { this.closeColorPicker(); } } onDragEnd(slider) { this.directiveInstance.sliderDragEnd({ slider: slider, color: this.outputColor }); } onDragStart(slider) { this.directiveInstance.sliderDragStart({ slider: slider, color: this.outputColor }); } onMouseDown(event) { if (this.show && !this.isIE10 && this.cpDialogDisplay === 'popup' && event.target !== this.directiveElementRef.nativeElement && !this.isDescendant(this.elRef.nativeElement, event.target) && !this.isDescendant(this.directiveElementRef.nativeElement, event.target) && this.cpIgnoredElements.filter((item) => item === event.target).length === 0) { this.ngZone.run(() => { if (this.cpSaveClickOutside) { this.directiveInstance.colorSelected(this.outputColor); } else { this.hsva = null; this.setColorFromString(this.initialColor, false); if (this.cpCmykEnabled) { this.directiveInstance.cmykChanged(this.cmykColor); } this.directiveInstance.colorChanged(this.initialColor); this.directiveInstance.colorCanceled(); } if (this.cpCloseClickOutside) { this.closeColorPicker(); } }); } } onAcceptColor(event) { event.stopPropagation(); if (this.outputColor) { this.directiveInstance.colorSelected(this.outputColor); } if (this.cpDialogDisplay === 'popup') { this.closeColorPicker(); } } onCancelColor(event) { this.hsva = null; event.stopPropagation(); this.directiveInstance.colorCanceled(); this.setColorFromString(this.initialColor, true); if (this.cpDialogDisplay === 'popup') { if (this.cpCmykEnabled) { this.directiveInstance.cmykChanged(this.cmykColor); } this.directiveInstance.colorChanged(this.initialColor, true); this.closeColorPicker(); } } onEyeDropper() { if (!this.eyeDropperSupported) return; const eyeDropper = new window.EyeDropper(); eyeDropper.open().then((eyeDropperResult) => { this.setColorFromString(eyeDropperResult.sRGBHex, true); }); } onFormatToggle(change) { const availableFormats = this.dialogInputFields.length - (this.cpCmykEnabled ? 0 : 1); const nextFormat = (((this.dialogInputFields.indexOf(this.format) + change) % availableFormats) + availableFormats) % availableFormats; this.format = this.dialogInputFields[nextFormat]; } onColorChange(value) { this.hsva.s = value.s / value.rgX; this.hsva.v = value.v / value.rgY; this.updateColorPicker(); this.directiveInstance.sliderChanged({ slider: 'lightness', value: this.hsva.v, color: this.outputColor }); this.directiveInstance.sliderChanged({ slider: 'saturation', value: this.hsva.s, color: this.outputColor }); } onHueChange(value) { this.hsva.h = value.v / value.rgX; this.sliderH = this.hsva.h; this.updateColorPicker(); this.directiveInstance.sliderChanged({ slider: 'hue', value: this.hsva.h, color: this.outputColor }); } onValueChange(value) { this.hsva.v = value.v / value.rgX; this.updateColorPicker(); this.directiveInstance.sliderChanged({ slider: 'value', value: this.hsva.v, color: this.outputColor }); } onAlphaChange(value) { this.hsva.a = value.v / value.rgX; this.updateColorPicker(); this.directiveInstance.sliderChanged({ slider: 'alpha', value: this.hsva.a, color: this.outputColor }); } onHexInput(value) { if (value === null) { this.updateColorPicker(); } else { if (value && value[0] !== '#') { value = '#' + value; } let validHex = /^#([a-f0-9]{3}|[a-f0-9]{6})$/gi; if (this.cpAlphaChannel === 'always') { validHex = /^#([a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/gi; } const valid = validHex.test(value); if (valid) { if (value.length < 5) { value = '#' + value.substring(1) .split('') .map(c => c + c) .join(''); } if (this.cpAlphaChannel === 'forced') { value += Math.round(this.hsva.a * 255).toString(16); } this.setColorFromString(value, true, false); } this.directiveInstance.inputChanged({ input: 'hex', valid: valid, value: value, color: this.outputColor }); } } onRedInput(value) { const rgba = this.service.hsvaToRgba(this.hsva); const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { rgba.r = value.v / value.rg; this.hsva = this.service.rgbaToHsva(rgba); this.sliderH = this.hsva.h; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'red', valid: valid, value: rgba.r, color: this.outputColor }); } onBlueInput(value) { const rgba = this.service.hsvaToRgba(this.hsva); const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { rgba.b = value.v / value.rg; this.hsva = this.service.rgbaToHsva(rgba); this.sliderH = this.hsva.h; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'blue', valid: valid, value: rgba.b, color: this.outputColor }); } onGreenInput(value) { const rgba = this.service.hsvaToRgba(this.hsva); const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { rgba.g = value.v / value.rg; this.hsva = this.service.rgbaToHsva(rgba); this.sliderH = this.hsva.h; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'green', valid: valid, value: rgba.g, color: this.outputColor }); } onHueInput(value) { const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { this.hsva.h = value.v / value.rg; this.sliderH = this.hsva.h; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'hue', valid: valid, value: this.hsva.h, color: this.outputColor }); } onValueInput(value) { const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { this.hsva.v = value.v / value.rg; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'value', valid: valid, value: this.hsva.v, color: this.outputColor }); } onAlphaInput(value) { const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { this.hsva.a = value.v / value.rg; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'alpha', valid: valid, value: this.hsva.a, color: this.outputColor }); } onLightnessInput(value) { const hsla = this.service.hsva2hsla(this.hsva); const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { hsla.l = value.v / value.rg; this.hsva = this.service.hsla2hsva(hsla); this.sliderH = this.hsva.h; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'lightness', valid: valid, value: hsla.l, color: this.outputColor }); } onSaturationInput(value) { const hsla = this.service.hsva2hsla(this.hsva); const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { hsla.s = value.v / value.rg; this.hsva = this.service.hsla2hsva(hsla); this.sliderH = this.hsva.h; this.updateColorPicker(); } this.directiveInstance.inputChanged({ input: 'saturation', valid: valid, value: hsla.s, color: this.outputColor }); } onCyanInput(value) { const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { this.cmyk.c = value.v; this.updateColorPicker(false, true, true); } this.directiveInstance.inputChanged({ input: 'cyan', valid: true, value: this.cmyk.c, color: this.outputColor }); } onMagentaInput(value) { const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { this.cmyk.m = value.v; this.updateColorPicker(false, true, true); } this.directiveInstance.inputChanged({ input: 'magenta', valid: true, value: this.cmyk.m, color: this.outputColor }); } onYellowInput(value) { const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { this.cmyk.y = value.v; this.updateColorPicker(false, true, true); } this.directiveInstance.inputChanged({ input: 'yellow', valid: true, value: this.cmyk.y, color: this.outputColor }); } onBlackInput(value) { const valid = !isNaN(value.v) && value.v >= 0 && value.v <= value.rg; if (valid) { this.cmyk.k = value.v; this.updateColorPicker(false, true, true); } this.directiveInstance.inputChanged({ input: 'black', valid: true, value: this.cmyk.k, color: this.outputColor }); } onAddPresetColor(event, value) { event.stopPropagation(); if (!this.cpPresetColors.filter((color) => (color === value)).length) { this.cpPresetColors = this.cpPresetColors.concat(value); this.directiveInstance.presetColorsChanged(this.cpPresetColors); } } onRemovePresetColor(event, value) { event.stopPropagation(); this.cpPresetColors = this.cpPresetColors.filter((color) => (color !== value)); this.directiveInstance.presetColorsChanged(this.cpPresetColors); } // Private helper functions for the color picker dialog status openColorPicker() { if (!this.show) { this.show = true; this.hidden = true; setTimeout(() => { this.hidden = false; this.setDialogPosition(); this.cdRef.detectChanges(); }, 0); this.directiveInstance.stateChanged(true); if (!this.isIE10) { // The change detection should be run on `mousedown` event only when the condition // is met within the `onMouseDown` method. this.ngZone.runOutsideAngular(() => { // There's no sense to add both event listeners on touch devices since the `touchstart` // event is handled earlier than `mousedown`, so we'll get 2 change detections and the // second one will be unnecessary. if (SUPPORTS_TOUCH) { document.addEventListener('touchstart', this.listenerMouseDown); } else { document.addEventListener('mousedown', this.listenerMouseDown); } }); } window.addEventListener('resize', this.listenerResize); } } closeColorPicker() { if (this.show) { this.show = false; this.directiveInstance.stateChanged(false); if (!this.isIE10) { if (SUPPORTS_TOUCH) { document.removeEventListener('touchstart', this.listenerMouseDown); } else { document.removeEventListener('mousedown', this.listenerMouseDown); } } window.removeEventListener('resize', this.listenerResize); if (!this.cdRef['destroyed']) { this.cdRef.detectChanges(); } } } updateColorPicker(emit = true, update = true, cmykInput = false) { if (this.sliderDimMax) { if (this.cpColorMode === 2) { this.hsva.s = 0; } let hue, hsla, rgba; const lastOutput = this.outputColor; hsla = this.service.hsva2hsla(this.hsva); if (!this.cpCmykEnabled) { rgba = this.service.denormalizeRGBA(this.service.hsvaToRgba(this.hsva)); } else { if (!cmykInput) { rgba = this.service.hsvaToRgba(this.hsva); this.cmyk = this.service.denormalizeCMYK(this.service.rgbaToCmyk(rgba)); } else { rgba = this.service.cmykToRgb(this.service.normalizeCMYK(this.cmyk)); this.hsva = this.service.rgbaToHsva(rgba); } rgba = this.service.denormalizeRGBA(rgba); this.sliderH = this.hsva.h; } hue = this.service.denormalizeRGBA(this.service.hsvaToRgba(new Hsva(this.sliderH || this.hsva.h, 1, 1, 1))); if (update) { this.hslaText = new Hsla(Math.round((hsla.h) * 360), Math.round(hsla.s * 100), Math.round(hsla.l * 100), Math.round(hsla.a * 100) / 100); this.rgbaText = new Rgba(rgba.r, rgba.g, rgba.b, Math.round(rgba.a * 100) / 100); if (this.cpCmykEnabled) { this.cmykText = new Cmyk(this.cmyk.c, this.cmyk.m, this.cmyk.y, this.cmyk.k, Math.round(this.cmyk.a * 100) / 100); } const allowHex8 = this.cpAlphaChannel === 'always'; this.hexText = this.service.rgbaToHex(rgba, allowHex8); this.hexAlpha = this.rgbaText.a; } if (this.cpOutputFormat === 'auto') { if (this.format !== ColorFormats.RGBA && this.format !== ColorFormats.CMYK && this.format !== ColorFormats.HSLA) { if (this.hsva.a < 1) { this.format = this.hsva.a < 1 ? ColorFormats.RGBA : ColorFormats.HEX; } } } this.hueSliderColor = 'rgb(' + hue.r + ',' + hue.g + ',' + hue.b + ')'; this.alphaSliderColor = 'rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')'; this.outputColor = this.service.outputFormat(this.hsva, this.cpOutputFormat, this.cpAlphaChannel); this.selectedColor = this.service.outputFormat(this.hsva, 'rgba', null); if (this.format !== ColorFormats.CMYK) { this.cmykColor = ''; } else { if (this.cpAlphaChannel === 'always' || this.cpAlphaChannel === 'enabled' || this.cpAlphaChannel === 'forced') { const alpha = Math.round(this.cmyk.a * 100) / 100; this.cmykColor = `cmyka(${this.cmyk.c},${this.cmyk.m},${this.cmyk.y},${this.cmyk.k},${alpha})`; } else { this.cmykColor = `cmyk(${this.cmyk.c},${this.cmyk.m},${this.cmyk.y},${this.cmyk.k})`; } } this.slider = new SliderPosition((this.sliderH || this.hsva.h) * this.sliderDimMax.h - 8, this.hsva.s * this.sliderDimMax.s - 8, (1 - this.hsva.v) * this.sliderDimMax.v - 8, this.hsva.a * this.sliderDimMax.a - 8); if (emit && lastOutput !== this.outputColor) { if (this.cpCmykEnabled) { this.directiveInstance.cmykChanged(this.cmykColor); } this.directiveInstance.colorChanged(this.outputColor); } } } // Private helper functions for the color picker dialog positioning setDialogPosition() { if (this.cpDialogDisplay === 'inline') { this.position = 'relative'; } else { let position = 'static', transform = '', style; let parentNode = null, transformNode = null; let node = this.directiveElementRef.nativeElement.parentNode; const dialogHeight = this.dialogElement.nativeElement.offsetHeight; while (node !== null && node.tagName !== 'HTML') { style = window.getComputedStyle(node); position = style.getPropertyValue('position'); transform = style.getPropertyValue('transform'); if (position !== 'static' && parentNode === null) { parentNode = node; } if (transform && transform !== 'none' && transformNode === null) { transformNode = node; } if (position === 'fixed') { parentNode = transformNode; break; } node = node.parentNode; } const boxDirective = this.createDialogBox(this.directiveElementRef.nativeElement, (position !== 'fixed')); if (this.useRootViewContainer || (position === 'fixed' && (!parentNode || parentNode instanceof HTMLUnknownElement))) { this.top = boxDirective.top; this.left = boxDirective.left; } else { if (parentNode === null) { parentNode = node; } const boxParent = this.createDialogBox(parentNode, (position !== 'fixed')); this.top = boxDirective.top - boxParent.top; this.left = boxDirective.left - boxParent.left; } if (position === 'fixed') { this.position = 'fixed'; } let usePosition = this.cpPosition; const dialogBounds = this.dialogElement.nativeElement.getBoundingClientRect(); if (this.cpPosition === 'auto') { const triggerBounds = this.cpTriggerElement.nativeElement.getBoundingClientRect(); usePosition = calculateAutoPositioning(dialogBounds, triggerBounds); } this.arrowTop = usePosition === 'top' ? dialogHeight - 1 : undefined; this.cpArrowPosition = undefined; switch (usePosition) { case 'top': this.top -= dialogHeight + this.dialogArrowSize; this.left += this.cpPositionOffset / 100 * boxDirective.width - this.dialogArrowOffset; break; case 'bottom': this.top += boxDirective.height + this.dialogArrowSize; this.left += this.cpPositionOffset / 100 * boxDirective.width - this.dialogArrowOffset; break; case 'top-left': case 'left-top': this.top -= dialogHeight - boxDirective.height + boxDirective.height * this.cpPositionOffset / 100; this.left -= this.cpWidth + this.dialogArrowSize - 2 - this.dialogArrowOffset; break; case 'top-right': case 'right-top': this.top -= dialogHeight - boxDirective.height + boxDirective.height * this.cpPositionOffset / 100; this.left += boxDirective.width + this.dialogArrowSize - 2 - this.dialogArrowOffset; break; case 'left': case 'bottom-left': case 'left-bottom': this.top += boxDirective.height * this.cpPositionOffset / 100 - this.dialogArrowOffset; this.left -= this.cpWidth + this.dialogArrowSize - 2; break; case 'right': case 'bottom-right': case 'right-bottom': default: this.top += boxDirective.height * this.cpPositionOffset / 100 - this.dialogArrowOffset; this.left += boxDirective.width + this.dialogArrowSize - 2; break; } const windowInnerHeight = window.innerHeight; const windowInnerWidth = window.innerWidth; const elRefClientRect = this.elRef.nativeElement.getBoundingClientRect(); const bottom = this.top + dialogBounds.height; if (bottom > windowInnerHeight) { this.top = windowInnerHeight - dialogBounds.height; this.cpArrowPosition = elRefClientRect.x / 2 - 20; } const right = this.left + dialogBounds.width; if (right > windowInnerWidth) { this.left = windowInnerWidth - dialogBounds.width; this.cpArrowPosition = elRefClientRect.x / 2 - 20; } this.cpUsePosition = usePosition; } } // Private helper functions for the color picker dialog positioning and opening isDescendant(parent, child) { let node = child.parentNode; while (node !== null) { if (node === parent) { return true; } node = node.parentNode; } return false; } createDialogBox(element, offset) { const { top, left } = element.getBoundingClientRect(); return { top: top + (offset ? window.pageYOffset : 0), left: left + (offset ? window.pageXOff