@trimble-oss/moduswebcomponents
Version:
Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust
976 lines (973 loc) • 55.6 kB
JavaScript
import { h, F as Fragment, p as proxyCustomElement, H, e as createEvent, c as Host } from './p-BMvVSi6Y.js';
import { S as SearchSolidIcon, d as defineCustomElement$2 } from './p-D9BxvM_A.js';
import { K as KEY, i as inheritAriaAttributes } from './p-COxr4v9W.js';
import { C as CloseSolidIcon } from './p-CyOAOIPl.js';
import { d as defineCustomElement$a } from './p-1YuUWYzU.js';
import { d as defineCustomElement$9 } from './p-B9S-cS6r.js';
import { d as defineCustomElement$8 } from './p-C8YB6DYg.js';
import { d as defineCustomElement$7 } from './p-DWQdP1KV.js';
import { d as defineCustomElement$6 } from './p-CXnOhVIy.js';
import { d as defineCustomElement$5 } from './p-DDd-wWjv.js';
import { d as defineCustomElement$4 } from './p-DRXt-rJG.js';
import { d as defineCustomElement$3 } from './p-THRlGHxO.js';
// Timeout constants for consistent behavior
const BLUR_FOCUSOUT_DELAY_MS = 200; // Delay before handling blur/focusout to allow related element focus
function getClasses(customClass) {
const classList = ['modus-wc-autocomplete'];
if (customClass)
classList.push(customClass);
return classList.join(' ');
}
function getMultiSelectClasses(props) {
return [
'modus-wc-autocomplete-multi-select',
'modus-wc-input',
'modus-wc-w-full',
'modus-wc-flex',
'modus-wc-items-center',
'modus-wc-gap-1',
props.bordered && 'modus-wc-input-bordered',
props.disabled && 'modus-wc-input-disabled',
props.readOnly && 'modus-wc-text-input--readonly',
props.size && `modus-wc-input-${props.size}`,
props.bordered && 'modus-wc-autocomplete-multi-select--bordered',
props.disabled && 'modus-wc-autocomplete-multi-select--disabled',
props.readOnly && 'modus-wc-autocomplete-multi-select--readonly',
]
.filter(Boolean)
.join(' ');
}
function getVisibleItems(filteredItems) {
return (filteredItems === null || filteredItems === void 0 ? void 0 : filteredItems.filter((item) => !item.disabled)) || [];
}
function syncFilteredItems(items, value, leaveMenuOpen, customInputChange) {
if (!items) {
return [];
}
// When leaveMenuOpen is true and an item is selected, show all items
if (leaveMenuOpen && items.some((item) => item.selected)) {
return [...items];
}
// if customInputChange is defined, return items that are visibleInMenu
if (customInputChange) {
return items.filter((item) => item.visibleInMenu);
}
const currentSearchText = (value === null || value === void 0 ? void 0 : value.toLowerCase()) || '';
if (currentSearchText === '') {
// When no search text, show all items that are visibleInMenu
return items.filter((item) => item.visibleInMenu);
}
else {
// Filter items based on current search text AND visibleInMenu
return items.filter((item) => item.visibleInMenu &&
item.label.toLowerCase().includes(currentSearchText));
}
}
function updateItemFocus(items, targetValue) {
if (!items)
return [];
return [
...items.map((item) => (Object.assign(Object.assign({}, item), { focused: item.value === targetValue }))),
];
}
function clearAllFocus(items) {
if (!items)
return [];
return [
...items.map((item) => (Object.assign(Object.assign({}, item), { focused: false }))),
];
}
function handleArrowDown(params) {
const { showMenuOnFocus, minChars, inputValue, initialNavigation, visibleItems, onUpdateFocus, onSetMenuVisible, onSetInitialNavigation, } = params;
if (showMenuOnFocus || inputValue.length >= minChars) {
onSetMenuVisible(true);
}
if (initialNavigation) {
onSetInitialNavigation(false);
return;
}
const currentIndex = visibleItems.findIndex((item) => item.focused);
const nextIndex = currentIndex < 0 ? 0 : Math.min(currentIndex + 1, visibleItems.length - 1);
if (nextIndex >= 0 &&
nextIndex < visibleItems.length &&
visibleItems[nextIndex]) {
const item = visibleItems[nextIndex];
if (item && item.value) {
onUpdateFocus(item.value);
}
}
}
function handleArrowUp(params) {
const { initialNavigation, visibleItems, onUpdateFocus, onSetInitialNavigation, } = params;
if (initialNavigation) {
onSetInitialNavigation(false);
return;
}
const currentIndex = visibleItems.findIndex((item) => item.focused);
const prevIndex = currentIndex < 0 ? visibleItems.length - 1 : Math.max(currentIndex - 1, 0);
if (prevIndex >= 0 &&
prevIndex < visibleItems.length &&
visibleItems[prevIndex]) {
const item = visibleItems[prevIndex];
if (item && item.value) {
onUpdateFocus(item.value);
}
}
}
function processKeyEvent(event, params) {
if (params.customKeyDown) {
params.customKeyDown(event);
return { handled: true, keyLower: '' };
}
// Don't process keyboard events when disabled or readOnly
if (params.disabled || params.readOnly) {
return { handled: true, keyLower: '' };
}
if (!(event.target instanceof HTMLInputElement)) {
return { handled: true, keyLower: '' };
}
const keyLower = event.key.toLowerCase();
if ([KEY.ArrowDown, KEY.ArrowUp, KEY.Enter, KEY.Escape]
.map((k) => k.toLowerCase())
.includes(keyLower)) {
event.preventDefault();
}
return { handled: false, keyLower };
}
function handleBackspace(input, params) {
var _a;
if (params.multiSelect && input.value.length === 0) {
// Get the last selected chip in selection order
if (params.selectionOrder.length > 0) {
const lastSelectedValue = params.selectionOrder[params.selectionOrder.length - 1];
const lastSelectedItem = (_a = params.items) === null || _a === void 0 ? void 0 : _a.find((item) => item.value === lastSelectedValue);
if (lastSelectedItem) {
// Remove the chip internally
params.onChipRemove(lastSelectedItem);
}
}
}
}
function processItemSelection(item, params) {
if (params.disabled || params.readOnly || !params.items) {
return {
updatedItems: params.items,
updatedValue: undefined,
updatedSelectionOrder: params.selectionOrder,
shouldExpandChips: false,
shouldCloseMenu: false,
};
}
if (params.customItemSelect) {
params.customItemSelect(item);
return {
updatedItems: undefined,
updatedValue: undefined,
updatedSelectionOrder: params.selectionOrder,
shouldExpandChips: false,
shouldCloseMenu: false,
};
}
let updatedItems;
let updatedValue = undefined;
let updatedSelectionOrder = params.selectionOrder;
let shouldExpandChips = false;
if (params.multiSelect) {
const currentItem = params.items.find((menuItem) => menuItem.value === item.value);
const isCurrentlySelected = (currentItem === null || currentItem === void 0 ? void 0 : currentItem.selected) || false;
// Also check if item is already in selectionOrder to prevent duplicates
const isInSelectionOrder = params.selectionOrder.includes(item.value);
if (isCurrentlySelected || isInSelectionOrder) {
return {
updatedItems: params.items,
updatedValue: '',
updatedSelectionOrder: params.selectionOrder,
shouldExpandChips: false,
shouldCloseMenu: !params.leaveMenuOpen,
};
}
updatedItems = [
...params.items.map((menuItem) => (Object.assign(Object.assign({}, menuItem), { selected: menuItem.value === item.value ? true : menuItem.selected, focused: params.leaveMenuOpen ? menuItem.value === item.value : false }))),
];
// Add to end of selection order (now guaranteed not to be a duplicate)
updatedSelectionOrder = [...params.selectionOrder, item.value];
// Clear the input value in multi-select mode
updatedValue = '';
// If we exceed maxChips, automatically expand
if (params.maxChips &&
params.maxChips > 0 &&
updatedSelectionOrder.length > params.maxChips) {
shouldExpandChips = true;
}
}
else {
updatedItems = [
...params.items.map((menuItem) => (Object.assign(Object.assign({}, menuItem), { selected: menuItem.value === item.value, focused: params.leaveMenuOpen ? menuItem.value === item.value : false }))),
];
// Always set the input value to show the selected item's label
updatedValue = item.label;
}
return {
updatedItems,
updatedValue,
updatedSelectionOrder,
shouldExpandChips,
shouldCloseMenu: !params.leaveMenuOpen,
};
}
function processChipRemoval(item, params) {
if (params.disabled || params.readOnly || !params.items) {
return {
updatedItems: params.items,
updatedSelectionOrder: params.selectionOrder,
};
}
const updatedItems = [
...params.items.map((menuItem) => (Object.assign(Object.assign({}, menuItem), { selected: menuItem.value === item.value ? false : menuItem.selected }))),
];
// Remove from selection order
const updatedSelectionOrder = params.selectionOrder.filter((value) => value !== item.value);
return {
updatedItems,
updatedSelectionOrder,
};
}
function processInputChange(event, params) {
if (params.disabled || params.readOnly) {
return {
inputValue: '',
shouldShowMenu: false,
updatedItems: params.items,
shouldResetNavigation: false,
};
}
// Add null checks for edge cases
if (!event.detail || !event.detail.target) {
return {
inputValue: '',
shouldShowMenu: false,
updatedItems: params.items,
shouldResetNavigation: false,
};
}
const input = event.detail.target;
const inputValue = input.value || '';
if (params.customInputChange) {
params.customInputChange(inputValue);
return {
inputValue,
shouldShowMenu: false,
updatedItems: undefined, // Don't update items - custom handler will do it
shouldResetNavigation: false,
};
}
// Update menu visibility
let shouldShowMenu;
if (params.showMenuOnFocus) {
shouldShowMenu = true;
}
else {
shouldShowMenu = inputValue.length >= params.minChars;
}
let updatedItems = params.items;
if (params.items) {
// Clear the focused state from all items
updatedItems = [
...params.items.map((item) => (Object.assign(Object.assign({}, item), { focused: false }))),
];
// In single select mode, if the input is cleared, also clear the selection
if (!params.multiSelect && inputValue === '') {
updatedItems = [
...updatedItems.map((item) => (Object.assign(Object.assign({}, item), { selected: false }))),
];
}
}
return {
inputValue,
shouldShowMenu,
updatedItems,
shouldResetNavigation: !!inputValue,
};
}
function renderNoResults(params) {
var _a, _b, _c;
return (h("div", { class: "modus-wc-autocomplete-no-results" },
h("div", { class: "icon-label", "aria-label": (_a = params.noResults) === null || _a === void 0 ? void 0 : _a.ariaLabel },
h(SearchSolidIcon, { className: "modus-wc-autocomplete-search-icon" }),
h("div", { class: "label" }, (_b = params.noResults) === null || _b === void 0 ? void 0 : _b.label)),
h("div", { class: "sub-label" }, (_c = params.noResults) === null || _c === void 0 ? void 0 : _c.subLabel)));
}
function renderChips(params) {
// Get selected items in selection order
const selectedItems = params.selectionOrder
.map((value) => { var _a; return (_a = params.items) === null || _a === void 0 ? void 0 : _a.find((item) => item.value === value && item.selected); })
.filter(Boolean);
if (selectedItems.length === 0) {
return h(Fragment, null);
}
// Chip display logic:
// - Not expanded: show up to maxChips (compact view)
// - Expanded: show all chips regardless of focus state
const effectiveMaxChips = !params.isChipsExpanded && params.maxChips && params.maxChips > 0
? params.maxChips
: selectedItems.length;
const visibleItems = selectedItems.slice(0, effectiveMaxChips);
return (h(Fragment, null, visibleItems.map((item) => (h("modus-wc-chip", { "aria-label": "Remove item button", label: item.label, "show-remove": true, size: "sm", disabled: params.disabled || params.readOnly, onChipRemove: (event) => {
event.stopPropagation();
params.onChipRemove(item);
}, variant: "filled" })))));
}
function renderClearButton(params) {
var _a, _b;
const showClear = params.includeClear &&
!params.disabled &&
!params.readOnly &&
(params.selectionOrder.length > 0 || ((_b = (_a = params.value) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0);
if (!showClear) {
return null;
}
return (h("modus-wc-button", { onClick: params.onClearAll, variant: "borderless", color: "secondary", "aria-label": "Clear all", disabled: params.disabled || params.readOnly, size: "xs", shape: "circle", type: "button" },
h(CloseSolidIcon, null)));
}
function renderExpandCollapseButton(params) {
const selectedItemsCount = params.selectionOrder.length;
// Show expand/collapse button when there are more chips than maxChips
if (!params.maxChips ||
params.maxChips <= 0 ||
selectedItemsCount <= params.maxChips) {
return null;
}
const remainingCount = selectedItemsCount - params.maxChips;
return (h("modus-wc-button", { "custom-class": `modus-wc-autocomplete-expand-button ${params.isChipsExpanded ? 'expanded' : ''}`, onClick: params.onToggleExpansion, variant: "borderless", color: "secondary", "aria-label": params.isChipsExpanded
? 'Collapse chips'
: `Show ${remainingCount} more`, disabled: params.disabled || params.readOnly, size: "xs", shape: "circle", type: "button" },
h("modus-wc-icon", { "aria-label": params.isChipsExpanded ? 'Collapse chips' : 'Expand chips', name: params.isChipsExpanded ? 'caret_up' : 'caret_down', size: "md" })));
}
function renderMoreChipsIndicator(params) {
const selectedItemsCount = params.selectionOrder.length;
// Show "+N more" when there are more chips than maxChips and not expanded
if (!params.maxChips || params.maxChips <= 0 || params.isChipsExpanded) {
return null;
}
const remainingCount = selectedItemsCount - params.maxChips;
if (remainingCount <= 0) {
return null;
}
return (h("modus-wc-chip", { label: `+${remainingCount}`, size: "sm", variant: "filled" }));
}
function renderInput(params) {
return (h("modus-wc-text-input", Object.assign({ bordered: params.bordered && !params.multiSelect, disabled: params.disabled, includeClear: !params.multiSelect && params.includeClear, includeSearch: !params.multiSelect && params.includeSearch, inputId: params.inputId, inputTabIndex: params.inputTabIndex, name: params.name, onInputBlur: params.onBlur, onInputChange: params.onChange, onInputFocus: params.onFocus, placeholder: params.placeholder, readOnly: params.readOnly, required: params.required, size: params.size, value: params.value }, params.inheritedAttributes)));
}
function renderMenuItems(params) {
var _a, _b, _c;
if (params.showSpinner) {
return (h("li", null,
h("modus-wc-loader", { variant: "spinner", size: params.size })));
}
const menuItems = params.filteredItems || params.items || [];
const noResults = ((_a = params.noResults) === null || _a === void 0 ? void 0 : _a.label) ||
((_b = params.noResults) === null || _b === void 0 ? void 0 : _b.subLabel) ||
((_c = params.noResults) === null || _c === void 0 ? void 0 : _c.ariaLabel);
return (h(Fragment, null, menuItems.length > 0 || !noResults || params.hasSlottedContent
? menuItems.map((item) => (h("modus-wc-menu-item", { disabled: item.disabled, focused: item.focused, label: item.label, onItemSelect: () => params.onItemSelect(item.value), onMouseDown: (e) => e.preventDefault(), selected: item.selected, value: item.value })))
: renderNoResults({ noResults: params.noResults })));
}
const modusWcAutocompleteCss = "modus-wc-autocomplete.modus-wc-autocomplete{display:inline-block;position:relative;width:100%}modus-wc-autocomplete.modus-wc-autocomplete modus-wc-loader{display:flex;justify-content:center}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select{align-items:flex-start;background-color:transparent;border-radius:var(--modus-wc-border-radius-md);display:flex;height:auto;min-height:fit-content;padding:var(--modus-wc-spacing-xs)}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-search-icon{align-self:center;flex-shrink:0}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select.modus-wc-input-sm .modus-wc-autocomplete-search-icon{height:var(--modus-wc-line-height-sm);width:var(--modus-wc-line-height-sm)}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select.modus-wc-input-md .modus-wc-autocomplete-search-icon{height:var(--modus-wc-line-height-md);width:var(--modus-wc-line-height-md)}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select.modus-wc-input-lg .modus-wc-autocomplete-search-icon{height:var(--modus-wc-line-height-lg);width:var(--modus-wc-line-height-lg)}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select:focus,modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select:focus-within{box-shadow:none !important;outline:none !important}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content{align-content:flex-start;align-items:center;display:flex;flex:1;flex-wrap:wrap;gap:0.25rem;min-height:fit-content;min-width:0}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content .modus-wc-text-input:focus-within{border:none !important}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-button-container{align-items:center;align-self:center;display:none;flex-shrink:0;justify-content:center}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-button-container:not(:empty){display:flex}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-button-container modus-wc-button{align-self:center;display:flex;margin:0}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content modus-wc-chip{align-self:center;flex-shrink:0}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content modus-wc-text-input{align-self:center;flex:1 1 var(--modus-autocomplete-min-input-width, 60px);max-width:100%;min-width:var(--modus-autocomplete-min-input-width, 60px)}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content modus-wc-text-input input{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content modus-wc-text-input:only-child{flex:1 1 100%}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content .modus-wc-autocomplete-more-indicator{align-items:center;align-self:center;display:flex;flex-shrink:0;height:32px;justify-content:center;padding:0 8px}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select .modus-wc-autocomplete-content .modus-wc-autocomplete-more-indicator .modus-wc-autocomplete-more-text{background-color:var(--modus-wc-color-gray-2);border-radius:var(--modus-wc-border-radius-sm);color:var(--modus-wc-color-text-secondary);font-size:var(--modus-wc-font-size-sm);font-weight:var(--modus-wc-font-weight-medium);padding:4px 8px;text-align:center;white-space:nowrap}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-menu{border-radius:0 0 var(--modus-wc-border-radius-md) var(--modus-wc-border-radius-md);display:block;left:0;margin-top:var(--modus-wc-spacing-2xs);max-height:320px;overflow:auto;position:absolute;top:100%;z-index:99}modus-wc-autocomplete.modus-wc-autocomplete .menu-hidden{display:none}modus-wc-autocomplete.modus-wc-autocomplete .menu-visible{display:block}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-no-results{padding:var(--modus-wc-spacing-md)}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-no-results .icon-label{align-items:center;display:flex;justify-content:center}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-no-results .icon-label .modus-wc-autocomplete-search-icon{color:var(--modus-wc-color-gray-6);height:1.5rem;width:1.5rem}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-no-results .icon-label .label{font-size:var(--modus-wc-font-size-md);font-weight:var(--modus-wc-font-weight-bold);margin-inline-start:var(--modus-wc-spacing-xs)}modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-no-results .sub-label{color:var(--modus-wc-color-gray-7);font-size:var(--modus-wc-font-size-sm);text-align:center}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--bordered,[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--bordered{border:var(--modus-wc-border-width-xs) solid var(--modus-wc-color-gray-6)}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--bordered:focus-within,[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--bordered:focus-within{border-color:var(--modus-wc-color-highlight-blue);border-width:var(--modus-wc-border-width-sm)}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete ul.modus-wc-menu,[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete ul.modus-wc-menu{box-shadow:0 0 4px 0 rgba(54, 53, 69, 0.3)}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-text-input-readonly,[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-text-input-readonly{background-color:var(--modus-wc-color-gray-10)}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--disabled,[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--disabled{cursor:not-allowed;opacity:0.6;user-select:none}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--readonly .modus-wc-text-input-readonly,[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--readonly .modus-wc-text-input-readonly{border:none}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select{background-color:var(--modus-wc-color-white)}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--disabled,[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select--readonly,[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-text-input-readonly{background-color:var(--modus-wc-color-gray-0);border:none}[data-theme=modus-classic-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-menu{background-color:var(--modus-wc-color-white)}[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-multi-select{background-color:var(--modus-wc-color-gray-10)}[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-autocomplete-no-results .sub-label{color:var(--modus-wc-color-white)}[data-theme=modus-classic-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-menu{background-color:var(--modus-wc-color-gray-10)}[data-theme=modus-modern-light] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-menu{background-color:var(--modus-wc-color-white)}[data-theme=modus-modern-dark] modus-wc-autocomplete.modus-wc-autocomplete .modus-wc-menu{background-color:var(--modus-wc-color-gray-10)}";
const ModusWcAutocomplete$1 = /*@__PURE__*/ proxyCustomElement(class ModusWcAutocomplete extends H {
constructor() {
super();
this.__registerHost();
this.chipRemove = createEvent(this, "chipRemove");
this.chipsExpansionChange = createEvent(this, "chipsExpansionChange");
this.inputBlur = createEvent(this, "inputBlur");
this.inputChange = createEvent(this, "inputChange");
this.inputFocus = createEvent(this, "inputFocus");
this.itemSelect = createEvent(this, "itemSelect");
this.menuVisible = false;
this.isChipsExpanded = false;
this.initialNavigation = true;
this.filteredItems = [];
this.selectionOrder = []; // Track order of chip selection
this.searchText = ''; // Dedicated state for active search query
this.inheritedAttributes = {};
this.programmaticOpen = false;
this.isNavigating = false; // Flag to prevent re-filtering during navigation
/** Indicates that the autocomplete should have a border. */
this.bordered = true;
/** Custom CSS class to apply to host element. */
this.customClass = '';
/**
* The debounce timeout in milliseconds.
* Set to 0 to disable debouncing.
*/
this.debounceMs = 300;
/** Whether the form control is disabled. */
this.disabled = false;
/** Show the clear button within the input field. */
this.includeClear = false;
/** Show the search icon within the input field. */
this.includeSearch = false;
/**
* The items to display in the menu.
* Creating a new array of items will ensure proper component re-render.
**/
this.items = [];
/** Whether the menu should remain open after an item is selected. */
this.leaveMenuOpen = false;
/** The minimum number of characters required to render the menu. */
this.minChars = 0;
/** Whether the input allows multiple items to be selected. */
this.multiSelect = false;
/** The content to display when no results are found. */
this.noResults = {
ariaLabel: 'No results found',
label: 'No results found',
subLabel: 'Check spelling or try a different keyword',
};
/** Text that appears in the form control when it has no value set. */
this.placeholder = '';
/** Whether the value is editable. */
this.readOnly = false;
/** A value is required for the form to be submittable. */
this.required = false;
/** Whether to show the menu whenever the input has focus, regardless of input value. */
this.showMenuOnFocus = false;
/** The size of the autocomplete (input and menu). */
this.size = 'md';
/** A spinner that appears when set to true */
this.showSpinner = false;
/** The value of the control. */
this.value = '';
/** Maximum number of chips to display. When exceeded, shows expand/collapse button. Set to -1 to disable limit. */
this.maxChips = -1;
/** Minimum width for the text input in pixels. When chips would make input smaller, container height increases instead. */
this.minInputWidth = 10;
this.handleFocusOutside = (event) => {
const relatedTarget = event.relatedTarget;
if (!relatedTarget || !this.el.contains(relatedTarget)) {
// Hide menu immediately to prevent flicker
if (!this.programmaticOpen) {
this.menuVisible = false;
}
// Use setTimeout for cleanup and blur event
setTimeout(() => {
// Reset filtered items after menu is hidden
if (this.items) {
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
this.inputBlur.emit(event);
}, BLUR_FOCUSOUT_DELAY_MS);
}
};
this.handleBlur = (event) => {
if (this.customBlur) {
this.customBlur(event.detail);
return;
}
event.stopPropagation();
this.initialNavigation = true;
if (this.items) {
this.items = [
...this.items.map((item) => (Object.assign(Object.assign({}, item), { focused: false }))),
];
}
this.handleFocusOutside(event.detail);
};
this.handleMenuFocusout = (event) => {
this.handleFocusOutside(event.detail);
};
this.handleChange = (event) => {
const result = processInputChange(event, {
disabled: this.disabled,
readOnly: this.readOnly,
customInputChange: this.customInputChange,
showMenuOnFocus: this.showMenuOnFocus,
minChars: this.minChars,
items: this.items,
multiSelect: this.multiSelect,
debounceMs: this.debounceMs,
});
if (!result.inputValue && !result.shouldShowMenu) {
return;
}
this.menuVisible = result.shouldShowMenu;
if (result.updatedItems) {
this.items = result.updatedItems;
}
this.value = result.inputValue;
this.searchText = result.inputValue; // Update search text as user types
// Sync filtered items based on new search value
this.syncFilteredItems();
if (result.shouldResetNavigation) {
this.initialNavigation = false;
}
// Handle immediate emit if no debounce
if (!this.debounceMs) {
this.inputChange.emit(event.detail);
}
else if (!this.customInputChange) {
// Handle debounced emit
if (this.debounceTimer) {
window.clearTimeout(this.debounceTimer);
}
this.debounceTimer = window.setTimeout(() => {
this.inputChange.emit(event.detail);
}, this.debounceMs);
}
};
this.handleFocus = (event) => {
var _a;
if (!this.disabled && !this.readOnly) {
// When focusing, clear searchText if value matches a selected item
// This prevents treating the display value as a search query
const hasSelectedItem = (_a = this.items) === null || _a === void 0 ? void 0 : _a.some((item) => item.selected && item.label === this.value);
if (hasSelectedItem) {
this.searchText = '';
}
// Show all items on focus
if (this.items) {
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
if (this.showMenuOnFocus) {
this.menuVisible = true;
}
}
this.inputFocus.emit(event.detail);
};
this.handleItemSelectByValue = (value) => {
var _a;
if (this.disabled || this.readOnly)
return;
const currentItem = (_a = this.items) === null || _a === void 0 ? void 0 : _a.find((item) => item.value === value);
if (!currentItem)
return;
this.handleItemSelect(currentItem);
};
this.handleItemSelect = (item) => {
const result = processItemSelection(item, {
disabled: this.disabled,
readOnly: this.readOnly,
items: this.items,
multiSelect: this.multiSelect,
leaveMenuOpen: this.leaveMenuOpen,
selectionOrder: this.selectionOrder,
maxChips: this.maxChips,
customItemSelect: this.customItemSelect,
});
if (result.updatedItems && result.updatedItems !== this.items) {
this.items = result.updatedItems;
}
if (result.updatedValue !== undefined) {
this.value = result.updatedValue;
}
if (result.updatedSelectionOrder) {
this.selectionOrder = result.updatedSelectionOrder;
}
if (result.shouldExpandChips) {
this.isChipsExpanded = true;
}
if (result.shouldCloseMenu) {
this.menuVisible = false;
}
// Clear search text after selection - this is critical to prevent state ambiguity
this.searchText = '';
// Reset filtered items to show all items after selection
if (this.items) {
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
// Only emit event and update navigation if not disabled/readonly
if (!this.disabled && !this.readOnly && this.items) {
this.initialNavigation = true;
this.itemSelect.emit(item);
}
};
this.handleChipRemove = (item) => {
const result = processChipRemoval(item, {
disabled: this.disabled,
readOnly: this.readOnly,
items: this.items,
selectionOrder: this.selectionOrder,
});
if (result.updatedItems) {
this.items = result.updatedItems;
this.selectionOrder = result.updatedSelectionOrder;
// When removing chips, show all items instead of applying text filtering
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
// Emit event for external handlers who want to know about the removal
if (!this.disabled && !this.readOnly) {
this.chipRemove.emit(item);
}
};
this.handleClearAll = () => {
void this.clearInput();
};
this.toggleChipsExpansion = () => {
if (this.leaveMenuOpen && this.isChipsExpanded) {
this.menuVisible = false;
}
this.isChipsExpanded = !this.isChipsExpanded;
this.chipsExpansionChange.emit({ expanded: this.isChipsExpanded });
};
this.handleOutsideClick = (event) => {
if (!this.el.contains(event.target) && !this.programmaticOpen) {
this.menuVisible = false;
this.isChipsExpanded = false;
}
// Reset programmaticOpen flag after handling the click
if (this.programmaticOpen) {
this.programmaticOpen = false;
}
};
}
handleMenuVisibilityChange() {
if (this.disabled || this.readOnly) {
this.menuVisible = false;
}
}
handleItemsChange(newItems, oldItems) {
// Only sync filtered items if items actually changed (not just focus updates)
// and we're not currently navigating
if (this.items &&
!this.isNavigating &&
JSON.stringify(newItems === null || newItems === void 0 ? void 0 : newItems.map((i) => ({
value: i.value,
selected: i.selected,
focused: i.focused,
}))) !==
JSON.stringify(oldItems === null || oldItems === void 0 ? void 0 : oldItems.map((i) => ({
value: i.value,
selected: i.selected,
focused: i.focused,
})))) {
if (this.multiSelect) {
// Keep items in selectionOrder that are still selected
const stillSelectedValues = this.selectionOrder.filter((value) => newItems.some((item) => item.value === value && item.selected));
// Add any newly selected items that aren't already in selectionOrder
const newlySelectedValues = newItems
.filter((item) => item.selected && !stillSelectedValues.includes(item.value))
.map((item) => item.value);
// Preserve the original selection order and append new selections
this.selectionOrder = [...stillSelectedValues, ...newlySelectedValues];
}
this.syncFilteredItems();
}
}
componentWillLoad() {
if (!this.el.ariaLabel) {
this.el.ariaLabel = 'Autocomplete input';
}
this.inheritedAttributes = inheritAriaAttributes(this.el);
document.addEventListener('click', this.handleOutsideClick);
if (this.items) {
this.filteredItems = [...this.items];
// Initialize selection order for pre-selected items
this.selectionOrder = this.items
.filter((item) => item.selected)
.map((item) => item.value);
}
}
disconnectedCallback() {
if (this.debounceTimer) {
window.clearTimeout(this.debounceTimer);
}
document.removeEventListener('click', this.handleOutsideClick);
}
getClasses() {
return getClasses(this.customClass);
}
getMultiSelectClasses() {
return getMultiSelectClasses({
bordered: this.bordered,
disabled: this.disabled,
readOnly: this.readOnly,
size: this.size,
});
}
getVisibleItems() {
return getVisibleItems(this.filteredItems);
}
syncFilteredItems() {
this.filteredItems = syncFilteredItems(this.items, this.searchText, this.leaveMenuOpen, this.customInputChange);
}
updateItemFocus(targetValue) {
this.isNavigating = true; // Prevent items watcher from re-filtering
const updated = updateItemFocus(this.items, targetValue);
if (updated) {
this.items = updated;
// We need to update filteredItems to reflect the focus change
// But only if we're actively filtering
if (this.searchText) {
this.syncFilteredItems();
}
else {
// When not filtering, update filteredItems to reflect the focus change
// without applying any filter
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
}
this.isNavigating = false; // Reset flag
}
clearAllFocus() {
const updated = clearAllFocus(this.items);
if (updated) {
this.items = updated;
// When clearing focus (e.g., on Escape), show all items instead of filtered
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
}
handleArrowDown() {
const input = this.el.querySelector('input');
if (!input)
return;
// Check if we're in filtering mode based on searchText BEFORE clearing it
const wasFiltering = this.searchText.length > 0;
if (this.initialNavigation) {
if (this.searchText) {
this.searchText = '';
}
// Reset filtered items when initial navigation to ensure all items are shown
if (this.items) {
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
}
handleArrowDown({
showMenuOnFocus: this.showMenuOnFocus,
minChars: this.minChars,
inputValue: input.value,
initialNavigation: this.initialNavigation,
visibleItems: this.getVisibleItems(),
onUpdateFocus: (value) => {
this.updateItemFocus(value);
// After updating focus, if not filtering, ensure we show all items
if (!wasFiltering && !this.searchText && this.items) {
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
},
onSetMenuVisible: (visible) => (this.menuVisible = visible),
onSetInitialNavigation: (value) => (this.initialNavigation = value),
});
}
handleArrowUp() {
// Check if we're in filtering mode based on searchText
const isFiltering = this.searchText.length > 0;
handleArrowUp({
initialNavigation: this.initialNavigation,
visibleItems: this.getVisibleItems(),
onUpdateFocus: (value) => {
this.updateItemFocus(value);
// After updating focus, if not filtering, ensure we show all items
if (!isFiltering && this.items) {
this.filteredItems = this.items.filter((item) => item.visibleInMenu);
}
},
onSetInitialNavigation: (value) => (this.initialNavigation = value),
});
}
handleEscape() {
this.clearAllFocus();
this.initialNavigation = true;
this.menuVisible = false;
this.searchText = ''; // Clear search text on escape
}
handleEnter() {
var _a, _b;
const visibleItems = this.getVisibleItems();
const focusedItem = visibleItems.find((item) => item.focused);
if (focusedItem) {
this.handleItemSelect(focusedItem);
}
else if (this.multiSelect) {
const selectedItems = ((_a = this.items) === null || _a === void 0 ? void 0 : _a.filter((item) => item.selected)) || [];
const lastSelectedItem = selectedItems[selectedItems.length - 1];
if (lastSelectedItem) {
this.itemSelect.emit(lastSelectedItem);
}
}
else {
const selectedItem = (_b = this.items) === null || _b === void 0 ? void 0 : _b.find((item) => item.selected);
if (selectedItem) {
this.itemSelect.emit(selectedItem);
}
}
}
handleBackspace(input) {
handleBackspace(input, {
multiSelect: this.multiSelect,
selectionOrder: this.selectionOrder,
items: this.items,
onChipRemove: (item) => this.handleChipRemove(item),
});
}
handleKeyDown(event) {
const { handled, keyLower } = processKeyEvent(event, {
disabled: this.disabled,
readOnly: this.readOnly,
customKeyDown: this.customKeyDown,
});
if (handled)
return;
const input = event.target;
switch (keyLower) {
case KEY.ArrowDown.toLowerCase(): {
this.handleArrowDown();
break;
}
case KEY.ArrowUp.toLowerCase(): {
this.handleArrowUp();
break;
}
case KEY.Escape.toLowerCase(): {
this.handleEscape();
break;
}
case KEY.Enter.toLowerCase(): {
this.handleEnter();
break;
}
case KEY.Backspace.toLowerCase(): {
this.handleBackspace(input);
break;
}
}
}
/**
* Programmatically select an item
*/
async selectItem(item) {
if (item) {
this.handleItemSelect(item);
}
else {
this.selectionOrder = []; // Clear selection order
if (this.items) {
this.items = [
...this.items.map((menuItem) => (Object.assign(Object.assign({}, menuItem), { selected: false }))),
];
}
this.value = '';
this.searchText = ''; // Clear search text when clearing selection
}
return Promise.resolve();
}
/**
* Programmatically open the menu
*/
async openMenu() {
this.programmaticOpen = true;
this.menuVisible = true;
return Promise.resolve();
}
/**
* Programmatically close the menu
*/
async closeMenu() {
this.programmaticOpen = false;
this.menuVisible = false;
return Promise.resolve();
}
/**
* Programmatically toggle the menu open/closed
*/
async toggleMenu() {
if (!this.menuVisible) {
this.programmaticOpen = true;
}
else {
this.programmaticOpen = false;
}
this.menuVisible = !this.menuVisible;
return Promise.resolve();
}
/**
* Programmatically set focus to input
*/
async focusInput() {
const inputElement = this.el.querySelector('input');
if (inputElement) {
inputElement.focus();
}
return Promise.resolve();
}
/**
* Clear the input value and reset items
*/
async clearInput() {
this.value = '';
this.searchText = ''; // Clear search text as well
this.selectionOrder = []; // Clear selection order
if (this.items) {
this.items = [
...this.items.map((item) => (Object.assign(Object.assign({}, item), { selected: false }))),
];
this.filteredItems = [...this.items];
}
return Promise.resolve();
}
render() {
// Set CSS custom properties for dynamic min-width control
const minWidth = this.minInputWidth || 10;
const cssVariables = {
'--modus-autocomplete-min-input-width': `${minWidth}px`,
};
// Check if we have slotted content
const hasSlottedContent = !!this.el.querySelector('[slot="menu-items"]');
return (h(Host, { key: '28c3ff2a5e2d0e52986b325cd685cab0fb3f2cd8', class: this.getClasses(), style: cssVariables }, this.label && (h("modus-wc-input-label", { key: 'a5aab1b68ea3de712fd526833a7e8977033159e7', forId: this.inputId, labelText: this.label, required: this.required, size: this.size })), this.multiSelect ? (h("div", { class: this.getMultiSelectClasses() }, this.includeSearch && (h(SearchSolidIcon, { className: "modus-wc-autocomplete-search-icon" })), h("div", { class: "modus-wc-autocomplete-content" }, renderChips({
selectionOrder: this.selectionOrder,
items: this.items,
isChipsExpanded: this.isChipsExpanded,
maxChips: this.maxChips,
disabled: this.disabled,
readOnly: this.readOnly,
onChipRemove: (item) => this.handleChipRemove(item),
}), renderMoreChipsIndicator({
selectionOrder: this.selectionOrder,
maxChips: this.maxChips,
isChipsExpanded: this.isChipsExpanded,
}), renderInput({
bordered: this.bordered,
multiSelect: this.multiSelect,
disabled: this.disabled,
includeClear: this.includeClear,
includeSearch: this.includeSearch,
inputId: this.inputId,
inputTabIndex: this.inputTabIndex,
name: this.name,
placeholder: this.placeholder,
readOnly: this.readOnly,
required: this.required,
size: this.size,
value: this.value,
inheritedAttributes: this.inheritedAttributes,
onBlur: this.handleBlur,
onChange: this.handleChange,
onFocus: this.handleFocus,
})), h("div", { class: "modus-wc-autocomplete-button-container" }, renderClearButton({
includeClear: this.includeClear,
disabled: this.disabled,
readOnly: this.readOnly,
selectionOrder: this.selectionOrder,
value: this.value,
onClearAll: this.handleClearAll,
}), renderExpandCollapseButton({
selectionOrder: this.selectionOrder,
maxChips: this.maxChips,
isChipsExpanded: this.isChipsExpanded,
disabled: this.disabled,
readOnly: this.readOnly,
onToggleExpansion: this.toggleChipsExpansion,
})))) : (h(Fragment, null, renderInput({
bordered: this.bordered,
multiSelect: this.multiSelect,
disabled: this.disabled,
includeClear: this.includeClear,
includeSearch: this.includeSearch,
inputId: this.inputId,
inputTabIndex: this.inputTabIndex,
name: this.name,
placeholder: this.placeholder,
readOnly: this.readOnly,
required: this.required,
size: this.size,
value: this.value,
inheritedAttributes: this.inheritedAttributes,