@progress/kendo-angular-inputs
Version:
Kendo UI for Angular Inputs Package - Everything you need to build professional form functionality (Checkbox, ColorGradient, ColorPalette, ColorPicker, FlatColorPicker, FormField, MaskedTextBox, NumericTextBox, RadioButton, RangeSlider, Slider, Switch, Te
621 lines (620 loc) • 23.1 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Component, Input, EventEmitter, Output, HostBinding, forwardRef, ChangeDetectorRef, Renderer2, ElementRef, NgZone } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { Keys, KendoInput, guid } from '@progress/kendo-angular-common';
import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
import { validatePackage } from '@progress/kendo-licensing';
import { Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { ColorPaletteLocalizationService } from './localization/colorpalette-localization.service';
import { packageMetadata } from '../package-metadata';
import { PALETTEPRESETS } from './models';
import { parseColor } from './utils';
import { getStylingClasses, isPresent } from '../common/utils';
import { ColorPaletteService } from './services/color-palette.service';
import { NgFor, NgStyle } from '@angular/common';
import { LocalizedColorPickerMessagesDirective } from './localization/localized-colorpicker-messages.directive';
import * as i0 from "@angular/core";
import * as i1 from "./services/color-palette.service";
import * as i2 from "@progress/kendo-angular-l10n";
const DEFAULT_COLUMNS_COUNT = 10;
const DEFAULT_PRESET = 'office';
const DEFAULT_ACCESSIBLE_PRESET = 'accessible';
const DEFAULT_SIZE = 'medium';
let serial = 0;
/**
* The ColorPalette component provides a set of predefined palette presets and enables you to implement a custom color palette.
* The ColorPalette is independently used by `kendo-colorpicker` and can be directly added to the page.
*/
export class ColorPaletteComponent {
host;
service;
cdr;
renderer;
localizationService;
ngZone;
/**
* @hidden
*/
direction;
/**
* @hidden
*/
role = 'grid';
/**
* @hidden
*/
get activeDescendant() {
return this.activeCellId;
}
/**
* @hidden
*/
get paletteId() {
return this.id;
}
/**
* @hidden
*/
id = `k-colorpalette-${serial++}`;
/**
* Specifies the output format of the ColorPaletteComponent.
* The input value may be in a different format. However, it will be parsed into the output `format`
* after the component processes it.
*
* The supported values are:
* * (Default) `hex`
* * `rgba`
* * `name`
*/
format = 'hex';
/**
* Specifies the value of the initially selected color.
*/
set value(value) {
this._value = parseColor(value, this.format);
}
get value() {
return this._value;
}
/**
* Specifies the number of columns that will be displayed.
* Defaults to `10`.
*/
set columns(value) {
const minColumnsCount = 1;
this._columns = value > minColumnsCount ? value : minColumnsCount;
}
get columns() {
return this._columns;
}
/**
* The color palette that will be displayed.
*
* The supported values are:
* * The name of the predefined palette preset (for example, `office`, `basic`, and `apex`).
* * A string with comma-separated colors.
* * A string array.
*/
set palette(value) {
if (!isPresent(value)) {
value = DEFAULT_PRESET;
}
if (typeof value === 'string' && isPresent(PALETTEPRESETS[value])) {
this.columns = this.columns || PALETTEPRESETS[value].columns;
value = PALETTEPRESETS[value].colors;
}
const colors = (typeof value === 'string') ? value.split(',') : value;
this._palette = colors.map(color => parseColor(color, this.format, false, false));
}
get palette() {
return this._palette;
}
/**
* The size property specifies the padding of the ColorPalette internal elements.
*
* The possible values are:
* * `small`
* * `medium` (default)
* * `large`
* * `none`
*/
set size(size) {
const newSize = size || DEFAULT_SIZE;
this.handleClasses(newSize, 'size');
this._size = newSize;
}
get size() {
return this._size;
}
/**
* Specifies the [tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) of the component.
*/
set tabindex(value) {
const tabindex = Number(value);
const defaultValue = 0;
this._tabindex = !isNaN(tabindex) ? tabindex : defaultValue;
}
get tabindex() {
return !this.disabled ? this._tabindex : undefined;
}
/**
* Sets the disabled state of the ColorPalette. To learn how to disable the component in reactive forms, refer to the article on [Forms Support](slug:formssupport_colorpalette#toc-managing-the-colorpalette-disabled-state-in-reactive-forms).
*/
disabled = false;
/**
* Sets the read-only state of the ColorPalette.
*
* @default false
*/
readonly = false;
/**
* Specifies the size of a color cell. The default tile size depends on the `size` of the component.
*/
tileSize;
/**
* @hidden
*/
get tileLayout() {
if (typeof this.tileSize !== 'number') {
return this.tileSize;
}
return { width: this.tileSize, height: this.tileSize };
}
/**
* Fires each time the color selection is changed.
*/
selectionChange = new EventEmitter();
/**
* Fires each time the value is changed.
*/
valueChange = new EventEmitter();
/**
* Fires each time the user selects a cell with the mouse or presses `Enter`.
*
* @hidden
*/
cellSelection = new EventEmitter();
/**
* @hidden
*/
get colorRows() {
return this.service.colorRows;
}
/**
* @hidden
*/
get hostTabindex() { return this.tabindex; }
/**
* @hidden
*/
hostClasses = true;
/**
* @hidden
*/
get disabledClass() { return this.disabled; }
/**
* @hidden
*/
get readonlyAttribute() { return this.readonly; }
/**
* @hidden
*/
activeCellId;
/**
* @hidden
*/
focusedCell;
/**
* @hidden
*/
selectedCell;
/**
* @hidden
*/
focusInComponent;
/**
* @hidden
*/
uniqueId = guid();
selection;
_size = 'medium';
_value;
_columns;
_palette;
_tabindex = 0;
subs = new Subscription();
dynamicRTLSubscription;
constructor(host, service, cdr, renderer, localizationService, ngZone) {
this.host = host;
this.service = service;
this.cdr = cdr;
this.renderer = renderer;
this.localizationService = localizationService;
this.ngZone = ngZone;
validatePackage(packageMetadata);
this.dynamicRTLSubscription = localizationService.changes.subscribe(({ rtl }) => {
this.direction = rtl ? 'rtl' : 'ltr';
});
}
ngOnInit() {
if (this.colorRows.length === 0) {
const defaultPreset = (this.format !== 'name') ? DEFAULT_PRESET : DEFAULT_ACCESSIBLE_PRESET;
this.palette = this.palette || defaultPreset;
this.setRows();
}
const elem = this.host.nativeElement;
this.subs.add(this.renderer.listen(elem, 'keydown', event => this.handleKeydown(event)));
this.subs.add(this.renderer.listen(elem, 'focus', () => this.handleFocus()));
this.subs.add(this.renderer.listen(elem, 'blur', () => this.handleHostBlur()));
}
ngAfterViewInit() {
const stylingInputs = ['size'];
stylingInputs.forEach(input => {
this.handleClasses(this[input], input);
});
this.setHostElementAriaLabel();
if (this.value) {
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
this.selectCell(this.value);
});
}
}
ngOnDestroy() {
this.subs.unsubscribe();
if (this.dynamicRTLSubscription) {
this.dynamicRTLSubscription.unsubscribe();
}
}
ngOnChanges(changes) {
if (changes['palette'] || changes['columns']) {
this.setRows();
}
if (changes['palette'] || changes['value'] || changes['columns']) {
this.selectCell(this.value);
this.setHostElementAriaLabel();
}
}
/**
* @hidden
*/
handleKeydown(event) {
const isRTL = this.direction === 'rtl';
switch (event.keyCode) {
case Keys.ArrowDown:
this.handleCellNavigation(0, 1);
break;
case Keys.ArrowUp:
this.handleCellNavigation(0, -1);
break;
case Keys.ArrowRight:
this.handleCellNavigation(isRTL ? -1 : 1, 0);
break;
case Keys.ArrowLeft:
this.handleCellNavigation(isRTL ? 1 : -1, 0);
break;
case Keys.Enter:
this.handleEnter();
break;
default: return;
}
event.preventDefault();
}
/**
* @hidden
*/
handleFocus() {
if (!this.focusInComponent) {
this.focus();
}
}
/**
* @hidden
*/
handleHostBlur() {
this.notifyNgTouched();
this.handleCellFocusOnBlur();
}
/**
* @hidden
*/
handleCellSelection(value, focusedCell) {
if (this.readonly) {
return;
}
this.selectedCell = focusedCell;
this.focusedCell = this.selectedCell;
this.focusInComponent = true;
const parsedColor = parseColor(value, this.format, false, false);
this.cellSelection.emit(parsedColor);
this.handleValueChange(parsedColor);
if (this.selection !== parsedColor) {
this.selection = parsedColor;
this.selectionChange.emit(parsedColor);
}
if (focusedCell) {
this.activeCellId = `k-${this.selectedCell.row}-${this.selectedCell.col}-${this.uniqueId}`;
}
}
/**
* @hidden
*/
writeValue(value) {
this.value = value;
this.selectCell(value);
}
/**
* @hidden
*/
registerOnChange(fn) {
this.notifyNgChanged = fn;
}
/**
* @hidden
*/
registerOnTouched(fn) {
this.notifyNgTouched = fn;
}
/**
* @hidden
*/
setDisabledState(isDisabled) {
this.cdr.markForCheck();
this.disabled = isDisabled;
}
/**
* @hidden
*/
focus() {
this.host.nativeElement.focus();
if (!this.focusedCell && !this.readonly && !this.disabled) {
this.focusedCell = {
row: 0,
col: 0
};
this.activeCellId = `k-${this.focusedCell.row}-${this.focusedCell.col}-${this.uniqueId}`;
}
}
/**
* @hidden
* Used by the FloatingLabel to determine if the component is empty.
*/
isEmpty() {
return false;
}
/**
* Clears the color value of the ColorPalette.
*/
reset() {
this.focusedCell = null;
if (isPresent(this.value)) {
this.handleValueChange(undefined);
}
this.selectedCell = undefined;
}
handleValueChange(color) {
if (this.value === color) {
return;
}
this.value = color;
this.valueChange.emit(color);
this.notifyNgChanged(color);
this.setHostElementAriaLabel();
}
handleCellFocusOnBlur() {
this.focusInComponent = false;
this.focusedCell = this.selectedCell;
}
selectCell(value) {
const parsedColor = parseColor(value, 'hex');
this.selectedCell = this.service.getCellCoordsFor(parsedColor);
this.focusedCell = this.selectedCell;
}
setRows() {
if (!isPresent(this.palette)) {
return;
}
this.columns = this.columns || DEFAULT_COLUMNS_COUNT;
this.service.setColorMatrix(this.palette, this.columns);
}
handleCellNavigation(horizontalStep, verticalStep) {
if (this.readonly) {
return;
}
this.focusedCell = this.service.getNextCell(this.focusedCell, horizontalStep, verticalStep);
this.focusInComponent = true;
if (this.focusedCell) {
this.activeCellId = `k-${this.focusedCell.row}-${this.focusedCell.col}-${this.uniqueId}`;
}
}
setHostElementAriaLabel() {
const parsed = parseColor(this.value, this.format);
this.renderer.setAttribute(this.host.nativeElement, 'aria-label', `${this.value ? parsed : this.localizationService.get('colorPaletteNoColor')}`);
}
handleEnter() {
if (!isPresent(this.focusedCell)) {
return;
}
const selectedColor = this.service.getColorAt(this.focusedCell);
this.handleCellSelection(selectedColor, this.focusedCell);
}
handleClasses(value, input) {
const elem = this.host.nativeElement;
const classes = getStylingClasses('colorpalette', input, this[input], value);
if (classes.toRemove) {
this.renderer.removeClass(elem, classes.toRemove);
}
if (classes.toAdd) {
this.renderer.addClass(elem, classes.toAdd);
}
}
notifyNgTouched = () => { };
notifyNgChanged = () => { };
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ColorPaletteComponent, deps: [{ token: i0.ElementRef }, { token: i1.ColorPaletteService }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }, { token: i2.LocalizationService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ColorPaletteComponent, isStandalone: true, selector: "kendo-colorpalette", inputs: { id: "id", format: "format", value: "value", columns: "columns", palette: "palette", size: "size", tabindex: "tabindex", disabled: "disabled", readonly: "readonly", tileSize: "tileSize" }, outputs: { selectionChange: "selectionChange", valueChange: "valueChange", cellSelection: "cellSelection" }, host: { properties: { "attr.dir": "this.direction", "attr.role": "this.role", "attr.aria-activedescendant": "this.activeDescendant", "attr.id": "this.paletteId", "class.k-readonly": "this.readonly", "attr.tabindex": "this.hostTabindex", "class.k-colorpalette": "this.hostClasses", "attr.aria-disabled": "this.disabledClass", "class.k-disabled": "this.disabledClass", "attr.aria-readonly": "this.readonlyAttribute" } }, providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ColorPaletteComponent)
}, {
provide: KendoInput,
useExisting: forwardRef(() => ColorPaletteComponent)
},
ColorPaletteService,
ColorPaletteLocalizationService,
{
provide: LocalizationService,
useExisting: ColorPaletteLocalizationService
},
{
provide: L10N_PREFIX,
useValue: 'kendo.colorpalette'
}
], exportAs: ["kendoColorPalette"], usesOnChanges: true, ngImport: i0, template: `
<ng-container kendoColorPaletteLocalizedMessages
i18n-colorPaletteNoColor="kendo.colorpalette.colorPaletteNoColor|The aria-label applied to the ColorPalette component when the value is empty."
colorPaletteNoColor="Colorpalette no color chosen">
</ng-container>
<table role="presentation" class="k-colorpalette-table">
<tbody>
<tr *ngFor="let row of colorRows; let rowIndex = index" role="row">
<td *ngFor="let color of row; let colIndex = index"
role="gridcell"
[class.k-selected]="selectedCell?.row === rowIndex && selectedCell?.col === colIndex"
[class.k-focus]="focusInComponent && focusedCell?.row === rowIndex && focusedCell?.col === colIndex"
[attr.aria-selected]="selectedCell?.row === rowIndex && selectedCell?.col === colIndex ? 'true' : undefined"
[attr.aria-label]="color"
class="k-colorpalette-tile"
[id]="'k-' + rowIndex + '-' + colIndex + '-' + uniqueId"
[attr.value]="color"
(click)="handleCellSelection(color, { row: rowIndex, col: colIndex })"
[ngStyle]="{
backgroundColor: color,
width: tileLayout?.width + 'px',
height: tileLayout?.height + 'px',
minWidth: tileLayout?.width + 'px'
}">
</td>
</tr>
</tbody>
</table>
`, isInline: true, dependencies: [{ kind: "directive", type: LocalizedColorPickerMessagesDirective, selector: "[kendoColorPickerLocalizedMessages], [kendoFlatColorPickerLocalizedMessages], [kendoColorGradientLocalizedMessages], [kendoColorPaletteLocalizedMessages]" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ColorPaletteComponent, decorators: [{
type: Component,
args: [{
exportAs: 'kendoColorPalette',
selector: 'kendo-colorpalette',
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ColorPaletteComponent)
}, {
provide: KendoInput,
useExisting: forwardRef(() => ColorPaletteComponent)
},
ColorPaletteService,
ColorPaletteLocalizationService,
{
provide: LocalizationService,
useExisting: ColorPaletteLocalizationService
},
{
provide: L10N_PREFIX,
useValue: 'kendo.colorpalette'
}
],
template: `
<ng-container kendoColorPaletteLocalizedMessages
i18n-colorPaletteNoColor="kendo.colorpalette.colorPaletteNoColor|The aria-label applied to the ColorPalette component when the value is empty."
colorPaletteNoColor="Colorpalette no color chosen">
</ng-container>
<table role="presentation" class="k-colorpalette-table">
<tbody>
<tr *ngFor="let row of colorRows; let rowIndex = index" role="row">
<td *ngFor="let color of row; let colIndex = index"
role="gridcell"
[class.k-selected]="selectedCell?.row === rowIndex && selectedCell?.col === colIndex"
[class.k-focus]="focusInComponent && focusedCell?.row === rowIndex && focusedCell?.col === colIndex"
[attr.aria-selected]="selectedCell?.row === rowIndex && selectedCell?.col === colIndex ? 'true' : undefined"
[attr.aria-label]="color"
class="k-colorpalette-tile"
[id]="'k-' + rowIndex + '-' + colIndex + '-' + uniqueId"
[attr.value]="color"
(click)="handleCellSelection(color, { row: rowIndex, col: colIndex })"
[ngStyle]="{
backgroundColor: color,
width: tileLayout?.width + 'px',
height: tileLayout?.height + 'px',
minWidth: tileLayout?.width + 'px'
}">
</td>
</tr>
</tbody>
</table>
`,
standalone: true,
imports: [LocalizedColorPickerMessagesDirective, NgFor, NgStyle]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.ColorPaletteService }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }, { type: i2.LocalizationService }, { type: i0.NgZone }]; }, propDecorators: { direction: [{
type: HostBinding,
args: ['attr.dir']
}], role: [{
type: HostBinding,
args: ['attr.role']
}], activeDescendant: [{
type: HostBinding,
args: ['attr.aria-activedescendant']
}], paletteId: [{
type: HostBinding,
args: ['attr.id']
}], id: [{
type: Input
}], format: [{
type: Input
}], value: [{
type: Input
}], columns: [{
type: Input
}], palette: [{
type: Input
}], size: [{
type: Input
}], tabindex: [{
type: Input
}], disabled: [{
type: Input
}], readonly: [{
type: Input
}, {
type: HostBinding,
args: ['class.k-readonly']
}], tileSize: [{
type: Input
}], selectionChange: [{
type: Output
}], valueChange: [{
type: Output
}], cellSelection: [{
type: Output
}], hostTabindex: [{
type: HostBinding,
args: ['attr.tabindex']
}], hostClasses: [{
type: HostBinding,
args: ['class.k-colorpalette']
}], disabledClass: [{
type: HostBinding,
args: ['attr.aria-disabled']
}, {
type: HostBinding,
args: ['class.k-disabled']
}], readonlyAttribute: [{
type: HostBinding,
args: ['attr.aria-readonly']
}] } });