UNPKG

igniteui-webcomponents

Version:

Ignite UI for Web Components is a complete library of UI components, giving you the ability to build modern web applications using encapsulation and the concept of reusable components in a dependency-free approach.

666 lines 22.8 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var IgcComboComponent_1; import { LitElement, html } from 'lit'; import { property, query, queryAssignedElements, state, } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import { themes } from '../../theming/theming-decorator.js'; import { addRootClickHandler } from '../common/controllers/root-click.js'; import { blazorAdditionalDependencies } from '../common/decorators/blazorAdditionalDependencies.js'; import { blazorIndirectRender } from '../common/decorators/blazorIndirectRender.js'; import { watch } from '../common/decorators/watch.js'; import { registerComponent } from '../common/definitions/register.js'; import { EventEmitterMixin } from '../common/mixins/event-emitter.js'; import { FormAssociatedRequiredMixin } from '../common/mixins/form-associated-required.js'; import { partNameMap } from '../common/util.js'; import IgcIconComponent from '../icon/icon.js'; import IgcInputComponent from '../input/input.js'; import IgcPopoverComponent from '../popover/popover.js'; import IgcComboHeaderComponent from './combo-header.js'; import IgcComboItemComponent from './combo-item.js'; import IgcComboListComponent from './combo-list.js'; import { DataController } from './controllers/data.js'; import { NavigationController } from './controllers/navigation.js'; import { SelectionController } from './controllers/selection.js'; import { styles } from './themes/combo.base.css.js'; import { styles as shared } from './themes/shared/combo.common.css.js'; import { all } from './themes/themes.js'; import { comboValidators } from './validators.js'; let IgcComboComponent = IgcComboComponent_1 = class IgcComboComponent extends FormAssociatedRequiredMixin(EventEmitterMixin(LitElement)) { static register() { registerComponent(IgcComboComponent_1, IgcIconComponent, IgcComboListComponent, IgcComboItemComponent, IgcComboHeaderComponent, IgcInputComponent, IgcPopoverComponent); } get __validators() { return comboValidators; } get filteringOptions() { return this._filteringOptions; } set filteringOptions(value) { this._filteringOptions = { ...this._filteringOptions, ...value }; this.requestUpdate('pipeline'); } dataChanged() { if (this.data.length === 0) return; this.dataState = structuredClone(this.data); if (this.hasUpdated) { this.pipeline(); } this.requestUpdate('value'); } updateDisplayKey() { this.displayKey = this.displayKey ?? this.valueKey; } updateFilterKey() { if (!this.filteringOptions.filterKey) { this.filteringOptions = { filterKey: this.displayKey }; } } async pipeline() { this.dataState = await this.dataController.apply([...this.data]); } toggleDirectiveChange() { this._rootClickController.update(); } updateOnDisableFiltering() { this.resetSearchTerm(); this.pipeline(); } constructor() { super(); this._value = []; this._displayValue = ''; this._filteringOptions = { filterKey: this.displayKey, caseSensitive: false, matchDiacritics: false, }; this.navigationController = new NavigationController(this); this.selectionController = new SelectionController(this); this.dataController = new DataController(this); this.data = []; this.outlined = false; this.singleSelect = false; this.autofocusList = false; this.placeholderSearch = 'Search'; this.open = false; this.displayKey = this.valueKey; this.groupKey = this.displayKey; this.groupSorting = 'asc'; this.caseSensitiveIcon = false; this.disableFiltering = false; this.itemTemplate = ({ item }) => { const template = this.displayKey ? `${item[this.displayKey]}` : `${item}`; return html `${template}`; }; this.groupHeaderTemplate = ({ item }) => { return html `${this.groupKey && item[this.groupKey]}`; }; this.dataState = []; this._rootClickController = addRootClickHandler(this, { hideCallback: async () => { if (!this.handleClosing()) return; this.open = false; await this.updateComplete; this.emitEvent('igcClosed'); }, }); this.itemRenderer = (item, index) => { const record = item; const dataItem = this.data.at(record.dataIndex); const active = this.navigationController.active === index; const selected = this.selectionController.selected.has(dataItem); const headerTemplate = html `<igc-combo-header part="group-header" >${this.groupHeaderTemplate({ item: record.value })}</igc-combo-header >`; const itemPosition = index + 1; const itemId = this.id ? `${this.id}-item-${itemPosition}` : `item-${itemPosition}`; if (active) { this._activeDescendant = itemId; } const itemTemplate = html `<igc-combo-item id=${itemId} part=${partNameMap({ item: true, selected, active })} aria-setsize=${this.dataState.length} aria-posinset=${itemPosition} exportparts="checkbox, checkbox-indicator, checked" @click=${this.itemClickHandler.bind(this)} .index=${index} ?active=${active} ?selected=${selected} ?hide-checkbox=${this.singleSelect} >${this.itemTemplate({ item: record.value })}</igc-combo-item >`; return html `${this.groupKey && record.header ? headerTemplate : itemTemplate}`; }; this.addEventListener('blur', () => { const { selected } = this.selectionController; if (selected.size === 0) { this._displayValue = ''; this.resetSearchTerm(); } this.invalid = !this.checkValidity(); }); this.addEventListener('keydown', this.navigationController.navigateHost.bind(this.navigationController)); } resetSearchTerm() { this.dataController.searchTerm = ''; } resetState() { this.selectionController.selected.clear(); this.updateValue(); this.resetSearchTerm(); this.navigationController.active = -1; } async requiredChange() { await this.updateComplete; this.updateValidity(); this.invalid = !this.checkValidity(); } selectItems() { if (!this._value || this.value.length === 0) { this.selectionController.deselect([]); } else { this.selectionController.deselect([]); this.selectionController.select(this._value); } this.updateValue(); } set value(items) { const oldValue = this._value; this._value = items; this.requestUpdate('value', oldValue); } get value() { return this._value; } setFormValue() { if (!this.name) { return; } const items = this._value; if (items.length < 1) { super.setFormValue(null); return; } const data = new FormData(); if (this.singleSelect) { data.set(this.name, `${items[0]}`); } else { for (const item of items) { data.append(this.name, `${item}`); } } super.setFormValue(data); } async updateValue() { if (this.data.length === 0) return; const selected = Array.from(this.selectionController.selected); this._value = this.selectionController.getValue(selected, this.valueKey); this._displayValue = this.selectionController .getValue(selected, this.displayKey) .join(', '); if (this.target && this.singleSelect) { this.target.value = this._displayValue; } this.setFormValue(); this.updateValidity(); this.setInvalidState(); await this.updateComplete; this.list.requestUpdate(); } connectedCallback() { super.connectedCallback(); this.updateValidity(); } focus(options) { this.target.focus(options); } blur() { this.target.blur(); super.blur(); } normalizeSelection(items = []) { return Array.isArray(items) ? items : [items]; } get selection() { return Array.from(this.selectionController.selected.values()); } select(items) { const _items = this.normalizeSelection(items); this.selectionController.select(_items, false); this.updateValue(); } deselect(items) { const _items = this.normalizeSelection(items); this.selectionController.deselect(_items, false); this.updateValue(); } async handleMainInput(e) { this._show(); this.dataController.searchTerm = e.detail; await this.updateComplete; const matchIndex = this.dataState.findIndex((i) => !i.header); this.navigationController.active = e.detail.length > 0 ? matchIndex : -1; this.list.requestUpdate(); this.clearSingleSelection(); } handleSearchInput(e) { this.dataController.searchTerm = e.detail; } handleOpening() { return this.emitEvent('igcOpening', { cancelable: true }); } handleClosing() { return this.emitEvent('igcClosing', { cancelable: true }); } async _show(emitEvent = true) { if (this.open || (emitEvent && !this.handleOpening())) { return false; } this.open = true; await this.updateComplete; if (emitEvent) { this.emitEvent('igcOpened'); } if (!this.singleSelect) { this.list.focus(); } if (!this.autofocusList) { this.input.focus(); } return true; } async show() { return this._show(false); } async _hide(emitEvent = true) { if (!this.open || (emitEvent && !this.handleClosing())) { return false; } this.open = false; await this.updateComplete; if (emitEvent) { this.emitEvent('igcClosed'); } this.navigationController.active = -1; return true; } async hide() { return this._hide(false); } _toggle(emit = true) { return this.open ? this._hide(emit) : this._show(emit); } async toggle() { return this._toggle(false); } listKeydownHandler(event) { const target = event .composedPath() .find((el) => el instanceof IgcComboListComponent); if (target) { this.navigationController.navigateList(event, target); } } itemClickHandler(event) { const input = this.singleSelect ? this.target : this.input; const target = event .composedPath() .find((el) => el instanceof IgcComboItemComponent); this.toggleSelect(target.index); input.focus(); if (this.singleSelect) { this._hide(); } } toggleSelect(index) { const { dataIndex } = this.dataState.at(index); this.selectionController.changeSelection(dataIndex); this.navigationController.active = index; this.updateValue(); } selectByIndex(index) { const { dataIndex } = this.dataState.at(index); this.selectionController.selectByIndex(dataIndex); this.navigationController.active = index; this.updateValue(); } navigateTo(item) { this.navigationController.navigateTo(item, this.list); } clearSingleSelection() { const { selected } = this.selectionController; const selection = selected.values().next().value; if (selection) { const item = this.valueKey ? selection[this.valueKey] : selection; this.selectionController.deselect([item], selected.size > 0); this._value = []; } } handleClearIconClick(e) { e.stopPropagation(); if (this.singleSelect) { this.resetSearchTerm(); this.clearSingleSelection(); } else { this.selectionController.deselect([], true); } this.updateValue(); this.navigationController.active = -1; } handleMainInputKeydown(e) { this.navigationController.navigateMainInput(e, this.list); } handleSearchInputKeydown(e) { this.navigationController.navigateSearchInput(e, this.list); } toggleCaseSensitivity() { this.filteringOptions = { caseSensitive: !this.filteringOptions.caseSensitive, }; } get hasPrefixes() { return this.inputPrefix.length > 0; } get hasSuffixes() { return this.inputSuffix.length > 0; } _stopPropagation(e) { e.stopPropagation(); } renderToggleIcon() { return html ` <span slot="suffix" part="${partNameMap({ 'toggle-icon': true, filled: this.value.length > 0, })}" > <slot name="toggle-icon"> <igc-icon name=${this.open ? 'input_collapse' : 'input_expand'} collection="default" aria-hidden="true" ></igc-icon> </slot> </span> `; } renderClearIcon() { const { selected } = this.selectionController; return html `<span slot="suffix" part="clear-icon" @click=${this.handleClearIconClick} ?hidden=${selected.size === 0} > <slot name="clear-icon"> <igc-icon name="input_clear" collection="default" aria-hidden="true" ></igc-icon> </slot> </span>`; } renderMainInput() { return html `<igc-input id="target" slot="anchor" role="combobox" aria-controls="dropdown" aria-owns="dropdown" aria-expanded=${this.open ? 'true' : 'false'} aria-describedby="helper-text" aria-disabled=${this.disabled} exportparts="container: input, input: native-input, label, prefix, suffix" @click=${(e) => { e.preventDefault(); this._toggle(true); }} placeholder=${ifDefined(this.placeholder)} label=${ifDefined(this.label)} @igcChange=${this._stopPropagation} @focus=${() => { requestAnimationFrame(() => { this.target.select(); }); }} @igcInput=${this.handleMainInput} @keydown=${this.handleMainInputKeydown} .value=${this._displayValue} .disabled=${this.disabled} .required=${this.required} .invalid=${live(this.invalid)} .outlined=${this.outlined} .autofocus=${this.autofocus} ?readonly=${!this.singleSelect} > <span slot=${this.hasPrefixes && 'prefix'}> <slot name="prefix"></slot> </span> ${this.renderClearIcon()} <span slot=${this.hasSuffixes && 'suffix'}> <slot name="suffix"></slot> </span> ${this.renderToggleIcon()} </igc-input>`; } renderSearchInput() { return html `<div part="filter-input" ?hidden=${this.disableFiltering || this.singleSelect} > <igc-input .value=${this.dataController.searchTerm} part="search-input" placeholder=${this.placeholderSearch} exportparts="input: search-input" @igcInput=${this.handleSearchInput} @keydown=${this.handleSearchInputKeydown} > <igc-icon slot=${this.caseSensitiveIcon && 'suffix'} name="case_sensitive" collection="default" part=${partNameMap({ 'case-icon': true, active: this.filteringOptions.caseSensitive ?? false, })} @click=${this.toggleCaseSensitivity} ></igc-icon> </igc-input> </div>`; } renderEmptyTemplate() { return html `<div part="empty" ?hidden=${this.dataState.length > 0}> <slot name="empty">The list is empty</slot> </div>`; } renderList() { const hasItems = this.dataState.length > 0; return html `<div .inert=${!this.open} @keydown=${this.listKeydownHandler} part="list-wrapper" > ${this.renderSearchInput()} <div part="header"> <slot name="header"></slot> </div> <igc-combo-list aria-multiselectable=${!this.singleSelect} id="dropdown" part="list" role="listbox" tabindex="0" aria-labelledby="target" aria-activedescendant=${ifDefined(this._activeDescendant)} .items=${this.dataState} .renderItem=${this.itemRenderer} ?hidden=${!hasItems} > </igc-combo-list> ${this.renderEmptyTemplate()} <div part="footer"> <slot name="footer"></slot> </div> </div>`; } renderHelperText() { return html `<div id="helper-text" part="helper-text" ?hidden="${this.helperText.length === 0}" > <slot name="helper-text"></slot> </div>`; } render() { return html ` <igc-popover ?open=${this.open} flip shift same-width> ${this.renderMainInput()} ${this.renderList()} </igc-popover> ${this.renderHelperText()} `; } }; IgcComboComponent.tagName = 'igc-combo'; IgcComboComponent.styles = [styles, shared]; __decorate([ state() ], IgcComboComponent.prototype, "_activeDescendant", void 0); __decorate([ state() ], IgcComboComponent.prototype, "_displayValue", void 0); __decorate([ queryAssignedElements({ slot: 'helper-text' }) ], IgcComboComponent.prototype, "helperText", void 0); __decorate([ queryAssignedElements({ slot: 'suffix' }) ], IgcComboComponent.prototype, "inputSuffix", void 0); __decorate([ queryAssignedElements({ slot: 'prefix' }) ], IgcComboComponent.prototype, "inputPrefix", void 0); __decorate([ query('[part="search-input"]') ], IgcComboComponent.prototype, "input", void 0); __decorate([ query('igc-input#target') ], IgcComboComponent.prototype, "target", void 0); __decorate([ query('igc-combo-list') ], IgcComboComponent.prototype, "list", void 0); __decorate([ property({ attribute: false }) ], IgcComboComponent.prototype, "data", void 0); __decorate([ property({ reflect: true, type: Boolean }) ], IgcComboComponent.prototype, "outlined", void 0); __decorate([ property({ attribute: 'single-select', reflect: true, type: Boolean }) ], IgcComboComponent.prototype, "singleSelect", void 0); __decorate([ property({ type: Boolean }) ], IgcComboComponent.prototype, "autofocus", void 0); __decorate([ property({ attribute: 'autofocus-list', type: Boolean }) ], IgcComboComponent.prototype, "autofocusList", void 0); __decorate([ property({ type: String }) ], IgcComboComponent.prototype, "label", void 0); __decorate([ property({ type: String }) ], IgcComboComponent.prototype, "placeholder", void 0); __decorate([ property({ attribute: 'placeholder-search', type: String }) ], IgcComboComponent.prototype, "placeholderSearch", void 0); __decorate([ property({ type: Boolean }) ], IgcComboComponent.prototype, "open", void 0); __decorate([ property({ attribute: 'value-key', reflect: false }) ], IgcComboComponent.prototype, "valueKey", void 0); __decorate([ property({ attribute: 'display-key', reflect: false }) ], IgcComboComponent.prototype, "displayKey", void 0); __decorate([ property({ attribute: 'group-key', reflect: false }) ], IgcComboComponent.prototype, "groupKey", void 0); __decorate([ property({ attribute: 'group-sorting', reflect: false }) ], IgcComboComponent.prototype, "groupSorting", void 0); __decorate([ property({ attribute: 'filtering-options', type: Object }) ], IgcComboComponent.prototype, "filteringOptions", null); __decorate([ property({ type: Boolean, attribute: 'case-sensitive-icon', reflect: false }) ], IgcComboComponent.prototype, "caseSensitiveIcon", void 0); __decorate([ property({ type: Boolean, attribute: 'disable-filtering', reflect: false }) ], IgcComboComponent.prototype, "disableFiltering", void 0); __decorate([ property({ attribute: false }) ], IgcComboComponent.prototype, "itemTemplate", void 0); __decorate([ property({ attribute: false }) ], IgcComboComponent.prototype, "groupHeaderTemplate", void 0); __decorate([ state() ], IgcComboComponent.prototype, "dataState", void 0); __decorate([ watch('data') ], IgcComboComponent.prototype, "dataChanged", null); __decorate([ watch('valueKey') ], IgcComboComponent.prototype, "updateDisplayKey", null); __decorate([ watch('displayKey') ], IgcComboComponent.prototype, "updateFilterKey", null); __decorate([ watch('groupKey'), watch('groupSorting'), watch('pipeline') ], IgcComboComponent.prototype, "pipeline", null); __decorate([ watch('open') ], IgcComboComponent.prototype, "toggleDirectiveChange", null); __decorate([ watch('disableFiltering') ], IgcComboComponent.prototype, "updateOnDisableFiltering", null); __decorate([ watch('singleSelect', { waitUntilFirstUpdate: true }) ], IgcComboComponent.prototype, "resetState", null); __decorate([ watch('required', { waitUntilFirstUpdate: true }) ], IgcComboComponent.prototype, "requiredChange", null); __decorate([ watch('value') ], IgcComboComponent.prototype, "selectItems", null); __decorate([ property({ attribute: true, type: Array }) ], IgcComboComponent.prototype, "value", null); IgcComboComponent = IgcComboComponent_1 = __decorate([ themes(all), blazorAdditionalDependencies('IgcIconComponent, IgcInputComponent'), blazorIndirectRender ], IgcComboComponent); export default IgcComboComponent; //# sourceMappingURL=combo.js.map