UNPKG

@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
/**----------------------------------------------------------------------------------------- * 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'] }] } });