UNPKG

@ux-aspects/ux-aspects

Version:

Open source user interface framework for building modern, responsive, mobile big data applications

762 lines 113 kB
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { BACKSPACE, DELETE, ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE } from '@angular/cdk/keycodes'; import { DOCUMENT } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, ElementRef, EventEmitter, HostBinding, HostListener, Input, Output, ViewChild, forwardRef, inject } from '@angular/core'; import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { tick } from '../../common/index'; import { TypeaheadComponent, TypeaheadKeyService } from '../typeahead/index'; import { TagInputEvent } from './tag-input-event'; import * as i0 from "@angular/core"; import * as i1 from "../../directives/accessibility/focus-indicator/focus-indicator.directive"; import * as i2 from "@angular/common"; import * as i3 from "@angular/forms"; import * as i4 from "../../directives/focus-if/focus-if.directive"; import * as i5 from "../icon/icon.component"; let uniqueId = 0; const TAGINPUT_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TagInputComponent), multi: true }; const TAGINPUT_VALIDATOR = { provide: NG_VALIDATORS, useExisting: forwardRef(() => TagInputComponent), multi: true }; export class TagInputComponent { constructor() { this._changeDetector = inject(ChangeDetectorRef); this._element = inject(ElementRef); this._typeaheadKeyService = inject(TypeaheadKeyService); this._document = inject(DOCUMENT); /** Specify a unique Id for the component */ this.id = `ux-tag-input-${++uniqueId}`; /** The editable text appearing in the tag input. */ this.input = ''; /** Controls whether pasting text into the text input area automatically converts that text into one or more tags. */ this.addOnPaste = true; /** Controls the disabled state of the tag input. */ this.disabled = false; /** * If set to `true`, the tag input will prevent addition and removal of tags to enforce the minTags and maxTags settings. * Otherwise, a validation error will be raised. */ this.enforceTagLimits = false; /** * If `true`, input entered into the text input area can be converted into a tag by pressing enter. * Otherwise, tags can only be added from the typeahead list or other external means. * (Note that the `maxTags` and `tagPattern` will prevent invalid inputs regardless of this setting.) */ this.freeInput = true; /** If `true` the input field will be readonly and selection can only occur by using the dropdown. */ this.readonlyInput = false; /** * The maximum number of tags permitted in the tag input. If the number of tags is equal to `maxTags` and * `enforceTagLimits` is `true`, addition of tags will be prevented until a tag is removed */ this.maxTags = Number.MAX_VALUE; /** * The minimum number of tags permitted in the tag input. If the number of tags is equal to `minTags` and `enforceTagLimits` is * `true`, removal of tags will be prevented until a new tag is added. */ this.minTags = 0; /** The placeholder text which appears in the text input area when it is empty. */ this.placeholder = ''; /** Controls whether the typeahead appears when the text input area is clicked. This has no effect if the ux-typeahead component is not configured. */ this.showTypeaheadOnClick = false; /** * A string containing the characters which delimit tags. * Typing one of the characters in `tagDelimiters` will cause the preceding text to be added as a tag, * and the text input area will be cleared. Pasting a string containing one or more of characters in * `tagDelimiters` will cause the string to be split into multiple tags. * Note that the delimiter character will not be part of the tag text. */ this.tagDelimiters = ''; /** * A function which returns either a string, string[], or Set<string>, compatible with the NgClass directive. The function receives the following parameters: * - `tag: any` - the string or custom object representing the tag. * - `index: number` - the zero-based index of the tag as it appears in the tag input. * - `selected: boolean` - true if the tag is currently selected. */ this.tagClass = () => undefined; /** * An object which contains details of validation errors. The following properties will be present if there is a related validation error: * - `tagRangeError` - present if the number of tags is outside the range specified by minTags and maxTags. * - `inputPattern` - present if an input has been submitted which does not match the tagPattern. */ this.validationErrors = {}; /** Defines the autocomplete property on the input field which can be used to prevent the browser from displaying autocomplete suggestions. */ this.autocomplete = 'off'; /** Determine if we should show the clear all button */ this.clearButton = false; /** Determine an aria label for the clear button */ this.clearButtonAriaLabel = 'Reset selection'; /** Emits when tags is changed. */ this.tagsChange = new EventEmitter(); /** Emits when input is changed. */ this.inputChange = new EventEmitter(); /** Raised when a tag is about to be added. The `tag` property of the event contains the tag to be added. Call `preventDefault()` on the event to prevent addition. */ this.tagAdding = new EventEmitter(); /** Raised when a tag has been added. The tag property of the event contains the tag. */ this.tagAdded = new EventEmitter(); /** Raised when a tag has failed validation according to the `tagPattern`. The tag property of the event contains the string which failed validation. */ this.tagInvalidated = new EventEmitter(); /** Raised when a tag is about to be removed. The `tag` property of the event contains the tag to be removed. Call `preventDefault()` on the event to prevent removal. */ this.tagRemoving = new EventEmitter(); /** Raised when a tag has been removed. The tag property of the event contains the tag. */ this.tagRemoved = new EventEmitter(); /** Raised when a tag has been clicked. The `tag` property of the event contains the clicked tag. Call `preventDefault()` on the event to prevent the default behaviour of selecting the tag. */ this.tagClick = new EventEmitter(); // When clicking on the input during multiple mode it will send a on touched event to the parent component this.inputFocus = new EventEmitter(); // Emits when the component loses focus this.inputBlur = new EventEmitter(); this.selectedIndex = -1; this.tagApi = { getTagDisplay: this.getTagDisplay.bind(this), removeTagAt: this.removeTagAt.bind(this), canRemoveTagAt: this.canRemoveTagAt.bind(this) }; this.valid = true; this.inputValid = true; this._tags = []; // eslint-disable-next-line @typescript-eslint/no-empty-function this._onChangeHandler = () => { }; // eslint-disable-next-line @typescript-eslint/no-empty-function this._onTouchedHandler = () => { }; this._onDestroy = new Subject(); this._autoCloseDropdown = true; } /** * The list of tags appearing in the tag input. This can be an array of strings or custom objects. * See the `displayProperty` property for details of using a custom object. */ get tags() { if (!this._tags) { this._tags = []; } return this._tags; } set tags(value) { this._tags = Array.isArray(value) ? value : []; } /** Determine if the dropdown panel should close on external click.*/ set autoCloseDropdown(value) { this._autoCloseDropdown = coerceBooleanProperty(value); } get autoCloseDropdown() { return this._autoCloseDropdown; } get _showClearButton() { return this.clearButton && this._tags && this._tags.length > 0; } ngAfterContentInit() { // Watch for optional child typeahead control this.connectTypeahead(this.typeaheadQuery.first); this.typeaheadQuery.changes.pipe(takeUntil(this._onDestroy)) .subscribe((query) => this.connectTypeahead(query.first)); } ngOnChanges(changes) { if (changes.disabled) { if (changes.disabled.currentValue) { // Clear selection and close dropdown this.selectedIndex = -1; if (this.typeahead) { this.typeahead.open = false; } } } // Update validation status this.validate(); } ngOnDestroy() { if (this._subscription) { this._subscription.unsubscribe(); } this._onDestroy.next(); this._onDestroy.complete(); } writeValue(value) { if (value) { this.tags = value; this._changeDetector.markForCheck(); } } registerOnChange(fn) { this._onChangeHandler = fn; } registerOnTouched(fn) { this._onTouchedHandler = fn; } setDisabledState(isDisabled) { this.disabled = isDisabled; this._changeDetector.markForCheck(); } /** * Set focus on the input field. */ focus() { if (this.tagInput) { this.tagInput.nativeElement.focus(); } } /** * Validate the value of the control (tags property). */ validate() { this.valid = true; let tagRangeError = null; if (this._tags && (this._tags.length < this.minTags || this._tags.length > this.maxTags)) { tagRangeError = { given: this._tags.length, min: this.minTags, max: this.maxTags }; this.valid = false; } this.validationErrors.tagRangeError = tagRangeError; // forward any error to the form control return tagRangeError; } keyHandler(event) { if (this.disabled) { return; } // Get the input field cursor location const inputCursorPos = this.tagInput?.nativeElement.selectionStart ?? 0; // Determine if the input field has any text selected const hasSelection = this.tagInput?.nativeElement.selectionStart !== this.tagInput?.nativeElement.selectionEnd; // Determine if a tag has focus const tagSelected = this.isValidTagIndex(this.selectedIndex); const inputLength = this.input ? this.input.length : 0; // Check whether the arrow keys can move the selection. Otherwise the input field takes the event. const canNavigateLeft = tagSelected || (inputCursorPos <= 0 && !hasSelection); const canNavigateRight = tagSelected || (inputCursorPos >= inputLength && !hasSelection); // Forward key events to the typeahead component. this._typeaheadKeyService.handleKey(event, this.typeahead); switch (event.which) { case ENTER: // Check if a typeahead option is highlighted if (this.typeahead && this.typeahead.open && this.typeahead.highlighted) { // Add the typeahead option as a tag, clear the input, and close the dropdown this.commitTypeahead(this.typeahead.highlighted); this.typeahead.open = false; } else if (this.typeahead && !this.typeahead.open && !this.freeInput) { this.typeahead.open = true; } else { // Validate and add the input text as a tag, if possible this.commitInput(); } event.preventDefault(); break; case BACKSPACE: if (canNavigateLeft) { this.backspace(); event.stopPropagation(); event.preventDefault(); } break; case DELETE: if (tagSelected) { this.removeTagAt(this.selectedIndex); } break; case LEFT_ARROW: if (canNavigateLeft) { this.moveSelection(-1); event.preventDefault(); } break; case RIGHT_ARROW: if (canNavigateRight) { this.moveSelection(1); event.preventDefault(); } break; } // Check for keys in the tagDelimiters if (this.tagDelimiters && this.tagDelimiters.indexOf(this.getKeyChar(event)) >= 0) { // Commit previous text this.commitInput(); event.stopPropagation(); event.preventDefault(); } } focusOutHandler() { // If a click on the typeahead is in progress, don't do anything. // This works around an issue in IE where clicking a scrollbar drops focus. if (this.typeahead?.clicking) { return; } // Close the dropdown on blur setTimeout(() => { if (!this._element.nativeElement.contains(this._document.activeElement) && this.autoCloseDropdown) { this.selectedIndex = -1; if (this.typeahead) { this.typeahead.open = false; this._changeDetector.markForCheck(); } } }, 200); } onClick() { // Prevent error if you click input when at max tag limit if (this.tagInput === undefined) { return; } // focus the input element this.tagInput.nativeElement.focus(); // show the typeahead if we need to this.inputClickHandler(); } tagClickHandler(event, tag, index) { if (this.disabled) { return; } // Send tagClick event const tagClickEvent = new TagInputEvent(tag); this.tagClick.emit(tagClickEvent); // Prevent focus if preventDefault() was called if (tagClickEvent.defaultPrevented()) { event.preventDefault(); return; } // Select the tag (for IE that doesn't propagate focus) this.selectTagAt(index); } inputClickHandler() { if (this.disabled) { return; } if (this.typeahead && this.showTypeaheadOnClick) { this.typeahead.open = true; } } inputFocusHandler() { if (this.disabled) { return; } this.selectInput(); // mark form control as touched this._onTouchedHandler(); } inputPasteHandler(event) { if (this.disabled) { return; } if (this.addOnPaste) { // Get text from the clipboard let input = null; if (event.clipboardData) { input = event.clipboardData.getData('text/plain'); } else if (window.clipboardData) { // Internet Explorer only input = window.clipboardData.getData('Text'); } // Commit the clipboard text directly if (this.commit(input)) { this.selectInput(); event.stopPropagation(); event.preventDefault(); } } } typeaheadOptionSelectedHandler(event) { if (this.disabled) { return; } // When the typeahead sends the optionSelected event, commit the object directly this.commitTypeahead(event.option); } /** * Commit the current input value and clear the input field if successful. */ commitInput() { if (this.commit(this.input)) { this.selectInput(); this.toggle(); this.setInputValue(''); } } /** * Commit the given tag object and clear the input if successful. */ commitTypeahead(tag) { if (this.addTag(tag)) { this.selectInput(); this.setInputValue(''); } } /** * Commit the given string value as one or more tags, if validation passes. Returns true if the tag(s) were created. */ commit(input) { if (input && this.freeInput) { // Split the tags by the tagDelimiters if configured const newTags = this.splitTagInput(input); // Check tag validation for all of the individual values let allValid = true; for (const newTag of newTags) { const valid = this.validateTag(newTag); if (!valid) { allValid = false; } } // Add the tags if all are valid if (allValid) { for (const newTag of newTags) { this.addTag(this.createTag(newTag)); } return true; } } return false; } /** * If no tag is selected, select the rightmost tag. If a tag is selected, remove it. */ backspace() { if (this.disabled) { return; } if (!this.isValidTagIndex(this.selectedIndex)) { this.selectTagAt(this._tags.length - 1); } else { this.removeTagAt(this.selectedIndex); } } /** * Move the highlighted option forwards or backwards in the list. Wraps at the limits. * @param delta Value to be added to the selected index, i.e. -1 to move backwards, +1 to move forwards. */ moveSelection(delta) { if (this.disabled) { return; } if (this.isValidSelectIndex(this.selectedIndex)) { this.selectedIndex += delta; // Do wrapping of selection when out of bounds if (this.selectedIndex < 0) { this.selectedIndex = this._tags.length; } else if (this.selectedIndex > this._tags.length) { this.selectedIndex = 0; } } } /** * Returns a value to display for the given tag. Uses display function/property name if set, otherwise assumes that the tag is a simple string. */ getTagDisplay(tag) { if (typeof this.display === 'function') { return this.display(tag); } if (typeof this.display === 'string') { return tag[this.display]; } return tag; } /** * Returns true if the given index is selected (tag index or input field). */ isSelected(index) { return index === this.selectedIndex; } /** * Select the tag at the given index. Does nothing if disabled is true. */ selectTagAt(tagIndex) { if (this.disabled) { return; } if (this.isValidTagIndex(tagIndex)) { this.selectedIndex = tagIndex; } } /** * Select the input field, giving it focus. Does nothing if disabled is true. */ selectInput() { if (this.disabled) { return; } this.selectedIndex = this._tags.length; } /** * Remove the tag at the given index. Does nothing if disabled is true or the minTags property prevents removal. */ removeTagAt(tagIndex) { if (this.disabled || !this.canRemoveTagAt()) { return; } // Check that the tagIndex is in range if (this.isValidTagIndex(tagIndex)) { const tag = this._tags[tagIndex]; const tagRemovingEvent = new TagInputEvent(tag); this.tagRemoving.emit(tagRemovingEvent); if (!tagRemovingEvent.defaultPrevented()) { // Select input first to avoid issues with dropping focus this.selectInput(); // Remove the tag this.tags = this._tags.filter((_tag, index) => index !== tagIndex); this.setTagsValue(this._tags); // Set focus again since indices have changed this.selectInput(); this.tagRemoved.emit(new TagInputEvent(tag)); this.validate(); } } } /** * Returns true if the tag at the given index can be removed. */ canRemoveTagAt() { return this._tags.length > this.minTags || !this.enforceTagLimits; } /** * Returns true if the input field should be available. */ isInputVisible() { return this._tags.length < this.maxTags || !this.enforceTagLimits; } /** * Returns true if any part of the control has focus. */ hasFocus() { return this.isValidSelectIndex(this.selectedIndex); } toggle() { this.typeahead && this.typeahead.open ? this.typeahead.open = false : this.inputClickHandler(); } clear() { if (this.disabled) { return; } this.tags = []; this.setTagsValue(this._tags); this.setInputValue(''); this.focus(); } setInputValue(text) { this.input = text; this.inputChange.emit(text); } setTagsValue(tags) { this._onChangeHandler(tags); this.tagsChange.emit(tags); } connectTypeahead(typeahead) { if (this._subscription) { this._subscription.unsubscribe(); this._subscription = null; } this.typeahead = typeahead; if (this.typeahead) { // Set up event handler for selected options this._subscription = this.typeahead.optionSelected.subscribe(this.typeaheadOptionSelectedHandler.bind(this)); // Set up event handler for the highlighted element // Added a delay to move it out of the current change detection cycle this._subscription.add(this.typeahead.highlightedElementChange.pipe(tick()) .subscribe((element) => this.highlightedElement = element)); } } /** * Validate the given tagValue with the tagPattern, if set. Update validationErrors on validation failure. */ validateTag(tagValue) { let inputPattern = null; this.inputValid = true; if (this.tagPattern && !this.tagPattern.test(tagValue)) { inputPattern = { given: tagValue, pattern: this.tagPattern }; this.inputValid = false; } this.validationErrors.inputPattern = inputPattern; return this.inputValid; } /** * Create a tag object for the given tagValue. If createTagHandler is specified, use it; otherwise if displayProperty is specified, create an object with the tagValue as the single named property; otherwise return the tagValue itself. */ createTag(tagValue) { let tag = null; if (this.createTagHandler && typeof this.createTagHandler === 'function') { tag = this.createTagHandler(tagValue); } else if (typeof this.display === 'string') { tag = {}; tag[this.display] = tagValue; } else { tag = tagValue; } return tag; } /** * Add a tag object, calling the tagAdding and tagAdded events. Returns true if the tag was added to the tags array. */ addTag(tag) { if (tag !== null) { // Verify that the new tag can be displayed const displayValue = this.getTagDisplay(tag); if (typeof displayValue === 'string') { const tagAddingEvent = new TagInputEvent(tag); this.tagAdding.emit(tagAddingEvent); if (!tagAddingEvent.defaultPrevented()) { this.tags = [...this._tags, tag]; this.setTagsValue(this._tags); this.tagAdded.emit(new TagInputEvent(tag)); this.validate(); return true; } } } return false; } /** * Returns true if the given tagIndex is a valid tag index. */ isValidTagIndex(tagIndex) { return tagIndex >= 0 && tagIndex < this._tags.length; } /** * Returns true if the given index is a valid selection index (tags or input field). */ isValidSelectIndex(index) { return index >= 0 && index <= this._tags.length; } /** * Returns the character corresponding to the given key event, mainly for IE compatibility. */ getKeyChar(event) { switch (event.which) { case SPACE: return ' '; } return event.key; } /** * Returns an array of strings corresponding to the input string split by the tagDelimiters characters. */ splitTagInput(input) { let tagValues = [input]; if (this.tagDelimiters && typeof this.tagDelimiters === 'string') { // eslint-disable-next-line no-useless-escape const escapedDelimiters = this.tagDelimiters.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); const delimiterRegex = new RegExp(`[${escapedDelimiters}]`, 'g'); tagValues = input.split(delimiterRegex).filter((s) => s.length > 0); } return tagValues; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: TagInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.12", type: TagInputComponent, selector: "ux-tag-input", inputs: { id: "id", tags: "tags", input: "input", display: "display", addOnPaste: "addOnPaste", ariaLabel: "ariaLabel", ariaLabelledby: "ariaLabelledby", disabled: "disabled", required: "required", enforceTagLimits: "enforceTagLimits", freeInput: "freeInput", readonlyInput: "readonlyInput", maxTags: "maxTags", minTags: "minTags", placeholder: "placeholder", showTypeaheadOnClick: "showTypeaheadOnClick", tagDelimiters: "tagDelimiters", tagPattern: "tagPattern", tagTemplate: "tagTemplate", tagClass: "tagClass", validationErrors: "validationErrors", autocomplete: "autocomplete", createTagHandler: ["createTag", "createTagHandler"], icon: "icon", clearButton: "clearButton", clearButtonAriaLabel: "clearButtonAriaLabel", autoCloseDropdown: "autoCloseDropdown" }, outputs: { tagsChange: "tagsChange", inputChange: "inputChange", tagAdding: "tagAdding", tagAdded: "tagAdded", tagInvalidated: "tagInvalidated", tagRemoving: "tagRemoving", tagRemoved: "tagRemoved", tagClick: "tagClick", inputFocus: "inputFocus", inputBlur: "inputBlur" }, host: { listeners: { "keydown": "keyHandler($event)", "focusout": "focusOutHandler()", "click": "onClick()" }, properties: { "class.disabled": "disabled", "class.focus": "hasFocus()", "class.invalid": "!valid || !inputValid", "attr.id": "this.id" } }, providers: [TAGINPUT_VALUE_ACCESSOR, TAGINPUT_VALIDATOR], queries: [{ propertyName: "typeaheadQuery", predicate: TypeaheadComponent }], viewQueries: [{ propertyName: "tagInput", first: true, predicate: ["tagInput"], descendants: true }], exportAs: ["ux-tag-input"], usesOnChanges: true, ngImport: i0, template: "<ol [attr.aria-haspopup]=\"typeahead ? 'listbox' : null\"\n [attr.aria-expanded]=\"typeahead ? typeahead.open : null\"\n [attr.aria-controls]=\"typeahead ? typeahead.id : null\"\n [class.ux-tag-input-clear-inset]=\"_showClearButton\"\n [class.ux-tag-input-icon-inset]=\"icon\"\n (click)=\"toggle()\">\n\n @for (tag of _tags; track tag; let i = $index) {\n <li class=\"ux-tag\"\n [class.disabled]=\"disabled\"\n [ngClass]=\"tagClass(tag, i, isSelected(i))\"\n [attr.tabindex]=\"disabled ? null : 0\"\n [focusIf]=\"isSelected(i)\"\n (click)=\"tagClickHandler($event, tag, i); $event.stopPropagation()\"\n (focus)=\"selectTagAt(i)\">\n <ng-container [ngTemplateOutlet]=\"tagTemplate || defaultTagTemplate\"\n [ngTemplateOutletContext]=\"{tag: tag, index: i, disabled: disabled, api: tagApi}\">\n </ng-container>\n </li>\n }\n @if (isInputVisible()) {\n <li class=\"ux-tag-input\">\n <input #tagInput type=\"text\" attr.id=\"{{id}}-input\" class=\"ux-tag-input\"\n [ngModel]=\"input\"\n (ngModelChange)=\"setInputValue($event)\"\n [autocomplete]=\"autocomplete\"\n [class.invalid]=\"!inputValid\"\n [required]=\"required\"\n [attr.aria-activedescendant]=\"highlightedElement?.id\"\n [attr.aria-autocomplete]=\"typeahead ? 'list' : 'none'\"\n [attr.aria-controls]=\"typeahead?.id\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.aria-labelledby]=\"ariaLabelledby\"\n aria-multiline=\"false\"\n [placeholder]=\"disabled ? '' : (placeholder || '')\"\n [disabled]=\"disabled\"\n [focusIf]=\"isSelected(_tags.length)\"\n (click)=\"toggle(); $event.stopPropagation()\"\n (focus)=\"inputFocusHandler(); inputFocus.emit($event)\"\n (blur)=\"inputBlur.emit($event)\"\n (paste)=\"inputPasteHandler($event)\"\n [readonly]=\"readonlyInput\">\n </li>\n }\n </ol>\n\n <!-- Insert the custom icon if provided -->\n @if (icon || _showClearButton) {\n <div class=\"ux-tag-icons\" (click)=\"toggle(); $event.stopPropagation()\">\n <!-- Clear All Button -->\n @if (_showClearButton) {\n <i uxFocusIndicator\n class=\"ux-tag-icon ux-icon ux-icon-close ux-select-clear-icon\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n [attr.aria-label]=\"clearButtonAriaLabel\"\n (click)=\"clear(); $event.stopPropagation()\"\n (keydown.enter)=\"clear(); $event.stopPropagation()\">\n </i>\n }\n <!-- Custom Icon -->\n @if (icon) {\n <div class=\"ux-custom-icon\">\n <ng-container [ngTemplateOutlet]=\"icon\"></ng-container>\n </div>\n }\n </div>\n }\n\n <ng-content #typeahead></ng-content>\n\n <ng-template #defaultTagTemplate let-tag=\"tag\" let-index=\"index\" let-disabled=\"disabled\" let-api=\"api\">\n <span class=\"ux-tag-text\">{{ api.getTagDisplay(tag) }}</span>\n @if (api.canRemoveTagAt(index)) {\n <button\n uxFocusIndicator\n type=\"button\"\n class=\"ux-tag-remove\"\n aria-label=\"Remove Item\"\n [disabled]=\"disabled\"\n (click)=\"api.removeTagAt(index); $event.stopPropagation();\">\n <ux-icon name=\"close\"></ux-icon>\n </button>\n }\n </ng-template>\n", dependencies: [{ kind: "directive", type: i1.FocusIndicatorDirective, selector: "[uxFocusIndicator]", inputs: ["checkChildren", "mouseFocusIndicator", "touchFocusIndicator", "keyboardFocusIndicator", "programmaticFocusIndicator"], outputs: ["indicator"], exportAs: ["ux-focus-indicator"] }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.FocusIfDirective, selector: "[focusIf]", inputs: ["focusIfDelay", "focusIfScroll", "focusIf"] }, { kind: "component", type: i5.IconComponent, selector: "ux-icon", inputs: ["name", "size", "rotate", "flipHorizontal", "flipVertical"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: TagInputComponent, decorators: [{ type: Component, args: [{ selector: 'ux-tag-input', exportAs: 'ux-tag-input', providers: [TAGINPUT_VALUE_ACCESSOR, TAGINPUT_VALIDATOR], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.disabled]': 'disabled', '[class.focus]': 'hasFocus()', '[class.invalid]': '!valid || !inputValid' }, template: "<ol [attr.aria-haspopup]=\"typeahead ? 'listbox' : null\"\n [attr.aria-expanded]=\"typeahead ? typeahead.open : null\"\n [attr.aria-controls]=\"typeahead ? typeahead.id : null\"\n [class.ux-tag-input-clear-inset]=\"_showClearButton\"\n [class.ux-tag-input-icon-inset]=\"icon\"\n (click)=\"toggle()\">\n\n @for (tag of _tags; track tag; let i = $index) {\n <li class=\"ux-tag\"\n [class.disabled]=\"disabled\"\n [ngClass]=\"tagClass(tag, i, isSelected(i))\"\n [attr.tabindex]=\"disabled ? null : 0\"\n [focusIf]=\"isSelected(i)\"\n (click)=\"tagClickHandler($event, tag, i); $event.stopPropagation()\"\n (focus)=\"selectTagAt(i)\">\n <ng-container [ngTemplateOutlet]=\"tagTemplate || defaultTagTemplate\"\n [ngTemplateOutletContext]=\"{tag: tag, index: i, disabled: disabled, api: tagApi}\">\n </ng-container>\n </li>\n }\n @if (isInputVisible()) {\n <li class=\"ux-tag-input\">\n <input #tagInput type=\"text\" attr.id=\"{{id}}-input\" class=\"ux-tag-input\"\n [ngModel]=\"input\"\n (ngModelChange)=\"setInputValue($event)\"\n [autocomplete]=\"autocomplete\"\n [class.invalid]=\"!inputValid\"\n [required]=\"required\"\n [attr.aria-activedescendant]=\"highlightedElement?.id\"\n [attr.aria-autocomplete]=\"typeahead ? 'list' : 'none'\"\n [attr.aria-controls]=\"typeahead?.id\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.aria-labelledby]=\"ariaLabelledby\"\n aria-multiline=\"false\"\n [placeholder]=\"disabled ? '' : (placeholder || '')\"\n [disabled]=\"disabled\"\n [focusIf]=\"isSelected(_tags.length)\"\n (click)=\"toggle(); $event.stopPropagation()\"\n (focus)=\"inputFocusHandler(); inputFocus.emit($event)\"\n (blur)=\"inputBlur.emit($event)\"\n (paste)=\"inputPasteHandler($event)\"\n [readonly]=\"readonlyInput\">\n </li>\n }\n </ol>\n\n <!-- Insert the custom icon if provided -->\n @if (icon || _showClearButton) {\n <div class=\"ux-tag-icons\" (click)=\"toggle(); $event.stopPropagation()\">\n <!-- Clear All Button -->\n @if (_showClearButton) {\n <i uxFocusIndicator\n class=\"ux-tag-icon ux-icon ux-icon-close ux-select-clear-icon\"\n [attr.tabindex]=\"disabled ? -1 : 0\"\n [attr.aria-label]=\"clearButtonAriaLabel\"\n (click)=\"clear(); $event.stopPropagation()\"\n (keydown.enter)=\"clear(); $event.stopPropagation()\">\n </i>\n }\n <!-- Custom Icon -->\n @if (icon) {\n <div class=\"ux-custom-icon\">\n <ng-container [ngTemplateOutlet]=\"icon\"></ng-container>\n </div>\n }\n </div>\n }\n\n <ng-content #typeahead></ng-content>\n\n <ng-template #defaultTagTemplate let-tag=\"tag\" let-index=\"index\" let-disabled=\"disabled\" let-api=\"api\">\n <span class=\"ux-tag-text\">{{ api.getTagDisplay(tag) }}</span>\n @if (api.canRemoveTagAt(index)) {\n <button\n uxFocusIndicator\n type=\"button\"\n class=\"ux-tag-remove\"\n aria-label=\"Remove Item\"\n [disabled]=\"disabled\"\n (click)=\"api.removeTagAt(index); $event.stopPropagation();\">\n <ux-icon name=\"close\"></ux-icon>\n </button>\n }\n </ng-template>\n" }] }], propDecorators: { id: [{ type: Input }, { type: HostBinding, args: ['attr.id'] }], tags: [{ type: Input }], input: [{ type: Input }], display: [{ type: Input }], addOnPaste: [{ type: Input }], ariaLabel: [{ type: Input }], ariaLabelledby: [{ type: Input }], disabled: [{ type: Input }], required: [{ type: Input }], enforceTagLimits: [{ type: Input }], freeInput: [{ type: Input }], readonlyInput: [{ type: Input }], maxTags: [{ type: Input }], minTags: [{ type: Input }], placeholder: [{ type: Input }], showTypeaheadOnClick: [{ type: Input }], tagDelimiters: [{ type: Input }], tagPattern: [{ type: Input }], tagTemplate: [{ type: Input }], tagClass: [{ type: Input }], validationErrors: [{ type: Input }], autocomplete: [{ type: Input }], createTagHandler: [{ type: Input, args: ['createTag'] }], icon: [{ type: Input }], clearButton: [{ type: Input }], clearButtonAriaLabel: [{ type: Input }], autoCloseDropdown: [{ type: Input }], tagsChange: [{ type: Output }], inputChange: [{ type: Output }], tagAdding: [{ type: Output }], tagAdded: [{ type: Output }], tagInvalidated: [{ type: Output }], tagRemoving: [{ type: Output }], tagRemoved: [{ type: Output }], tagClick: [{ type: Output }], inputFocus: [{ type: Output }], inputBlur: [{ type: Output }], typeaheadQuery: [{ type: ContentChildren, args: [TypeaheadComponent] }], tagInput: [{ type: ViewChild, args: ['tagInput', { static: false }] }], keyHandler: [{ type: HostListener, args: ['keydown', ['$event']] }], focusOutHandler: [{ type: HostListener, args: ['focusout'] }], onClick: [{ type: HostListener, args: ['click'] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGFnLWlucHV0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jb21wb25lbnRzL3RhZy1pbnB1dC90YWctaW5wdXQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvdGFnLWlucHV0L3RhZy1pbnB1dC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQWdCLHFCQUFxQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDNUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDakcsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzNDLE9BQU8sRUFBb0IsdUJBQXVCLEVBQUUsaUJBQWlCLEVBQUUsU0FBUyxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUF3QixNQUFNLEVBQXlDLFNBQVMsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3pSLE9BQU8sRUFBd0IsYUFBYSxFQUFFLGlCQUFpQixFQUErQixNQUFNLGdCQUFnQixDQUFDO0FBQ3JILE9BQU8sRUFBRSxPQUFPLEVBQWdCLE1BQU0sTUFBTSxDQUFDO0FBQzdDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDMUMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFN0UsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLG1CQUFtQixDQUFDOzs7Ozs7O0FBRWxELElBQUksUUFBUSxHQUFHLENBQUMsQ0FBQztB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