ngx-color-picker
Version:
Color picker widget for Angular
1,326 lines (1,322 loc) • 141 kB
JavaScript
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