UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

1,055 lines • 54.6 kB
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified. See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details. v3.2.1 */ import { D as DEBOUNCE, c as customElement } from "../../chunks/runtime.js"; import { keyed } from "lit-html/directives/keyed.js"; import { live } from "lit-html/directives/live.js"; import { debounce, escapeRegExp } from "lodash-es"; import { d as calciteSize48 } from "../../chunks/core.js"; import { nothing, html } from "lit"; import { createRef, ref } from "lit-html/directives/ref.js"; import { LitElement, createEvent, stringOrBoolean, safeClassMap } from "@arcgis/lumina"; import { f as filter } from "../../chunks/filter.js"; import { B as getElementWidth, A as getTextWidth } from "../../chunks/dom.js"; import { b as defaultMenuPlacement, r as reposition, c as connectFloatingUI, a as disconnectFloatingUI, f as filterValidFlipPlacements, h as hideFloatingUI, F as FloatingCSS } from "../../chunks/floating-ui.js"; import { c as connectForm, a as afterConnectDefaultValueSet, d as disconnectForm, s as submitForm, H as HiddenFormInputSlot } from "../../chunks/form.js"; import { g as guid } from "../../chunks/guid.js"; import { u as updateHostInteraction, I as InteractiveContainer } from "../../chunks/interactive.js"; import { c as connectLabel, d as disconnectLabel, g as getLabelText } from "../../chunks/label.js"; import { i as isHidden, c as componentFocusable, g as getIconScale } from "../../chunks/component.js"; import { c as createObserver } from "../../chunks/observers.js"; import { o as onToggleOpenCloseComponent } from "../../chunks/openCloseComponent.js"; import { C as CSS, X as XButton } from "../../chunks/XButton.js"; import { V as Validation } from "../../chunks/Validation.js"; import { u as useT9n } from "../../chunks/useT9n.js"; import { h as hasActiveChildren, i as isSingleLike, C as CSS$1, b as ComboboxItemSelector, c as ComboboxItemGroupSelector, d as getLabel, e as getItemAncestors, f as getItemChildren, I as IDS } from "../../chunks/utils3.js"; import { css } from "@lit/reactive-element/css-tag.js"; const styles = css`:host([disabled]){cursor:default;-webkit-user-select:none;user-select:none;opacity:var(--calcite-opacity-disabled)}:host([disabled]) *,:host([disabled]) ::slotted(*){pointer-events:none}:host{position:relative;display:block}:host([scale=s]){font-size:var(--calcite-font-size--2);--calcite-internal-combobox-spacing-unit-l: .5rem;--calcite-internal-combobox-spacing-unit-s: .25rem;--calcite-combobox-input-height: 1rem;--calcite-internal-combobox-input-margin-block: calc(.25rem - 1px) ;--calcite-internal-close-size: 1rem}:host([scale=s]) .x-button{margin-inline:.5rem}:host([scale=m]){font-size:var(--calcite-font-size--1);--calcite-internal-combobox-spacing-unit-l: .75rem;--calcite-internal-combobox-spacing-unit-s: .5rem;--calcite-combobox-input-height: 1rem;--calcite-internal-combobox-input-margin-block: calc(.5rem - 1px) ;--calcite-internal-close-size: 1.5rem}:host([scale=m]) .x-button{margin-inline-end:.75rem}:host([scale=l]){font-size:var(--calcite-font-size-0);--calcite-internal-combobox-spacing-unit-l: 1rem;--calcite-internal-combobox-spacing-unit-s: .75rem;--calcite-combobox-input-height: 1.5rem;--calcite-internal-combobox-input-margin-block: calc(.625rem - 1px) ;--calcite-internal-close-size: 2rem}:host([scale=l]) .x-button{margin-inline-end:1rem}.x-button{align-self:center}.wrapper{display:flex;border-width:1px;border-style:solid;outline-color:transparent;padding-block:calc(var(--calcite-internal-combobox-spacing-unit-s) / 4);padding-inline:var(--calcite-internal-combobox-spacing-unit-l);background-color:var(--calcite-combobox-input-background-color, var(--calcite-color-foreground-1));color:var(--calcite-combobox-input-text-color, var(--calcite-color-text-1));border-color:var(--calcite-combobox-input-border-color, var(--calcite-color-border-input))}.wrapper:hover .icon{color:var(--calcite-combobox-icon-color-hover, var(--calcite-color-text-1))}:host(:focus-within) .wrapper,.wrapper--active{outline:2px solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(-2px*(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}:host([read-only]) .wrapper{background-color:var(--calcite-color-background)}:host([read-only]) .label{font-weight:var(--calcite-font-weight-medium)}:host([status=invalid]) .wrapper{border-color:var(--calcite-color-status-danger)}:host([status=invalid]:focus-within) .wrapper{outline:2px solid var(--calcite-color-status-danger);outline-offset:calc(-2px*(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}.wrapper--single{padding-block:0;padding-inline:var(--calcite-internal-combobox-spacing-unit-l);cursor:pointer;flex-wrap:nowrap}.grid-input{position:relative;display:flex;flex-grow:1;flex-wrap:wrap;align-items:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0;gap:var(--calcite-internal-combobox-spacing-unit-s);margin-inline-end:var(--calcite-internal-combobox-spacing-unit-s)}.grid-input.selection-display--fit,.grid-input.selection-display--single{flex-wrap:nowrap;overflow:hidden}.input{flex-grow:1;appearance:none;overflow:hidden;text-overflow:ellipsis;border-style:none;background-color:transparent;padding:0;font-family:inherit;color:var(--calcite-color-text-1);font-size:inherit;block-size:var(--calcite-combobox-input-height);line-height:var(--calcite-combobox-input-height);inline-size:100%;margin-block-end:var(--calcite-internal-combobox-spacing-unit-s);min-inline-size:4.8125rem}.input:focus{outline:2px solid transparent;outline-offset:2px}.input:placeholder-shown{text-overflow:ellipsis}.input--single{padding:0;margin-block:var(--calcite-internal-combobox-input-margin-block)}.wrapper--active .input-single{cursor:text}.input--hidden{pointer-events:none;width:0px;min-width:0px;opacity:0}.input--icon{padding-block:0;padding-inline:var(--calcite-internal-combobox-spacing-unit-l)}.placeholder-icon{color:var(--calcite-combobox-icon-color, var(--calcite-color-text-3))}.input-wrap{display:flex;flex-grow:1;align-items:center}.input-wrap--single{flex:1 1 0%;overflow:hidden}.label{pointer-events:none;max-width:100%;flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0;font-weight:var(--calcite-font-weight-normal);block-size:var(--calcite-combobox-input-height);line-height:var(--calcite-combobox-input-height)}.label--icon{padding-inline:var(--calcite-internal-combobox-spacing-unit-l)}.icon-end,.icon-start{display:flex;cursor:pointer;align-items:center}.icon-end{flex:none}.icon-end .icon{color:var(--calcite-combobox-icon-color, var(--calcite-color-text-3))}.floating-ui-container{--calcite-floating-ui-z-index: var(--calcite-z-index-dropdown);inline-size:max-content;display:none;max-inline-size:100vw;max-block-size:100vh;inset-block-start:0;left:0;z-index:var(--calcite-floating-ui-z-index)}.floating-ui-container .calcite-floating-ui-anim{position:relative;transition:var(--calcite-floating-ui-transition);transition-property:inset,left,opacity;opacity:0;box-shadow:0 0 16px #00000029;z-index:var(--calcite-z-index);border-radius:.25rem}.floating-ui-container[data-placement^=bottom] .calcite-floating-ui-anim{inset-block-start:-5px}.floating-ui-container[data-placement^=top] .calcite-floating-ui-anim{inset-block-start:5px}.floating-ui-container[data-placement^=left] .calcite-floating-ui-anim{left:5px}.floating-ui-container[data-placement^=right] .calcite-floating-ui-anim{left:-5px}.floating-ui-container[data-placement] .calcite-floating-ui-anim--active{opacity:1;inset-block-start:0;left:0}@media (forced-colors: active){.wrapper,.floating-ui-container{border:1px solid canvasText}}.screen-readers-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.list-container{max-height:45vh;overflow-y:auto;inline-size:var(--calcite-dropdown-width, 100%);background-color:var(--calcite-combobox-background-color, var(--calcite-color-foreground-1))}.list{margin:0;display:block;padding:0}.list--hide{height:0px;overflow:hidden}calcite-chip{--calcite-animation-timing: 0}.chip{margin-block:calc(var(--calcite-internal-combobox-spacing-unit-s) / 4);max-inline-size:100%}.chip--invisible{visibility:hidden;position:absolute}.item{display:block}.select-all{background-color:var(--calcite-combobox-item-background-color-active, var(--calcite-color-foreground-1));border-block-end-color:var(--calcite-combobox-item-border-color, var(--calcite-color-border-3));border-block-end-style:solid;border-block-end-width:var(--calcite-border-width-sm);inset-block-start:0;position:sticky;z-index:var(--calcite-z-index-sticky)}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}.x-button{transition-property:background-color,block-size,border-color,box-shadow,color,inset-block-end,inset-block-start,inset-inline-end,inset-inline-start,inset-size,opacity,outline-color,transform;transition-duration:var(--calcite-animation-timing);transition-timing-function:ease-in-out;border-style:none;cursor:pointer;outline-color:transparent;align-items:center;margin:0;background-color:var(--calcite-close-background-color, var(--calcite-color-foreground-2));-webkit-appearance:none;display:flex;align-content:center;justify-content:center;color:var(--calcite-close-icon-color, var(--calcite-color-text-3));block-size:var(--calcite-internal-close-size, 1.5rem);inline-size:var(--calcite-internal-close-size, 1.5rem);min-block-size:var(--calcite-internal-close-size, 1.5rem);min-inline-size:var(--calcite-internal-close-size, 1.5rem);padding:0}.x-button:hover,.x-button:focus{background-color:var(--calcite-close-background-color-hover, var(--calcite-color-foreground-3))}.x-button:focus{outline:2px solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(-2px*(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}.x-button:active{background-color:var(--calcite-close-background-color-press, var(--calcite-color-transparent-press))}.x-button calcite-icon{color:inherit}.x-button--round{border-radius:9999px}.validation-container{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch}:host([scale=m]) .validation-container,:host([scale=l]) .validation-container{padding-block-start:.5rem}:host([scale=s]) .validation-container{padding-block-start:.25rem}::slotted(input[slot=hidden-form-input]){margin:0!important;opacity:0!important;outline:none!important;padding:0!important;position:absolute!important;inset:0!important;transform:none!important;-webkit-appearance:none!important;z-index:-1!important}:host([hidden]){display:none}[hidden]{display:none}::slotted(calcite-combobox-item-group:not(:first-child)){padding-block-start:var(--calcite-internal-combobox-spacing-unit-l)}::placeholder{font-weight:var(--calcite-font-weight-normal);color:var(--calcite-input-placeholder-text-color, var(--calcite-color-text-3))}`; const itemUidPrefix = "combobox-item-"; const chipUidPrefix = "combobox-chip-"; const labelUidPrefix = "combobox-label-"; const listboxUidPrefix = "combobox-listbox-"; const inputUidPrefix = "combobox-input-"; class Combobox extends LitElement { constructor() { super(); this.closeButtonEl = createRef(); this.filterItems = (() => { const find = (item, filteredData) => item && filteredData.some(({ el }) => item === el); return debounce((text, setOpenToEmptyState = false, emit = true) => { const filteredData = filter([...this.data, ...this.groupData], text, this.effectiveFilterProps); const itemsAndGroups = this.getItemsAndGroups(); const matchAll = text === ""; itemsAndGroups.forEach((item) => { if (matchAll) { item.itemHidden = false; return; } const hidden = !find(item, filteredData); item.itemHidden = hidden; const [parent, grandparent] = item.ancestors; if (find(parent, filteredData) || find(grandparent, filteredData)) { item.itemHidden = false; } if (!hidden) { item.ancestors.forEach((ancestor) => ancestor.itemHidden = false); } }); this.filterTextMatchPattern = this.filterText && new RegExp(`(${escapeRegExp(this.filterText)})`, "i"); this.filteredItems.forEach((item) => { item.filterTextMatchPattern = this.filterTextMatchPattern; }); if (setOpenToEmptyState) { this.open = this.filterText.trim().length > 0 && this.filteredItems.length > 0; } if (emit) { this.calciteComboboxFilterChange.emit(); } }, DEBOUNCE.filter); })(); this._filterText = ""; this.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))).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; }); }; this.groupItems = []; this.guid = guid(); this.ignoreSelectedEventsFlag = false; this.inputHeight = 0; this.internalValueChangeFlag = false; this.mutationObserver = createObserver("mutation", () => this.updateItems()); this.onLabelClick = () => { this.setFocus(); }; this.transitionProp = "opacity"; this.placement = defaultMenuPlacement; this.resizeObserver = createObserver("resize", () => { this.setMaxScrollerHeight(); this.refreshSelectionDisplay(); }); this._selectedItems = []; this.textInput = createRef(); this._value = null; this.messages = useT9n(); this.activeChipIndex = -1; this.activeDescendant = ""; this.activeItemIndex = -1; this.compactSelectionDisplay = false; this.selectedHiddenChipsCount = 0; this.selectedVisibleChipsCount = 0; this.items = []; this.clearDisabled = false; this.disabled = false; this.maxItems = 0; this.open = false; this.overlayPositioning = "absolute"; this.placeholderIconFlipRtl = false; this.readOnly = false; this.required = false; this.scale = "m"; this.selectAllEnabled = false; this.selectionDisplay = "all"; this.selectionMode = "multiple"; this.status = "idle"; this.validity = { valid: false, badInput: false, customError: false, patternMismatch: false, rangeOverflow: false, rangeUnderflow: false, stepMismatch: false, tooLong: false, tooShort: false, typeMismatch: false, valueMissing: false }; this.calciteComboboxBeforeClose = createEvent({ cancelable: false }); this.calciteComboboxBeforeOpen = createEvent({ cancelable: false }); this.calciteComboboxChange = createEvent({ cancelable: false }); this.calciteComboboxChipClose = createEvent({ cancelable: false }); this.calciteComboboxClose = createEvent({ cancelable: false }); this.calciteComboboxFilterChange = createEvent({ cancelable: false }); this.calciteComboboxOpen = createEvent({ cancelable: false }); this.listenOn(document, "click", this.documentClickHandler); this.listen("calciteComboboxItemChange", this.calciteComboboxItemChangeHandler); this.listen("calciteInternalComboboxItemChange", this.calciteInternalComboboxItemChangeHandler); this.listen("click", this.comboboxFocusHandler); } static { this.properties = { activeChipIndex: [16, {}, { state: true }], activeDescendant: [16, {}, { state: true }], activeItemIndex: [16, {}, { state: true }], compactSelectionDisplay: [16, {}, { state: true }], selectedHiddenChipsCount: [16, {}, { state: true }], selectedVisibleChipsCount: [16, {}, { state: true }], selectAllComboboxItemReferenceEl: [16, {}, { state: true }], items: [16, {}, { state: true }], allSelected: [16, {}, { state: true }], indeterminate: [16, {}, { state: true }], keyboardNavItems: [16, {}, { state: true }], allowCustomValues: [7, {}, { reflect: true, type: Boolean }], clearDisabled: [7, {}, { reflect: true, type: Boolean }], disabled: [7, {}, { reflect: true, type: Boolean }], filterText: [3, {}, { reflect: true }], filterProps: [0, {}, { attribute: false }], filteredItems: [32, {}, { attribute: false, readOnly: true }], flipPlacements: [0, {}, { attribute: false }], form: [3, {}, { reflect: true }], label: 1, maxItems: [11, {}, { reflect: true, type: Number }], messageOverrides: [0, {}, { attribute: false }], name: [3, {}, { reflect: true }], open: [7, {}, { reflect: true, type: Boolean }], overlayPositioning: [3, {}, { reflect: true }], placeholder: 1, placeholderIcon: [3, {}, { reflect: true }], placeholderIconFlipRtl: [7, {}, { reflect: true, type: Boolean }], readOnly: [7, {}, { reflect: true, type: Boolean }], required: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], selectAllEnabled: [7, {}, { reflect: true, type: Boolean }], selectedItems: [0, {}, { attribute: false }], selectionDisplay: [3, {}, { reflect: true }], selectionMode: [3, {}, { reflect: true }], status: [3, {}, { reflect: true }], validationIcon: [3, { converter: stringOrBoolean }, { reflect: true }], validationMessage: 1, validity: [0, {}, { attribute: false }], value: 1 }; } static { this.styles = styles; } get allSelected() { return this.selectedItems.length === this.items.length; } get indeterminate() { return this.selectedItems.length > 0 && !this.allSelected; } get keyboardNavItems() { const { selectAllComboboxItemReferenceEl, items } = this; if (selectAllComboboxItemReferenceEl) { return [selectAllComboboxItemReferenceEl, ...items.filter((item) => !item.disabled)]; } return items.filter((item) => !item.disabled); } get filterText() { return this._filterText; } set filterText(filterText) { const oldFilterText = this._filterText; if (filterText !== oldFilterText) { this._filterText = filterText; this.filterTextChange(filterText); } } get filteredItems() { return this.keyboardNavItems.filter((item) => !isHidden(item)); } get selectedItems() { return this._selectedItems; } set selectedItems(selectedItems) { const oldSelectedItems = this._selectedItems; if (selectedItems !== oldSelectedItems) { this._selectedItems = selectedItems; this.selectedItemsHandler(); } } get value() { return this._value; } set value(value) { const oldValue = this._value; if (value !== oldValue) { this._value = value; this.valueHandler(value); } } async reposition(delayed = false) { const { floatingEl, referenceEl, placement, overlayPositioning, filteredFlipPlacements } = this; return reposition(this, { floatingEl, referenceEl, overlayPositioning, placement, flipPlacements: filteredFlipPlacements, type: "menu" }, delayed); } async setFocus() { await componentFocusable(this); this.textInput.value?.focus(); this.activeChipIndex = -1; this.activeItemIndex = -1; } connectedCallback() { super.connectedCallback(); connectLabel(this); connectForm(this); this.internalValueChangeFlag = true; this.value = this.getValue(); this.internalValueChangeFlag = false; this.mutationObserver?.observe(this.el, { childList: true, subtree: true }); this.setFilteredPlacements(); connectFloatingUI(this); } willUpdate(changes) { if (changes.has("open") && (this.hasUpdated || this.open !== false)) { this.openHandler(); } if (changes.has("disabled") && (this.hasUpdated || this.disabled !== false)) { this.handleDisabledChange(this.disabled); } if (changes.has("maxItems") && (this.hasUpdated || this.maxItems !== 0)) { this.setMaxScrollerHeight(); } if (changes.has("overlayPositioning") && (this.hasUpdated || this.overlayPositioning !== "absolute")) { this.reposition(true); } if (changes.has("selectionMode") || changes.has("scale")) { this.updateItems(); } if (changes.has("flipPlacements")) { this.flipPlacementsHandler(); } } updated() { if (this.el.offsetHeight !== this.inputHeight) { this.reposition(true); this.inputHeight = this.el.offsetHeight; } updateHostInteraction(this); this.refreshSelectionDisplay(); } loaded() { afterConnectDefaultValueSet(this, this.getValue()); connectFloatingUI(this); this.updateItems(); this.filterItems(this.filterText, false, false); } disconnectedCallback() { super.disconnectedCallback(); this.mutationObserver?.disconnect(); this.resizeObserver?.disconnect(); disconnectLabel(this); disconnectForm(this); disconnectFloatingUI(this); } emitComboboxChange() { this.calciteComboboxChange.emit(); } get effectiveFilterProps() { if (!this.filterProps) { return ["description", "label", "metadata", "shortHeading", "textLabel"]; } return this.filterProps.filter((prop) => prop !== "el"); } get showingInlineIcon() { const { placeholderIcon, selectionMode, selectedItems, open } = this; const selectedItem = selectedItems[0]; const selectedIcon = selectedItem?.icon; const singleSelectionMode = isSingleLike(selectionMode); return !open && selectedItem ? !!selectedIcon && singleSelectionMode : !!placeholderIcon && (!selectedItem || singleSelectionMode); } filterTextChange(value) { this.updateActiveItemIndex(-1); this.filterItems(value, true); } openHandler() { onToggleOpenCloseComponent(this); if (this.disabled) { return; } this.setMaxScrollerHeight(); } handleDisabledChange(value) { if (!value) { this.open = false; } } valueHandler(value) { if (!this.internalValueChangeFlag) { this.getItems().forEach((item) => { item.selected = Array.isArray(value) ? value.includes(item.value) : value === item.value; }); this.updateItems(); } } flipPlacementsHandler() { this.setFilteredPlacements(); this.reposition(true); } selectedItemsHandler() { this.internalValueChangeFlag = true; this.value = this.getValue(); this.internalValueChangeFlag = false; } async documentClickHandler(event) { if (this.disabled || event.composedPath().includes(this.el)) { return; } await this.componentOnReady(); if (!this.allowCustomValues && this.filterText) { this.clearInputValue(); this.filterItems(""); this.updateActiveItemIndex(-1); } if (this.allowCustomValues && this.filterText.trim().length) { this.addCustomChip(this.filterText); } this.open = false; } handleSelectAll(isSelectAllTarget) { if (isSelectAllTarget) { this.toggleSelectAll(); } if (this.allSelected) { this.selectedItems.forEach((item) => { const chipEl = this.referenceEl.querySelector(`#${chipUidPrefix}${item.guid}`); if (chipEl) { this.hideChip(chipEl); } }); } } calciteComboboxItemChangeHandler(event) { if (this.ignoreSelectedEventsFlag) { return; } const target = event.target; const isSelectAllTarget = event.composedPath().includes(this.selectAllComboboxItemReferenceEl); if (this.selectAllEnabled) { this.handleSelectAll(isSelectAllTarget); } const newIndex = this.filteredItems.indexOf(target); this.updateActiveItemIndex(newIndex); this.toggleSelection(target, target.selected); this.selectedItems = this.getSelectedItems(); } calciteInternalComboboxItemChangeHandler(event) { event.stopPropagation(); if (this.hasUpdated) { this.updateItems(); } } 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(); } clearInputValue() { this.textInput.value.value = ""; this.filterText = ""; } setFilteredPlacements() { const { el, flipPlacements } = this; this.filteredFlipPlacements = flipPlacements ? filterValidFlipPlacements(flipPlacements, el) : null; } getValue() { const items = this.selectedItems.map((item) => item.value?.toString()); return items.length ? items.length > 1 ? items : items[0] : ""; } 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); } toggleSelectAll() { const toggledValue = !this.allSelected; this.selectedItems = this.items.filter((item) => { item.selected = toggledValue; return toggledValue; }); this.emitComboboxChange(); } keyDownHandler(event) { if (this.readOnly) { return; } const { key } = event; switch (key) { case "Tab": this.activeChipIndex = -1; this.activeItemIndex = -1; if (this.allowCustomValues && this.filterText) { this.addCustomChip(this.filterText, true); event.preventDefault(); } else if (this.open) { this.open = false; event.preventDefault(); } else if (!this.allowCustomValues && this.filterText) { this.clearInputValue(); this.filterItems(""); this.updateActiveItemIndex(-1); } break; case "ArrowLeft": if (this.activeChipIndex !== -1 || this.textInput.value.selectionStart === 0) { this.previousChip(); event.preventDefault(); } break; case "ArrowRight": if (this.activeChipIndex !== -1) { this.nextChip(); event.preventDefault(); } break; case "ArrowUp": if (this.filteredItems.length) { event.preventDefault(); if (this.open) { this.shiftActiveItemIndex(-1); } this.scrollToActiveOrSelectedItem(); if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } } this.scrollToActiveOrSelectedItem(); break; case "ArrowDown": if (this.filteredItems.length) { event.preventDefault(); if (this.open) { this.shiftActiveItemIndex(1); } else { this.open = true; this.ensureRecentSelectedItemIsActive(); } this.scrollToActiveOrSelectedItem(); if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } } break; case " ": if (!this.textInput.value.value && !event.defaultPrevented) { if (!this.open) { this.open = true; this.shiftActiveItemIndex(1); } event.preventDefault(); } break; case "Home": if (!this.open) { return; } event.preventDefault(); this.updateActiveItemIndex(0); this.scrollToActiveOrSelectedItem(); if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } break; case "End": if (!this.open) { return; } event.preventDefault(); this.updateActiveItemIndex(this.filteredItems.length - 1); this.scrollToActiveOrSelectedItem(); 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.open && this.activeItemIndex > -1) { const item = this.filteredItems[this.activeItemIndex]; this.toggleSelection(item, !item.selected); event.preventDefault(); if (this.selectAllEnabled) { this.handleSelectAll(item === this.selectAllComboboxItemReferenceEl); } } else if (this.activeChipIndex > -1) { this.removeActiveChip(); event.preventDefault(); } else if (this.allowCustomValues && this.filterText) { this.addCustomChip(this.filterText, true); event.preventDefault(); } else if (!event.defaultPrevented) { if (submitForm(this)) { event.preventDefault(); } } break; case "Delete": case "Backspace": { const notDeletable = this.selectionDisplay === "single" || this.selectionDisplay === "fit" && this.selectedHiddenChipsCount > 0; if (notDeletable) { return; } if (this.activeChipIndex > -1) { event.preventDefault(); this.removeActiveChip(); } else if (!this.filterText && this.isMulti()) { event.preventDefault(); this.removeLastChip(); } break; } } } onBeforeOpen() { this.scrollToActiveOrSelectedItem(); this.calciteComboboxBeforeOpen.emit(); } onOpen() { this.scrollToActiveOrSelectedItem(true); this.calciteComboboxOpen.emit(); } onBeforeClose() { this.calciteComboboxBeforeClose.emit(); } onClose() { this.calciteComboboxClose.emit(); hideFloatingUI(this); } async setMaxScrollerHeight() { const { listContainerEl, open, referenceEl } = this; if (!listContainerEl || !open) { return; } await this.reposition(true); const maxScrollerHeight = this.getMaxScrollerHeight(); listContainerEl.style.maxBlockSize = maxScrollerHeight > 0 ? `${maxScrollerHeight}px` : ""; listContainerEl.style.inlineSize = `${referenceEl.clientWidth}px`; await this.reposition(true); } calciteChipCloseHandler(comboboxItem) { this.open = false; const selection = this.items.find((item) => item === comboboxItem); if (selection) { this.toggleSelection(selection, false); } this.calciteComboboxChipClose.emit(); } clickHandler(event) { if (this.readOnly) { return; } 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(CSS.button))) { this.clearValue(); event.preventDefault(); return; } this.open = !this.open; this.ensureRecentSelectedItemIsActive(); } ensureRecentSelectedItemIsActive() { const { selectedItems } = this; const targetIndex = selectedItems.length === 0 ? 0 : this.items.indexOf(selectedItems[selectedItems.length - 1]); this.updateActiveItemIndex(targetIndex); } hideChip(chipEl) { chipEl.classList.add(CSS$1.chipInvisible); } showChip(chipEl) { chipEl.classList.remove(CSS$1.chipInvisible); } refreshChipDisplay({ chipEls, availableHorizontalChipElSpace, chipContainerElGap }) { chipEls.forEach((chipEl) => { if (!chipEl.selected) { this.hideChip(chipEl); } else { const chipElWidth = getElementWidth(chipEl); if (chipElWidth && chipElWidth < availableHorizontalChipElSpace) { availableHorizontalChipElSpace -= chipElWidth + chipContainerElGap; this.showChip(chipEl); return; } } this.hideChip(chipEl); }); } async refreshSelectionDisplay() { this.componentOnReady(); if (isSingleLike(this.selectionMode)) { return; } if (!this.textInput.value) { return; } const { allSelectedIndicatorChipEl, chipContainerEl, selectionDisplay, placeholder, selectedIndicatorChipEl, textInput: { value: textInput } } = this; const chipContainerElGap = parseInt(getComputedStyle(chipContainerEl).gap.replace("px", "")); const chipContainerElWidth = getElementWidth(chipContainerEl); const { fontSize, fontFamily } = getComputedStyle(textInput); const inputTextWidth = getTextWidth(placeholder, `${fontSize} ${fontFamily}`); const inputWidth = (inputTextWidth || parseInt(calciteSize48)) + chipContainerElGap; const allSelectedIndicatorChipElWidth = getElementWidth(allSelectedIndicatorChipEl); const selectedIndicatorChipElWidth = getElementWidth(selectedIndicatorChipEl); const largestSelectedIndicatorChipWidth = Math.max(allSelectedIndicatorChipElWidth, selectedIndicatorChipElWidth); this.setCompactSelectionDisplay({ chipContainerElGap, chipContainerElWidth, inputWidth, largestSelectedIndicatorChipWidth }); if (this.allSelected && this.selectAllEnabled) { this.selectedItems.forEach((item) => { const chipEl = this.referenceEl.querySelector(`#${chipUidPrefix}${item.guid}`); if (chipEl) { this.hideChip(chipEl); } }); } if (this.indeterminate) { this.selectedItems.forEach((item) => { const chipEl = this.referenceEl.querySelector(`#${chipUidPrefix}${item.guid}`); if (chipEl) { this.showChip(chipEl); } }); } if (selectionDisplay === "fit") { const chipEls = Array.from(this.el.shadowRoot.querySelectorAll("calcite-chip")).filter((chipEl) => chipEl.closable); const availableHorizontalChipElSpace = Math.round(chipContainerElWidth - ((this.selectedHiddenChipsCount > 0 ? selectedIndicatorChipElWidth : 0) + chipContainerElGap + inputWidth + chipContainerElGap)); this.refreshChipDisplay({ availableHorizontalChipElSpace, chipContainerElGap, chipEls }); this.setVisibleAndHiddenChips(chipEls); } } setFloatingEl(el) { this.floatingEl = el; connectFloatingUI(this); } setCompactSelectionDisplay({ chipContainerElGap, chipContainerElWidth, inputWidth, largestSelectedIndicatorChipWidth }) { const newCompactBreakpoint = Math.round(largestSelectedIndicatorChipWidth + chipContainerElGap + inputWidth); if (!this.maxCompactBreakpoint || this.maxCompactBreakpoint < newCompactBreakpoint) { this.maxCompactBreakpoint = newCompactBreakpoint; } this.compactSelectionDisplay = chipContainerElWidth < this.maxCompactBreakpoint; } setContainerEl(el) { if (!el) { return; } this.resizeObserver?.observe(el); this.listContainerEl = el; this.transitionEl = el; } setChipContainerEl(el) { if (el) { this.resizeObserver?.observe(el); } this.chipContainerEl = el; } setReferenceEl(el) { this.referenceEl = el; connectFloatingUI(this); } setSelectAllComboboxItemReferenceEl(el) { this.selectAllComboboxItemReferenceEl = el; } setAllSelectedIndicatorChipEl(el) { this.allSelectedIndicatorChipEl = el; } setSelectedIndicatorChipEl(el) { this.selectedIndicatorChipEl = el; } setVisibleAndHiddenChips(chipEls) { let newSelectedVisibleChipsCount = 0; chipEls.forEach((chipEl) => { if (chipEl.selected && !chipEl.classList.contains(CSS$1.chipInvisible)) { newSelectedVisibleChipsCount++; } }); if (newSelectedVisibleChipsCount !== this.selectedVisibleChipsCount) { this.selectedVisibleChipsCount = newSelectedVisibleChipsCount; } const newSelectedHiddenChipsCount = this.getSelectedItems().length - newSelectedVisibleChipsCount; if (newSelectedHiddenChipsCount !== this.selectedHiddenChipsCount) { this.selectedHiddenChipsCount = newSelectedHiddenChipsCount; } } getMaxScrollerHeight() { const allItemsAndGroups = [...this.groupItems, ...this.getItems(true)]; const items = allItemsAndGroups.filter((item) => !isHidden(item)); const { maxItems } = this; let itemsToProcess = 0; let maxScrollerHeight = 0; if (items.length >= maxItems) { items.forEach((item) => { if (itemsToProcess < maxItems) { const height = this.calculateScrollerHeight(item); maxScrollerHeight += height; itemsToProcess += 1; } }); } return maxScrollerHeight; } calculateScrollerHeight(item) { if (!item) { return 0; } const parentHeight = item.getBoundingClientRect().height; const DirectComboboxChildrenSelector = `:scope > ${ComboboxItemSelector}, :scope > ${ComboboxItemGroupSelector}`; const childrenTotalHeight = Array.from(item.querySelectorAll(DirectComboboxChildrenSelector)).reduce((total, child) => total + child.getBoundingClientRect().height, 0); return parentHeight - childrenTotalHeight; } inputHandler(event) { const value = event.target.value; this.filterText = value; } getItemsAndGroups() { return [...this.groupItems, ...this.items]; } toggleSelection(item, value) { if (!item || this.selectionMode === "single-persist" && item.selected && item.value === this.value && !value) { return; } if (this.isMulti()) { this.handleMultiSelection(item, value); } else { this.handleSingleSelection(item, value); } } handleMultiSelection(item, value) { item.selected = value; this.updateAncestors(item); this.selectedItems = this.getSelectedItems(); this.emitComboboxChange(); this.resetText(); this.filterItems(""); } handleSingleSelection(item, value) { 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.value) { this.textInput.value.value = getLabel(item); } 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; } }); } } updateItems() { this.items = this.getItems(); this.groupItems = this.getGroupItems(); this.data = this.getData(); this.groupData = this.getGroupData(); this.updateItemProps(); this.selectedItems = this.getSelectedItems(); } updateItemProps() { this.getItems(true).forEach((item) => { item.selectionMode = this.selectionMode; item.scale = this.scale; }); this.groupItems.forEach((groupItem) => groupItem.scale = this.scale); if (!this.allowCustomValues) { this.setMaxScrollerHeight(); } this.groupItems.forEach((groupItem, index, items) => { if (index === 0) { groupItem.afterEmptyGroup = false; } const nextGroupItem = items[index + 1]; if (nextGroupItem) { nextGroupItem.afterEmptyGroup = groupItem.children.length === 0; } }); } getData() { return this.items.map((item) => ({ description: item.description, filterDisabled: item.filterDisabled, label: item.heading, metadata: item.metadata, shortHeading: item.shortHeading, textLabel: item.textLabel, el: item // used for matching items to data })); } getGroupData() { return this.groupItems.map((groupItem) => ({ label: groupItem.label, el: groupItem })); } resetText() { if (this.textInput.value) { this.textInput.value.value = ""; } this.filterText = ""; } getItems(withDisabled = false) { const items = Array.from(this.el.querySelectorAll(ComboboxItemSelector)); return items.filter((item) => withDisabled || !item.disabled); } getGroupItems() { return Array.from(this.el.querySelectorAll(ComboboxItemGroupSelector)); } addCustomChip(value, focus) { const existingItem = this.items.find((el) => (el.heading || el.textLabel) === value); if (existingItem) { this.toggleSelection(existingItem, true); } else { const item = document.createElement( // TODO: [MIGRATION] If this is dynamically creating a web component, please read the docs: https://qawebgis.esri.com/arcgis-components/?path=/docs/lumina-jsx--docs#rendering-jsx-outside-the-component "calcite-combobox-item" ); item.value = value; item.heading = value; this.el.prepend(item); this.updateItems(); this.toggleSelection(item, true); this.open = true; if (focus) { this.setFocus(); } } } removeActiveChip() { this.toggleSelection(this.selectedItems[this.activeChipIndex], false); this.setFocus(); } removeLastChip() { this.toggleSelection(this.selectedItems[this.selectedItems.length - 1], false); this.setFocus(); } previousChip() { 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() { 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 guid2 = this.selectedItems[this.activeChipIndex]?.guid; const chip = guid2 ? this.referenceEl.querySelector(`#${chipUidPrefix}${guid2}`) : null; chip?.setFocus(); } scrollToActiveOrSelectedItem(scrollToSelected = false) { const item = scrollToSelected && this.selectedItems?.length ? this.selectedItems[0] : this.filteredItems[this.activeItemIndex]; if (!item) { return; } item.scrollIntoView({ block: "nearest" }); const stickyElement = this.selectAllComboboxItemReferenceEl; const stickyHeight = stickyElement?.offsetHeight || 0; const listContainer = this.listContainerEl; const itemRect = item.getBoundingClientRect(); const containerRect = listContainer.getBoundingClientRect(); if (itemRect.top < containerRect.top + stickyHeight) { listContainer.scrollTop -= containerRect.top + stickyHeight - itemRect.top; } } shiftActiveItemIndex(delta) { const { length } = this.filteredItems; const newIndex = (this.activeItemIndex + length + delta) % length; this.updateActiveItemIndex(newIndex); this.scrollToActiveOrSelectedItem(); } 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 !isSingleLike(this.selectionMode); } comboboxFocusHandler() { if (this.disabled) { return; } this.textInput.value?.focus(); } renderChips() { const { activeChipIndex, readOnly, scale, selectionMode, messages } = this; if (this.selectAllEnabled && this.allSelected) { return null; } return this.selectedItems.map((item, i) => { const chipClasses = { [CSS$1.chip]: true }; const ancestors = [...getItemAncestors(item)].reverse(); const itemLabel = getLabel(item); const pathLabel = [...ancestors, item].map((el) => getLabel(el)); const label = selectionMode !== "ancestors" ? itemLabel : pathLabel.join(" / "); return keyed(itemLabel, html`<calcite-chip .appearance=${readOnly ? "outline" : "solid"} class=${safeClassMap(chipClasses)} .closable=${!readOnly} data-test-id=${`chip-${i}`} .icon=${item.icon} .iconFlipRtl=${item.iconFlipRtl} id=${(item.guid ? `${chipUidPrefix}${item.guid}` : null) ?? nothing} .label=${label} .messageOverrides=${{ dismissLabel: messages.removeTag }} @focusin=${() => this.activeChipIndex = i} @calciteChipClose=${() => this.calciteChipCloseHandler(item)} .scale=${scale} .selected=${item.selected} .tabIndex=${activeChipIndex === i ? 0 : -1} title=${label ?? nothing} .value=${item.value}>${label}</calcite-chip>`); }); } renderAllSelectedIndicatorChip() { const { compactSelectionDisplay, scale, selectedVisibleChipsCount, setAllSelectedIndicatorChipEl } = this; const label = compactSelectionDisplay ? this.messages.all : this.messages.allSelected; return html`<calcite-chip class=${safeClassMap({ chip: true, [CSS$1.chipInvisible]: !(this.allSelected && !selectedVisibleChipsCount), [CSS$1.allSelected]: true })} .label=${label} .scale=${scale} title=${label ?? nothing} value ${ref(setAllSelectedIndicatorChipEl)}>${label}</calcite-chip>`; } renderSelectedIndicatorChip() { const { compactSelectionDisplay, selectionDisplay, getSelectedItems, scale, selectedHiddenChipsCount, selectedVisibleChipsCount, setSelectedIndicatorChipEl } = this; let chipInvisible; let label; if (compactSelectionDisplay) { chipInvisible = true; } else { if (selectionDisplay === "single") { const selectedItemsCount = getSelectedItems().length; if (this.allSelected) { chipInvisible = true; } else if (selectedItemsCount > 0) { chipInvisible = false; } else { chipInvisible = true; } label = `${selectedItemsCount} ${this.messages.selected}`; } else if (selectionDisplay === "fit") { chipInvisible = !!(this.allSelected && selectedVisibleChipsCount === 0 || selectedHiddenChipsCount === 0); label = selectedVisibleChipsCount > 0 ? `+${selectedHiddenChipsCount}` : `${selectedHiddenChipsCount} ${this.messages.selected}`; } } return html`<calcite-chip class=${safeClassMap({ chip: true, [CSS$1.chipInvisible]: chipInvisible })} .label=${label} .scale=${scale} title=${label ?? nothing} value ${ref(setSelectedIndicatorChipEl)}>${label}</calcite-chip>`; } renderSelectedIndicatorChipCompact() { const { compactSelectionDisplay, selectionDisplay, getSelectedItems, scale, selectedHiddenChipsCount } = this; let chipInvisible; let label; if (compactSelectionDisplay) { const selectedItemsCount = getSelectedItems().length; if (this.allSelected) { chipInvisible = true; } else if (selectionDisplay === "fit") { chipInvisible = !(selectedHiddenChipsCount > 0); label = `${selectedHiddenChipsCount || 0}`; } else if (selectionDisplay === "single") { chipInvisible = !(selectedItemsCount > 0); label = `${selectedItemsCount}`; } } else { chipInvisible = true; } return html`<calcite-chip class=${safeClassMap({ chip: true, [CSS$1.chipInvisible]: chipInvisible })} .label=${label} .scale=${scale} title=${label ?? nothing} value>${label}</calcite-chip>`; } renderInput() { const { guid: guid2, disabled, placeholder, selectionMode, selectedItems, open } = this; const single = isSingleLike(selectionMode); const selectedItem = selectedItems[0]; const showLabel = !open && single && !!selectedItem && !this.filterText; return html`<span class=${safeClassMap({ "input-wrap": true, "input-wrap--single": single })}>${showLabel && keyed("label", html`<span class=${safeClassMap({ [CSS$1.label]: true, [CSS$1.labelIcon]: !!selectedItem?.icon })}>${getLabel(selectedItem)}</span>`) || ""}${keyed("input", html`<input aria-activedescendant=${this.activeDescendant ?? nothing} aria-controls=${`${listboxUidPrefix}${guid2}`} aria-errormessage=${IDS.validationMessage} aria-owns=${`${listboxUidPrefix}${guid2}`} aria-autocomplete=list .ariaExpanded=${open} aria-haspopup=listbox .ariaInvalid=${this.status === "invalid"} .ariaLabel=${getLabelText(this)} class=${safeClassMap({ [CSS$1.input]: true, "input--single": true, [CSS$1.inputHidden]: showLabel, "input--icon": this.showingInlineIcon && !!this.placeholderIcon })} data-test-id=input .disabled=${disabled} .id=${`${inputUidPrefix}${guid2}`} @focus=${this.comboboxFocusHandler} @input=${this.inputHandler} placeholder=${placeholder ?? nothing} .readOnly=${this.readOnly} role=combobox .tabIndex=${this.activeChipIndex === -1 ? 0 : -1} type=text .value=${live(this.filterText ?? "")} ${ref(this.textInput)}>`)}</span>`; } renderListBoxOptions() { const mappedListBoxOptions = this.filteredItems.map((item) => { return html`<li .ariaLabel=${item.label} .ariaSelected=${item.selected} id=${(item.guid ? `${itemUidPrefix}${item.guid}` : null) ?? nothing} role=option tabindex=-1>${item.heading || item.textLabel}</li>`; }); if (this.selectAllEnabled && this.selectionMode !== "single" && this.selectionMode !== "single-persist") { const selectAllComboboxItem = html`<li .ariaLabel=${this.messages.selectAll} .ariaSelected=${this.allSelected} .id=${`${this.guid}-select-all-enabled-screen-reader`} role=option tabindex=-1>${this.messages.selectAll}<