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
JavaScript
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"
=${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"
=${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"
=${(e) => {
e.preventDefault();
this._toggle(true);
}}
placeholder=${ifDefined(this.placeholder)}
label=${ifDefined(this.label)}
=${this._stopPropagation}
=${() => {
requestAnimationFrame(() => {
this.target.select();
});
}}
=${this.handleMainInput}
=${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"
=${this.handleSearchInput}
=${this.handleSearchInputKeydown}
>
<igc-icon
slot=${this.caseSensitiveIcon && 'suffix'}
name="case_sensitive"
collection="default"
part=${partNameMap({
'case-icon': true,
active: this.filteringOptions.caseSensitive ?? false,
})}
=${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}
=${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