UNPKG

igniteui-angular

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

883 lines (876 loc) 30.2 kB
import * as i0 from '@angular/core'; import { inject, ChangeDetectorRef, signal, QueryList, EventEmitter, effect, booleanAttribute, Input, HostListener, HostBinding, Output, Directive, Component, NgModule } from '@angular/core'; import { ɵIgxDirectionality as _IgxDirectionality, EDITOR_PROVIDER } from 'igniteui-angular/core'; import { CheckboxBaseDirective, IgxRippleDirective } from 'igniteui-angular/directives'; import { NgControl, Validators } from '@angular/forms'; import { noop, Subject, takeUntil, fromEvent } from 'rxjs'; /** * Determines the Radio Group alignment */ const RadioGroupAlignment = { horizontal: 'horizontal', vertical: 'vertical' }; let nextId = 0; /** * Radio group directive renders set of radio buttons. * * @igxModule IgxRadioModule * * @igxTheme igx-radio-theme * * @igxKeywords radiogroup, radio, button, input * * @igxGroup Data Entry & Display * * @remarks * The Ignite UI Radio Group allows the user to select a single option from an available set of options that are listed side by side. * * @example: * ```html * <igx-radio-group name="radioGroup"> * <igx-radio *ngFor="let item of ['Foo', 'Bar', 'Baz']" value="{{item}}"> * {{item}} * </igx-radio> * </igx-radio-group> * ``` */ class IgxRadioGroupDirective { /** * Returns reference to the child radio buttons. * * @example * ```typescript * let radioButtons = this.radioGroup.radioButtons; * ``` */ get radioButtons() { this._radioButtonsList.reset(this._radioButtons()); return this._radioButtonsList; } /** * Sets/gets the `value` attribute. * * @example * ```html * <igx-radio-group [value] = "'radioButtonValue'"></igx-radio-group> * ``` */ get value() { return this._value; } set value(newValue) { if (this._value !== newValue) { this._value = newValue; this._selectRadioButton(); } } /** * Sets/gets the `name` attribute of the radio group component. All child radio buttons inherits this name. * * @example * ```html * <igx-radio-group name = "Radio1"></igx-radio-group> * ``` */ get name() { return this._name; } set name(newValue) { if (this._name !== newValue) { this._name = newValue; this._setRadioButtonNames(); } } /** * Sets/gets whether the radio group is required. * * @remarks * If not set, `required` will have value `false`. * * @example * ```html * <igx-radio-group [required] = "true"></igx-radio-group> * ``` */ get required() { return this._required; } set required(value) { this._required = value; this._setRadioButtonsRequired(); } /** * Sets/gets the selected child radio button. * * @example * ```typescript * let selectedButton = this.radioGroup.selected; * this.radioGroup.selected = selectedButton; * ``` */ get selected() { return this._selected; } set selected(selected) { if (this._selected !== selected) { this._selected = selected; this.value = selected ? selected.value : null; } } /** * Sets/gets whether the radio group is invalid. * * @remarks * If not set, `invalid` will have value `false`. * * @example * ```html * <igx-radio-group [invalid] = "true"></igx-radio-group> * ``` */ get invalid() { return this._invalid; } set invalid(value) { this._invalid = value; this._setRadioButtonsInvalid(); } /** * A css class applied to the component if any of the * child radio buttons labelPosition is set to `before`. * * @hidden * @internal */ get labelBefore() { return this._radioButtons().some((radio) => radio.labelPosition === 'before'); } /** * A css class applied to the component if all * child radio buttons are disabled. * * @hidden * @internal */ get disabled() { return this._radioButtons().every((radio) => radio.disabled); } handleClick(event) { event.stopPropagation(); if (this.selected) { this.selected.nativeElement.focus(); } } handleKeyDown(event) { const { key } = event; const buttons = this._radioButtons().filter(radio => !radio.disabled); const checked = buttons.find((radio) => radio.checked); if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) { let index = checked ? buttons.indexOf(checked) : -1; const ltr = this._directionality.value === 'ltr'; switch (key) { case 'ArrowUp': index += -1; break; case 'ArrowLeft': index += ltr ? -1 : 1; break; case 'ArrowRight': index += ltr ? 1 : -1; break; default: index += 1; } if (index < 0) index = buttons.length - 1; if (index > buttons.length - 1) index = 0; buttons.forEach((radio) => { radio.deselect(); radio.nativeElement.blur(); }); buttons[index].focused = true; buttons[index].nativeElement.focus(); buttons[index].select(); event.preventDefault(); } if (event.key === "Tab") { buttons.forEach((radio) => { if (radio !== checked) { event.stopPropagation(); } }); } } /** * Returns the alignment of the `igx-radio-group`. * ```typescript * @ViewChild("MyRadioGroup") * public radioGroup: IgxRadioGroupDirective; * ngAfterViewInit(){ * let radioAlignment = this.radioGroup.alignment; * } * ``` */ get alignment() { return this.vertical ? RadioGroupAlignment.vertical : RadioGroupAlignment.horizontal; } /** * Allows you to set the radio group alignment. * Available options are `RadioGroupAlignment.horizontal` (default) and `RadioGroupAlignment.vertical`. * ```typescript * public alignment = RadioGroupAlignment.vertical; * //.. * ``` * ```html * <igx-radio-group [alignment]="alignment"></igx-radio-group> * ``` */ set alignment(value) { this.vertical = value === RadioGroupAlignment.vertical; } /** * @hidden * @internal */ updateValidityOnBlur() { this._radioButtons().forEach((button) => { button.focused = false; if (button.invalid) { this.invalid = true; } }); } /** * @hidden * @internal */ updateOnKeyUp(event) { const checked = this._radioButtons().find(x => x.checked); if (event.key === "Tab") { this._radioButtons().forEach((radio) => { if (radio === checked) { checked.focused = true; } }); } } ngDoCheck() { this._updateTabIndex(); } _updateTabIndex() { // Needed so that the keyboard navigation of a radio group // placed inside a dialog works properly if (this._radioButtons) { const checked = this._radioButtons().find(x => x.checked); if (checked) { this._radioButtons().forEach((button) => { checked.nativeElement.tabIndex = 0; if (button !== checked) { button.nativeElement.tabIndex = -1; button.focused = false; } }); } } } /** * Sets the "checked" property value on the radio input element. * * @remarks * Checks whether the provided value is consistent to the current radio button. * If it is, the checked attribute will have value `true` and selected property will contain the selected `IgxRadioComponent`. * * @example * ```typescript * this.radioGroup.writeValue('radioButtonValue'); * ``` */ writeValue(value) { this.value = value; } /** * Registers a function called when the control value changes. * * @hidden * @internal */ registerOnChange(fn) { this._onChangeCallback = fn; } /** * Registers a function called when the control is touched. * * @hidden * @internal */ registerOnTouched(fn) { if (this._radioButtons) { this._radioButtons().forEach((button) => { button.registerOnTouched(fn); }); } } /** * @hidden * @internal */ ngOnDestroy() { this.destroy$.next(true); this.destroy$.complete(); } constructor() { this.ngControl = inject(NgControl, { optional: true, self: true }); this._directionality = inject(_IgxDirectionality); this.cdr = inject(ChangeDetectorRef); this._radioButtons = signal([], { ...(ngDevMode ? { debugName: "_radioButtons" } : {}) }); this._radioButtonsList = new QueryList(); /** * An event that is emitted after the radio group `value` is changed. * * @remarks * Provides references to the selected `IgxRadioComponent` and the `value` property as event arguments. * * @example * ```html * <igx-radio-group (change)="handler($event)"></igx-radio-group> * ``` */ // eslint-disable-next-line @angular-eslint/no-output-native this.change = new EventEmitter(); /** * The css class applied to the component. * * @hidden * @internal */ this.cssClass = 'igx-radio-group'; /** * @hidden * @internal * Sets vertical alignment to the radio group, if `alignment` is set to `vertical`. * By default the alignment is horizontal. * * @example * ```html * <igx-radio-group alignment="vertical"></igx-radio-group> * ``` */ this.vertical = false; /** * @hidden * @internal */ this._onChangeCallback = noop; /** * @hidden * @internal */ this._name = `igx-radio-group-${nextId++}`; /** * @hidden * @internal */ this._value = null; /** * @hidden * @internal */ this._selected = null; /** * @hidden * @internal */ this._isInitialized = signal(false, { ...(ngDevMode ? { debugName: "_isInitialized" } : {}) }); /** * @hidden * @internal */ this._required = false; /** * @hidden * @internal */ this._invalid = false; /** * @hidden * @internal */ this.destroy$ = new Subject(); /** * @hidden * @internal */ this.queryChange$ = new Subject(); if (this.ngControl !== null) { this.ngControl.valueAccessor = this; } effect(() => { this.initialize(); this.setRadioButtons(); }); } /** * @hidden * @internal */ initialize() { // The initial value can possibly be set by NgModel and it is possible that // the OnInit of the NgModel occurs after the OnInit of this class. this._isInitialized.set(true); if (this.ngControl) { this.ngControl.statusChanges .pipe(takeUntil(this.destroy$)) .subscribe(() => { this.invalid = false; }); if (this.ngControl.control.validator || this.ngControl.control.asyncValidator) { this._required = this.ngControl?.control?.hasValidator(Validators.required); } this._radioButtons().forEach((button) => { if (this.ngControl.disabled) { button.disabled = this.ngControl.disabled; } }); } } /** * @hidden * @internal */ setRadioButtons() { this._radioButtons().forEach((button) => { Promise.resolve().then(() => { button.name = this._name; button.required = this._required; }); if (button.value === this._value) { button.checked = true; this._selected = button; this.cdr.markForCheck(); } this._setRadioButtonEvents(button); }); } /** * @hidden * @internal */ _setRadioButtonEvents(button) { button.change.pipe(takeUntil(button.destroy$), takeUntil(this.destroy$), takeUntil(this.queryChange$)).subscribe((ev) => this._selectedRadioButtonChanged(ev)); button.blurRadio .pipe(takeUntil(this.destroy$)) .subscribe(() => this.updateValidityOnBlur()); fromEvent(button.nativeElement, 'keyup') .pipe(takeUntil(this.destroy$)) .subscribe((event) => this.updateOnKeyUp(event)); } /** * @hidden * @internal */ _selectedRadioButtonChanged(args) { this._radioButtons().forEach((button) => { button.checked = button.id === args.owner.id; if (button.checked && button.ngControl) { this.invalid = button.ngControl.invalid; } else if (button.checked) { this.invalid = false; } }); this._selected = args.owner; this._value = args.value; if (this._isInitialized) { this.change.emit(args); this._onChangeCallback(this.value); } } /** * @hidden * @internal */ _setRadioButtonNames() { if (this._radioButtons) { this._radioButtons().forEach((button) => { button.name = this._name; }); } } /** * @hidden * @internal */ _selectRadioButton() { if (this._radioButtons) { this._radioButtons().forEach((button) => { if (this._value === null) { // no value - uncheck all radio buttons if (button.checked) { button.checked = false; } } else { if (this._value === button.value) { // selected button if (this._selected !== button) { this._selected = button; } if (!button.checked) { button.checked = true; } } else { // non-selected button if (button.checked) { button.checked = false; } } } }); } } /** * @hidden * @internal */ _setRadioButtonsRequired() { if (this._radioButtons) { this._radioButtons().forEach((button) => { button.required = this._required; }); } } /** * Registers a radio button with this radio group. * This method is called by radio button components when they are created. * @hidden @internal */ _addRadioButton(radioButton) { this._radioButtons.update(buttons => { if (!buttons.includes(radioButton)) { this._setRadioButtonEvents(radioButton); return [...buttons, radioButton]; } return buttons; }); } /** * Unregisters a radio button from this radio group. * This method is called by radio button components when they are destroyed. * @hidden @internal */ _removeRadioButton(radioButton) { this._radioButtons.update(buttons => buttons.filter(btn => btn !== radioButton)); } /** * @hidden * @internal */ _setRadioButtonsInvalid() { if (this._radioButtons) { this._radioButtons().forEach((button) => { button.invalid = this._invalid; }); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioGroupDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: IgxRadioGroupDirective, isStandalone: true, selector: "igx-radio-group, [igxRadioGroup]", inputs: { value: "value", name: "name", required: ["required", "required", booleanAttribute], selected: "selected", invalid: ["invalid", "invalid", booleanAttribute], alignment: "alignment" }, outputs: { change: "change" }, host: { listeners: { "click": "handleClick($event)", "keydown": "handleKeyDown($event)" }, properties: { "class.igx-radio-group": "this.cssClass", "class.igx-radio-group--vertical": "this.vertical", "class.igx-radio-group--before": "this.labelBefore", "class.igx-radio-group--disabled": "this.disabled" } }, exportAs: ["igxRadioGroup"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioGroupDirective, decorators: [{ type: Directive, args: [{ exportAs: 'igxRadioGroup', selector: 'igx-radio-group, [igxRadioGroup]', standalone: true }] }], ctorParameters: () => [], propDecorators: { value: [{ type: Input }], name: [{ type: Input }], required: [{ type: Input, args: [{ transform: booleanAttribute }] }], selected: [{ type: Input }], invalid: [{ type: Input, args: [{ transform: booleanAttribute }] }], change: [{ type: Output }], cssClass: [{ type: HostBinding, args: ['class.igx-radio-group'] }], vertical: [{ type: HostBinding, args: ['class.igx-radio-group--vertical'] }], labelBefore: [{ type: HostBinding, args: ['class.igx-radio-group--before'] }], disabled: [{ type: HostBinding, args: ['class.igx-radio-group--disabled'] }], handleClick: [{ type: HostListener, args: ['click', ['$event']] }], handleKeyDown: [{ type: HostListener, args: ['keydown', ['$event']] }], alignment: [{ type: Input }] } }); /** * **Ignite UI for Angular Radio Button** - * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/radio_button.html) * * The Ignite UI Radio Button allows the user to select a single option from an available set of options that are listed side by side. * * Example: * ```html * <igx-radio> * Simple radio button * </igx-radio> * ``` */ class IgxRadioComponent extends CheckboxBaseDirective { constructor() { super(...arguments); /** @hidden @internal */ this.blurRadio = new EventEmitter(); this.radioGroup = inject(IgxRadioGroupDirective, { optional: true, skipSelf: true }); /** * Returns the class of the radio component. * ```typescript * let radioClass = this.radio.cssClass; * ``` * * @memberof IgxRadioComponent */ this.cssClass = 'igx-radio'; /** * Sets/gets the `disabled` attribute. * Default value is `false`. * ```html * <igx-radio disabled></igx-radio> * ``` * ```typescript * let isDisabled = this.radio.disabled; * ``` * * @memberof IgxRadioComponent */ this.disabled = false; /** * Sets/gets whether the radio button is invalid. * Default value is `false`. * ```html * <igx-radio invalid></igx-radio> * ``` * ```typescript * let isInvalid = this.radio.invalid; * ``` * * @memberof IgxRadioComponent */ this.invalid = false; /** * Sets/gets whether the radio component is on focus. * Default value is `false`. * ```typescript * this.radio.focus = true; * ``` * ```typescript * let isFocused = this.radio.focused; * ``` * * @memberof IgxRadioComponent */ this.focused = false; } /** * Sets/gets the `checked` attribute. * Default value is `false`. * ```html * <igx-radio [checked]="true"></igx-radio> * ``` * ```typescript * let isChecked = this.radio.checked; * ``` * * @memberof IgxRadioComponent */ set checked(value) { this._checked = value; } get checked() { return this._checked; } /** * @hidden * @internal */ _changed(event) { if (event instanceof Event) { event.preventDefault(); } } /** * @hidden */ _onCheckboxClick() { this.select(); } /** * Selects the current radio button. * ```typescript * this.radio.select(); * ``` * * @memberof IgxRadioComponent */ select() { if (!this.checked) { this.checked = true; this.change.emit({ value: this.value, owner: this, checked: this.checked, }); this._onChangeCallback(this.value); } } /** * Deselects the current radio button. * ```typescript * this.radio.deselect(); * ``` * * @memberof IgxRadioComponent */ deselect() { this.checked = false; this.focused = false; this.cdr.markForCheck(); } /** * Checks whether the provided value is consistent to the current radio button. * If it is, the checked attribute will have value `true`; * ```typescript * this.radio.writeValue('radioButtonValue'); * ``` */ writeValue(value) { this.value = this.value ?? value; if (value === this.value) { if (!this.checked) { this.checked = true; } } else { this.deselect(); } } /** * @hidden */ onBlur() { super.onBlur(); this.blurRadio.emit(); } /** * @hidden * @internal */ ngAfterViewInit() { super.ngAfterViewInit(); // Register with parent radio group if it exists if (this.radioGroup) { this.radioGroup._addRadioButton(this); } } /** * @hidden * @internal */ ngOnDestroy() { // Unregister from parent radio group if it exists if (this.radioGroup) { this.radioGroup._removeRadioButton(this); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "21.0.2", type: IgxRadioComponent, isStandalone: true, selector: "igx-radio", inputs: { checked: ["checked", "checked", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], invalid: ["invalid", "invalid", booleanAttribute] }, host: { listeners: { "change": "_changed($event)", "click": "_onCheckboxClick()", "blur": "onBlur()" }, properties: { "class.igx-radio": "this.cssClass", "class.igx-radio--checked": "this.checked", "class.igx-radio--disabled": "this.disabled", "class.igx-radio--invalid": "this.invalid", "class.igx-radio--focused": "this.focused" } }, providers: [{ provide: EDITOR_PROVIDER, useExisting: IgxRadioComponent, multi: true }], usesInheritance: true, ngImport: i0, template: "<input #checkbox class=\"igx-radio__input\" type=\"radio\"\n [id]=\"inputId\"\n [name]=\"name\"\n [value]=\"value\"\n [tabindex]=\"tabindex\"\n [disabled]=\"disabled\"\n [checked]=\"checked\"\n [required]=\"required\"\n [attr.aria-required]=\"required\"\n [attr.aria-invalid]=\"invalid\"\n [attr.aria-checked]=\"checked\"\n [attr.aria-labelledby]=\"ariaLabel ? null : ariaLabelledBy\"\n [attr.aria-label]=\"ariaLabel\"\n (blur)=\"onBlur()\" />\n\n<span #label class=\"igx-radio__composite\" igxRipple\n igxRippleTarget=\".igx-radio__ripple\"\n [igxRippleDisabled]=\"disableRipple\"\n [igxRippleCentered]=\"true\"\n [igxRippleDuration]=\"300\">\n <div class=\"igx-radio__ripple\"></div>\n</span>\n\n<span #placeholderLabel\n [id]=\"labelId\"\n [class]=\"labelClass\">\n <ng-content></ng-content>\n</span>\n", dependencies: [{ kind: "directive", type: IgxRippleDirective, selector: "[igxRipple]", inputs: ["igxRippleTarget", "igxRipple", "igxRippleDuration", "igxRippleCentered", "igxRippleDisabled"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioComponent, decorators: [{ type: Component, args: [{ selector: 'igx-radio', providers: [{ provide: EDITOR_PROVIDER, useExisting: IgxRadioComponent, multi: true }], imports: [IgxRippleDirective], template: "<input #checkbox class=\"igx-radio__input\" type=\"radio\"\n [id]=\"inputId\"\n [name]=\"name\"\n [value]=\"value\"\n [tabindex]=\"tabindex\"\n [disabled]=\"disabled\"\n [checked]=\"checked\"\n [required]=\"required\"\n [attr.aria-required]=\"required\"\n [attr.aria-invalid]=\"invalid\"\n [attr.aria-checked]=\"checked\"\n [attr.aria-labelledby]=\"ariaLabel ? null : ariaLabelledBy\"\n [attr.aria-label]=\"ariaLabel\"\n (blur)=\"onBlur()\" />\n\n<span #label class=\"igx-radio__composite\" igxRipple\n igxRippleTarget=\".igx-radio__ripple\"\n [igxRippleDisabled]=\"disableRipple\"\n [igxRippleCentered]=\"true\"\n [igxRippleDuration]=\"300\">\n <div class=\"igx-radio__ripple\"></div>\n</span>\n\n<span #placeholderLabel\n [id]=\"labelId\"\n [class]=\"labelClass\">\n <ng-content></ng-content>\n</span>\n" }] }], propDecorators: { cssClass: [{ type: HostBinding, args: ['class.igx-radio'] }], checked: [{ type: HostBinding, args: ['class.igx-radio--checked'] }, { type: Input, args: [{ transform: booleanAttribute }] }], disabled: [{ type: HostBinding, args: ['class.igx-radio--disabled'] }, { type: Input, args: [{ transform: booleanAttribute }] }], invalid: [{ type: HostBinding, args: ['class.igx-radio--invalid'] }, { type: Input, args: [{ transform: booleanAttribute }] }], focused: [{ type: HostBinding, args: ['class.igx-radio--focused'] }], _changed: [{ type: HostListener, args: ['change', ['$event']] }], _onCheckboxClick: [{ type: HostListener, args: ['click'] }], onBlur: [{ type: HostListener, args: ['blur'] }] } }); /* NOTE: Radio Group directives collection for ease-of-use import in standalone components scenario */ const IGX_RADIO_GROUP_DIRECTIVES = [ IgxRadioGroupDirective, IgxRadioComponent ]; /** * @hidden * @deprecated * IMPORTANT: The following is NgModule exported for backwards-compatibility before standalone components */ class IgxRadioModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioModule, imports: [IgxRadioGroupDirective, IgxRadioComponent], exports: [IgxRadioGroupDirective, IgxRadioComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxRadioModule, decorators: [{ type: NgModule, args: [{ imports: [IgxRadioGroupDirective, IgxRadioComponent], exports: [IgxRadioGroupDirective, IgxRadioComponent] }] }] }); /** * Generated bundle index. Do not edit. */ export { IGX_RADIO_GROUP_DIRECTIVES, IgxRadioComponent, IgxRadioGroupDirective, IgxRadioModule, RadioGroupAlignment }; //# sourceMappingURL=igniteui-angular-radio.mjs.map