UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

994 lines • 59.8 kB
/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */ import { D as DEBOUNCE, c as customElement } from "../../chunks/runtime.js"; import { keyed } from "lit/directives/keyed.js"; import { live } from "lit/directives/live.js"; import { debounce } from "es-toolkit"; import { escapeRegExp } from "es-toolkit/compat"; import { c as calciteSize48 } from "../../chunks/core.js"; import { nothing, html, css } from "lit"; import { ref, createRef } from "lit/directives/ref.js"; import { safeClassMap, LitElement, createEvent, stringOrBoolean } from "@arcgis/lumina"; import { f as filter } from "../../chunks/filter.js"; import { l as getElementWidth, m as getTextWidth, j as focusElement } from "../../chunks/dom.js"; import { d 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 { c as connectLabel, d as disconnectLabel, g as getLabelText } from "../../chunks/label.js"; import { c as createObserver, u as updateRefObserver } from "../../chunks/observers.js"; import { t as toggleOpenClose } from "../../chunks/openCloseComponent.js"; import { g as getIconScale, i as isHidden } from "../../chunks/component.js"; import { I as InternalLabel } from "../../chunks/InternalLabel.js"; import { V as Validation } from "../../chunks/Validation.js"; import { u as useT9n } from "../../chunks/useT9n.js"; import { h as highlightText } from "../../chunks/text.js"; import { u as useSetFocus } from "../../chunks/useSetFocus.js"; import { u as useCancelable } from "../../chunks/useCancelable.js"; import { u as useInteractive } from "../../chunks/useInteractive.js"; import { u as useTopLayer } from "../../chunks/useTopLayer.js"; import { I as IDS, C as CSS$1, a as ComboboxItemSelector, b as ComboboxItemGroupSelector, c as ICONS } from "../../chunks/resources5.js"; import { h as hasActiveChildren, i as isSingleLike, g as getLabel, a as getItemAncestors, b as getItemChildren } from "../../chunks/utils2.js"; const CSS = { button: "x-button", buttonRound: "x-button--round" }; const XButton = ({ disabled, focusable, key, label, onClick, ref: ref$1, round = true, scale, title }) => keyed(key, html`<button .ariaLabel=${label} class=${safeClassMap({ [((scale2) => `x-button--${scale2}`)(scale)]: true, [CSS.button]: true, [CSS.buttonRound]: round })} .disabled=${disabled} @click=${onClick} .tabIndex=${focusable ? 0 : -1} title=${title ?? nothing} type=button ${ref(ref$1)}><calcite-icon icon=x .scale=${getIconScale(scale)}></calcite-icon></button>`); 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-relative-sm);--calcite-internal-combobox-spacing-unit-l: .5rem;--calcite-internal-combobox-spacing-unit-s: .25rem;--calcite-internal-combobox-no-matches-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-relative-base);--calcite-internal-combobox-spacing-unit-l: .75rem;--calcite-internal-combobox-spacing-unit-s: .5rem;--calcite-internal-combobox-no-matches-spacing-unit-s: .375rem;--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-relative-md);--calcite-internal-combobox-spacing-unit-l: 1rem;--calcite-internal-combobox-spacing-unit-s: .75rem;--calcite-internal-combobox-no-matches-spacing-unit-s: .625rem;--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:var(--calcite-border-width-md) solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(calc(-1 * var(--calcite-spacing-base)) * calc(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:var(--calcite-border-width-md) solid var(--calcite-color-status-danger);outline-offset:calc(calc(-1 * var(--calcite-spacing-base)) * calc(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{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)}@starting-style{.floating-ui-container{opacity:0;inset-block-start:0;left:0}}:host([top-layer-disabled]) .floating-ui-container{--calcite-floating-ui-z-index: var(--calcite-z-index-dropdown)}.floating-ui-container[popover]{padding:0;margin:0;border:none;background-color:transparent;overflow:visible;display:none}.floating-ui-container:popover-open{display:block}.floating-ui-container .calcite-floating-ui-anim{position:relative;transition-duration:var(--calcite-floating-ui-transition);transition-property:inset-block-start,left,opacity,display;transition-behavior:allow-discrete;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}@starting-style{.floating-ui-container[data-placement] .calcite-floating-ui-anim--active{opacity: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-divider-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)}.no-matches{padding-block:var(--calcite-internal-combobox-no-matches-spacing-unit-s);padding-inline:var(--calcite-internal-combobox-spacing-unit-l);color:var(--calcite-combobox-input-text-color, var(--calcite-color-text-1));background:var(--calcite-combobox-background-color, var(--calcite-color-foreground-1));cursor:pointer}.no-matches-placeholder{color:var(--calcite-combobox-icon-color, var(--calcite-color-text-3));cursor:default}: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:var(--calcite-border-width-md) solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(calc(-1 * var(--calcite-spacing-base)) * calc(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}.text-match{background-color:transparent;color:inherit;font-weight:var(--calcite-font-weight-bold)}.internal-label-alignment--center{align-items:center}.internal-label-alignment--end{align-items:end}.internal-label--container{display:flex;justify-content:space-between;color:var(--calcite-color-text-1)}.internal-label-required--indicator{font-weight:var(--calcite-font-weight-medium);color:var(--calcite-color-status-danger);padding-inline:var(--calcite-spacing-base)}.internal-label-required--indicator:hover{cursor:help}.internal-label--text{line-height:1}:host([scale=s]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-xxs)}:host([scale=s]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-sm)}:host([scale=s]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-sm)}:host([scale=s]) .internal-label--text{font-size:var(--calcite-font-size--2)}:host([scale=m]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label--text{font-size:var(--calcite-font-size--1)}:host([scale=l]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-sm)}:host([scale=l]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-md)}:host([scale=l]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-md)}:host([scale=l]) .internal-label--text{font-size:var(--calcite-font-size-0)}::placeholder{font-weight:var(--calcite-font-weight-normal);color:var(--calcite-input-placeholder-text-color, var(--calcite-color-text-3))}`; class Combobox extends LitElement { constructor() { super(); this.closeButtonRef = createRef(); this.selectAllComboboxItemRef = createRef(); this.allSelectedIndicatorChipRef = createRef(); this.cancelable = useCancelable()(this); 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.noMatchesFound = this.filteredItems.length === 0 && !!this.filterText; this.filterTextMatchPattern = this.filterText && new RegExp(`(${escapeRegExp(this.filterText)})`, "i"); this.keyboardNavItems.forEach((item) => { item.filterTextMatchPattern = this.filterTextMatchPattern; }); if (setOpenToEmptyState) { this.open = this.filterText.trim().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.internalValueChangeFlag = false; this.mutationObserver = createObserver("mutation", () => this.updateItems()); this.onLabelClick = () => { this.setFocus(); }; this.transitionProp = "opacity"; this.resizeObserver = createObserver("resize", () => { this.setMaxScrollerHeight(); this.refreshSelectionDisplay(); }); this.selectedIndicatorChipRef = createRef(); this._selectedItems = []; this.textInputRef = createRef(); this._value = null; this.messages = useT9n(); this.focusSetter = useSetFocus()(this); this.customChipAddHandler = () => { this.addCustomChip(this.filterText, true); }; this.interactiveContainer = useInteractive(this); this.topLayer = useTopLayer({ target: () => this.floatingEl })(this); 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.placement = defaultMenuPlacement; this.readOnly = false; this.required = false; this.scale = "m"; this.selectAllEnabled = false; this.selectionDisplay = "all"; this.selectionMode = "multiple"; this.status = "idle"; this.topLayerDisabled = false; 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 }], items: [16, {}, { state: true }], noMatchesFound: [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, labelText: 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, { type: String }, { reflect: true }], placeholderIconFlipRtl: [7, {}, { reflect: true, type: Boolean }], placement: [3, {}, { reflect: true }], 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 }], topLayerDisabled: [7, {}, { reflect: true, type: Boolean }], validationIcon: [3, { converter: stringOrBoolean, type: String }, { reflect: true }], validationMessage: 1, validity: [0, {}, { attribute: false }], value: 1 }; } static { this.styles = styles; } get effectiveFilterProps() { if (!this.filterProps) { return ["description", "label", "metadata", "shortHeading"]; } 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); } get allSelected() { return this.selectedItems.length === this.items.length; } get indeterminate() { return this.selectedItems.length > 0 && !this.allSelected; } get keyboardNavItems() { const { selectAllComboboxItemRef } = this; const filteredItems = this.filteredItems.filter((item) => !item.disabled); if (selectAllComboboxItemRef.value) { return [selectAllComboboxItemRef.value, ...filteredItems]; } return filteredItems; } 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.items.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(options) { return this.focusSetter(() => { this.activeChipIndex = -1; this.activeItemIndex = -1; return this.textInputRef.value; }, options); } 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); this.cancelable.add(this.filterItems); } async load() { this.handleSelectionModeWarning(); } 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") || changes.has("placement") && (this.hasUpdated || this.placement !== defaultMenuPlacement)) { this.reposition(true); } if (changes.has("selectionMode") || changes.has("scale")) { this.updateItems(); } if (changes.has("flipPlacements")) { this.flipPlacementsHandler(); } } 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(); } filterTextChange(value) { this.updateActiveItemIndex(-1); this.filterItems(value, true); } openHandler() { if (this.disabled) { return; } toggleOpenClose(this); } 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(`#${IDS.chip(item.guid)}`); if (chipEl) { this.hideChip(chipEl); } }); } } calciteComboboxItemChangeHandler(event) { if (this.ignoreSelectedEventsFlag) { return; } const target = event.target; const isSelectAllTarget = event.composedPath().includes(this.selectAllComboboxItemRef.value); if (this.selectAllEnabled) { this.handleSelectAll(isSelectAllTarget); } const newIndex = this.keyboardNavItems.indexOf(isSelectAllTarget ? this.selectAllComboboxItemRef.value : 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() { if (this.textInputRef.value) { this.textInputRef.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.items.forEach((item) => item.selected = toggledValue); this.selectedItems = toggledValue ? this.items : []; 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.textInputRef.value.selectionStart === 0) { this.previousChip(); event.preventDefault(); } break; case "ArrowRight": if (this.activeChipIndex !== -1) { this.nextChip(); event.preventDefault(); } break; case "ArrowUp": if (this.keyboardNavItems.length) { event.preventDefault(); if (this.open) { this.shiftActiveItemIndex(-1); } this.scrollToActiveOrSelectedItem(); if (!this.comboboxInViewport()) { this.el.scrollIntoView(); } } this.scrollToActiveOrSelectedItem(); break; case "ArrowDown": if (this.keyboardNavItems.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.textInputRef.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.keyboardNavItems[this.activeItemIndex]; item.toggleSelection(); event.preventDefault(); } else if (this.activeChipIndex > -1) { this.removeActiveChip(event.target); 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(event.target); } else if (!this.filterText && this.isMulti()) { event.preventDefault(); this.removeLastChip(); } break; } } } onBeforeOpen() { this.topLayer.show(); this.reposition(); this.calciteComboboxBeforeOpen.emit(); setTimeout(() => this.scrollToActiveOrSelectedItem(true), 0); } onOpen() { this.calciteComboboxOpen.emit(); } onBeforeClose() { this.calciteComboboxBeforeClose.emit(); } onClose() { this.calciteComboboxClose.emit(); hideFloatingUI(this); this.topLayer.hide(); } async setMaxScrollerHeight() { const { listContainerEl, open, referenceEl } = this; if (!listContainerEl || !open) { return; } const maxScrollerHeight = this.getMaxScrollerHeight(); listContainerEl.style.maxBlockSize = maxScrollerHeight > 0 ? `${maxScrollerHeight}px` : ""; listContainerEl.style.inlineSize = `${referenceEl.clientWidth}px`; } 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.textInputRef.value || !this.chipContainerEl) { return; } const { allSelectedIndicatorChipRef, chipContainerEl, selectionDisplay, placeholder, selectedIndicatorChipRef, textInputRef } = this; const chipContainerElGap = parseInt(getComputedStyle(chipContainerEl).gap.replace("px", "")); const chipContainerElWidth = getElementWidth(chipContainerEl); const { fontSize, fontFamily } = getComputedStyle(textInputRef.value); const inputTextWidth = getTextWidth(placeholder, `${fontSize} ${fontFamily}`); const inputWidth = (inputTextWidth || parseInt(calciteSize48)) + chipContainerElGap; const allSelectedIndicatorChipElWidth = getElementWidth(allSelectedIndicatorChipRef.value); const selectedIndicatorChipElWidth = getElementWidth(selectedIndicatorChipRef.value); 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(`#${IDS.chip(item.guid)}`); if (chipEl) { this.hideChip(chipEl); } }); } if (this.indeterminate) { this.selectedItems.forEach((item) => { const chipEl = this.referenceEl.querySelector(`#${IDS.chip(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) { updateRefObserver(this.resizeObserver, this.listContainerEl, el); this.listContainerEl = el; this.transitionEl = el; } setChipContainerEl(el) { updateRefObserver(this.resizeObserver, this.chipContainerEl, el); this.chipContainerEl = el; } setReferenceEl(el) { this.referenceEl = el; connectFloatingUI(this); } 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.textInputRef.value) { this.textInputRef.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, index) => (groupItem.scale = this.scale, groupItem.position = index)); 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, el: item // used for matching items to data })); } getGroupData() { return this.groupItems.map((groupItem) => ({ label: groupItem.label, el: groupItem })); } resetText() { if (this.textInputRef.value) { this.textInputRef.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 === 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://webgis.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(chip) { const activeItem = this.selectedItems[this.activeChipIndex]; if (activeItem && `${IDS.chip(activeItem.guid)}` === chip.id) { this.toggleSelection(activeItem, 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; focusElement(this.textInputRef.value); } else { this.activeChipIndex = newIndex; this.focusChip(); } this.updateActiveItemIndex(-1); } focusChip() { const guid2 = this.selectedItems[this.activeChipIndex]?.guid; const chip = guid2 ? this.referenceEl.querySelector(`#${IDS.chip(guid2)}`) : null; chip?.setFocus(); } scrollToActiveOrSelectedItem(scrollToSelected = false) { const item = scrollToSelected && this.selectedItems?.length ? this.selectedItems[0] : this.keyboardNavItems[this.activeItemIndex]; const listContainer = this.listContainerEl; if (!item || !listContainer) { return; } item.scrollIntoView({ block: "nearest" }); const stickyElement = this.selectAllComboboxItemRef.value; const stickyHeight = stickyElement?.offsetHeight || 0; 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.keyboardNavItems; const newIndex = (this.activeItemIndex + length + delta) % length; this.updateActiveItemIndex(newIndex); this.scrollToActiveOrSelectedItem(); } updateActiveItemIndex(index) { this.activeItemIndex = index; let activeDescendant = null; this.keyboardNavItems.forEach((el, i) => { if (i === index) { el.active = true; activeDescendant = `${IDS.item(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.textInputRef.value.focus(); } createScreenReaderItem({ ariaLabel, ariaSelected, id, textContent }) { return html`<li aria-label=${ariaLabel ?? nothing} aria-selected=${ariaSelected ?? nothing} id=${id ?? nothing} role=option tabindex=-1>${textContent}</li>`; } handleSelectionModeWarning() { if (this.selectionMode === "single-persist" && this.clearDisabled) { console.warn(`clearDisabled is ignored when selection-mode is set to "single-persist"`); } } getDescriptionMessage() { const value = Array.isArray(this.value) ? this.value.join(", ") : this.value; return this.readOnly ? this.messages.nonEditable?.replace("{value}", `${value}`) : value; } 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} .icon=${item.icon} .iconFlipRtl=${item.iconFlipRtl} id=${(item.guid ? `${IDS.chip(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 { allSelectedIndicatorChipRef, compactSelectionDisplay, scale, selectedVisibleChipsCount } = this; const label = compactSelectionDisplay ? this.messages.all : this.messages.allSelected; return html`<calcite-chip class=${safeClassMap({ [CSS$1.chip]: true, [CSS$1.chipInvisible]: !(this.allSelected && !selectedVisibleChipsCount), [CSS$1.allSelected]: true })} .label=${label} .scale=${scale} title=${label ?? nothing} value $