@eccenca/gui-elements
Version:
GUI elements based on other libraries, usable in React application, written in Typescript.
273 lines • 15.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(require("react"));
const select_1 = require("@blueprintjs/select");
const constants_1 = require("../../configuration/constants");
const index_1 = require("../../index");
AutoCompleteField.defaultProps = {
autoFocus: false,
disabled: false,
onlyDropdownWithQuery: false, // FIXME: this should be `true` by default, otherwise similarity to `<Select />` is very close
fill: true,
requestErrorPrefix: "",
hasBackDrop: false,
};
/**
* @deprecated (support already removed) use `SuggestField` as replacement.
*/
function AutoCompleteField(props) {
var _a, _b, _c;
const { className, reset, noResultText, disabled, onlyDropdownWithQuery, itemValueSelector, itemRenderer, onSearch, onChange, initialValue, autoFocus, createNewItem, itemValueRenderer, resetQueryToValue, itemValueString, requestErrorPrefix, hasBackDrop, fill, loadMoreResults } = props, otherProps = __rest(props, ["className", "reset", "noResultText", "disabled", "onlyDropdownWithQuery", "itemValueSelector", "itemRenderer", "onSearch", "onChange", "initialValue", "autoFocus", "createNewItem", "itemValueRenderer", "resetQueryToValue", "itemValueString", "requestErrorPrefix", "hasBackDrop", "fill", "loadMoreResults"]);
const [selectedItem, setSelectedItem] = (0, react_1.useState)(initialValue);
// If the selection list elements are currently fetched from the backend
const [listLoading, setListLoading] = (0, react_1.useState)(false);
const [query, setQuery] = (0, react_1.useState)("");
// If the input field has focus
const [inputHasFocus, setInputHasFocus] = (0, react_1.useState)(false);
const [highlightingEnabled, setHighlightingEnabled] = (0, react_1.useState)(true);
const [requestError, setRequestError] = (0, react_1.useState)(undefined);
// The suggestions that match the user's input
const [filtered, setFiltered] = (0, react_1.useState)([]);
const readOnly = !!((_a = otherProps.inputProps) === null || _a === void 0 ? void 0 : _a.readOnly);
// Sets the query to the item value if it has a valid string value
const setQueryToSelectedValue = (item) => {
if (item) {
// If new values can be created, always reset the query value to the actual value of the selected item.
// This e.g. prevents that the "create new" option will be shown, since an item with the same value already exists.
const defaultResetValue = createNewItem
? itemValueString(item)
: itemValueRenderer(item);
const resetVal = resetQueryToValue ? resetQueryToValue(item) : defaultResetValue;
setQuery(resetVal);
}
};
// The key for the option elements
const itemKey = (item) => {
return itemValueString(item);
};
(0, react_1.useEffect)(() => {
setQueryToSelectedValue(selectedItem);
}, [selectedItem]);
(0, react_1.useEffect)(() => {
if (!disabled && !readOnly && inputHasFocus) {
setListLoading(true);
const timeout = window.setTimeout(() => __awaiter(this, void 0, void 0, function* () {
fetchQueryResults(query);
}), 200);
return () => {
clearTimeout(timeout);
setListLoading(false);
};
}
return;
}, [inputHasFocus, query]);
// We need to fire some actions when the auto-complete widget gets or loses focus
const handleOnFocusIn = () => {
setInputHasFocus(true);
};
const handleOnFocusOut = () => {
setInputHasFocus(false);
};
// On popover close reset query to selected item
const onPopoverClose = () => {
// Reset query to selected value when loosing focus, so the selected value can always be edited.
setQueryToSelectedValue(selectedItem);
// Reset option list when the popover closes, so next use there is not displayed a stale list
setFiltered([]);
};
// Triggered when an item from the selection list gets selected
const onSelectionChange = (value, e) => {
setSelectedItem(value);
onChange === null || onChange === void 0 ? void 0 : onChange(itemValueSelector(value), e);
setQueryToSelectedValue(value);
};
const areEqualItems = (itemA, itemB) => itemValueSelector(itemA) === itemValueSelector(itemB);
// Return the index of the item in the array based on the itemValueRenderer value
const itemIndexOf = (arr, searchItem) => {
let idx = -1;
const searchItemString = itemValueString(searchItem);
arr.forEach((v, i) => {
if (itemValueString(v) === searchItemString) {
idx = i;
}
});
return idx;
};
// Fetches the results for the given query
const fetchQueryResults = (input) => __awaiter(this, void 0, void 0, function* () {
var _a;
setListLoading(true);
setRequestError(undefined);
try {
let result = yield onSearch(input);
const onlySelectItemReturned = result.length <= 1 &&
selectedItem &&
input.length > 0 &&
(itemValueRenderer(selectedItem) === input || itemValueString(selectedItem) === input);
let enableHighlighting = true;
if (onlySelectItemReturned) {
// If the auto-completion only returns no suggestion or the selected item itself, query with empty string.
const emptyStringResults = yield onSearch("");
// Disable highlighting, since we used empty string search
enableHighlighting = false;
// Put selected item at the top if it is not in the result list
if (!!selectedItem && itemIndexOf(emptyStringResults, selectedItem) > -1) {
// Do not mutate original array
const withoutSelected = [...emptyStringResults];
withoutSelected.splice(itemIndexOf(emptyStringResults, selectedItem), 1);
result = [selectedItem, ...withoutSelected];
}
else {
result = emptyStringResults;
}
}
setHighlightingEnabled(enableHighlighting);
setFiltered(result);
}
catch (e) {
const details = (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : "";
setRequestError(requestErrorPrefix + details);
}
finally {
setListLoading(false);
}
});
// Renders the item in the selection list
const optionRenderer = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
const relevantModifiers = {
active: modifiers.active,
disabled: modifiers.disabled,
highlightingEnabled: highlightingEnabled,
};
const renderedItem = itemRenderer(item, query, relevantModifiers, handleClick);
if (typeof renderedItem === "string") {
return (react_1.default.createElement(index_1.MenuItem, { active: modifiers.active, disabled: modifiers.disabled, key: itemKey(item), onClick: handleClick, text: react_1.default.createElement(index_1.OverflowText, null, !highlightingEnabled ? (renderedItem) : (react_1.default.createElement(index_1.Highlighter, { label: renderedItem, searchValue: query }))) }));
}
else {
return renderedItem;
}
};
// Resets the selection
const clearSelection = (resetValue) => () => {
setSelectedItem(undefined);
onChange === null || onChange === void 0 ? void 0 : onChange(resetValue);
setQuery("");
};
const requestErrorRenderer = () => {
return react_1.default.createElement(index_1.Notification, { danger: true, message: requestError });
};
// Optional clear button to reset the selected value
const clearButton = !readOnly && !disabled && reset && selectedItem != null && reset.resettableValue(selectedItem) ? (react_1.default.createElement(index_1.IconButton, { "data-test-id": (((_b = otherProps.inputProps) === null || _b === void 0 ? void 0 : _b.id) ? `${otherProps.inputProps.id}-` : "") + "auto-complete-clear-btn", name: "operation-clear", text: reset.resetButtonText, onClick: clearSelection(reset.resetValue) })) : undefined;
// Additional properties for the input element of the auto-completion widget
const updatedInputProps = Object.assign(Object.assign({ rightElement: clearButton || onlyDropdownWithQuery === false ? (react_1.default.createElement(react_1.default.Fragment, null,
clearButton,
onlyDropdownWithQuery === false && (react_1.default.createElement(index_1.IconButton, { name: "toggler-caretdown", onClick: (e) => {
var _a;
const target = (_a = e.currentTarget
.closest(`.${constants_1.CLASSPREFIX}-autocompletefield__input`)) === null || _a === void 0 ? void 0 : _a.querySelector("input");
target.focus();
e.stopPropagation();
} })))) : undefined, autoFocus: autoFocus, onBlur: handleOnFocusOut, onFocus: handleOnFocusIn }, otherProps.inputProps), { title: selectedItem !== undefined && (readOnly || disabled)
? itemValueString(selectedItem)
: (_c = otherProps.inputProps) === null || _c === void 0 ? void 0 : _c.title });
const preventOverlayOnReadonly = readOnly ? { isOpen: false } : {};
const updatedContextOverlayProps = Object.assign(Object.assign(Object.assign({ minimal: true, matchTargetWidth: fill, popoverClassName: `${constants_1.CLASSPREFIX}-autocompletefield__options`, onClosed: onPopoverClose }, otherProps.contextOverlayProps), preventOverlayOnReadonly), {
// Needed to capture clicks outside of the popover, e.g. in order to close it.
hasBackdrop: hasBackDrop });
if (selectedItem !== undefined) {
// Makes sure that even when an empty string is selected, the placeholder won't be shown.
updatedInputProps.placeholder = "";
}
// For some reason Typescript is not able to infer the union type from the ternary expression
const createNewItemPosition = (createNewItem === null || createNewItem === void 0 ? void 0 : createNewItem.showNewItemOptionFirst) ? "first" : "last";
const createNewItemProps = createNewItem
? {
createNewItemFromQuery: createNewItem.itemFromQuery,
createNewItemRenderer: (query, active, handleClick) => {
if (selectedItem && query === itemValueString(selectedItem)) {
// Never show create new item option if the same item is already selected
return undefined;
}
else {
return createNewItem.itemRenderer(query, { active, highlightingEnabled: false }, handleClick);
}
},
createNewItemPosition,
}
: {};
const handleMenuScroll = react_1.default.useCallback((event) => __awaiter(this, void 0, void 0, function* () {
const menu = event.target;
const { scrollTop, scrollHeight, clientHeight } = menu;
// Check if scrolled to the bottom of the list
if (Math.round(scrollTop + clientHeight) >= scrollHeight && loadMoreResults) {
const results = yield loadMoreResults();
if (results) {
setFiltered((prev) => [...prev, ...results]);
setTimeout(() => {
menu.scrollTop = scrollTop; //safari adaptation
menu.scrollTo({ left: 0, top: scrollTop, behavior: "auto" });
});
}
}
}), [loadMoreResults]);
return (react_1.default.createElement(select_1.Suggest, Object.assign({ className: `${constants_1.CLASSPREFIX}-autocompletefield__input` + (className ? ` ${className}` : ""), disabled: disabled,
// Need to display error messages in list
items: requestError ? [requestError] : filtered, initialContent: onlyDropdownWithQuery ? null : undefined, inputValueRenderer: selectedItem !== undefined ? itemValueRenderer : () => "", itemRenderer: requestError ? requestErrorRenderer : optionRenderer, itemsEqual: areEqualItems, noResults: react_1.default.createElement(index_1.MenuItem, { disabled: true, text: noResultText }), onItemSelect: onSelectionChange, onQueryChange: (q) => setQuery(q), resetOnQuery: false, closeOnSelect: true, menuProps: {
onScroll: handleMenuScroll,
}, query: query,
// This leads to odd compile errors without "as any"
popoverProps: updatedContextOverlayProps, selectedItem: selectedItem, fill: fill }, createNewItemProps, {
// This leads to odd compile errors without "as any"
inputProps: updatedInputProps, itemListRenderer: listLoading
? () => (react_1.default.createElement(index_1.Menu, null,
react_1.default.createElement(index_1.MenuItem, { disabled: true, text: react_1.default.createElement(index_1.Spinner, { position: "inline" }) })))
: undefined })));
}
exports.default = AutoCompleteField;
//# sourceMappingURL=AutoCompleteField.js.map