@limetech/lime-elements
Version:
483 lines (479 loc) • 17.9 kB
JavaScript
import { r as registerInstance, c as createEvent, h, a as getElement } from './index-DBTJNfo7.js';
import { i as isDescendant } from './dom-B9Ofc5RB.js';
import { T as TAB, E as ESCAPE, a as ENTER, A as ARROW_UP, b as ARROW_DOWN } from './keycodes-rI0IeKpx.js';
import { c as createRandomString } from './random-string-JbKhhoXs.js';
import { a as getIconFillColor, g as getIconName } from './get-icon-props-CgNJbSP4.js';
import { d as debounce } from './debounce-B67JMchz.js';
import './isObject-BJQylLSL.js';
import './toNumber-D6JYpQI9.js';
import './isSymbol-ClAke5ga.js';
import './isObjectLike-oiMYqRQ0.js';
const pickerCss = () => `:host{position:relative;display:block}:host([hidden]){display:none}`;
const SEARCH_DEBOUNCE = 300;
const CHIP_SET_TAG_NAME = 'limel-chip-set';
const DEFAULT_SEARCHER_MAX_RESULTS = 20;
const Picker = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.change = createEvent(this, "change");
this.interact = createEvent(this, "interact");
this.action = createEvent(this, "action");
/**
* True if the picker should be disabled
*/
this.disabled = false;
/**
* Set to `true` to disable adding and removing items,
* but allow interaction with existing items.
*/
this.readonly = false;
/**
* True if the control requires a value
*/
this.required = false;
/**
* Set to `true` to indicate that the current value of the input field is
* invalid.
*/
this.invalid = false;
/**
* Only used if no `searcher` is provided. The picker will then use a
* default search function that filters the `allItems` based on the
* `text` and `secondaryText` properties of the items.
* This way, custom search functions are typically only needed when the
* search is done on the server.
* For performance reasons, the default searcher will never return more
* than 20 items, but if there are more than 20 items, the rest can be
* found by typing more characters in the search field.
*/
this.allItems = [];
/**
* True if multiple values are allowed
*/
this.multiple = false;
/**
* Sets delimiters between chips. Works only when `multiple` is `true`.
*/
this.delimiter = null;
/**
* Static actions that can be clicked by the user.
*/
this.actions = [];
/**
* Position of the custom static actions in the picker's results dropdown.
* Can be set to `'top'` or `'bottom'`.
*/
this.actionPosition = 'bottom';
/**
* Scroll behavior of the custom static actions, when user scrolls
* in the picker's results dropdown. Can be set to `'scroll'` which means
* the action items will scroll together with the list, or `'sticky'` which
* retains their position at the top or bottom of the drop down while
* scrolling.
*/
this.actionScrollBehavior = 'sticky';
/**
* Whether badge icons should be used in the result list or not
*/
this.badgeIcons = false;
this.textValue = '';
this.loading = false;
this.chips = [];
// Should NOT be decorated with State(), since this
// should not trigger a re-render by itself.
this.chipSetEditMode = false;
this.getValueId = (item) => {
const value = item.value;
if (!!value && typeof value === 'object') {
return value.id;
}
return value;
};
this.createChips = (value) => {
if (!value) {
return [];
}
if (this.multiple) {
const listItems = value;
return listItems.map(this.createChip);
}
const listItem = value;
return [this.createChip(listItem)];
};
this.createChip = (listItem) => {
const name = getIconName(listItem.icon);
const color = getIconFillColor(listItem.icon, listItem.iconColor);
const valueId = this.getValueId(listItem);
return {
id: `${valueId}`,
text: listItem.text,
removable: true,
icon: name ? { name: name, color: color } : undefined,
image: listItem.image,
value: listItem,
menuItems: listItem.actions,
};
};
this.search = async (query) => {
const timeoutId = setTimeout(() => {
this.loading = true;
});
const searcher = this.searcher || this.defaultSearcher;
const result = (await searcher(this.textValue));
// If the search function resolves immediately,
// the loading spinner will not be shown.
clearTimeout(timeoutId);
this.handleSearchResult(query, result);
};
this.defaultSearcher = async (query) => {
if (query === '') {
return this.allItems.slice(0, DEFAULT_SEARCHER_MAX_RESULTS);
}
const filteredItems = this.allItems.filter((item) => {
let searchText = item.text.toLowerCase();
if (item.secondaryText) {
searchText =
searchText + ' ' + item.secondaryText.toLowerCase();
}
return searchText.includes(query.toLowerCase());
});
return filteredItems.slice(0, DEFAULT_SEARCHER_MAX_RESULTS);
};
this.handleTextInput = this.handleTextInput.bind(this);
this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
this.handleDropdownKeyDown = this.handleDropdownKeyDown.bind(this);
this.handleInputFieldFocus = this.handleInputFieldFocus.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleInteract = this.handleInteract.bind(this);
this.handleListChange = this.handleListChange.bind(this);
this.handleActionListChange = this.handleActionListChange.bind(this);
this.handleStopEditAndBlur = this.handleStopEditAndBlur.bind(this);
this.handleCloseMenu = this.handleCloseMenu.bind(this);
this.onListKeyDown = this.onListKeyDown.bind(this);
this.portalId = createRandomString();
this.debouncedSearch = debounce(this.search, SEARCH_DEBOUNCE);
}
componentWillLoad() {
this.chips = this.createChips(this.value);
}
componentDidLoad() {
this.chipSet = this.host.shadowRoot.querySelector(CHIP_SET_TAG_NAME);
this.updateTabIndex();
}
disconnectedCallback() {
this.debouncedSearch.cancel();
}
onDisabledChange() {
this.updateTabIndex();
}
updateTabIndex() {
if (this.disabled || this.readonly) {
this.host.setAttribute('tabindex', '-1');
return;
}
this.host.setAttribute('tabindex', '0');
}
async componentWillUpdate() {
this.chipSetEditMode = false;
if (this.chipSet) {
this.chipSetEditMode = await this.chipSet.getEditMode();
}
}
render() {
const props = {};
if (!this.multiple) {
props.maxItems = 1;
}
return [
h("limel-chip-set", Object.assign({ key: 'e20f22b6907ad1d28f8b8c0847e5080faca09b3b', type: "input", inputType: "search", label: this.label, helperText: this.helperText, leadingIcon: this.leadingIcon, value: this.chips, disabled: this.disabled, invalid: this.invalid, delimiter: this.renderDelimiter(), readonly: this.readonly, required: this.required, searchLabel: this.searchLabel, onInput: this.handleTextInput, onKeyDown: this.handleInputKeyDown, onChange: this.handleChange, onInteract: this.handleInteract, onStartEdit: this.handleInputFieldFocus, onStopEdit: this.handleStopEditAndBlur, emptyInputOnBlur: false, clearAllButton: this.multiple && !this.chipSetEditMode }, props)),
this.renderDropdown(),
];
}
onChangeValue() {
this.chips = this.createChips(this.value);
}
renderDelimiter() {
if (this.multiple) {
return this.delimiter;
}
return null;
}
/**
* Renders the dropdown with the items to pick from, or a spinner if the picker
* is waiting for items to be received
*
* @returns picker dropdown
*/
renderDropdown() {
const dropDownContent = this.getDropdownContent();
const content = [];
if (this.shouldShowDropDownContent()) {
const actionContent = this.getActionContent();
if (this.actionPosition === 'top') {
content.push(actionContent);
}
if (dropDownContent) {
content.push(dropDownContent);
}
if (this.actionPosition === 'bottom') {
content.push(actionContent);
}
}
return this.renderPortal(content);
}
getActionContent() {
var _a, _b;
const actionCount = (_b = (_a = this.actions) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
if (actionCount === 0) {
return null;
}
return [
h("limel-list", { class: {
'static-actions-list': true,
'is-on-top': this.actionPosition === 'top',
'is-at-bottom': this.actionPosition === 'bottom',
'has-position-sticky': this.actionScrollBehavior === 'sticky',
}, badgeIcons: true, type: 'selectable', onChange: this.handleActionListChange, items: this.actions.map(this.removeUnusedPropertiesOnAction) }),
];
}
removeUnusedPropertiesOnAction(action) {
return Object.assign(Object.assign({}, action), { actions: [] });
}
shouldShowDropDownContent() {
if (this.isFull()) {
return false;
}
return !!this.chipSetEditMode;
}
getDropdownContent() {
var _a;
if (!this.shouldShowDropDownContent()) {
return;
}
if (this.loading) {
return this.renderSpinner();
}
if (!((_a = this.items) === null || _a === void 0 ? void 0 : _a.length)) {
return this.renderEmptyMessage();
}
return this.renderListResult();
}
/**
* Returns true if the picker is "full"
* The picker is considered to be full if it has a value and only one is allowed
*
* @returns true if the picker is full
*/
isFull() {
return !this.multiple && !!this.value;
}
renderSpinner() {
return (h("div", { style: {
width: '100%',
display: 'flex',
'align-items': 'center',
'justify-content': 'center',
padding: '1rem 0',
} }, h("limel-spinner", { limeBranded: false })));
}
renderEmptyMessage() {
if (!this.emptyResultMessage) {
return;
}
const style = {
color: 'rgb(var(--contrast-1100))',
'text-align': 'center',
margin: '0.5rem 1rem',
};
return h("p", { style: style }, this.emptyResultMessage);
}
renderListResult() {
return (h("limel-list", { badgeIcons: this.badgeIcons, onChange: this.handleListChange, onKeyDown: this.onListKeyDown, type: "selectable", items: this.items }));
}
onListKeyDown(event) {
const keyFound = [TAB, ESCAPE, ENTER].includes(event.key);
if (keyFound) {
this.chipSet.setFocus();
}
}
renderPortal(content = []) {
const dropdownZIndex = getComputedStyle(this.host).getPropertyValue('--dropdown-z-index');
return (h("limel-portal", { visible: content.length > 0, containerId: this.portalId, inheritParentWidth: true, containerStyle: { 'z-index': dropdownZIndex } }, h("limel-menu-surface", { open: content.length > 0, allowClicksElement: this.host, style: {
'--menu-surface-width': '100%',
'max-height': 'inherit',
display: 'flex',
}, onDismiss: this.handleCloseMenu }, content)));
}
/**
* Check if a descendant still has focus. If not, reset text value and search result.
*/
handleStopEditAndBlur() {
// In browsers where shadow DOM is not supported activeElement on shadowRoot will return null
// However, document.activeElement will return the actual focused element instead of the outermost shadow host
const element = this.host.shadowRoot.activeElement || document.activeElement;
const portalElement = document.querySelector(`#${this.portalId}`);
if (isDescendant(element, this.host) ||
isDescendant(element, portalElement)) {
return;
}
this.clearInputField();
}
/**
* Input handler for the input field
*
* @param event - event
*/
async handleTextInput(event) {
event.stopPropagation();
const query = event.detail;
this.textValue = query;
this.debouncedSearch(query);
// If the search-query is an empty string, bypass debouncing.
if (query === '') {
this.debouncedSearch.flush();
}
}
/**
* Change handler for the list
*
* @param event - event
*/
handleListChange(event) {
var _a;
event.stopPropagation();
if (!this.value || this.value !== event.detail) {
let newValue = event.detail;
if (this.multiple) {
newValue = [
...this.value,
event.detail,
];
}
this.change.emit(newValue);
this.items = [];
}
if (this.multiple) {
this.textValue = '';
(_a = this.chipSet) === null || _a === void 0 ? void 0 : _a.setFocus(true);
}
}
/**
* Change handler for the list
*
* @param event - event
*/
handleActionListChange(event) {
event.stopPropagation();
if (!event.detail) {
return;
}
this.action.emit(event.detail.value);
this.items = [];
}
/**
* Focus handler for the chip set
* Prevent focus if the picker has a value and does not support multiple values
*/
handleInputFieldFocus() {
const query = this.textValue;
this.debouncedSearch(query);
}
handleChange(event) {
event.stopPropagation();
let newValue = null;
if (this.multiple) {
const chips = event.detail;
newValue = chips.map((chip) => {
return this.value.find((item) => {
const valueId = this.getValueId(item);
return `${valueId}` === chip.id;
});
});
}
this.change.emit(newValue);
}
handleInteract(event) {
event.stopPropagation();
this.interact.emit(event.detail ? event.detail.value : event.detail);
}
/**
* Key handler for the input field
* Will change focus to the first/last item in the dropdown list to enable selection with the keyboard
*
* @param event - event
*/
handleInputKeyDown(event) {
const isForwardTab = event.key === TAB &&
!event.altKey &&
!event.metaKey &&
!event.shiftKey;
const isUp = event.key === ARROW_UP;
const isDown = event.key === ARROW_DOWN;
if (!isForwardTab && !isUp && !isDown) {
return;
}
const list = document.querySelector(` #${this.portalId} limel-list`);
if (!list) {
return;
}
event.preventDefault();
if (isForwardTab || isDown) {
const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:first-child');
listElement.focus();
return;
}
if (isUp) {
const listElement = list.shadowRoot.querySelector('.mdc-deprecated-list-item:last-child');
listElement.focus();
}
}
/**
* Key handler for the dropdown
*
* @param event - event
*/
handleDropdownKeyDown(event) {
const isEscape = event.key === ESCAPE;
if (isEscape) {
event.preventDefault();
this.textValue = '';
this.chipSet.setFocus(true);
}
}
handleSearchResult(query, result) {
if (query === this.textValue) {
this.items = result;
if (this.multiple) {
const values = this.value;
this.items = result.filter((item) => {
return !values.includes(item);
});
}
this.loading = false;
}
}
handleCloseMenu() {
if (this.items.length > 0) {
return;
}
this.clearInputField();
}
clearInputField() {
this.chipSet.emptyInput();
this.textValue = '';
this.handleSearchResult('', []);
this.debouncedSearch.cancel();
}
static get delegatesFocus() { return true; }
get host() { return getElement(this); }
static get watchers() { return {
"disabled": [{
"onDisabledChange": 0
}],
"value": [{
"onChangeValue": 0
}]
}; }
};
Picker.style = pickerCss();
export { Picker as limel_picker };