UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

1,493 lines (1,492 loc) • 48.2 kB
/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. * v1.5.0-next.4 */ import { h, Host } from "@stencil/core"; import { debounce } from "lodash-es"; import { filter } from "../../utils/filter"; import { isPrimaryPointerButton, toAriaBoolean } from "../../utils/dom"; import { connectFloatingUI, defaultMenuPlacement, disconnectFloatingUI, filterComputedPlacements, FloatingCSS, reposition } from "../../utils/floating-ui"; import { afterConnectDefaultValueSet, connectForm, disconnectForm, HiddenFormInputSlot, submitForm } from "../../utils/form"; import { guid } from "../../utils/guid"; import { connectInteractive, disconnectInteractive, updateHostInteraction } from "../../utils/interactive"; import { connectLabel, disconnectLabel, getLabelText } from "../../utils/label"; import { componentLoaded, setComponentLoaded, setUpLoadableComponent } from "../../utils/loadable"; import { connectLocalized, disconnectLocalized } from "../../utils/locale"; import { createObserver } from "../../utils/observers"; import { connectOpenCloseComponent, disconnectOpenCloseComponent } from "../../utils/openCloseComponent"; import { connectMessages, disconnectMessages, setUpMessages, updateMessages } from "../../utils/t9n"; import { ComboboxChildSelector, ComboboxItem, ComboboxItemGroup, CSS } from "./resources"; import { getItemAncestors, getItemChildren, hasActiveChildren } from "./utils"; import { XButton, CSS as XButtonCSS } from "../functional/XButton"; const isGroup = (el) => el.tagName === ComboboxItemGroup; const itemUidPrefix = "combobox-item-"; const chipUidPrefix = "combobox-chip-"; const labelUidPrefix = "combobox-label-"; const listboxUidPrefix = "combobox-listbox-"; const inputUidPrefix = "combobox-input-"; /** * @slot - A slot for adding `calcite-combobox-item`s. */ export class Combobox { constructor() { //-------------------------------------------------------------------------- // // Private State/Props // //-------------------------------------------------------------------------- this.placement = defaultMenuPlacement; this.internalValueChangeFlag = false; this.textInput = null; this.mutationObserver = createObserver("mutation", () => this.updateItems()); this.resizeObserver = createObserver("resize", () => this.setMaxScrollerHeight()); this.guid = guid(); this.inputHeight = 0; this.ignoreSelectedEventsFlag = false; this.openTransitionProp = "opacity"; this.setFilteredPlacements = () => { const { el, flipPlacements } = this; this.filteredFlipPlacements = flipPlacements ? filterComputedPlacements(flipPlacements, el) : null; }; this.getValue = () => { const items = this.selectedItems.map((item) => item?.value?.toString()); return items?.length ? (items.length > 1 ? items : items[0]) : ""; }; this.onLabelClick = () => { this.setFocus(); }; this.keyDownHandler = (event) => { const { key } = event; switch (key) { case "Tab": this.activeChipIndex = -1; this.activeItemIndex = -1; if (this.allowCustomValues && this.text) { this.addCustomChip(this.text, true); event.preventDefault(); } else if (this.open) { this.open = false; event.preventDefault(); } break; case "ArrowLeft": this.previousChip(); event.preventDefault(); break; case "ArrowRight": this.nextChip(); event.preventDefault(); break; case "ArrowUp": event.preventDefault(); if (this.open) { this.shiftActiveItemIndex(-1); } if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } break; case "ArrowDown": event.preventDefault(); if (this.open) { this.shiftActiveItemIndex(1); } else { this.open = true; this.ensureRecentSelectedItemIsActive(); } if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } break; case " ": if (!this.textInput.value) { event.preventDefault(); this.open = true; this.shiftActiveItemIndex(1); } break; case "Home": if (!this.open) { return; } event.preventDefault(); this.updateActiveItemIndex(0); this.scrollToActiveItem(); if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } break; case "End": if (!this.open) { return; } event.preventDefault(); this.updateActiveItemIndex(this.filteredItems.length - 1); this.scrollToActiveItem(); if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } break; case "Escape": if (!this.clearDisabled && !this.open) { this.clearValue(); } this.open = false; event.preventDefault(); break; case "Enter": if (this.activeItemIndex > -1) { this.toggleSelection(this.filteredItems[this.activeItemIndex]); event.preventDefault(); } else if (this.activeChipIndex > -1) { this.removeActiveChip(); event.preventDefault(); } else if (this.allowCustomValues && this.text) { this.addCustomChip(this.text, true); event.preventDefault(); } else if (!event.defaultPrevented) { if (submitForm(this)) { event.preventDefault(); } } break; case "Delete": case "Backspace": if (this.activeChipIndex > -1) { event.preventDefault(); this.removeActiveChip(); } else if (!this.text && this.isMulti()) { event.preventDefault(); this.removeLastChip(); } break; } }; this.toggleCloseEnd = () => { this.open = false; this.el.removeEventListener("calciteComboboxClose", this.toggleCloseEnd); }; this.toggleOpenEnd = () => { this.open = false; this.el.removeEventListener("calciteComboboxOpen", this.toggleOpenEnd); }; this.setMaxScrollerHeight = async () => { const { listContainerEl, open, referenceEl } = this; if (!listContainerEl || !open) { return; } await this.reposition(true); const maxScrollerHeight = this.getMaxScrollerHeight(); listContainerEl.style.maxHeight = maxScrollerHeight > 0 ? `${maxScrollerHeight}px` : ""; listContainerEl.style.minWidth = `${referenceEl.clientWidth}px`; await this.reposition(true); }; this.calciteChipCloseHandler = (comboboxItem) => { this.open = false; const selection = this.items.find((item) => item === comboboxItem); if (selection) { this.toggleSelection(selection, false); } this.calciteComboboxChipClose.emit(); }; this.clickHandler = (event) => { const composedPath = event.composedPath(); if (composedPath.some((node) => node.tagName === "CALCITE-CHIP")) { this.open = false; event.preventDefault(); return; } if (composedPath.some((node) => node.classList?.contains(XButtonCSS.button))) { this.clearValue(); event.preventDefault(); return; } this.open = !this.open; this.ensureRecentSelectedItemIsActive(); }; this.setInactiveIfNotContained = (event) => { const composedPath = event.composedPath(); if (!this.open || composedPath.includes(this.el) || composedPath.includes(this.referenceEl)) { return; } if (this.allowCustomValues && this.text.trim().length) { this.addCustomChip(this.text); } if (this.selectionMode === "single") { if (this.textInput) { this.textInput.value = ""; } this.text = ""; this.filterItems(""); this.updateActiveItemIndex(-1); } this.open = false; }; this.setFloatingEl = (el) => { this.floatingEl = el; connectFloatingUI(this, this.referenceEl, this.floatingEl); }; this.setContainerEl = (el) => { this.resizeObserver.observe(el); this.listContainerEl = el; this.transitionEl = el; connectOpenCloseComponent(this); }; this.setReferenceEl = (el) => { this.referenceEl = el; connectFloatingUI(this, this.referenceEl, this.floatingEl); }; this.inputHandler = (event) => { const value = event.target.value; this.text = value; this.filterItems(value); if (value) { this.activeChipIndex = -1; } }; this.filterItems = (() => { const find = (item, filteredData) => item && filteredData.some(({ label, value }) => { if (isGroup(item)) { return value === item.label; } return (value === item.textLabel || value === item.value || label === item.textLabel || label === item.value); }); return debounce((text) => { const filteredData = filter(this.data, text); const items = this.getCombinedItems(); items.forEach((item) => { const hidden = !find(item, filteredData); item.hidden = hidden; const [parent, grandparent] = item.ancestors; if (find(parent, filteredData) || find(grandparent, filteredData)) { item.hidden = false; } if (!hidden) { item.ancestors.forEach((ancestor) => (ancestor.hidden = false)); } }); this.filteredItems = this.getfilteredItems(); this.calciteComboboxFilterChange.emit(); }, 100); })(); this.internalComboboxChangeEvent = () => { this.calciteComboboxChange.emit(); }; this.emitComboboxChange = debounce(this.internalComboboxChangeEvent, 0); this.updateItems = () => { this.items = this.getItems(); this.groupItems = this.getGroupItems(); this.data = this.getData(); this.selectedItems = this.getSelectedItems(); this.filteredItems = this.getfilteredItems(); this.needsIcon = this.getNeedsIcon(); if (!this.allowCustomValues) { this.setMaxScrollerHeight(); } }; this.scrollToActiveItem = () => { const activeItem = this.filteredItems[this.activeItemIndex]; if (!activeItem) { return; } const height = this.calculateSingleItemHeight(activeItem); const { offsetHeight, scrollTop } = this.listContainerEl; if (offsetHeight + scrollTop < activeItem.offsetTop + height) { this.listContainerEl.scrollTop = activeItem.offsetTop - offsetHeight + height; } else if (activeItem.offsetTop < scrollTop) { this.listContainerEl.scrollTop = activeItem.offsetTop; } }; this.comboboxFocusHandler = () => { if (this.disabled) { return; } this.textInput?.focus(); }; this.comboboxBlurHandler = (event) => { this.setInactiveIfNotContained(event); }; this.clearDisabled = false; this.open = false; this.disabled = false; this.form = undefined; this.label = undefined; this.placeholder = undefined; this.placeholderIcon = undefined; this.placeholderIconFlipRtl = false; this.maxItems = 0; this.name = undefined; this.allowCustomValues = undefined; this.overlayPositioning = "absolute"; this.required = false; this.selectionMode = "multiple"; this.scale = "m"; this.value = null; this.flipPlacements = undefined; this.messages = undefined; this.messageOverrides = undefined; this.selectedItems = []; this.filteredItems = []; this.items = []; this.groupItems = []; this.needsIcon = undefined; this.activeItemIndex = -1; this.activeChipIndex = -1; this.activeDescendant = ""; this.text = ""; this.effectiveLocale = undefined; this.defaultMessages = undefined; } openHandler() { if (this.disabled) { this.open = false; return; } this.setMaxScrollerHeight(); } handleDisabledChange(value) { if (!value) { this.open = false; } } maxItemsHandler() { this.setMaxScrollerHeight(); } overlayPositioningHandler() { this.reposition(true); } valueHandler(value) { if (!this.internalValueChangeFlag) { const items = this.getItems(); if (Array.isArray(value)) { items.forEach((item) => (item.selected = value.includes(item.value))); } else if (value) { items.forEach((item) => (item.selected = value === item.value)); } else { items.forEach((item) => (item.selected = false)); } this.updateItems(); } } onMessagesChange() { /* wired up by t9n util */ } flipPlacementsHandler() { this.setFilteredPlacements(); this.reposition(true); } selectedItemsHandler() { this.internalValueChangeFlag = true; this.value = this.getValue(); this.internalValueChangeFlag = false; } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- documentClickHandler(event) { if (this.disabled || !isPrimaryPointerButton(event)) { return; } this.setInactiveIfNotContained(event); } calciteComboboxItemChangeHandler(event) { if (this.ignoreSelectedEventsFlag) { return; } const target = event.target; const newIndex = this.filteredItems.indexOf(target); this.updateActiveItemIndex(newIndex); this.toggleSelection(target, target.selected); } //-------------------------------------------------------------------------- // // Public Methods // //-------------------------------------------------------------------------- /** * Updates the position of the component. * * @param delayed */ async reposition(delayed = false) { const { floatingEl, referenceEl, placement, overlayPositioning, filteredFlipPlacements } = this; return reposition(this, { floatingEl, referenceEl, overlayPositioning, placement, flipPlacements: filteredFlipPlacements, type: "menu" }, delayed); } /** Sets focus on the component. */ async setFocus() { await componentLoaded(this); this.textInput?.focus(); this.activeChipIndex = -1; this.activeItemIndex = -1; } // -------------------------------------------------------------------------- // // Lifecycle // // -------------------------------------------------------------------------- connectedCallback() { connectInteractive(this); connectLocalized(this); connectMessages(this); this.internalValueChangeFlag = true; this.value = this.getValue(); this.internalValueChangeFlag = false; this.mutationObserver?.observe(this.el, { childList: true, subtree: true }); connectLabel(this); connectForm(this); connectOpenCloseComponent(this); this.setFilteredPlacements(); this.reposition(true); if (this.open) { this.openHandler(); } } async componentWillLoad() { setUpLoadableComponent(this); this.updateItems(); await setUpMessages(this); } componentDidLoad() { afterConnectDefaultValueSet(this, this.getValue()); this.reposition(true); setComponentLoaded(this); } componentDidRender() { if (this.el.offsetHeight !== this.inputHeight) { this.reposition(true); this.inputHeight = this.el.offsetHeight; } updateHostInteraction(this); } disconnectedCallback() { this.mutationObserver?.disconnect(); this.resizeObserver?.disconnect(); disconnectInteractive(this); disconnectLabel(this); disconnectForm(this); disconnectFloatingUI(this, this.referenceEl, this.floatingEl); disconnectOpenCloseComponent(this); disconnectLocalized(this); disconnectMessages(this); } /** when search text is cleared, reset active to */ textHandler() { this.updateActiveItemIndex(-1); } effectiveLocaleChange() { updateMessages(this, this.effectiveLocale); } // -------------------------------------------------------------------------- // // Private Methods // // -------------------------------------------------------------------------- clearValue() { this.ignoreSelectedEventsFlag = true; this.items.forEach((el) => (el.selected = false)); this.ignoreSelectedEventsFlag = false; this.selectedItems = []; this.emitComboboxChange(); this.open = false; this.updateActiveItemIndex(-1); this.resetText(); this.filterItems(""); this.setFocus(); } comboboxInViewport() { const bounding = this.el.getBoundingClientRect(); return (bounding.top >= 0 && bounding.left >= 0 && bounding.right <= (window.innerWidth || document.documentElement.clientWidth) && bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight)); } onBeforeOpen() { this.scrollToActiveItem(); this.calciteComboboxBeforeOpen.emit(); } onOpen() { this.calciteComboboxOpen.emit(); } onBeforeClose() { this.calciteComboboxBeforeClose.emit(); } onClose() { this.calciteComboboxClose.emit(); } ensureRecentSelectedItemIsActive() { const { selectedItems } = this; const targetIndex = selectedItems.length === 0 ? 0 : this.items.indexOf(selectedItems[selectedItems.length - 1]); this.updateActiveItemIndex(targetIndex); } getMaxScrollerHeight() { const items = this.getCombinedItems().filter((item) => !item.hidden); const { maxItems } = this; let itemsToProcess = 0; let maxScrollerHeight = 0; if (items.length > maxItems) { items.forEach((item) => { if (itemsToProcess < maxItems && maxItems > 0) { const height = this.calculateSingleItemHeight(item); if (height > 0) { maxScrollerHeight += height; itemsToProcess++; } } }); } return maxScrollerHeight; } calculateSingleItemHeight(item) { if (!item) { return; } let height = item.offsetHeight; // if item has children items, don't count their height twice const children = Array.from(item.querySelectorAll(ComboboxChildSelector)); children .map((child) => child?.offsetHeight) .forEach((offsetHeight) => { height -= offsetHeight; }); return height; } getCombinedItems() { return [...this.groupItems, ...this.items]; } toggleSelection(item, value = !item.selected) { if (!item) { return; } if (this.isMulti()) { item.selected = value; this.updateAncestors(item); this.selectedItems = this.getSelectedItems(); this.emitComboboxChange(); this.resetText(); this.filterItems(""); } else { this.ignoreSelectedEventsFlag = true; this.items.forEach((el) => (el.selected = el === item ? value : false)); this.ignoreSelectedEventsFlag = false; this.selectedItems = this.getSelectedItems(); this.emitComboboxChange(); if (this.textInput) { this.textInput.value = item.textLabel; } this.open = false; this.updateActiveItemIndex(-1); this.resetText(); this.filterItems(""); } } updateAncestors(item) { if (this.selectionMode !== "ancestors") { return; } const ancestors = getItemAncestors(item); const children = getItemChildren(item); if (item.selected) { ancestors.forEach((el) => { el.selected = true; }); } else { children.forEach((el) => (el.selected = false)); [...ancestors].forEach((el) => { if (!hasActiveChildren(el)) { el.selected = false; } }); } } getfilteredItems() { return this.items.filter((item) => !item.hidden); } getSelectedItems() { if (!this.isMulti()) { const match = this.items.find(({ selected }) => selected); return match ? [match] : []; } return (this.items .filter((item) => item.selected && (this.selectionMode !== "ancestors" || !hasActiveChildren(item))) /** Preserve order of entered tags */ .sort((a, b) => { const aIdx = this.selectedItems.indexOf(a); const bIdx = this.selectedItems.indexOf(b); if (aIdx > -1 && bIdx > -1) { return aIdx - bIdx; } return bIdx - aIdx; })); } getData() { return this.items.map((item) => ({ filterDisabled: item.filterDisabled, value: item.value, label: item.textLabel })); } getNeedsIcon() { return this.selectionMode === "single" && this.items.some((item) => item.icon); } resetText() { if (this.textInput) { this.textInput.value = ""; } this.text = ""; } getItems() { const items = Array.from(this.el.querySelectorAll(ComboboxItem)); return items.filter((item) => !item.disabled); } getGroupItems() { return Array.from(this.el.querySelectorAll(ComboboxItemGroup)); } addCustomChip(value, focus) { const existingItem = this.items.find((el) => el.textLabel === value); if (existingItem) { this.toggleSelection(existingItem, true); } else { if (!this.isMulti()) { this.toggleSelection(this.selectedItems[this.selectedItems.length - 1], false); } const item = document.createElement(ComboboxItem); item.value = value; item.textLabel = value; item.selected = true; this.el.appendChild(item); this.resetText(); if (focus) { this.setFocus(); } this.updateItems(); this.filterItems(""); this.emitComboboxChange(); } } removeActiveChip() { this.toggleSelection(this.selectedItems[this.activeChipIndex], false); this.setFocus(); } removeLastChip() { this.toggleSelection(this.selectedItems[this.selectedItems.length - 1], false); this.setFocus(); } previousChip() { if (this.text) { return; } const length = this.selectedItems.length - 1; const active = this.activeChipIndex; this.activeChipIndex = active === -1 ? length : Math.max(active - 1, 0); this.updateActiveItemIndex(-1); this.focusChip(); } nextChip() { if (this.text || this.activeChipIndex === -1) { return; } const last = this.selectedItems.length - 1; const newIndex = this.activeChipIndex + 1; if (newIndex > last) { this.activeChipIndex = -1; this.setFocus(); } else { this.activeChipIndex = newIndex; this.focusChip(); } this.updateActiveItemIndex(-1); } focusChip() { const guid = this.selectedItems[this.activeChipIndex]?.guid; const chip = guid ? this.referenceEl.querySelector(`#${chipUidPrefix}${guid}`) : null; chip?.setFocus(); } shiftActiveItemIndex(delta) { const { length } = this.filteredItems; const newIndex = (this.activeItemIndex + length + delta) % length; this.updateActiveItemIndex(newIndex); this.scrollToActiveItem(); } updateActiveItemIndex(index) { this.activeItemIndex = index; let activeDescendant = null; this.filteredItems.forEach((el, i) => { if (i === index) { el.active = true; activeDescendant = `${itemUidPrefix}${el.guid}`; } else { el.active = false; } }); this.activeDescendant = activeDescendant; if (this.activeItemIndex > -1) { this.activeChipIndex = -1; } } isMulti() { return this.selectionMode !== "single"; } //-------------------------------------------------------------------------- // // Render Methods // //-------------------------------------------------------------------------- renderChips() { const { activeChipIndex, scale, selectionMode, messages } = this; return this.selectedItems.map((item, i) => { const chipClasses = { chip: true, "chip--active": activeChipIndex === i }; const ancestors = [...getItemAncestors(item)].reverse(); const pathLabel = [...ancestors, item].map((el) => el.textLabel); const label = selectionMode !== "ancestors" ? item.textLabel : pathLabel.join(" / "); return (h("calcite-chip", { class: chipClasses, closable: true, icon: item.icon, iconFlipRtl: item.iconFlipRtl, id: item.guid ? `${chipUidPrefix}${item.guid}` : null, key: item.textLabel, messageOverrides: { dismissLabel: messages.removeTag }, onCalciteChipClose: () => this.calciteChipCloseHandler(item), scale: scale, title: label, value: item.value }, label)); }); } renderInput() { const { guid, disabled, placeholder, selectionMode, selectedItems, open } = this; const single = selectionMode === "single"; const selectedItem = selectedItems[0]; const showLabel = !open && single && !!selectedItem; return (h("span", { class: { "input-wrap": true, "input-wrap--single": single } }, showLabel && (h("span", { class: { label: true, "label--icon": !!selectedItem?.icon }, key: "label" }, selectedItem.textLabel)), h("input", { "aria-activedescendant": this.activeDescendant, "aria-autocomplete": "list", "aria-controls": `${listboxUidPrefix}${guid}`, "aria-label": getLabelText(this), class: { input: true, "input--single": true, "input--transparent": this.activeChipIndex > -1, "input--hidden": showLabel, "input--icon": !!this.placeholderIcon }, disabled: disabled, id: `${inputUidPrefix}${guid}`, key: "input", onBlur: this.comboboxBlurHandler, onFocus: this.comboboxFocusHandler, onInput: this.inputHandler, placeholder: placeholder, type: "text", // eslint-disable-next-line react/jsx-sort-props ref: (el) => (this.textInput = el) }))); } renderListBoxOptions() { return this.filteredItems.map((item) => (h("li", { "aria-selected": toAriaBoolean(item.selected), id: item.guid ? `${itemUidPrefix}${item.guid}` : null, role: "option", tabindex: "-1" }, item.textLabel))); } renderFloatingUIContainer() { const { setFloatingEl, setContainerEl, open } = this; const classes = { [CSS.listContainer]: true, [FloatingCSS.animation]: true, [FloatingCSS.animationActive]: open }; return (h("div", { "aria-hidden": "true", class: { "floating-ui-container": true, "floating-ui-container--active": open }, // eslint-disable-next-line react/jsx-sort-props ref: setFloatingEl }, h("div", { class: classes, // eslint-disable-next-line react/jsx-sort-props ref: setContainerEl }, h("ul", { class: { list: true, "list--hide": !open } }, h("slot", null))))); } renderIconStart() { const { selectedItems, placeholderIcon, selectionMode, placeholderIconFlipRtl } = this; const selectedItem = selectedItems[0]; const selectedIcon = selectedItem?.icon; const singleSelectionMode = selectionMode === "single"; const iconAtStart = !this.open && selectedItem ? !!selectedIcon && singleSelectionMode : !!this.placeholderIcon && (!selectedItem || singleSelectionMode); return (iconAtStart && (h("span", { class: "icon-start" }, h("calcite-icon", { class: "selected-icon", flipRtl: this.open && selectedItem ? selectedItem.iconFlipRtl : placeholderIconFlipRtl, icon: !this.open && selectedItem ? selectedIcon : placeholderIcon, scale: "s" })))); } renderIconEnd() { const { open } = this; return (h("span", { class: "icon-end" }, h("calcite-icon", { icon: open ? "chevron-up" : "chevron-down", scale: "s" }))); } render() { const { guid, label, open } = this; const single = this.selectionMode === "single"; const isClearable = !this.clearDisabled && this.value?.length > 0; return (h(Host, { onClick: this.comboboxFocusHandler }, h("div", { "aria-autocomplete": "list", "aria-controls": `${listboxUidPrefix}${guid}`, "aria-expanded": toAriaBoolean(open), "aria-haspopup": "listbox", "aria-label": getLabelText(this), "aria-live": "polite", "aria-owns": `${listboxUidPrefix}${guid}`, class: { wrapper: true, "wrapper--single": single || !this.selectedItems.length, "wrapper--active": open }, onClick: this.clickHandler, onKeyDown: this.keyDownHandler, role: "combobox", // eslint-disable-next-line react/jsx-sort-props ref: this.setReferenceEl }, h("div", { class: "grid-input" }, this.renderIconStart(), !single && this.renderChips(), h("label", { class: "screen-readers-only", htmlFor: `${inputUidPrefix}${guid}`, id: `${labelUidPrefix}${guid}` }, label), this.renderInput()), isClearable ? (h(XButton, { disabled: this.disabled, key: "close-button", label: this.messages.clear, scale: this.scale })) : null, this.renderIconEnd()), h("ul", { "aria-labelledby": `${labelUidPrefix}${guid}`, "aria-multiselectable": "true", class: "screen-readers-only", id: `${listboxUidPrefix}${guid}`, role: "listbox", tabIndex: -1 }, this.renderListBoxOptions()), this.renderFloatingUIContainer(), h(HiddenFormInputSlot, { component: this }))); } static get is() { return "calcite-combobox"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["combobox.scss"] }; } static get styleUrls() { return { "$": ["combobox.css"] }; } static get assetsDirs() { return ["assets"]; } static get properties() { return { "clearDisabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, the value-clearing will be disabled." }, "attribute": "clear-disabled", "reflect": true, "defaultValue": "false" }, "open": { "type": "boolean", "mutable": true, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, displays and positions the component." }, "attribute": "open", "reflect": true, "defaultValue": "false" }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, interaction is prevented and the component is displayed with lower opacity." }, "attribute": "disabled", "reflect": true, "defaultValue": "false" }, "form": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The ID of the form that will be associated with the component.\n\nWhen not set, the component will be associated with its ancestor form element, if any." }, "attribute": "form", "reflect": true }, "label": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": true, "optional": false, "docs": { "tags": [], "text": "Accessible name for the component." }, "attribute": "label", "reflect": false }, "placeholder": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the placeholder text for the input." }, "attribute": "placeholder", "reflect": false }, "placeholderIcon": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the placeholder icon for the input." }, "attribute": "placeholder-icon", "reflect": true }, "placeholderIconFlipRtl": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, the icon will be flipped when the element direction is right-to-left (`\"rtl\"`)." }, "attribute": "placeholder-icon-flip-rtl", "reflect": true, "defaultValue": "false" }, "maxItems": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the maximum number of `calcite-combobox-item`s (including nested children) to display before displaying a scrollbar." }, "attribute": "max-items", "reflect": true, "defaultValue": "0" }, "name": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the name of the component.\n\nRequired to pass the component's `value` on form submission." }, "attribute": "name", "reflect": true }, "allowCustomValues": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, allows entry of custom values, which are not in the original set of items." }, "attribute": "allow-custom-values", "reflect": true }, "overlayPositioning": { "type": "string", "mutable": false, "complexType": { "original": "OverlayPositioning", "resolved": "\"absolute\" | \"fixed\"", "references": { "OverlayPositioning": { "location": "import", "path": "../../utils/floating-ui" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Determines the type of positioning to use for the overlaid content.\n\nUsing `\"absolute\"` will work for most cases. The component will be positioned inside of overflowing parent containers and will affect the container's layout.\n\n`\"fixed\"` should be used to escape an overflowing parent container, or when the reference element's `position` CSS property is `\"fixed\"`." }, "attribute": "overlay-positioning", "reflect": true, "defaultValue": "\"absolute\"" }, "required": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "When `true`, the component must have a value in order for the form to submit." }, "attribute": "required", "reflect": true, "defaultValue": "false" }, "selectionMode": { "type": "string", "mutable": false, "complexType": { "original": "Extract<\n \"single\" | \"ancestors\" | \"multiple\",\n SelectionMode\n >", "resolved": "\"ancestors\" | \"multiple\" | \"single\"", "references": { "Extract": { "location": "global" }, "SelectionMode": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "specify the selection mode\n- multiple: allow any number of selected items (default)\n- single: only one selection)\n- ancestors: like multiple, but show ancestors of selected items as selected, only deepest children shown in chips" }, "attribute": "selection-mode", "reflect": true, "defaultValue": "\"multiple\"" }, "scale": { "type": "string", "mutable": false, "complexType": { "original": "Scale", "resolved": "\"l\" | \"m\" | \"s\"", "references": { "Scale": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the size of the component." }, "attribute": "scale", "reflect": true, "defaultValue": "\"m\"" }, "value": { "type": "string", "mutable": true, "complexType": { "original": "string | string[]", "resolved": "string | string[]", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The component's value(s) from the selected `calcite-combobox-item`(s)." }, "attribute": "value", "reflect": false, "defaultValue": "null" }, "flipPlacements": { "type": "unknown", "mutable": false, "complexType": { "original": "EffectivePlacement[]", "resolved": "Placement[]", "references": { "EffectivePlacement": { "location": "import", "path": "../../utils/floating-ui" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Defines the available placements that can be used when a flip occurs." } }, "messages": { "type": "unknown", "mutable": true, "complexType": { "original": "ComboboxMessages", "resolved": "{ clear: string; removeTag: string; }", "references": { "ComboboxMessages": { "location": "import", "path": "./assets/combobox/t9n" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "Made into a prop for testing purposes only" } }, "messageOverrides": { "type": "unknown", "mutable": true, "complexType": { "original": "Partial<ComboboxMessages>", "resolved": "{ clear?: string; removeTag?: string; }", "references": { "Partial": { "location": "global" }, "ComboboxMessages": { "location": "import", "path": "./assets/combobox/t9n" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Use this property to override individual strings used by the component." } }, "selectedItems": { "type": "unknown", "mutable": true, "complexType": { "original": "HTMLCalciteComboboxItemElement[]", "resolved": "HTMLCalciteComboboxItemElement[]", "references": { "HTMLCalciteComboboxItemElement": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "readonly", "text": undefined }], "text": "Specifies the component's selected items." }, "defaultValue": "[]" }, "filteredItems": { "type": "unknown", "mutable": true, "complexType": { "original": "HTMLCalciteComboboxItemElement[]", "resolved": "HTMLCalciteComboboxItemElement[]", "references": { "HTMLCalciteComboboxItemElement": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "readonly", "text": undefined }], "text": "Specifies the component's filtered items." }, "defaultValue": "[]" } }; } static get states() { return { "items": {}, "groupItems": {}, "needsIcon": {}, "activeItemIndex": {}, "activeChipIndex": {}, "activeDescendant": {}, "text": {}, "effectiveLocale": {}, "defaultMessages": {} }; } static get events() { return [{ "method": "calciteComboboxChange", "name": "calciteComboboxChange", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the selected item(s) changes." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteComboboxFilterChange", "name": "calciteComboboxFilterChange", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when text is added to filter the options list." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteComboboxChipClose", "name": "calciteComboboxChipClose", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when a selected item in the component is closed via its `calcite-chip`." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteComboboxBeforeClose", "name": "calciteComboboxBeforeClose", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the component is requested to be closed, and before the closing transition begins." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteComboboxClose", "name": "calciteComboboxClose", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the component is closed and animation is complete." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteComboboxBeforeOpen", "name": "calciteComboboxBeforeOpen", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the component is added to the DOM but not rendered, and before the opening transition begins." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "calciteComboboxOpen", "name": "calciteComboboxOpen", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the component is open and animation is complete." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }]; } static get methods() { return { "reposition": { "complexType": { "signature": "(delayed?: boolean) => Promise<void>", "parameters": [{ "tags": [{ "name": "param", "text": "delayed" }], "text": "" }], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Updates the position of the component.", "tags": [{ "name": "param", "text": "delayed" }] } }, "setFocus": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Sets focus on the component.", "tags": [] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "open", "methodName": "openHandler" }, { "propName": "disabled", "methodName": "handleDisabledChange" }, { "propName": "maxItems", "methodName": "maxItemsHandler" }, { "propName": "overlayPositioning", "methodName": "overlayPositioningHandler" }, { "propName": "value", "methodName": "valueHandler" }, { "propName": "messageOverrides", "methodName": "onMessagesChange" }, { "propName": "flipPlacements", "methodName": "flipPlacementsHandler" }, { "propName": "selectedItems", "methodName": "selectedItemsHandler" }, { "propName": "text", "methodName": "textHandler" }, { "propName": "effectiveLocale", "methodName": "effectiveLocaleChange" }]; } static get listeners() { return [{ "name": "pointerdown", "method": "documentClickHandler", "target": "document", "capture": false, "passive": true }, { "name": "calciteComboboxItemChange", "method": "calciteComboboxItemChangeHandler", "target": undefined, "capture": false, "passive": false }]; } }