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

586 lines (583 loc) 20.8 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { ElementRef, Renderer2, Component, EventEmitter, HostBinding, Input, Output, ViewChild, forwardRef, ChangeDetectorRef, NgZone, Injector } from '@angular/core'; import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n'; import { hasObservers, guid, Keys, KendoInput } from '@progress/kendo-angular-common'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from '../package-metadata'; import { requiresZoneOnBlur, getStylingClasses } from '../common/utils'; import { skip, take } from "rxjs/operators"; import { LocalizedSwitchMessagesDirective } from './localization/localized-switch-messages.directive'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; const FOCUSED = 'k-focus'; const DEFAULT_SIZE = 'medium'; const DEFAULT_THUMB_ROUNDED = 'full'; const DEFAULT_TRACK_ROUNDED = 'full'; /** * Represents the [Kendo UI Switch component for Angular]({% slug overview_switch %}). * * @example * ```html * <kendo-switch [(ngModel)]="checked"></kendo-switch>` * ``` * * @remarks * Supported children components are: {@link SwitchCustomMessagesComponent}. */ export class SwitchComponent { renderer; hostElement; localizationService; injector; changeDetector; ngZone; /** * @hidden */ get focusableId() { if (this.hostElement.nativeElement.hasAttribute('id')) { return this.hostElement.nativeElement.getAttribute('id'); } return `k-${guid()}`; } /** * Set the **On** label. * This label takes precedence over the [custom messages component]({% slug api_inputs_switchcustommessagescomponent %}). * [See example]({% slug labels_switch %}). */ onLabel; /** * Set the **Off** label. * This label takes precedence over the [custom messages component]({% slug api_inputs_switchcustommessagescomponent %}). * [See example]({% slug labels_switch %}). */ offLabel; /** * Sets the value of the Switch when it first appears. */ set checked(value) { this.setHostClasses(value); this._checked = value; } get checked() { return this._checked; } /** * When `true`, disables the Switch. * [See example]({% slug disabled_switch %}). * To disable the component in reactive forms, see [Forms Support](slug:formssupport_switch#toc-managing-the-switch-disabled-state-in-reactive-forms). * @default false */ disabled = false; /** * When `true`, sets the Switch to read-only. * [See example]({% slug readonly_switch %}). * @default false */ readonly = false; /** * Set the [`tabindex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) of the Switch. * @default 0 */ tabindex = 0; /** * Sets the size of the Switch. * * @default "medium" */ set size(size) { const newSize = size || DEFAULT_SIZE; this.handleClasses(newSize, 'size'); this._size = newSize; } get size() { return this._size; } /** * Sets the border radius of the Switch. * * @default "full" */ set thumbRounded(thumbRounded) { const newThumbRounded = thumbRounded || DEFAULT_THUMB_ROUNDED; this.handleThumbClasses(newThumbRounded); this._thumbRounded = newThumbRounded; } get thumbRounded() { return this._thumbRounded; } /** * Sets the border radius of the Switch track. * * @default "full" */ set trackRounded(trackRounded) { const newTrackRounded = trackRounded || DEFAULT_TRACK_ROUNDED; this.handleTrackClasses(newTrackRounded); this._trackRounded = newTrackRounded; } get trackRounded() { return this._trackRounded; } /** * @hidden */ set tabIndex(tabIndex) { this.tabindex = tabIndex; } get tabIndex() { return this.tabindex; } /** * Fires when the user focuses the Switch. */ onFocus = new EventEmitter(); /** * Fires when the user blurs the Switch. */ onBlur = new EventEmitter(); /** * Fires when the value of the Switch changes. */ valueChange = new EventEmitter(); direction; hostRole = 'switch'; get hostId() { return this.focusableId; } get ariaChecked() { return this.checked; } get ariaInvalid() { return this.isControlInvalid ? true : undefined; } get hostTabIndex() { return this.disabled ? undefined : this.tabIndex; } get ariaDisabled() { return this.disabled ? true : undefined; } get ariaReadonly() { return this.readonly; } hostClasses = true; get disabledClass() { return this.disabled; } track; thumb; /** * @hidden */ initialized = false; localizationChangeSubscription; isFocused; control; domSubscriptions = []; _checked = false; _size = 'medium'; _trackRounded = 'full'; _thumbRounded = 'full'; constructor(renderer, hostElement, localizationService, injector, changeDetector, ngZone) { this.renderer = renderer; this.hostElement = hostElement; this.localizationService = localizationService; this.injector = injector; this.changeDetector = changeDetector; this.ngZone = ngZone; validatePackage(packageMetadata); this.direction = localizationService.rtl ? 'rtl' : 'ltr'; this.keyDownHandler = this.keyDownHandler.bind(this); this.clickHandler = this.clickHandler.bind(this); } /** * @hidden */ get onLabelMessage() { return this.onLabel !== undefined ? this.onLabel : this.localizationService.get('on'); } /** * @hidden */ get offLabelMessage() { return this.offLabel !== undefined ? this.offLabel : this.localizationService.get('off'); } ngChange = (_) => { }; ngTouched = () => { }; get isEnabled() { return !this.disabled && !this.readonly; } ngOnInit() { if (this.hostElement) { const wrapper = this.hostElement.nativeElement; this.renderer.removeAttribute(wrapper, "tabindex"); } this.localizationChangeSubscription = this.localizationService .changes .pipe(skip(1)) .subscribe(({ rtl }) => { this.direction = rtl ? 'rtl' : 'ltr'; }); this.control = this.injector.get(NgControl, null); this.ngZone.onStable.pipe(take(1)).subscribe(() => this.initialized = true); } ngAfterViewInit() { const wrapper = this.hostElement.nativeElement; if (!this.checked && !wrapper.classList.contains('k-switch-off')) { this.renderer.addClass(wrapper, 'k-switch-off'); } this.handleClasses(this.size, 'size'); this.handleTrackClasses(this.trackRounded); this.handleThumbClasses(this.thumbRounded); this.attachHostHandlers(); } ngOnDestroy() { if (this.localizationChangeSubscription) { this.localizationChangeSubscription.unsubscribe(); } this.domSubscriptions.forEach(subscription => subscription()); const wrapper = this.hostElement.nativeElement; wrapper.removeEventListener('focus', this.handleFocus, true); wrapper.removeEventListener('blur', this.handleBlur, true); } /** * Focuses the Switch. * */ focus() { if (!this.hostElement) { return; } this.hostElement.nativeElement.focus(); } /** * Blurs the Switch. */ blur() { if (!this.hostElement) { return; } this.hostElement.nativeElement.blur(); } /** * @hidden * Called when the status of the component changes to or from `disabled`. * Depending on the value, it enables or disables the appropriate DOM element. */ setDisabledState(isDisabled) { this.disabled = isDisabled; this.changeDetector.markForCheck(); } /** * @hidden */ handleFocus = (event) => { if (this.isFocused) { return; } event.stopImmediatePropagation(); this.focused = true; if (hasObservers(this.onFocus)) { this.ngZone.run(() => { const eventArgs = { originalEvent: event }; this.onFocus.emit(eventArgs); }); } }; /** * @hidden */ handleBlur = (event) => { const relatedTarget = event && event.relatedTarget; if (this.hostElement.nativeElement.contains(relatedTarget)) { return; } event.stopImmediatePropagation(); this.changeDetector.markForCheck(); this.focused = false; if (hasObservers(this.onBlur) || requiresZoneOnBlur(this.control)) { this.ngZone.run(() => { this.ngTouched(); const eventArgs = { originalEvent: event }; this.onBlur.emit(eventArgs); }); } }; /** * @hidden */ get isControlInvalid() { return this.control && this.control.touched && !this.control.valid; } /** * @hidden */ writeValue(value) { this.checked = value === null ? false : value; this.changeDetector.markForCheck(); } /** * @hidden */ registerOnChange(fn) { this.ngChange = fn; } /** * @hidden */ registerOnTouched(fn) { this.ngTouched = fn; } /** * @hidden */ keyDownHandler(e) { const keyCode = e.code; if (this.isEnabled && (keyCode === Keys.Space || keyCode === Keys.Enter || keyCode === Keys.NumpadEnter)) { this.changeValue(!this.checked); e.preventDefault(); } } /** * @hidden */ clickHandler() { if (this.isEnabled) { this.changeValue(!this.checked); } } /** * @hidden * Used by the FloatingLabel to determine if the component is empty. */ isEmpty() { return false; } changeValue(value) { if (this.checked !== value) { this.ngZone.run(() => { this.checked = value; this.ngChange(value); this.valueChange.emit(value); this.changeDetector.markForCheck(); }); } } set focused(value) { if (this.isFocused !== value && this.hostElement) { const wrapper = this.hostElement.nativeElement; if (value) { this.renderer.addClass(wrapper, FOCUSED); } else { this.renderer.removeClass(wrapper, FOCUSED); } this.isFocused = value; } } attachHostHandlers() { this.ngZone.runOutsideAngular(() => { const wrapper = this.hostElement.nativeElement; this.domSubscriptions.push(this.renderer.listen(wrapper, 'click', this.clickHandler), this.renderer.listen(wrapper, 'keydown', this.keyDownHandler)); wrapper.addEventListener('focus', this.handleFocus, true); wrapper.addEventListener('blur', this.handleBlur, true); }); } setHostClasses(value) { const wrapper = this.hostElement.nativeElement; if (value) { this.renderer.removeClass(wrapper, 'k-switch-off'); this.renderer.addClass(wrapper, 'k-switch-on'); } else { this.renderer.removeClass(wrapper, 'k-switch-on'); this.renderer.addClass(wrapper, 'k-switch-off'); } } handleClasses(value, input) { const elem = this.hostElement.nativeElement; const classes = getStylingClasses('switch', input, this[input], value); if (classes.toRemove) { this.renderer.removeClass(elem, classes.toRemove); } if (classes.toAdd) { this.renderer.addClass(elem, classes.toAdd); } } handleTrackClasses(value) { const track = this.track?.nativeElement; if (!track) { return; } const classes = getStylingClasses('switch', 'rounded', this.trackRounded, value); if (classes.toRemove) { this.renderer.removeClass(track, classes.toRemove); } if (classes.toAdd) { this.renderer.addClass(track, classes.toAdd); } } handleThumbClasses(value) { const thumb = this.thumb?.nativeElement; if (!thumb) { return; } const classes = getStylingClasses('switch', 'rounded', this.thumbRounded, value); if (classes.toRemove) { this.renderer.removeClass(thumb, classes.toRemove); } if (classes.toAdd) { this.renderer.addClass(thumb, classes.toAdd); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SwitchComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i1.LocalizationService }, { token: i0.Injector }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SwitchComponent, isStandalone: true, selector: "kendo-switch", inputs: { focusableId: "focusableId", onLabel: "onLabel", offLabel: "offLabel", checked: "checked", disabled: "disabled", readonly: "readonly", tabindex: "tabindex", size: "size", thumbRounded: "thumbRounded", trackRounded: "trackRounded", tabIndex: "tabIndex" }, outputs: { onFocus: "focus", onBlur: "blur", valueChange: "valueChange" }, host: { properties: { "class.k-readonly": "this.readonly", "attr.dir": "this.direction", "attr.role": "this.hostRole", "attr.id": "this.hostId", "attr.aria-checked": "this.ariaChecked", "attr.aria-invalid": "this.ariaInvalid", "attr.tabindex": "this.hostTabIndex", "attr.aria-disabled": "this.ariaDisabled", "attr.aria-readonly": "this.ariaReadonly", "class.k-switch": "this.hostClasses", "class.k-disabled": "this.disabledClass" } }, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.switch' }, { multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SwitchComponent) /* eslint-disable-line*/ }, { provide: KendoInput, useExisting: forwardRef(() => SwitchComponent) } ], viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, static: true }, { propertyName: "thumb", first: true, predicate: ["thumb"], descendants: true, static: true }], exportAs: ["kendoSwitch"], ngImport: i0, template: ` <ng-container kendoSwitchLocalizedMessages i18n-on="kendo.switch.on|The **On** label of the Switch." on="ON" i18n-off="kendo.switch.off|The **Off** label of the Switch." off="OFF" > <span #track class="k-switch-track" [style.transitionDuration]="initialized ? '200ms' : '0ms'" > <span class="k-switch-label-on" [attr.aria-hidden]="true" >{{onLabelMessage}}</span> <span class="k-switch-label-off" [attr.aria-hidden]="true">{{offLabelMessage}}</span> </span> <span class="k-switch-thumb-wrap" [style.transitionDuration]="initialized ? '200ms' : '0ms'"> <span #thumb class="k-switch-thumb"></span> </span> `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedSwitchMessagesDirective, selector: "[kendoSwitchLocalizedMessages]" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SwitchComponent, decorators: [{ type: Component, args: [{ exportAs: 'kendoSwitch', providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.switch' }, { multi: true, provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SwitchComponent) /* eslint-disable-line*/ }, { provide: KendoInput, useExisting: forwardRef(() => SwitchComponent) } ], selector: 'kendo-switch', template: ` <ng-container kendoSwitchLocalizedMessages i18n-on="kendo.switch.on|The **On** label of the Switch." on="ON" i18n-off="kendo.switch.off|The **Off** label of the Switch." off="OFF" > <span #track class="k-switch-track" [style.transitionDuration]="initialized ? '200ms' : '0ms'" > <span class="k-switch-label-on" [attr.aria-hidden]="true" >{{onLabelMessage}}</span> <span class="k-switch-label-off" [attr.aria-hidden]="true">{{offLabelMessage}}</span> </span> <span class="k-switch-thumb-wrap" [style.transitionDuration]="initialized ? '200ms' : '0ms'"> <span #thumb class="k-switch-thumb"></span> </span> `, standalone: true, imports: [LocalizedSwitchMessagesDirective] }] }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i1.LocalizationService }, { type: i0.Injector }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }]; }, propDecorators: { focusableId: [{ type: Input }], onLabel: [{ type: Input }], offLabel: [{ type: Input }], checked: [{ type: Input }], disabled: [{ type: Input }], readonly: [{ type: Input }, { type: HostBinding, args: ['class.k-readonly'] }], tabindex: [{ type: Input }], size: [{ type: Input }], thumbRounded: [{ type: Input }], trackRounded: [{ type: Input }], tabIndex: [{ type: Input }], onFocus: [{ type: Output, args: ['focus'] }], onBlur: [{ type: Output, args: ['blur'] }], valueChange: [{ type: Output }], direction: [{ type: HostBinding, args: ['attr.dir'] }], hostRole: [{ type: HostBinding, args: ['attr.role'] }], hostId: [{ type: HostBinding, args: ['attr.id'] }], ariaChecked: [{ type: HostBinding, args: ['attr.aria-checked'] }], ariaInvalid: [{ type: HostBinding, args: ['attr.aria-invalid'] }], hostTabIndex: [{ type: HostBinding, args: ['attr.tabindex'] }], ariaDisabled: [{ type: HostBinding, args: ['attr.aria-disabled'] }], ariaReadonly: [{ type: HostBinding, args: ['attr.aria-readonly'] }], hostClasses: [{ type: HostBinding, args: ['class.k-switch'] }], disabledClass: [{ type: HostBinding, args: ['class.k-disabled'] }], track: [{ type: ViewChild, args: ['track', { static: true }] }], thumb: [{ type: ViewChild, args: ['thumb', { static: true }] }] } });