UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

976 lines (955 loc) 48.2 kB
import { r as registerInstance, c as createEvent, h, F as Fragment, g as getElement, H as Host } from './index-aa8afca4.js'; import { a as isSymbol, S as Symbol, d as debounce } from './debounce-047e09f4.js'; import { i as isArray, f as forIn } from './forIn-d55260fc.js'; import { n as nodeListToArray, g as getElementDir, c as getElementProp, C as CSS_UTILITY } from './dom-466af3c7.js'; import { g as getKey } from './key-ec82f942.js'; import { u as updatePopper, c as createPopper, C as CSS$2 } from './popper-d89fb7b0.js'; import { g as guid } from './guid-09142681.js'; /** * A specialized version of `_.map` for arrays without support for iteratee * shorthands. * * @private * @param {Array} [array] The array to iterate over. * @param {Function} iteratee The function invoked per iteration. * @returns {Array} Returns the new mapped array. */ function arrayMap(array, iteratee) { var index = -1, length = array == null ? 0 : array.length, result = Array(length); while (++index < length) { result[index] = iteratee(array[index], index, array); } return result; } /** Used as references for various `Number` constants. */ var INFINITY = 1 / 0; /** Used to convert symbols to primitives and strings. */ var symbolProto = Symbol ? Symbol.prototype : undefined, symbolToString = symbolProto ? symbolProto.toString : undefined; /** * The base implementation of `_.toString` which doesn't convert nullish * values to empty strings. * * @private * @param {*} value The value to process. * @returns {string} Returns the string. */ function baseToString(value) { // Exit early for strings to avoid a performance hit in some environments. if (typeof value == 'string') { return value; } if (isArray(value)) { // Recursively convert values (susceptible to call stack limits). return arrayMap(value, baseToString) + ''; } if (isSymbol(value)) { return symbolToString ? symbolToString.call(value) : ''; } var result = (value + ''); return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; } /** * Converts `value` to a string. An empty string is returned for `null` * and `undefined` values. The sign of `-0` is preserved. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to convert. * @returns {string} Returns the converted string. * @example * * _.toString(null); * // => '' * * _.toString(-0); * // => '-0' * * _.toString([1, 2, 3]); * // => '1,2,3' */ function toString(value) { return value == null ? '' : baseToString(value); } /** * Used to match `RegExp` * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). */ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, reHasRegExpChar = RegExp(reRegExpChar.source); /** * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. * * @static * @memberOf _ * @since 3.0.0 * @category String * @param {string} [string=''] The string to escape. * @returns {string} Returns the escaped string. * @example * * _.escapeRegExp('[lodash](https://lodash.com/)'); * // => '\[lodash\]\(https://lodash\.com/\)' */ function escapeRegExp(string) { string = toString(string); return (string && reHasRegExpChar.test(string)) ? string.replace(reRegExpChar, '\\$&') : string; } const filter = (data, value) => { const escapedValue = escapeRegExp(value); const regex = new RegExp(escapedValue, "ig"); if (data.length === 0) { console.warn(`No data was passed to the filter function. The data argument should be an array of objects`); } const find = (input, RE) => { var _a; if ((_a = input) === null || _a === void 0 ? void 0 : _a.constant) { return true; } let found = false; forIn(input, (val) => { if (typeof val === "function") { return; } if (Array.isArray(val) || (typeof val === "object" && val !== null)) { if (find(val, RE)) { found = true; } } else if (RE.test(val)) { found = true; } }); return found; }; const result = data.filter((item) => { return find(item, regex); }); return result; }; const ComboboxItem = "CALCITE-COMBOBOX-ITEM"; const ComboboxItemGroup = "CALCITE-COMBOBOX-ITEM-GROUP"; const ComboboxChildSelector = `${ComboboxItem}, ${ComboboxItemGroup}`; const ComboboxDefaultPlacement = "bottom-leading"; const ComboboxTransitionDuration = 150; function getAncestors(element) { var _a, _b; const parent = (_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.closest(ComboboxChildSelector); const grandparent = (_b = parent === null || parent === void 0 ? void 0 : parent.parentElement) === null || _b === void 0 ? void 0 : _b.closest(ComboboxChildSelector); return [parent, grandparent].filter((el) => el); } function getItemAncestors(item) { var _a; return (((_a = item.ancestors) === null || _a === void 0 ? void 0 : _a.filter((el) => el.nodeName === "CALCITE-COMBOBOX-ITEM")) || []); } function getItemChildren(item) { return nodeListToArray(item.querySelectorAll("calcite-combobox-item")); } function hasActiveChildren(node) { const items = nodeListToArray(node.querySelectorAll("calcite-combobox-item")); return items.filter((item) => item.selected).length > 0; } function getDepth(element) { const result = document.evaluate("ancestor::calcite-combobox-item | ancestor::calcite-combobox-item-group", element, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); return result.snapshotLength; } const calciteComboboxCss = "@-webkit-keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}@keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}:root{--calcite-popper-transition:150ms ease-in-out}:host([hidden]){display:none}:host{display:block;position:relative}:host([disabled]){pointer-events:none;-webkit-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;opacity:0.5}:host([scale=s]){font-size:var(--calcite-font-size--2);--calcite-combobox-item-spacing-unit-l:0.5rem;--calcite-combobox-item-spacing-unit-s:0.25rem;--calcite-combobox-input-height:1.5rem}:host([scale=m]){font-size:var(--calcite-font-size--1);--calcite-combobox-item-spacing-unit-l:0.75rem;--calcite-combobox-item-spacing-unit-s:0.5rem;--calcite-combobox-input-height:2rem}:host([scale=l]){font-size:var(--calcite-font-size-0);--calcite-combobox-item-spacing-unit-l:1rem;--calcite-combobox-item-spacing-unit-s:0.75rem;--calcite-combobox-input-height:2.75rem}.wrapper{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;background-color:var(--calcite-ui-foreground-1);color:var(--calcite-ui-text-1);outline-offset:0;outline-color:transparent;-webkit-transition:outline-offset 100ms ease-in-out, outline-color 100ms ease-in-out;transition:outline-offset 100ms ease-in-out, outline-color 100ms ease-in-out;-webkit-box-shadow:inset 0 0 0 1px var(--calcite-ui-border-input);box-shadow:inset 0 0 0 1px var(--calcite-ui-border-input);padding:var(--calcite-combobox-item-spacing-unit-s) var(--calcite-combobox-item-spacing-unit-l) 0 var(--calcite-combobox-item-spacing-unit-l)}.wrapper--active{outline:2px solid var(--calcite-ui-brand);outline-offset:-2px}.wrapper--single{padding:0 var(--calcite-combobox-item-spacing-unit-l);cursor:pointer;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.input{-ms-flex-positive:1;flex-grow:1;font-family:inherit;padding:0;background-color:transparent;border-style:none;color:var(--calcite-ui-text-1);-webkit-appearance:none;-moz-appearance:none;appearance:none;font-size:inherit;height:var(--calcite-combobox-input-height);line-height:var(--calcite-combobox-input-height);min-width:120px;margin-bottom:var(--calcite-combobox-item-spacing-unit-s)}.input:focus{outline:2px solid transparent;outline-offset:2px}.input--transparent{opacity:0}.input--single{margin-bottom:0;margin-top:0;padding:0}.wrapper--active .input-single{cursor:text}.input--hidden{width:0;min-width:0;opacity:0;pointer-events:none}.input--icon{padding:0 var(--calcite-combobox-item-spacing-unit-l)}.input-wrap{display:-ms-flexbox;display:flex}.input-wrap--single{-ms-flex:1 1 auto;flex:1 1 auto;overflow:hidden}.label{padding:0;display:block;-ms-flex:1 1 auto;flex:1 1 auto;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;height:var(--calcite-combobox-input-height);line-height:var(--calcite-combobox-input-height)}.label--spaced{padding-top:0;padding-bottom:0;padding-left:var(--calcite-combobox-item-spacing-unit-l);padding-right:var(--calcite-combobox-item-spacing-unit-l)}.icon-end,.icon-start{cursor:pointer;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;width:1rem}.popper-container{width:100%;display:block;position:absolute;z-index:900;-webkit-transform:scale(0);transform:scale(0);visibility:hidden;pointer-events:none}.popper-container .calcite-popper-anim{position:relative;z-index:1;-webkit-transition:var(--calcite-popper-transition);transition:var(--calcite-popper-transition);visibility:hidden;-webkit-transition-property:visibility, opacity, -webkit-transform;transition-property:visibility, opacity, -webkit-transform;transition-property:transform, visibility, opacity;transition-property:transform, visibility, opacity, -webkit-transform;opacity:0;-webkit-box-shadow:0 0 16px 0 rgba(0, 0, 0, 0.16);box-shadow:0 0 16px 0 rgba(0, 0, 0, 0.16);border-radius:0.25rem}.popper-container[data-popper-placement^=bottom] .calcite-popper-anim{-webkit-transform:translateY(-5px);transform:translateY(-5px)}.popper-container[data-popper-placement^=top] .calcite-popper-anim{-webkit-transform:translateY(5px);transform:translateY(5px)}.popper-container[data-popper-placement^=left] .calcite-popper-anim{-webkit-transform:translateX(5px);transform:translateX(5px)}.popper-container[data-popper-placement^=right] .calcite-popper-anim{-webkit-transform:translateX(-5px);transform:translateX(-5px)}.popper-container[data-popper-placement] .calcite-popper-anim--active{opacity:1;visibility:visible;-webkit-transform:translate(0);transform:translate(0)}.popper-container--active{pointer-events:initial;visibility:visible}.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{overflow-y:auto;max-height:100vh;background-color:var(--calcite-ui-foreground-1);width:var(--calcite-dropdown-width)}.list{display:block;margin:0;padding:0}.list--hide{height:0;overflow:hidden}.chip{margin-right:var(--calcite-combobox-item-spacing-unit-s);margin-bottom:var(--calcite-combobox-item-spacing-unit-s);max-width:100%}.chip--active{background-color:var(--calcite-ui-foreground-3)}.chip:last-child{margin-right:0}.chip--rtl{margin-right:unset;margin-left:var(--calcite-combobox-item-spacing-unit-l)}.chip--rtl:last-child{margin-left:0}.item{display:block}"; const isGroup = (el) => el.tagName === ComboboxItemGroup; const CalciteCombobox = class { constructor(hostRef) { registerInstance(this, hostRef); this.calciteLookupChange = createEvent(this, "calciteLookupChange", 7); this.calciteComboboxFilterChange = createEvent(this, "calciteComboboxFilterChange", 7); this.calciteComboboxChipDismiss = createEvent(this, "calciteComboboxChipDismiss", 7); //-------------------------------------------------------------------------- // // Public Properties // //-------------------------------------------------------------------------- /** Open and close combobox */ this.active = false; /** Disable combobox input */ this.disabled = false; /** Specify the maximum number of combobox items (including nested children) to display before showing the scroller */ this.maxItems = 0; /** Describes the type of positioning to use for the overlaid content. If your element is in a fixed container, use the 'fixed' value. */ this.overlayPositioning = "absolute"; /** specify the selection mode * - multi: allow any number of selected items (default) * - single: only one selection) * - ancestors: like multi, but show ancestors of selected items as selected, only deepest children shown in chips */ this.selectionMode = "multi"; /** Specify the scale of the combobox, defaults to m */ this.scale = "m"; //-------------------------------------------------------------------------- // // Private State/Props // //-------------------------------------------------------------------------- this.items = []; this.groupItems = []; this.selectedItems = []; this.visibleItems = []; this.hideList = !this.active; this.activeItemIndex = -1; this.activeChipIndex = -1; this.activeDescendant = ""; this.text = ""; this.open = this.active; /** specifies the item wrapper height; it is updated when maxItems is > 0 **/ this.maxScrollerHeight = 0; this.textInput = null; this.observer = null; this.guid = guid(); this.inputHeight = 0; // -------------------------------------------------------------------------- // // Private Methods // // -------------------------------------------------------------------------- this.setMaxScrollerHeight = () => { if (this.active) { this.maxScrollerHeight = this.getMaxScrollerHeight(this.getCombinedItems()); } }; this.calciteChipDismissHandler = (event, comboboxItem) => { this.active = false; const selection = this.items.find((item) => item === comboboxItem); if (selection) { this.toggleSelection(selection, false); } this.calciteComboboxChipDismiss.emit(event.detail); }; this.setFocusClick = () => { this.setFocus(); }; this.setInactiveIfNotContained = (event) => { const composedPath = event.composedPath(); if (!this.active || composedPath.includes(this.el) || composedPath.includes(this.referenceEl)) { return; } if (this.allowCustomValues && this.text) { this.addCustomChip(this.text); } if (this.selectionMode === "single") { if (this.textInput) { this.textInput.value = ""; } this.text = ""; this.filterItems(""); this.updateActiveItemIndex(-1); } this.active = false; }; this.setMenuEl = (el) => { this.menuEl = el; }; this.setListContainerEl = (el) => { this.listContainerEl = el; }; this.setReferenceEl = (el) => { this.referenceEl = el; }; 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 || 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.visibleItems = this.getVisibleItems(); this.calciteComboboxFilterChange.emit({ visibleItems: [...this.visibleItems], text: text }); }, 100); })(); this.internalCalciteLookupChangeEvent = () => { this.calciteLookupChange.emit(this.selectedItems); }; this.emitCalciteLookupChange = debounce(this.internalCalciteLookupChangeEvent, 0); this.updateItems = () => { this.items = this.getItems(); this.groupItems = this.getGroupItems(); this.data = this.getData(); this.selectedItems = this.getSelectedItems(); this.visibleItems = this.getVisibleItems(); this.needsIcon = this.getNeedsIcon(); if (this.selectionMode === "single" && this.selectedItems.length) { this.selectedItem = this.selectedItems[0]; } if (!this.allowCustomValues) { this.setMaxScrollerHeight(); } }; this.comboboxFocusHandler = () => { this.active = true; this.textInput.focus(); }; this.comboboxBlurHandler = (event) => { this.setInactiveIfNotContained(event); }; } activeHandler(newValue, oldValue) { clearTimeout(this.hideListTimeout); // when closing, wait transition time then hide to prevent overscroll if (oldValue && !newValue) { this.open = false; this.hideListTimeout = window.setTimeout(() => { this.hideList = true; }, ComboboxTransitionDuration); } else if (!oldValue && newValue) { // give the combobox height, then reposition prior to opening this.hideList = false; requestAnimationFrame(() => { this.reposition(); this.setMaxScrollerHeight(); this.open = true; }); } this.reposition(); this.setMaxScrollerHeight(); } maxItemsHandler() { this.setMaxScrollerHeight(); } //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- documentClickHandler(event) { this.setInactiveIfNotContained(event); } calciteComboboxItemChangeHandler(event) { const target = event.target; this.toggleSelection(target, target.selected); } keydownHandler(event) { const key = getKey(event.key, getElementDir(this.el)); switch (key) { case "Tab": this.activeChipIndex = -1; this.activeItemIndex = -1; if (this.allowCustomValues && this.text) { this.addCustomChip(this.text, true); event.preventDefault(); } else { this.active = false; } break; case "ArrowLeft": this.previousChip(); break; case "ArrowRight": this.nextChip(); break; case "ArrowUp": event.preventDefault(); this.active = true; this.shiftActiveItemIndex(-1); break; case "ArrowDown": event.preventDefault(); this.active = true; this.shiftActiveItemIndex(1); break; case "Home": this.active = true; this.updateActiveItemIndex(0); break; case "End": this.active = true; this.updateActiveItemIndex(this.visibleItems.length - 1); break; case "Escape": this.active = false; break; case "Enter": if (this.activeItemIndex > -1) { this.toggleSelection(this.visibleItems[this.activeItemIndex]); } else if (this.activeChipIndex > -1) { this.removeActiveChip(); } else if (this.allowCustomValues && this.text) { this.addCustomChip(this.text, true); } break; case "Delete": case "Backspace": if (this.activeChipIndex > -1) { this.removeActiveChip(); } else if (!this.text && this.isMulti()) { this.removeLastChip(); } break; default: if (!this.active) { this.setFocus(); } break; } } //-------------------------------------------------------------------------- // // Public Methods // //-------------------------------------------------------------------------- async reposition() { const { popper, menuEl } = this; const modifiers = this.getModifiers(); popper ? updatePopper({ el: menuEl, modifiers, placement: ComboboxDefaultPlacement, popper }) : this.createPopper(); } async setFocus() { var _a; this.active = true; (_a = this.textInput) === null || _a === void 0 ? void 0 : _a.focus(); this.activeChipIndex = -1; this.activeItemIndex = -1; } // -------------------------------------------------------------------------- // // Lifecycle // // -------------------------------------------------------------------------- connectedCallback() { { this.observer = new MutationObserver(this.updateItems); } this.createPopper(); } componentWillLoad() { this.updateItems(); } componentDidLoad() { var _a; (_a = this.observer) === null || _a === void 0 ? void 0 : _a.observe(this.el, { childList: true, subtree: true }); } componentDidRender() { if (this.el.offsetHeight !== this.inputHeight) { this.reposition(); this.inputHeight = this.el.offsetHeight; } } disconnectedCallback() { var _a; (_a = this.observer) === null || _a === void 0 ? void 0 : _a.disconnect(); this.destroyPopper(); } /** when search text is cleared, reset active to */ textHandler() { this.updateActiveItemIndex(-1); } getModifiers() { const flipModifier = { name: "flip", enabled: true }; flipModifier.options = { fallbackPlacements: ["top-start", "top", "top-end", "bottom-start", "bottom", "bottom-end"] }; return [flipModifier]; } createPopper() { this.destroyPopper(); const { menuEl, referenceEl, overlayPositioning } = this; const modifiers = this.getModifiers(); this.popper = createPopper({ el: menuEl, modifiers, overlayPositioning, placement: ComboboxDefaultPlacement, referenceEl }); } destroyPopper() { const { popper } = this; if (popper) { popper.destroy(); } this.popper = null; } getMaxScrollerHeight(items) { const { maxItems } = this; let itemsToProcess = 0; let maxScrollerHeight = 0; items.forEach((item) => { if (itemsToProcess < maxItems && maxItems > 0) { const height = this.calculateSingleItemHeight(item); if (height > 0) { maxScrollerHeight += height; itemsToProcess++; } } }); return maxScrollerHeight; } calculateSingleItemHeight(item) { let height = item.offsetHeight; // if item has children items, don't count their height twice const children = item.querySelectorAll(ComboboxChildSelector); children.forEach((child) => { height -= child.offsetHeight; }); return height; } getCombinedItems() { return [...this.groupItems, ...this.items]; } toggleSelection(item, value = !item.selected) { if (!item) { return; } item.selected = value; if (this.isMulti()) { this.updateAncestors(item); this.selectedItems = this.getSelectedItems(); this.emitCalciteLookupChange(); this.resetText(); this.textInput.focus(); this.filterItems(""); } else { this.items.filter((el) => el !== item).forEach((el) => (el.selected = false)); this.selectedItem = item; this.textInput.value = item.textLabel; this.active = 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; } }); } } getVisibleItems() { return this.items.filter((item) => !item.hidden); } getSelectedItems() { 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) => ({ constant: item.constant, value: item.value, label: item.textLabel, guid: item.guid })); } getNeedsIcon() { return this.selectionMode === "single" && this.items.some((item) => item.icon); } resetText() { 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 { const item = document.createElement(ComboboxItem); item.value = value; item.textLabel = value; item.guid = guid(); item.selected = true; this.el.appendChild(item); this.resetText(); if (focus) { this.setFocus(); } this.updateItems(); this.filterItems(""); this.emitCalciteLookupChange(); } } 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() { var _a; const guid = (_a = this.selectedItems[this.activeChipIndex]) === null || _a === void 0 ? void 0 : _a.guid; const chip = this.referenceEl.querySelector(`#chip-${guid}`); chip === null || chip === void 0 ? void 0 : chip.setFocus(); } shiftActiveItemIndex(delta) { const length = this.visibleItems.length; const newIndex = (this.activeItemIndex + length + delta) % length; this.updateActiveItemIndex(newIndex); // ensure active item is in view if we have scrolling const activeItem = this.visibleItems[this.activeItemIndex]; 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; } } updateActiveItemIndex(index) { this.activeItemIndex = index; let activeDescendant = null; this.visibleItems.forEach((el, i) => { if (i === index) { el.active = true; activeDescendant = el.guid; } else { el.active = false; } }); this.activeDescendant = activeDescendant; if (this.activeItemIndex > -1) { this.activeChipIndex = -1; this.textInput.focus(); } } isMulti() { return this.selectionMode === "multi" || this.selectionMode === "ancestors"; } //-------------------------------------------------------------------------- // // Render Methods // //-------------------------------------------------------------------------- renderChips() { const { activeChipIndex, scale, selectionMode, el } = this; const dir = getElementDir(el); return this.selectedItems.map((item, i) => { const chipClasses = { chip: true, "chip--active": activeChipIndex === i, "chip--rtl": dir === "rtl" }; 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", { "aria-label": label, class: chipClasses, dismissLabel: "remove tag", dismissible: true, icon: item.icon, id: `chip-${item.guid}`, key: item.textLabel, onCalciteChipDismiss: (event) => this.calciteChipDismissHandler(event, item), scale: scale, title: label, value: item.value }, label)); }); } renderInput() { const { active, disabled, placeholder, selectionMode, needsIcon, label } = this; const single = selectionMode === "single"; const showLabel = !active && single && !!this.selectedItem; return (h("span", { class: { "input-wrap": true, "input-wrap--single": single } }, showLabel && (h("span", { class: { label: true, "label--spaced": needsIcon }, key: "label", onFocus: this.comboboxFocusHandler, tabindex: "0" }, this.selectedItem.textLabel)), h("input", { "aria-activedescendant": this.activeDescendant, "aria-autocomplete": "list", "aria-controls": guid, "aria-label": label, class: { input: true, "input--transparent": this.activeChipIndex > -1, "input--single": single, "input--hidden": showLabel, "input--icon": single && needsIcon }, disabled: disabled, id: `${guid}-input`, key: "input", onBlur: this.comboboxBlurHandler, onFocus: this.comboboxFocusHandler, onInput: this.inputHandler, placeholder: placeholder, ref: (el) => (this.textInput = el), type: "text" }))); } renderListBoxOptions() { return this.visibleItems.map((item) => (h("li", { "aria-selected": (!!item.selected).toString(), id: item.guid, role: "option", tabindex: "-1" }, item.textLabel))); } renderPopperContainer() { const { active, maxScrollerHeight, setMenuEl, setListContainerEl, hideList, open } = this; const classes = { "list-container": true, [CSS$2.animation]: true, [CSS$2.animationActive]: active }; const style = { maxHeight: maxScrollerHeight > 0 ? `${maxScrollerHeight}px` : "" }; return (h("div", { "aria-hidden": "true", class: { "popper-container": true, "popper-container--active": open }, ref: setMenuEl }, h("div", { class: classes, ref: setListContainerEl, style: style }, h("ul", { class: { list: true, "list--hide": hideList } }, h("slot", null))))); } renderIconStart() { const { selectionMode, needsIcon, selectedItem } = this; return (selectionMode === "single" && needsIcon && (h("span", { class: "icon-start" }, (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon) && (h("calcite-icon", { class: "selected-icon", icon: selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.icon, scale: "s" }))))); } renderIconEnd() { return (this.selectionMode === "single" && (h("span", { class: "icon-end" }, h("calcite-icon", { icon: "chevron-down", scale: "s" })))); } render() { const { guid, open, label } = this; const single = this.selectionMode === "single"; const labelId = `${guid}-label`; return (h(Fragment, null, h("div", { "aria-autocomplete": "list", "aria-expanded": open.toString(), "aria-haspopup": "listbox", "aria-labelledby": labelId, "aria-owns": guid, class: { wrapper: true, "wrapper--active": open, "wrapper--single": single }, onClick: this.setFocusClick, ref: this.setReferenceEl, role: "combobox" }, this.renderIconStart(), !single && this.renderChips(), h("label", { class: "screen-readers-only", htmlFor: `${guid}-input`, id: labelId }, label), this.renderInput(), this.renderIconEnd()), h("ul", { "aria-labelledby": labelId, "aria-multiselectable": "true", class: "screen-readers-only", id: guid, role: "listbox", tabIndex: -1 }, this.renderListBoxOptions()), this.renderPopperContainer())); } get el() { return getElement(this); } static get watchers() { return { "active": ["activeHandler"], "maxItems": ["maxItemsHandler"], "text": ["textHandler"] }; } }; CalciteCombobox.style = calciteComboboxCss; const CSS$1 = { icon: "icon", iconActive: "icon--active", custom: "icon--custom", dot: "icon--dot", single: "label--single", label: "label", active: "label--active", selected: "label--selected", title: "title", textContainer: "text-container" }; const calciteComboboxItemCss = "@charset \"UTF-8\";@-webkit-keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}@keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}:root{--calcite-popper-transition:150ms ease-in-out}:host([hidden]){display:none}:host{--calcite-combobox-item-indent-value:calc(\n var(--calcite-combobox-item-spacing-indent) * var(--calcite-combobox-item-spacing-indent-multiplier)\n );--calcite-combobox-item-indent-start:var(--calcite-combobox-item-indent-value);--calcite-combobox-item-indent-end:unset}.scale--s{font-size:var(--calcite-font-size--2);line-height:1rem;--calcite-combobox-item-spacing-unit-l:0.5rem;--calcite-combobox-item-spacing-unit-s:0.25rem;--calcite-combobox-item-spacing-indent:0.5rem}.scale--m{font-size:var(--calcite-font-size--1);line-height:1rem;--calcite-combobox-item-spacing-unit-l:0.75rem;--calcite-combobox-item-spacing-unit-s:0.5rem;--calcite-combobox-item-spacing-indent:0.75rem}.scale--l{font-size:var(--calcite-font-size-0);line-height:1.25rem;--calcite-combobox-item-spacing-unit-l:1rem;--calcite-combobox-item-spacing-unit-s:0.75rem;--calcite-combobox-item-spacing-indent:1rem}.calcite--rtl{--calcite-combobox-item-indent-start:unset;--calcite-combobox-item-indent-end:var(--calcite-combobox-item-indent-value)}:host(:focus){-webkit-box-shadow:none;box-shadow:none}:host,ul{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;margin:0;padding:0;outline:2px solid transparent;outline-offset:2px}.label{display:-ms-flexbox;display:flex;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;min-width:100%;-ms-flex-align:center;align-items:center;color:var(--calcite-ui-text-3);cursor:pointer;position:relative;-webkit-transition-duration:150ms;transition-duration:150ms;-webkit-transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);outline-offset:0;outline-color:transparent;-webkit-transition:outline-offset 100ms ease-in-out, outline-color 100ms ease-in-out;transition:outline-offset 100ms ease-in-out, outline-color 100ms ease-in-out;text-decoration:none;padding:var(--calcite-combobox-item-spacing-unit-l)}:host([disabled]) .label{cursor:default}.label--selected{color:var(--calcite-ui-text-1);font-weight:var(--calcite-font-weight-medium)}.label--active{outline:2px solid var(--calcite-ui-brand);outline-offset:-2px}.label:hover,.label:active{color:var(--calcite-ui-text-1);background-color:var(--calcite-ui-foreground-2);-webkit-box-shadow:none;box-shadow:none;text-decoration:none}.title{padding:0 var(--calcite-combobox-item-spacing-unit-l)}.icon{display:-ms-inline-flexbox;display:inline-flex;opacity:0;-webkit-transition-duration:150ms;transition-duration:150ms;-webkit-transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);color:var(--calcite-ui-border-1)}.icon--indent{padding-left:var(--calcite-combobox-item-indent-start);padding-right:var(--calcite-combobox-item-indent-end)}.icon--custom{margin-top:-1px;color:var(--calcite-ui-text-3)}.icon--active{color:var(--calcite-ui-text-1)}.icon--dot{display:-ms-flexbox;display:flex;-ms-flex-pack:end;justify-content:flex-end;width:var(--calcite-combobox-item-spacing-unit-l)}.icon--dot:before{content:\"•\"}.calcite--rtl .icon--dot:before{text-align:right}.label--active .icon{opacity:1}.label--selected .icon{opacity:1;color:var(--calcite-ui-brand)}:host(:hover[disabled]) .icon{opacity:1}"; const CalciteComboboxItem = class { constructor(hostRef) { registerInstance(this, hostRef); this.calciteComboboxItemChange = createEvent(this, "calciteComboboxItemChange", 7); // -------------------------------------------------------------------------- // // Properties // // -------------------------------------------------------------------------- /** When true, the item cannot be clicked and is visually muted. */ this.disabled = false; /** Set this to true to pre-select an item. Toggles when an item is checked/unchecked. */ this.selected = false; /** True when item is highlighted either from keyboard or mouse hover */ this.active = false; /** Unique identifier, used for accessibility */ this.guid = guid(); this.scale = "m"; // -------------------------------------------------------------------------- // // Private Methods // // -------------------------------------------------------------------------- this.itemClickHandler = (event) => { event.preventDefault(); if (this.disabled) { return; } this.selected = !this.selected; }; } selectedWatchHandler() { this.calciteComboboxItemChange.emit(this.el); } // -------------------------------------------------------------------------- // // Lifecycle // // -------------------------------------------------------------------------- connectedCallback() { this.ancestors = getAncestors(this.el); this.scale = getElementProp(this.el, "scale", this.scale); } componentWillLoad() { this.hasDefaultSlot = this.el.querySelector(":not([slot])") !== null; } // -------------------------------------------------------------------------- // // Public Methods // // -------------------------------------------------------------------------- /** * Used to toggle the selection state. By default this won't trigger an event. * The first argument allows the value to be coerced, rather than swapping values. */ async toggleSelected(coerce) { if (this.disabled) { return; } this.selected = typeof coerce === "boolean" ? coerce : !this.selected; } // -------------------------------------------------------------------------- // // Render Methods // // -------------------------------------------------------------------------- renderIcon(isSingle) { const { icon, disabled, selected } = this; const level = `${CSS$1.icon}--indent`; const defaultIcon = isSingle ? "dot" : "check"; const iconPath = disabled ? "circle-disallowed" : defaultIcon; const showDot = isSingle && !icon && !disabled; return showDot ? (h("span", { class: { [CSS$1.icon]: true, [CSS$1.dot]: true, [level]: true } })) : (h("calcite-icon", { class: { [CSS$1.icon]: !icon, [CSS$1.custom]: !!icon, [CSS$1.iconActive]: icon && selected, [level]: true }, icon: icon || iconPath, scale: "s" })); } renderChildren() { if (!this.hasDefaultSlot) { return null; } return (h("ul", null, h("slot", null))); } render() { const isSingleSelect = getElementProp(this.el, "selection-mode", "multi") === "single"; const dir = getElementDir(this.el); const classes = { [CSS_UTILITY.rtl]: dir === "rtl", [CSS$1.label]: true, [CSS$1.selected]: this.selected, [CSS$1.active]: this.active, [CSS$1.single]: isSingleSelect }; const depth = getDepth(this.el); return (h(Host, { "aria-hidden": "true" }, h("div", { class: `scale--${this.scale}`, style: { "--calcite-combobox-item-spacing-indent-multiplier": `${depth}` } }, h("li", { class: classes, id: this.guid, onClick: this.itemClickHandler }, this.renderIcon(isSingleSelect), h("span", { class: CSS$1.title }, this.textLabel)), this.renderChildren()))); } get el() { return getElement(this); } static get watchers() { return { "selected": ["selectedWatchHandler"] }; } }; CalciteComboboxItem.style = calciteComboboxItemCss; const CSS = { list: "list", label: "label", title: "title" }; const calciteComboboxItemGroupCss = "@-webkit-keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}@keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}:root{--calcite-popper-transition:150ms ease-in-out}:host([hidden]){display:none}:host{--calcite-combobox-item-indent-start-1:var(--calcite-combobox-item-spacing-indent-1);--calcite-combobox-item-indent-end-1:unset;--calcite-combobox-item-indent-start-2:var(--calcite-combobox-item-spacing-indent-2);--calcite-combobox-item-indent-end-2:unset}.scale--s{font-size:var(--calcite-font-size--2);line-height:1rem;--calcite-combobox-item-spacing-unit-l:0.5rem;--calcite-combobox-item-spacing-unit-s:0.25rem;--calcite-combobox-item-spacing-indent-1:0.5rem;--calcite-combobox-item-spacing-indent-2:1rem}.scale--m{font-size:var(--calcite-font-size--1);line-height:1rem;--calcite-combobox-item-spacing-unit-l:0.75rem;--calcite-combobox-item-spacing-unit-s:0.5rem;--calcite-combobox-item-spacing-indent-1:0.75rem;--calcite-combobox-item-spacing-indent-2:1.5rem}.scale--l{font-size:var(--calcite-font-size-0);line-height:1.25rem;--calcite-combobox-item-spacing-unit-l:1rem;--calcite-combobox-item-spacing-unit-s:0.75rem;--calcite-combobox-item-spacing-indent-1:1rem;--calcite-combobox-item-spacing-indent-2:2rem}.calcite--rtl{--calcite-combobox-item-indent-start-1:unset;--calcite-combobox-item-indent-end-1:var(--calcite-combobox-item-spacing-indent-1);--calcite-combobox-item-indent-start-2:unset;--calcite-combobox-item-indent-end-2:var(--calcite-combobox-item-spacing-indent-2)}:host,.list{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;margin:0;padding:0;outline:2px solid transparent;outline-offset:2px}.label{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;max-width:100%;min-width:0;color:var(--calcite-ui-text-3);display:-ms-flexbox;display:flex}.label--indent-1{padding-left:var(--calcite-combobox-item-indent-start-1);padding-right:var(--calcite-combobox-item-indent-end-1)}.label--indent-2{padding-left:var(--calcite-combobox-item-indent-start-2);padding-right:var(--calcite-combobox-item-indent-end-2)}.title{border:0 solid;display:block;-ms-flex:1 1 0%;flex:1 1 0%;border-bottom-color:var(--calcite-ui-border-3);border-bottom-width:1px;color:var(--calcite-ui-text-2);font-weight:var(--calcite-font-weight-bold);word-wrap:break-word;word-break:break-word;padding:var(--calcite-combobox-item-spacing-unit-l) 0;margin-left:var(--calcite-combobox-item-spacing-unit-s);margin-right:var(--calcite-combobox-item-spacing-unit-s)}"; const CalciteComboboxItemGroup = class { constructor(hostRef) { registerInstance(this, hostRef); this.guid = guid(); this.scale = "m"; } // -------------------------------------------------------------------------- // // Lifecycle // // -------------------------------------------------------------------------- connectedCallback() { this.ancestors = getAncestors(this.el); this.scale = getElementProp(this.el, "scale", this.scale); } // -------------------------------------------------------------------------- // // Render Methods // // -------------------------------------------------------------------------- render() { const { el, scale } = this; const dir = getElementDir(el); const indent = `${CSS.label}--indent-${getDepth(el)}`; return (h("ul", { "aria-labelledby": this.guid, class: { [CSS.list]: true, [CSS_UTILITY.rtl]: dir === "rtl", [`scale--${scale}`]: true }, role: "group" }, h("li", { class: { [CSS.label]: true, [indent]: true }, id: this.guid, role: "presentation" }, h("span", { class: CSS.title }, this.label)), h("slot", null))); } get el() { return getElement(this); } }; CalciteComboboxItemGroup.style = calciteComboboxItemGroupCss; export { CalciteCombobox as calcite_combobox, CalciteComboboxItem as calcite_combobox_item, CalciteComboboxItemGroup as calcite_combobox_item_group };