@ux-aspects/ux-aspects
Version:
Open source user interface framework for building modern, responsive, mobile big data applications
762 lines • 113 kB
JavaScript
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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