@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
1,265 lines (1,264 loc) • 57.8 kB
JavaScript
import { Fragment, h, Host, } from "@stencil/core";
import { SearchSolidIcon } from "../../icons/search-solid.icon";
import { inheritAriaAttributes, KEY } from "../utils";
import { BLUR_FOCUSOUT_DELAY_MS, clearAllFocus, getClasses, getMultiSelectClasses, getVisibleItems, handleArrowDown as processArrowDown, handleArrowUp as processArrowUp, handleBackspace as processBackspace, processChipRemoval, processInputChange, processItemSelection, processKeyEvent, renderChips, renderClearButton, renderExpandCollapseButton, renderInput, renderMenuItems, renderMoreChipsIndicator, syncFilteredItems, updateItemFocus, } from "./modus-wc-autocomplete-core";
/**
* A customizable autocomplete component used to create searchable text inputs.
*/
export class ModusWcAutocomplete {
constructor() {
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);
}
}
processArrowDown({
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;
processArrowUp({
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) {
processBackspace(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,
onBlur: this.handleBlur,
onChange: this.handleChange,
onFocus: this.handleFocus,
}))), hasSlottedContent ? (
// When using custom slots, keep menu in DOM and use CSS to hide/show
h("modus-wc-menu", { "aria-label": "Autocomplete menu", bordered: this.bordered, class: this.menuVisible ? 'menu-visible' : 'menu-hidden', onMenuFocusout: this.handleMenuFocusout, onMouseDown: (e) => e.preventDefault(), size: this.size }, renderMenuItems({
showSpinner: this.showSpinner,
size: this.size,
filteredItems: this.filteredItems,
items: this.items,
noResults: this.noResults,
hasSlottedContent: hasSlottedContent,
onItemSelect: this.handleItemSelectByValue,
}), h("slot", { name: "menu-items" }))) : (
// When NOT using slots, conditionally render menu (automatic scroll reset)
this.menuVisible && (h("modus-wc-menu", { "aria-label": "Autocomplete menu", bordered: this.bordered, class: "menu-visible", onMenuFocusout: this.handleMenuFocusout, onMouseDown: (e) => e.preventDefault(), size: this.size }, renderMenuItems({
showSpinner: this.showSpinner,
size: this.size,
filteredItems: this.filteredItems,
items: this.items,
noResults: this.noResults,
hasSlottedContent: hasSlottedContent,
onItemSelect: this.handleItemSelectByValue,
}), h("slot", { name: "menu-items" }))))));
}
static get is() { return "modus-wc-autocomplete"; }
static get originalStyleUrls() {
return {
"$": ["modus-wc-autocomplete.scss"]
};
}
static get styleUrls() {
return {
"$": ["modus-wc-autocomplete.css"]
};
}
static get properties() {
return {
"bordered": {
"type": "boolean",
"attribute": "bordered",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Indicates that the autocomplete should have a border."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "true"
},
"customClass": {
"type": "string",
"attribute": "custom-class",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Custom CSS class to apply to host element."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "''"
},
"debounceMs": {
"type": "number",
"attribute": "debounce-ms",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The debounce timeout in milliseconds.\nSet to 0 to disable debouncing."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "300"
},
"disabled": {
"type": "boolean",
"attribute": "disabled",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Whether the form control is disabled."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"includeClear": {
"type": "boolean",
"attribute": "include-clear",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Show the clear button within the input field."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"includeSearch": {
"type": "boolean",
"attribute": "include-search",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Show the search icon within the input field."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"inputId": {
"type": "string",
"attribute": "input-id",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The ID of the input element."
},
"getter": false,
"setter": false,
"reflect": false
},
"inputTabIndex": {
"type": "number",
"attribute": "input-tab-index",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Determine the control's relative ordering for sequential focus navigation (typically with the Tab key)."
},
"getter": false,
"setter": false,
"reflect": false
},
"items": {
"type": "unknown",
"attribute": "items",
"mutable": false,
"complexType": {
"original": "IAutocompleteItem[]",
"resolved": "IAutocompleteItem[] | undefined",
"references": {
"IAutocompleteItem": {
"location": "import",
"path": "../types",
"id": "src/components/types.ts::IAutocompleteItem"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The items to display in the menu.\nCreating a new array of items will ensure proper component re-render."
},
"getter": false,
"setter": false,
"defaultValue": "[]"
},
"label": {
"type": "string",
"attribute": "label",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The text to display within the label."
},
"getter": false,
"setter": false,
"reflect": false
},
"leaveMenuOpen": {
"type": "boolean",
"attribute": "leave-menu-open",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Whether the menu should remain open after an item is selected."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"minChars": {
"type": "number",
"attribute": "min-chars",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The minimum number of characters required to render the menu."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "0"
},
"multiSelect": {
"type": "boolean",
"attribute": "multi-select",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Whether the input allows multiple items to be selected."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"name": {
"type": "string",
"attribute": "name",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Name of the form control. Submitted with the form as part of a name/value pair."
},
"getter": false,
"setter": false,
"reflect": false
},
"noResults": {
"type": "unknown",
"attribute": "no-results",
"mutable": false,
"complexType": {
"original": "IAutocompleteNoResults",
"resolved": "IAutocompleteNoResults | undefined",
"references": {
"IAutocompleteNoResults": {
"location": "import",
"path": "../types",
"id": "src/components/types.ts::IAutocompleteNoResults"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The content to display when no results are found."
},
"getter": false,
"setter": false,
"defaultValue": "{\n ariaLabel: 'No results found',\n label: 'No results found',\n subLabel: 'Check spelling or try a different keyword',\n }"
},
"placeholder": {
"type": "string",
"attribute": "placeholder",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Text that appears in the form control when it has no value set."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "''"
},
"readOnly": {
"type": "boolean",
"attribute": "read-only",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Whether the value is editable."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"required": {
"type": "boolean",
"attribute": "required",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "A value is required for the form to be submittable."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"showMenuOnFocus": {
"type": "boolean",
"attribute": "show-menu-on-focus",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Whether to show the menu whenever the input has focus, regardless of input value."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"size": {
"type": "string",
"attribute": "size",
"mutable": false,
"complexType": {
"original": "ModusSize",
"resolved": "\"lg\" | \"md\" | \"sm\" | undefined",
"references": {
"ModusSize": {
"location": "import",
"path": "../types",
"id": "src/components/types.ts::ModusSize"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The size of the autocomplete (input and menu)."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "'md'"
},
"showSpinner": {
"type": "boolean",
"attribute": "show-spinner",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "A spinner that appears when set to true"
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "false"
},
"value": {
"type": "string",
"attribute": "value",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The value of the control."
},
"getter": false,
"setter": false,
"reflect": true,
"defaultValue": "''"
},
"maxChips": {
"type": "number",
"attribute": "max-chips",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Maximum number of chips to display. When exceeded, shows expand/collapse button. Set to -1 to disable limit."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "-1"
},
"customItemSelect": {
"type": "unknown",
"attribute": "custom-item-select",
"mutable": false,
"complexType": {
"original": "(item: IAutocompleteItem) => void",
"resolved": "((item: IAutocompleteItem) => void) | undefined",
"references": {
"IAutocompleteItem": {
"location": "import",
"path": "../types",
"id": "src/components/types.ts::IAutocompleteItem"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Custom item selection handler - if provided, overrides default selection logic"
},
"getter": false,
"setter": false
},
"customInputChange": {
"type": "unknown",
"attribute": "custom-input-change",
"mutable": false,
"complexType": {
"original": "(value: string) => void",
"resolved": "((value: string) => void) | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Custom input change handler - if provided, overrides default search filtering"
},
"getter": false,
"setter": false
},
"customKeyDown": {
"type": "unknown",
"attribute": "custom-key-down",
"mutable": false,
"complexType": {
"original": "(event: KeyboardEvent) => void",
"resolved": "((event: KeyboardEvent) => void) | undefined",
"references": {
"KeyboardEvent": {
"location": "global",
"id": "global::KeyboardEvent"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Custom key down handler - if provided, overrides default keyboard navigation"
},
"getter": false,
"setter": false
},
"customBlur": {
"type": "unknown",
"attribute": "custom-blur",
"mutable": false,
"complexType": {
"original": "(event: FocusEvent) => void",
"resolved": "((event: FocusEvent) => void) | undefined",
"references": {
"FocusEvent": {
"location": "global",
"id": "global::FocusEvent"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Custom blur handler - if provided, overrides default blur behavior"
},
"getter": false,
"setter": false
},
"minInputWidth": {
"type": "number",
"attribute": "min-input-width",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Minimum width for the text input in pixels. When chips would make input smaller, container height increases instead."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "10"
}
};
}
static get states() {
return {
"menuVisible": {},
"isChipsExpanded": {},
"initialNavigation": {},
"filteredItems": {},
"selectionOrder": {},
"searchText": {}
};
}
static get events() {
return [{
"method": "chipRemove",
"name": "chipRemove",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Event emitted when a selected item chip is removed."
},
"complexType": {
"original": "IAutocompleteItem",
"resolved": "IAutocompleteItem",
"references": {
"IAutocompleteItem": {
"location": "import",
"path": "../types",
"id": "src/components/types.ts::IAutocompleteItem"
}
}
}
}, {
"method": "chipsExpansionChange",
"name": "chipsExpansionChange",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Event emitted when chips expansion state changes."
},
"complexType": {
"original": "{ expanded: boolean }",
"resolved": "{ expanded: boolean; }",
"references": {}
}