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