@freshworks/crayons
Version:
Crayons Web Components library
436 lines (432 loc) • 16.3 kB
JavaScript
import { attachShadow, createEvent, h, proxyCustomElement } from '@stencil/core/internal/client';
import { d as debounce, b as cyclicDecrement, e as cyclicIncrement, i as isEqual } from './index2.js';
import { i as i18n } from './Translation.js';
import { d as defineCustomElement$7 } from './avatar.js';
import { d as defineCustomElement$6 } from './checkbox.js';
import { d as defineCustomElement$1, a as defineCustomElement$5 } from './icon.js';
import { d as defineCustomElement$4 } from './input.js';
import { d as defineCustomElement$3 } from './select-option.js';
import { d as defineCustomElement$2 } from './spinner.js';
const listOptionsCss = ":host{font-family:var(--fw-font-family, -apple-system, blinkmacsystemfont, \"Segoe UI\", roboto, oxygen, ubuntu, cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-box-sizing:border-box;box-sizing:border-box}.container{margin:0px;padding:12px 8px 8px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}";
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if (d = decorators[i])
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
let ListOptions = class extends HTMLElement {
constructor() {
super();
this.__registerHost();
attachShadow(this);
this.fwChange = createEvent(this, "fwChange", 7);
this.fwLoading = createEvent(this, "fwLoading", 7);
this.isInternalValueChange = false;
this.arrowKeyCounter = 0;
this.optionRefs = [];
this.defaultSearchFunction = (text, dataSource) => {
return new Promise((resolve) => {
const value = text.toLowerCase();
const filteredValue = value !== ''
? dataSource.filter((option) => option.text.toLowerCase().includes(value.toLowerCase()))
: dataSource;
resolve(filteredValue);
});
};
this.filteredOptions = [];
this.selectOptions = [];
this.selectedOptionsState = [];
this.isLoading = false;
/**
* Value corresponding to the option, that is saved when the form data is saved.
*/
this.options = [];
/**
* Value of the option that is displayed as the default selection, in the list box. Must be a valid value corresponding to the fw-select-option components used in Select.
*/
this.value = '';
/**
* Works with `multiple` enabled. Configures the maximum number of options that can be selected with a multi-select component.
*/
this.max = Number.MAX_VALUE;
/**
* Enables selection of multiple options. If the attribute’s value is undefined, the value is set to false.
*/
this.multiple = false;
/**
* Enables the input with in the popup for filtering the options.
*/
this.searchable = false;
/**
* Disables the component on the interface. If the attribute’s value is undefined, the value is set to false.
*/
this.disabled = false;
/**
* Standard is the default option without any graphics other options are icon and avatar which places either the icon or avatar at the beginning of the row.
* The props for the icon or avatar are passed as an object via the graphicsProps.
*/
this.variant = 'standard';
/**
* Place a checkbox.
*/
this.checkbox = false;
/**
* Default option to be shown if the option doesn't match the filterText.
*/
this.notFoundText = '';
/**
* Filter function which takes in filterText and dataSource and return a Promise.
* Where filter text is the text to filter the value in dataSource array.
* The returned promise should contain the array of options to be displayed.
*/
this.search = this.defaultSearchFunction;
/**
* Placeholder to placed on the search text box.
*/
this.searchText = '';
/**
* Text to be displayed when there is no data available in the select.
*/
this.noDataText = '';
/**
* Debounce timer for the search promise function.
*/
this.debounceTimer = 300;
/**
* The option that is displayed as the default selection, in the list box. Must be a valid value corresponding to the fw-select-option components used in Select.
*/
this.selectedOptions = [];
/**
* Whether clicking on the already selected option disables it.
*/
this.allowDeselect = true;
this.handleSearchWithDebounce = debounce((filterText) => {
var _a;
this.isLoading = true;
this.fwLoading.emit({ isLoading: this.isLoading });
if (filterText) {
this.search(filterText, this.selectOptions).then((options) => {
this.filteredOptions =
(options === null || options === void 0 ? void 0 : options.length) > 0
? this.serializeData(options)
: [{ text: this.notFoundText, disabled: true }];
this.isLoading = false;
this.fwLoading.emit({ isLoading: this.isLoading });
});
}
else {
this.filteredOptions =
((_a = this.selectOptions) === null || _a === void 0 ? void 0 : _a.length) > 0
? this.selectOptions
: [{ text: this.noDataText, disabled: true }];
this.isLoading = false;
this.fwLoading.emit({ isLoading: this.isLoading });
}
}, this, this.debounceTimer);
}
fwSelectedHandler(selectedItem) {
const { value, selected } = selectedItem.detail;
if (selected) {
const selectedObj = this.filteredOptions.filter((option) => option.value === value)[0];
this.selectedOptionsState = this.multiple
? [...this.selectedOptionsState, selectedObj]
: [selectedObj];
}
else {
this.selectedOptionsState = this.multiple
? this.selectedOptionsState.filter((option) => option.value !== value)
: [];
}
this.isInternalValueChange = true;
this.setValue(this.selectedOptionsState);
}
onKeyDown(ev) {
switch (ev.key) {
case 'ArrowDown':
// If focus is on the last option, moves focus to the first option.
// Ref - https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
this.arrowKeyCounter = cyclicIncrement(this.arrowKeyCounter, this.optionRefs.length - 1);
this.optionRefs[this.arrowKeyCounter].setFocus();
ev.preventDefault();
ev.stopPropagation();
break;
case 'ArrowUp':
// If focus is on the first option, moves focus to the last option.
// Ref - https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
this.arrowKeyCounter = cyclicDecrement(this.arrowKeyCounter, this.optionRefs.length - 1);
this.optionRefs[this.arrowKeyCounter].setFocus();
ev.preventDefault();
ev.stopPropagation();
break;
}
}
async clearFilter() {
this.filteredOptions = this.selectOptions;
if (this.searchable) {
this.searchInput.value = '';
}
}
async scrollToLastSelected() {
var _a;
if (this.filteredOptions.length > 0 && this.valueExists()) {
(_a = this.container
.querySelector(`fw-select-option[id='${this.host.id}-option-${this.getLastSelectedValue()}']`)) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'nearest' });
}
}
async getSelectedOptions() {
return this.selectedOptionsState;
}
/**
* Pass an array of string in case of multi-select or string for single-select.
*/
async setSelectedValues(values) {
if (this.options) {
this.selectedOptionsState = this.options.filter((option) => this.isValueEqual(values, option));
this.isInternalValueChange = true;
this.setValue(this.selectedOptionsState);
}
}
async setSelectedOptions(options) {
this.selectedOptionsState = options;
this.isInternalValueChange = true;
this.setValue(options);
}
async setFocus() {
this.optionRefs = [
...this.container.getElementsByTagName('fw-select-option'),
];
const lastValue = this.getLastSelectedValue();
if (lastValue && this.optionRefs.length > 0) {
const lastValueIndex = this.optionRefs.findIndex((option) => option.value === lastValue);
this.arrowKeyCounter = lastValueIndex === -1 ? 0 : lastValueIndex;
}
this.optionRefs[this.arrowKeyCounter].setFocus();
}
onOptionsChange(newValue) {
this.setDataSource(newValue);
}
disabledWatcher() {
const options = this.options;
// updating the object to retrigger
this.options = [...options];
}
onValueChange(newValue, oldValue) {
if (!isEqual(newValue, oldValue)) {
if (newValue) {
this.validateValue(newValue);
}
else {
newValue = this.multiple ? [] : '';
}
this.selectOptions = this.selectOptions.map((option) => {
option.selected = this.isValueEqual(newValue, option);
return option;
});
// Warning: Before mutating this.value inside this file set the isInternalValueChange to true.
// This is to prevent triggering the below code which is executed whenever there is a change in the prop this.value
if (!this.isInternalValueChange) {
// source might change during dynamic select
const source = this.options.length > 0 ? this.options : this.selectedOptionsState;
this.selectedOptionsState = source.filter((option) => this.isValueEqual(newValue, option));
}
this.fwChange.emit({
value: newValue,
meta: { selectedOptions: this.selectedOptionsState },
});
this.isInternalValueChange = false;
}
}
onFilterTextChange(newValue) {
this.handleSearchWithDebounce(newValue);
}
valueExists() {
return this.multiple ? this.value.length > 0 : !!this.value;
}
validateValue(value) {
if (this.multiple && !Array.isArray(value)) {
throw new Error('Value must be a array for multi-select');
}
if (!this.multiple &&
typeof value !== 'string' &&
typeof value !== 'number' &&
typeof value !== 'bigint') {
throw new Error('Value must be a string or number or bigint for single-select');
}
}
getLastSelectedValue() {
if (this.valueExists()) {
return this.multiple ? this.value.slice(-1)[0] : this.value;
}
}
setSelectedOptionsByValue(values) {
if (this.options) {
this.selectedOptionsState = this.options.filter((option) => this.isValueEqual(values, option));
}
else {
throw new Error('Options must be passed if value is set');
}
}
serializeData(dataSource) {
return dataSource.map((option) => {
var _a;
return Object.assign(Object.assign({}, option), {
checkbox: option.checkbox || this.checkbox,
variant: option.variant || this.variant,
selected: this.isValueEqual(this.value, option) || option.selected,
disabled: option.disabled ||
this.disabled ||
(this.multiple && ((_a = this.value) === null || _a === void 0 ? void 0 : _a.length) >= this.max),
allowDeselect: this.allowDeselect,
});
});
}
isValueEqual(value, option) {
return this.multiple
? value.includes(option.value)
: value === option.value;
}
setValue(options) {
if ((options === null || options === void 0 ? void 0 : options.length) > 0) {
this.value = this.multiple
? options.map((option) => option.value)
: options[0].value;
}
else {
this.value = this.multiple ? [] : '';
}
}
setDataSource(dataSource) {
if (dataSource.length > 0) {
this.selectOptions = this.serializeData(dataSource);
}
else {
this.selectOptions = [{ text: this.noDataText, disabled: true }];
}
this.filteredOptions = this.selectOptions;
}
renderSelectOptions(options) {
return options.map((option) => (h("fw-select-option", Object.assign({ id: `${this.host.id}-option-${option.value}`, key: option.value }, option))));
}
renderSearchInput() {
return (h("fw-input", { ref: (searchInput) => (this.searchInput = searchInput), placeholder: this.searchText, onInput: () => this.handleSearchWithDebounce(this.searchInput.value) }));
}
componentWillLoad() {
this.validateValue(this.value);
if (this.selectedOptions.length > 0) {
this.selectedOptionsState = this.selectedOptions;
this.value = this.multiple
? this.selectedOptionsState.map((option) => option.value)
: this.selectedOptionsState[0].value;
}
else if (this.valueExists()) {
this.setSelectedOptionsByValue(this.value);
}
else {
this.setValue([]);
}
if (this.multiple && typeof this.value === 'string') {
throw Error('value must be a array of string when multiple is true');
}
this.setDataSource(this.options);
}
render() {
return (h("div", { class: 'container', ref: (container) => {
this.container = container;
} }, this.searchable && this.renderSearchInput(), this.renderSelectOptions(this.filteredOptions)));
}
get host() { return this; }
static get watchers() { return {
"options": ["onOptionsChange"],
"disabled": ["disabledWatcher"],
"value": ["onValueChange"],
"filterText": ["onFilterTextChange"]
}; }
static get style() { return listOptionsCss; }
};
__decorate([
i18n({ keyName: 'search.noItemsFound' })
], ListOptions.prototype, "notFoundText", void 0);
__decorate([
i18n({ keyName: 'search.search' })
], ListOptions.prototype, "searchText", void 0);
__decorate([
i18n({ keyName: 'search.noDataAvailable' })
], ListOptions.prototype, "noDataText", void 0);
ListOptions = /*@__PURE__*/ proxyCustomElement(ListOptions, [1, "fw-list-options", {
"options": [16],
"value": [1032],
"max": [2],
"multiple": [4],
"searchable": [4],
"disabled": [4],
"variant": [1],
"filterText": [8, "filter-text"],
"checkbox": [4],
"notFoundText": [1025, "not-found-text"],
"search": [16],
"searchText": [1025, "search-text"],
"noDataText": [1025, "no-data-text"],
"debounceTimer": [2, "debounce-timer"],
"selectedOptions": [16],
"allowDeselect": [4, "allow-deselect"],
"filteredOptions": [32],
"selectOptions": [32],
"selectedOptionsState": [32],
"isLoading": [32],
"clearFilter": [64],
"scrollToLastSelected": [64],
"getSelectedOptions": [64],
"setSelectedValues": [64],
"setSelectedOptions": [64],
"setFocus": [64]
}, [[0, "fwSelected", "fwSelectedHandler"], [0, "keydown", "onKeyDown"]]]);
function defineCustomElement() {
const components = ["fw-list-options", "fw-avatar", "fw-checkbox", "fw-icon", "fw-input", "fw-select-option", "fw-spinner", "fw-toast-message"];
components.forEach(tagName => { switch (tagName) {
case "fw-list-options":
if (!customElements.get(tagName)) {
customElements.define(tagName, ListOptions);
}
break;
case "fw-avatar":
if (!customElements.get(tagName)) {
defineCustomElement$7();
}
break;
case "fw-checkbox":
if (!customElements.get(tagName)) {
defineCustomElement$6();
}
break;
case "fw-icon":
if (!customElements.get(tagName)) {
defineCustomElement$5();
}
break;
case "fw-input":
if (!customElements.get(tagName)) {
defineCustomElement$4();
}
break;
case "fw-select-option":
if (!customElements.get(tagName)) {
defineCustomElement$3();
}
break;
case "fw-spinner":
if (!customElements.get(tagName)) {
defineCustomElement$2();
}
break;
case "fw-toast-message":
if (!customElements.get(tagName)) {
defineCustomElement$1();
}
break;
} });
}
export { ListOptions as L, defineCustomElement as d };