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