carbon-components-angular
Version:
Next generation components
1,117 lines (1,112 loc) • 42.2 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, TemplateRef, Component, Input, Output, ContentChild, ViewChild, HostBinding, HostListener, NgModule } from '@angular/core';
import * as i1 from 'carbon-components-angular/dropdown';
import { AbstractDropdownView, DropdownModule, DropdownService } from 'carbon-components-angular/dropdown';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { filter } from 'rxjs/operators';
import { hasScrollableParents, getScrollableParents, UtilsModule } from 'carbon-components-angular/utils';
import * as i2 from 'carbon-components-angular/i18n';
import { I18nModule } from 'carbon-components-angular/i18n';
import * as i3 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i4 from 'carbon-components-angular/icon';
import { IconModule } from 'carbon-components-angular/icon';
/**
* Get started with importing the module:
*
* ```typescript
* import { ComboBoxModule } from 'carbon-components-angular';
* ```
*
* ComboBoxes are similar to dropdowns, except a combobox provides an input field for users to search items and (optionally) add their own.
* Multi-select comboboxes also provide "pills" of selected items.
*
* [See demo](../../?path=/story/components-combobox--basic)
*/
class ComboBox {
/**
* Creates an instance of ComboBox.
*/
constructor(elementRef, dropdownService, i18n) {
this.elementRef = elementRef;
this.dropdownService = dropdownService;
this.i18n = i18n;
this.id = `combobox-${ComboBox.comboBoxCount++}`;
this.labelId = `combobox-label-${ComboBox.comboBoxCount++}`;
/**
* List of items to fill the content with.
*
* **Example:**
* ```javascript
* items = [
* {
* content: "Abacus",
* selected: false
* },
* {
* content: "Byte",
* selected: false,
* },
* {
* content: "Computer",
* selected: false
* },
* {
* content: "Digital",
* selected: false
* }
* ];
* ```
*
*/
this.items = [];
/**
* Combo box type (supporting single or multi selection of items).
*/
this.type = "single";
/**
* Combo box render size.
*/
this.size = "md";
/**
* Hide label while keeping it accessible for screen readers
*/
this.hideLabel = false;
/**
* set to `true` to place the dropdown view inline with the component
*/
this.appendInline = null;
/**
* Set to `true` to show the invalid state.
*/
this.invalid = false;
/**
* Set to `true` to show a warning (contents set by warnText)
*/
this.warn = false;
/**
* Max length value to limit input characters
*/
this.maxLength = null;
/**
* @deprecated since v5 - Use `cdsLayer` directive instead
*/
this.theme = "dark";
/**
* Specify feedback (mode) of the selection.
* `top`: selected item jumps to top
* `fixed`: selected item stays at its position
* `top-after-reopen`: selected item jump to top after reopen dropdown
*/
this.selectionFeedback = "top-after-reopen";
/**
* Specify aria-autocomplete attribute of text input.
* "list", is the expected value for a combobox that invokes a drop-down list
*/
this.autocomplete = "list";
/**
* Set to `true` to disable combobox.
*/
this.disabled = false;
/**
* Set to `true` for readonly state.
*/
this.readonly = false;
/**
* Experimental: enable fluid state
*/
this.fluid = false;
/**
* Emits a ListItem
*
* Example:
* ```javascript
* {
* content: "one",
* selected: true
* }
* ```
*/
this.selected = new EventEmitter();
/**
* Intended to be used to add items to the list.
*
* Emits an event that includes the current item list, the suggested index for the new item, and a simple ListItem
*
* Example:
* ```javascript
* {
* items: [{content: "one", selected: true}, {content: "two", selected: true}],
* index: 1,
* value: {
* content: "some user string",
* selected: false
* }
* }
* ```
*
*
* Example:
* ```javascript
* {
* after: 1,
* value: "some user string"
* }
* ```
*/
this.submit = new EventEmitter();
/** Emits an empty event when the menu is closed */
this.close = new EventEmitter();
/** Emits the search string from the input */
this.search = new EventEmitter();
/** Emits an event when the clear button is clicked. */
this.clear = new EventEmitter();
this.hostClass = true;
this.open = false;
this.showClearButton = false;
/** Selected items for multi-select combo-boxes. */
this.pills = [];
/** used to update the displayValue */
this.selectedValue = "";
this.outsideClick = this._outsideClick.bind(this);
this.keyboardNav = this._keyboardNav.bind(this);
/**
* controls whether the `drop-up` class is applied
*/
this._dropUp = false;
this.noop = this._noop.bind(this);
this.onTouchedCallback = this._noop;
this.propagateChangeCallback = this._noop;
this._placeholder = this.i18n.getOverridable("COMBOBOX.PLACEHOLDER");
this._closeMenuAria = this.i18n.getOverridable("COMBOBOX.A11Y.CLOSE_MENU");
this._openMenuAria = this.i18n.getOverridable("COMBOBOX.A11Y.OPEN_MENU");
this._clearSelectionsTitle = this.i18n.getOverridable("COMBOBOX.CLEAR_SELECTIONS");
this._clearSelectionsAria = this.i18n.getOverridable("COMBOBOX.A11Y.CLEAR_SELECTIONS");
this._clearSelectionTitle = this.i18n.getOverridable("COMBOBOX.CLEAR_SELECTED");
this._clearSelectionAria = this.i18n.getOverridable("COMBOBOX.A11Y.CLEAR_SELECTED");
this._isFocused = false;
}
/**
* Text to show when nothing is selected.
*/
set placeholder(value) {
this._placeholder.override(value);
}
get placeholder() {
return this._placeholder.value;
}
/**
* Value to display for accessibility purposes on the combobox control menu when closed
*/
set openMenuAria(value) {
this._openMenuAria.override(value);
}
get openMenuAria() {
return this._openMenuAria.value;
}
/**
* Value to display for accessibility purposes on the combobox control menu when opened
*/
set closeMenuAria(value) {
this._closeMenuAria.override(value);
}
get closeMenuAria() {
return this._closeMenuAria.value;
}
/**
* Value to display on the clear selections icon, when multi is selected
*/
set clearSelectionsTitle(value) {
this._clearSelectionsTitle.override(value);
}
get clearSelectionsTitle() {
return this._clearSelectionsTitle.value;
}
/**
* Value to display for accessibility purposes to clear selections, when multi is selected
*/
set clearSelectionsAria(value) {
this._clearSelectionsAria.override(value);
}
get clearSelectionsAria() {
return this._clearSelectionsAria.value;
}
/**
* Value to display on the clear the selected item icon, when single is selected
*/
set clearSelectionTitle(value) {
this._clearSelectionTitle.override(value);
}
get clearSelectionTitle() {
return this._clearSelectionTitle.value;
}
/**
* Value to display for accessibility purposes on the clear the selected item icon, when single is selected
*/
set clearSelectionAria(value) {
this._clearSelectionAria.override(value);
}
get clearSelectionAria() {
return this._clearSelectionAria.value;
}
/**
* Lifecycle hook.
* Updates pills if necessary.
*
*/
ngOnChanges(changes) {
if (changes.items) {
this.view.items = changes.items.currentValue;
this.updateSelected();
// If new items are added into the combobox while there is search input,
// repeat the search. Search should only trigger for type 'single' when there is no value selected.
if (this.type === "multi" || (this.type === "single" && !this.selectedValue)) {
this.onSearch(this.input.nativeElement.value, false);
}
}
}
/**
* Sets initial state that depends on child components
* Subscribes to select events and handles focus/filtering/initial list updates
*/
ngAfterContentInit() {
if (this.view) {
this.view.type = this.type;
// function to check if the event is organic (isUpdate === false) or programmatic
const isUpdate = event => event && event.isUpdate;
this.view.select.subscribe(event => {
if (Array.isArray(event)) {
this.updatePills();
if (!isUpdate(event)) {
if (this.itemValueKey && this.view.getSelected()) {
const values = this.view.getSelected().map(item => item[this.itemValueKey]);
this.propagateChangeCallback(values);
// otherwise just pass up the values from `getSelected`
}
else {
this.propagateChangeCallback(this.view.getSelected());
}
this.selected.emit(event);
}
}
else {
// If type is single, dropdown list will emit an object
if (event.item && event.item.selected) {
this.showClearButton = true;
this.selectedValue = event.item.content;
if (!isUpdate(event)) {
if (this.itemValueKey) {
this.propagateChangeCallback(event.item[this.itemValueKey]);
}
else {
this.propagateChangeCallback(event.item);
}
}
}
else {
this.selectedValue = "";
if (!isUpdate(event)) {
this.propagateChangeCallback(null);
}
}
// not guarding these since the nativeElement has to be loaded
// for select to even fire
// only focus for "organic" selections
if (!isUpdate(event)) {
this.elementRef.nativeElement.querySelector("input").focus();
this.view.filterBy("");
this.selected.emit(event.item);
}
this.closeDropdown();
}
});
// update the rest of combobox with any pre-selected items
// setTimeout just defers the call to the next check cycle
setTimeout(() => {
this.updateSelected();
});
this.view.blurIntent.pipe(filter(v => v === "top")).subscribe(() => {
this.elementRef.nativeElement.querySelector(".cds--text-input").focus();
});
}
}
/**
* Binds event handlers against the rendered view
*/
ngAfterViewInit() {
// if appendInline is default valued (null) we should:
// 1. if there are scrollable parents (not including body) don't append inline
// this should also cover the case where the dropdown is in a modal
// (where we _do_ want to append to the placeholder)
if (this.appendInline === null && hasScrollableParents(this.elementRef.nativeElement)) {
this.appendInline = false;
// 2. otherwise we should append inline
}
else if (this.appendInline === null) {
this.appendInline = true;
}
}
/**
* Removing the `Dropdown` from the body if it is appended to the body.
*/
ngOnDestroy() {
if (!this.appendInline) {
this._appendToDropdown();
}
}
/**
* Handles `Escape/Tab` key closing the dropdown, and arrow up/down focus to/from the dropdown list.
*/
hostkeys(ev) {
if (ev.key === "Escape") {
this.closeDropdown();
}
else if ((ev.key === "ArrowDown")
&& (!this.dropdownMenu || !this.dropdownMenu.nativeElement.contains(ev.target))) {
ev.preventDefault();
this.openDropdown();
setTimeout(() => { this.view.initFocus(); }, 0);
}
if (this.open && ev.key === "Tab" &&
(this.dropdownMenu.nativeElement.contains(ev.target) || ev.target === this.input.nativeElement)) {
this.closeDropdown();
}
if (this.open && ev.key === "Tab" && ev.shiftKey) {
this.closeDropdown();
}
}
/*
* no-op method for null event listeners, and other no op calls
*/
_noop() { }
/*
* propagates the value provided from ngModel
*/
writeValue(value) {
if (this.type === "single") {
if (this.itemValueKey) {
// clone the specified item and update its state
const newValue = Object.assign({}, this.view.getListItems().find(item => item[this.itemValueKey] === value));
newValue.selected = true;
this.view.propagateSelected([newValue]);
}
else {
// all items in propagateSelected must be iterable
this.view.propagateSelected([value || ""]);
}
this.showClearButton = !!(value && this.view.getSelected().length);
}
else {
if (this.itemValueKey) {
// clone the items and update their state based on the received value array
// this way we don't lose any additional metadata that may be passed in via the `items` Input
let newValues = [];
for (const v of value !== null && value !== void 0 ? value : []) {
for (const item of this.view.getListItems()) {
if (item[this.itemValueKey] === v) {
newValues.push(Object.assign({}, item, { selected: true }));
}
}
}
this.view.propagateSelected(newValues);
}
else {
this.view.propagateSelected(value ? value : [""]);
}
}
this.updateSelected();
}
onBlur() {
this.onTouchedCallback();
}
registerOnChange(fn) {
this.propagateChangeCallback = fn;
}
registerOnTouched(fn) {
this.onTouchedCallback = fn;
}
/**
* `ControlValueAccessor` method to programmatically disable the combobox.
*
* ex: `this.formGroup.get("myCoolCombobox").disable();`
*/
setDisabledState(isDisabled) {
this.disabled = isDisabled;
}
/**
* Called by `n-pill-input` when the selected pills have changed.
*/
updatePills() {
this.pills = this.view.getSelected() || [];
this.checkForReorder();
}
clearSelected(event) {
this.items = this.items.map(item => {
if (!item.disabled) {
item.selected = false;
}
return item;
});
this.view.items = this.items;
this.updatePills();
// clearSelected can only fire on type=multi
// so we just emit getSelected() (just in case there's any disabled but selected items)
const selected = this.view.getSelected();
// in case there are disabled items they should be mapped according to itemValueKey
if (this.itemValueKey && selected) {
const values = selected.map((item) => item[this.itemValueKey]);
this.propagateChangeCallback(values);
}
else {
this.propagateChangeCallback(selected);
}
this.selected.emit(selected);
this.clear.emit(event);
}
/**
* Closes the dropdown and emits the close event.
*/
closeDropdown() {
this.open = false;
this.checkForReorder();
this.close.emit();
if (!this.appendInline) {
this._appendToDropdown();
}
document.removeEventListener("click", this.outsideClick, true);
}
/**
* Opens the dropdown.
*/
openDropdown() {
if (this.disabled || this.readonly) {
return;
}
this.open = true;
this._dropUp = false;
if (!this.appendInline) {
this._appendToBody();
}
document.addEventListener("click", this.outsideClick, true);
// set the dropdown menu to drop up if it is near the bottom of the screen
// setTimeout lets us do the calculations after it is visible in the DOM
setTimeout(() => {
if (this.dropUp === null || this.dropUp === undefined) {
this._dropUp = this._shouldDropUp();
}
}, 0);
}
/**
* Toggles the dropdown.
*/
toggleDropdown() {
if (this.open) {
this.closeDropdown();
}
else {
this.openDropdown();
}
}
/**
* Sets the list group filter, and manages single select item selection.
*/
onSearch(searchString, shouldEmitSearch = true) {
if (shouldEmitSearch) {
this.search.emit(searchString);
}
this.showClearButton = !!searchString;
this.view.filterBy(searchString);
if (searchString !== "") {
if (!this.open) {
this.openDropdown();
}
}
else {
this.selectedValue = "";
if (this.type === "multi" &&
(this.selectionFeedback === "top" || this.selectionFeedback === "top-after-reopen")) {
this.view.reorderSelected();
}
}
if (this.type === "single") {
// deselect if the input doesn't match the content
// of any given item
const matches = this.view.getListItems().some(item => item.content.toLowerCase().includes(searchString.toLowerCase()));
if (!matches) {
const selected = this.view.getSelected();
if (!selected || !selected[0]) {
this.view.filterBy(searchString);
}
}
}
}
/**
* Intended to be used to add items to the list.
*/
onSubmit(event) {
this.submit.emit({
items: this.view.getListItems(),
index: 0,
value: {
content: event.target.value,
selected: false
}
});
}
clearInput(event) {
event.stopPropagation();
event.preventDefault();
if (this.disabled || this.readonly) {
return;
}
if (this.type === "single") { // don't want to clear selected or close if multi
this.clearSelected(event);
this.closeDropdown();
}
this.selectedValue = "";
this.input.nativeElement.value = "";
this.showClearButton = false;
this.input.nativeElement.focus();
this.onSearch(this.input.nativeElement.value);
}
isTemplate(value) {
return value instanceof TemplateRef;
}
/**
* Handles keyboard events so users are controlling the `Dropdown` instead of unintentionally controlling outside elements.
*/
_keyboardNav(event) {
if ((event.key === "Escape") && this.open) {
event.stopImmediatePropagation(); // don't unintentionally close modal if inside of it
}
if (event.key === "Escape") {
event.preventDefault();
this.closeDropdown();
this.input.nativeElement.focus();
}
else if (this.open && event.key === "Tab") {
// this way focus will start on the next focusable item from the dropdown
// not the top of the body!
this.input.nativeElement.focus();
this.input.nativeElement.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: "Tab" }));
this.closeDropdown();
}
}
/**
* Creates the `Dropdown` list as an element that is appended to the DOM body.
*/
_appendToBody() {
this.dropdownService.appendToBody(this.listbox.nativeElement, this.dropdownMenu.nativeElement, `${this.elementRef.nativeElement.className}${this.open ? " cds--list-box--expanded" : ""}`);
this.dropdownMenu.nativeElement.addEventListener("keydown", this.keyboardNav, true);
}
/**
* Creates the `Dropdown` list appending it to the dropdown parent object instead of the body.
*/
_appendToDropdown() {
this.dropdownService.appendToDropdown(this.elementRef.nativeElement);
this.dropdownMenu.nativeElement.removeEventListener("keydown", this.keyboardNav, true);
}
/**
* Detects whether or not the `Dropdown` list is visible within all scrollable parents.
* This can be overridden by passing in a value to the `dropUp` input.
*/
_shouldDropUp() {
// check if dropdownMenu exists first.
const menu = this.dropdownMenu && this.dropdownMenu.nativeElement.querySelector(".cds--list-box__menu");
// check if menu exists first.
const menuRect = menu && menu.getBoundingClientRect();
if (menu && menuRect) {
const scrollableParents = getScrollableParents(menu);
return scrollableParents.reduce((shouldDropUp, parent) => {
const parentRect = parent.getBoundingClientRect();
const isBelowParent = !(menuRect.bottom <= parentRect.bottom);
return shouldDropUp || isBelowParent;
}, false);
}
return false;
}
/**
* Handles clicks outside of the `Dropdown` list.
*/
_outsideClick(event) {
if (!this.elementRef.nativeElement.contains(event.target) &&
// if we're appendToBody the list isn't within the _elementRef,
// so we've got to check if our target is possibly in there too.
!this.dropdownMenu.nativeElement.contains(event.target)) {
this.closeDropdown();
}
}
handleFocus(event) {
this._isFocused = event.type === "focus";
}
updateSelected() {
const selected = this.view.getSelected();
if (this.type === "multi") {
this.updatePills();
}
else if (selected) {
const value = selected[0] ? selected[0].content : "";
const changeCallbackValue = selected[0] ? selected[0] : "";
this.selectedValue = value;
this.showClearButton = !!value;
}
}
checkForReorder() {
const topAfterReopen = !this.open && this.selectionFeedback === "top-after-reopen";
if ((this.type === "multi") && (topAfterReopen || this.selectionFeedback === "top")) {
this.view.reorderSelected(true);
}
}
}
ComboBox.comboBoxCount = 0;
ComboBox.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboBox, deps: [{ token: i0.ElementRef }, { token: i1.DropdownService }, { token: i2.I18n }], target: i0.ɵɵFactoryTarget.Component });
ComboBox.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: ComboBox, selector: "cds-combo-box, ibm-combo-box", inputs: { placeholder: "placeholder", openMenuAria: "openMenuAria", closeMenuAria: "closeMenuAria", clearSelectionsTitle: "clearSelectionsTitle", clearSelectionsAria: "clearSelectionsAria", clearSelectionTitle: "clearSelectionTitle", clearSelectionAria: "clearSelectionAria", id: "id", labelId: "labelId", items: "items", type: "type", size: "size", itemValueKey: "itemValueKey", label: "label", hideLabel: "hideLabel", helperText: "helperText", appendInline: "appendInline", invalid: "invalid", invalidText: "invalidText", warn: "warn", warnText: "warnText", maxLength: "maxLength", theme: "theme", selectionFeedback: "selectionFeedback", autocomplete: "autocomplete", dropUp: "dropUp", disabled: "disabled", readonly: "readonly", fluid: "fluid" }, outputs: { selected: "selected", submit: "submit", close: "close", search: "search", clear: "clear" }, host: { listeners: { "keydown": "hostkeys($event)" }, properties: { "class.cds--list-box__wrapper": "this.hostClass" } }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ComboBox,
multi: true
}
], queries: [{ propertyName: "view", first: true, predicate: AbstractDropdownView, descendants: true, static: true }], viewQueries: [{ propertyName: "dropdownMenu", first: true, predicate: ["dropdownMenu"], descendants: true }, { propertyName: "input", first: true, predicate: ["input"], descendants: true, static: true }, { propertyName: "listbox", first: true, predicate: ["listbox"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
<div
class="cds--list-box__wrapper"
[ngClass]="{
'cds--list-box__wrapper--fluid': fluid,
'cds--list-box__wrapper--fluid--invalid': fluid && invalid,
'cds--list-box__wrapper--fluid--focus': fluid && _isFocused
}">
<label
*ngIf="label"
[for]="id"
[id]="labelId"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel
}">
<ng-container *ngIf="!isTemplate(label)">{{label}}</ng-container>
<ng-template *ngIf="isTemplate(label)" [ngTemplateOutlet]="label"></ng-template>
</label>
<div
#listbox
[ngClass]="{
'cds--multi-select cds--multi-select--filterable': type === 'multi',
'cds--list-box--light': theme === 'light',
'cds--list-box--expanded': open,
'cds--list-box--sm': size === 'sm',
'cds--list-box--md': size === 'md',
'cds--list-box--lg': size === 'lg',
'cds--list-box--disabled': disabled,
'cds--combo-box--readonly': readonly,
'cds--combo-box--warning cds--list-box--warning': warn,
'cds--list-box--invalid': invalid
}"
class="cds--list-box cds--combo-box"
[attr.data-invalid]="(invalid ? true : null)">
<div
class="cds--list-box__field"
(click)="toggleDropdown()"
(blur)="onBlur()">
<div
*ngIf="type === 'multi' && pills.length > 0"
class="cds--tag cds--tag--filter cds--tag--high-contrast"
[ngClass]="{'cds--tag--disabled': disabled || readonly}">
<span class="cds--tag__label">{{ pills.length }}</span>
<button
type="button"
(click)="clearSelected($event)"
(blur)="onBlur()"
(keydown.enter)="clearSelected($event)"
class="cds--tag__close-icon"
tabindex="0"
[title]="clearSelectionsTitle"
[disabled]="disabled || readonly"
[attr.aria-label]="clearSelectionAria">
<svg
focusable="false"
preserveAspectRatio="xMidYMid meet"
style="will-change: transform;"
role="img"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true">
<path d="M12 4.7l-.7-.7L8 7.3 4.7 4l-.7.7L7.3 8 4 11.3l.7.7L8 8.7l3.3 3.3.7-.7L8.7 8z"></path>
</svg>
</button>
</div>
<input
#input
type="text"
autocomplete="off"
role="combobox"
[disabled]="disabled"
[readOnly]="readonly"
(input)="onSearch($event.target.value)"
(focus)="fluid ? handleFocus($event) : null"
(blur)="fluid ? handleFocus($event) : onBlur()"
(keydown.enter)="onSubmit($event)"
[value]="selectedValue"
class="cds--text-input"
[ngClass]="{'cds--text-input--empty': !showClearButton}"
tabindex="0"
[id]="id"
[attr.aria-labelledby]="labelId"
[attr.aria-expanded]="open"
aria-haspopup="listbox"
[attr.maxlength]="maxLength"
[attr.aria-controls]="open ? view?.listId : null"
[attr.aria-autocomplete]="autocomplete"
[placeholder]="placeholder"/>
<svg
*ngIf="invalid"
cdsIcon="warning--filled"
size="16"
class="cds--list-box__invalid-icon">
</svg>
<svg
*ngIf="!invalid && warn"
cdsIcon="warning--alt--filled"
size="16"
class="cds--list-box__invalid-icon cds--list-box__invalid-icon--warning">
</svg>
<div
*ngIf="showClearButton"
role="button"
class="cds--list-box__selection"
tabindex="0"
[attr.aria-label]="clearSelectionAria"
[title]="clearSelectionTitle"
(keyup.enter)="clearInput($event)"
(click)="clearInput($event)"
(blur)="onBlur()">
<svg cdsIcon="close" size="16"></svg>
</div>
<button
type="button"
role="button"
class="cds--list-box__menu-icon"
tabindex="-1"
[title]="open ? closeMenuAria : openMenuAria"
[attr.aria-label]="open ? closeMenuAria : openMenuAria"
[ngClass]="{'cds--list-box__menu-icon--open': open}">
<svg cdsIcon="chevron--down" size="16"></svg>
</button>
</div>
<div
#dropdownMenu
[ngClass]="{
'cds--list-box--up': this.dropUp !== null && this.dropUp !== undefined ? dropUp : _dropUp
}">
<ng-content *ngIf="open"></ng-content>
</div>
</div>
<hr *ngIf="fluid" class="cds--list-box__divider" />
<div
*ngIf="helperText && !invalid && !warn && !fluid"
class="cds--form__helper-text"
[ngClass]="{'cds--form__helper-text--disabled': disabled}">
<ng-container *ngIf="!isTemplate(helperText)">{{helperText}}</ng-container>
<ng-template *ngIf="isTemplate(helperText)" [ngTemplateOutlet]="helperText"></ng-template>
</div>
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{ invalidText }}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</div>
`, isInline: true, dependencies: [{ kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i4.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboBox, decorators: [{
type: Component,
args: [{
selector: "cds-combo-box, ibm-combo-box",
template: `
<div
class="cds--list-box__wrapper"
[ngClass]="{
'cds--list-box__wrapper--fluid': fluid,
'cds--list-box__wrapper--fluid--invalid': fluid && invalid,
'cds--list-box__wrapper--fluid--focus': fluid && _isFocused
}">
<label
*ngIf="label"
[for]="id"
[id]="labelId"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel
}">
<ng-container *ngIf="!isTemplate(label)">{{label}}</ng-container>
<ng-template *ngIf="isTemplate(label)" [ngTemplateOutlet]="label"></ng-template>
</label>
<div
#listbox
[ngClass]="{
'cds--multi-select cds--multi-select--filterable': type === 'multi',
'cds--list-box--light': theme === 'light',
'cds--list-box--expanded': open,
'cds--list-box--sm': size === 'sm',
'cds--list-box--md': size === 'md',
'cds--list-box--lg': size === 'lg',
'cds--list-box--disabled': disabled,
'cds--combo-box--readonly': readonly,
'cds--combo-box--warning cds--list-box--warning': warn,
'cds--list-box--invalid': invalid
}"
class="cds--list-box cds--combo-box"
[attr.data-invalid]="(invalid ? true : null)">
<div
class="cds--list-box__field"
(click)="toggleDropdown()"
(blur)="onBlur()">
<div
*ngIf="type === 'multi' && pills.length > 0"
class="cds--tag cds--tag--filter cds--tag--high-contrast"
[ngClass]="{'cds--tag--disabled': disabled || readonly}">
<span class="cds--tag__label">{{ pills.length }}</span>
<button
type="button"
(click)="clearSelected($event)"
(blur)="onBlur()"
(keydown.enter)="clearSelected($event)"
class="cds--tag__close-icon"
tabindex="0"
[title]="clearSelectionsTitle"
[disabled]="disabled || readonly"
[attr.aria-label]="clearSelectionAria">
<svg
focusable="false"
preserveAspectRatio="xMidYMid meet"
style="will-change: transform;"
role="img"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true">
<path d="M12 4.7l-.7-.7L8 7.3 4.7 4l-.7.7L7.3 8 4 11.3l.7.7L8 8.7l3.3 3.3.7-.7L8.7 8z"></path>
</svg>
</button>
</div>
<input
#input
type="text"
autocomplete="off"
role="combobox"
[disabled]="disabled"
[readOnly]="readonly"
(input)="onSearch($event.target.value)"
(focus)="fluid ? handleFocus($event) : null"
(blur)="fluid ? handleFocus($event) : onBlur()"
(keydown.enter)="onSubmit($event)"
[value]="selectedValue"
class="cds--text-input"
[ngClass]="{'cds--text-input--empty': !showClearButton}"
tabindex="0"
[id]="id"
[attr.aria-labelledby]="labelId"
[attr.aria-expanded]="open"
aria-haspopup="listbox"
[attr.maxlength]="maxLength"
[attr.aria-controls]="open ? view?.listId : null"
[attr.aria-autocomplete]="autocomplete"
[placeholder]="placeholder"/>
<svg
*ngIf="invalid"
cdsIcon="warning--filled"
size="16"
class="cds--list-box__invalid-icon">
</svg>
<svg
*ngIf="!invalid && warn"
cdsIcon="warning--alt--filled"
size="16"
class="cds--list-box__invalid-icon cds--list-box__invalid-icon--warning">
</svg>
<div
*ngIf="showClearButton"
role="button"
class="cds--list-box__selection"
tabindex="0"
[attr.aria-label]="clearSelectionAria"
[title]="clearSelectionTitle"
(keyup.enter)="clearInput($event)"
(click)="clearInput($event)"
(blur)="onBlur()">
<svg cdsIcon="close" size="16"></svg>
</div>
<button
type="button"
role="button"
class="cds--list-box__menu-icon"
tabindex="-1"
[title]="open ? closeMenuAria : openMenuAria"
[attr.aria-label]="open ? closeMenuAria : openMenuAria"
[ngClass]="{'cds--list-box__menu-icon--open': open}">
<svg cdsIcon="chevron--down" size="16"></svg>
</button>
</div>
<div
#dropdownMenu
[ngClass]="{
'cds--list-box--up': this.dropUp !== null && this.dropUp !== undefined ? dropUp : _dropUp
}">
<ng-content *ngIf="open"></ng-content>
</div>
</div>
<hr *ngIf="fluid" class="cds--list-box__divider" />
<div
*ngIf="helperText && !invalid && !warn && !fluid"
class="cds--form__helper-text"
[ngClass]="{'cds--form__helper-text--disabled': disabled}">
<ng-container *ngIf="!isTemplate(helperText)">{{helperText}}</ng-container>
<ng-template *ngIf="isTemplate(helperText)" [ngTemplateOutlet]="helperText"></ng-template>
</div>
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{ invalidText }}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ComboBox,
multi: true
}
]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.DropdownService }, { type: i2.I18n }]; }, propDecorators: { placeholder: [{
type: Input
}], openMenuAria: [{
type: Input
}], closeMenuAria: [{
type: Input
}], clearSelectionsTitle: [{
type: Input
}], clearSelectionsAria: [{
type: Input
}], clearSelectionTitle: [{
type: Input
}], clearSelectionAria: [{
type: Input
}], id: [{
type: Input
}], labelId: [{
type: Input
}], items: [{
type: Input
}], type: [{
type: Input
}], size: [{
type: Input
}], itemValueKey: [{
type: Input
}], label: [{
type: Input
}], hideLabel: [{
type: Input
}], helperText: [{
type: Input
}], appendInline: [{
type: Input
}], invalid: [{
type: Input
}], invalidText: [{
type: Input
}], warn: [{
type: Input
}], warnText: [{
type: Input
}], maxLength: [{
type: Input
}], theme: [{
type: Input
}], selectionFeedback: [{
type: Input
}], autocomplete: [{
type: Input
}], dropUp: [{
type: Input
}], disabled: [{
type: Input
}], readonly: [{
type: Input
}], fluid: [{
type: Input
}], selected: [{
type: Output
}], submit: [{
type: Output
}], close: [{
type: Output
}], search: [{
type: Output
}], clear: [{
type: Output
}], view: [{
type: ContentChild,
args: [AbstractDropdownView, { static: true }]
}], dropdownMenu: [{
type: ViewChild,
args: ["dropdownMenu"]
}], input: [{
type: ViewChild,
args: ["input", { static: true }]
}], listbox: [{
type: ViewChild,
args: ["listbox", { static: true }]
}], hostClass: [{
type: HostBinding,
args: ["class.cds--list-box__wrapper"]
}], hostkeys: [{
type: HostListener,
args: ["keydown", ["$event"]]
}] } });
class ComboBoxModule {
}
ComboBoxModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboBoxModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
ComboBoxModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: ComboBoxModule, declarations: [ComboBox], imports: [CommonModule,
DropdownModule,
I18nModule,
UtilsModule,
IconModule], exports: [ComboBox,
DropdownModule] });
ComboBoxModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboBoxModule, providers: [DropdownService], imports: [CommonModule,
DropdownModule,
I18nModule,
UtilsModule,
IconModule, DropdownModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ComboBoxModule, decorators: [{
type: NgModule,
args: [{
declarations: [
ComboBox
],
exports: [
ComboBox,
DropdownModule
],
imports: [
CommonModule,
DropdownModule,
I18nModule,
UtilsModule,
IconModule
],
providers: [DropdownService]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { ComboBox, ComboBoxModule };
//# sourceMappingURL=carbon-components-angular-combobox.mjs.map