@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
267 lines • 19.7 kB
JavaScript
import { __rest } from "tslib";
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useImperativeHandle, useRef, useState } from 'react';
import clsx from 'clsx';
import { getAnalyticsMetadataAttribute } from '@awsui/component-toolkit/internal/analytics-metadata';
import { InternalButton } from '../button/internal';
import { getBaseProps } from '../internal/base-component';
import TokenList from '../internal/components/token-list';
import { fireNonCancelableEvent } from '../internal/events';
import { useListFocusController } from '../internal/hooks/use-list-focus-controller';
import { useMergeRefs } from '../internal/hooks/use-merge-refs';
import { useUniqueId } from '../internal/hooks/use-unique-id/index';
import { joinStrings } from '../internal/utils/strings';
import InternalSpaceBetween from '../space-between/internal';
import { SearchResults } from '../text-filter/search-results';
import useDebounceSearchResultCallback from '../text-filter/use-debounce-search-result-callback';
import { getAllowedOperators, getAutosuggestOptions, getQueryActions, parseText } from './controller';
import { usePropertyFilterI18n } from './i18n-utils';
import { PropertyEditorContentCustom, PropertyEditorContentEnum, PropertyEditorFooter } from './property-editor';
import PropertyFilterAutosuggest from './property-filter-autosuggest';
import { TokenButton } from './token';
import { useLoadItems } from './use-load-items';
import tokenListStyles from '../internal/components/token-list/styles.css.js';
import analyticsSelectors from './analytics-metadata/styles.css.js';
import styles from './styles.css.js';
const PropertyFilterInternal = React.forwardRef((_a, ref) => {
var _b;
var { disabled, countText, query, hideOperations, onChange, filteringProperties, filteringOptions, customGroupsText, disableFreeTextFiltering, freeTextFiltering, onLoadItems, virtualScroll, customControl, customFilterActions, filteringPlaceholder, filteringAriaLabel, filteringEmpty, filteringLoadingText, filteringFinishedText, filteringErrorText, filteringRecoveryText, filteringConstraintText, filteringStatusType, asyncProperties, tokenLimit, expandToViewport, tokenLimitShowFewerAriaLabel, tokenLimitShowMoreAriaLabel, enableTokenGroups, loading = false, __internalRootRef } = _a, rest = __rest(_a, ["disabled", "countText", "query", "hideOperations", "onChange", "filteringProperties", "filteringOptions", "customGroupsText", "disableFreeTextFiltering", "freeTextFiltering", "onLoadItems", "virtualScroll", "customControl", "customFilterActions", "filteringPlaceholder", "filteringAriaLabel", "filteringEmpty", "filteringLoadingText", "filteringFinishedText", "filteringErrorText", "filteringRecoveryText", "filteringConstraintText", "filteringStatusType", "asyncProperties", "tokenLimit", "expandToViewport", "tokenLimitShowFewerAriaLabel", "tokenLimitShowMoreAriaLabel", "enableTokenGroups", "loading", "__internalRootRef"]);
const [nextFocusIndex, setNextFocusIndex] = useState(null);
const tokenListRef = useListFocusController({
nextFocusIndex,
onFocusMoved: (target, targetType) => {
var _a;
if (targetType === 'fallback') {
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventDropdown: true });
}
else {
target.focus();
}
setNextFocusIndex(null);
},
listItemSelector: `.${tokenListStyles['list-item']}`,
showMoreSelector: `.${tokenListStyles.toggle}`,
fallbackSelector: `.${styles.input}`,
});
const mergedRef = useMergeRefs(tokenListRef, __internalRootRef);
const inputRef = useRef(null);
const searchResultsRef = useRef(null);
const baseProps = getBaseProps(rest);
const i18nStrings = usePropertyFilterI18n(rest.i18nStrings);
useImperativeHandle(ref, () => ({ focus: () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); } }), []);
const [filteringText, setFilteringText] = useState('');
const { internalProperties, internalOptions, internalQuery, internalFreeText } = (() => {
var _a, _b;
const propertyByKey = filteringProperties.reduce((acc, property) => {
var _a, _b, _c, _d, _e;
const extendedOperators = ((_a = property === null || property === void 0 ? void 0 : property.operators) !== null && _a !== void 0 ? _a : []).reduce((acc, operator) => (typeof operator === 'object' ? acc.set(operator.operator, operator) : acc), new Map());
acc.set(property.key, {
propertyKey: property.key,
propertyLabel: (_b = property === null || property === void 0 ? void 0 : property.propertyLabel) !== null && _b !== void 0 ? _b : '',
groupValuesLabel: (_c = property === null || property === void 0 ? void 0 : property.groupValuesLabel) !== null && _c !== void 0 ? _c : '',
propertyGroup: property === null || property === void 0 ? void 0 : property.group,
operators: ((_d = property === null || property === void 0 ? void 0 : property.operators) !== null && _d !== void 0 ? _d : []).map(op => (typeof op === 'string' ? op : op.operator)),
defaultOperator: (_e = property === null || property === void 0 ? void 0 : property.defaultOperator) !== null && _e !== void 0 ? _e : '=',
getTokenType: operator => { var _a, _b; return (operator ? (_b = (_a = extendedOperators.get(operator)) === null || _a === void 0 ? void 0 : _a.tokenType) !== null && _b !== void 0 ? _b : 'value' : 'value'); },
getValueFormatter: operator => { var _a, _b; return (operator ? (_b = (_a = extendedOperators.get(operator)) === null || _a === void 0 ? void 0 : _a.format) !== null && _b !== void 0 ? _b : null : null); },
getValueFormRenderer: operator => { var _a, _b; return (operator ? (_b = (_a = extendedOperators.get(operator)) === null || _a === void 0 ? void 0 : _a.form) !== null && _b !== void 0 ? _b : null : null); },
externalProperty: property,
});
return acc;
}, new Map());
const getProperty = (propertyKey) => { var _a; return (_a = propertyByKey.get(propertyKey)) !== null && _a !== void 0 ? _a : null; };
const internalOptions = filteringOptions.map(option => {
var _a, _b;
return ({
property: getProperty(option.propertyKey),
value: option.value,
label: (_b = (_a = option.label) !== null && _a !== void 0 ? _a : option.value) !== null && _b !== void 0 ? _b : '',
});
});
function transformToken(tokenOrGroup, standaloneIndex) {
return 'operation' in tokenOrGroup
? {
operation: tokenOrGroup.operation,
tokens: tokenOrGroup.tokens.map(token => transformToken(token)),
}
: {
standaloneIndex,
property: tokenOrGroup.propertyKey ? getProperty(tokenOrGroup.propertyKey) : null,
operator: tokenOrGroup.operator,
value: tokenOrGroup.value,
};
}
const internalQuery = {
operation: query.operation,
tokens: (enableTokenGroups && query.tokenGroups ? query.tokenGroups : query.tokens).map(transformToken),
};
const internalFreeText = {
disabled: disableFreeTextFiltering,
operators: (_a = freeTextFiltering === null || freeTextFiltering === void 0 ? void 0 : freeTextFiltering.operators) !== null && _a !== void 0 ? _a : [':', '!:'],
defaultOperator: (_b = freeTextFiltering === null || freeTextFiltering === void 0 ? void 0 : freeTextFiltering.defaultOperator) !== null && _b !== void 0 ? _b : ':',
};
return { internalProperties: [...propertyByKey.values()], internalOptions, internalQuery, internalFreeText };
})();
const { addToken, updateToken, updateOperation, removeToken, removeAllTokens } = getQueryActions({
query: internalQuery,
filteringOptions: internalOptions,
onChange,
enableTokenGroups,
});
const parsedText = parseText(filteringText, internalProperties, internalFreeText);
const autosuggestOptions = getAutosuggestOptions(parsedText, internalProperties, internalOptions, customGroupsText, i18nStrings);
const createToken = (currentText) => {
const parsedText = parseText(currentText, internalProperties, internalFreeText);
let newToken;
switch (parsedText.step) {
case 'property': {
newToken = {
property: parsedText.property,
operator: parsedText.operator,
value: parsedText.value,
};
break;
}
case 'free-text': {
newToken = {
property: null,
operator: parsedText.operator || internalFreeText.defaultOperator,
value: parsedText.value,
};
break;
}
case 'operator': {
newToken = {
property: null,
operator: internalFreeText.defaultOperator,
value: currentText,
};
break;
}
}
if (internalFreeText.disabled && !newToken.property) {
return;
}
addToken(newToken);
setFilteringText('');
};
const getLoadMoreDetail = (parsedText, filteringText) => {
const loadMoreDetail = {
filteringProperty: undefined,
filteringText,
filteringOperator: undefined,
};
if (parsedText.step === 'property') {
loadMoreDetail.filteringProperty = parsedText.property.externalProperty;
loadMoreDetail.filteringText = parsedText.value;
loadMoreDetail.filteringOperator = parsedText.operator;
}
return loadMoreDetail;
};
const loadMoreDetail = getLoadMoreDetail(parsedText, filteringText);
const inputLoadItemsHandlers = useLoadItems(onLoadItems, loadMoreDetail.filteringText, loadMoreDetail.filteringProperty, loadMoreDetail.filteringText, loadMoreDetail.filteringOperator);
const asyncProps = {
empty: filteringEmpty,
loadingText: filteringLoadingText,
finishedText: filteringFinishedText,
errorText: filteringErrorText,
recoveryText: filteringRecoveryText,
statusType: filteringStatusType,
};
const asyncAutosuggestProps = !!filteringText.length || asyncProperties
? Object.assign(Object.assign({}, inputLoadItemsHandlers), asyncProps) : {};
const handleSelected = event => {
var _a;
const { detail: option } = event;
const value = option.value || '';
if (!value) {
return;
}
if (!('keepOpenOnSelect' in option)) {
createToken(value);
return;
}
// stop dropdown from closing
event.preventDefault();
const parsedText = parseText(value, internalProperties, internalFreeText);
const loadMoreDetail = getLoadMoreDetail(parsedText, value);
// Insert operator automatically if only one operator is defined for the given property.
if (parsedText.step === 'operator') {
const operators = getAllowedOperators(parsedText.property);
if (value.trim() === parsedText.property.propertyLabel && operators.length === 1) {
loadMoreDetail.filteringProperty = (_a = parsedText.property.externalProperty) !== null && _a !== void 0 ? _a : undefined;
loadMoreDetail.filteringOperator = operators[0];
loadMoreDetail.filteringText = '';
setFilteringText(parsedText.property.propertyLabel + ' ' + operators[0] + ' ');
}
}
fireNonCancelableEvent(onLoadItems, Object.assign(Object.assign({}, loadMoreDetail), { firstPage: true, samePage: false }));
};
useDebounceSearchResultCallback({
searchQuery: query,
countText,
loading,
announceCallback: () => {
var _a;
(_a = searchResultsRef.current) === null || _a === void 0 ? void 0 : _a.reannounce();
},
});
const propertyStep = parsedText.step === 'property' ? parsedText : null;
const customValueKey = propertyStep ? propertyStep.property.propertyKey + ':' + propertyStep.operator : '';
const [customFormValueRecord, setCustomFormValueRecord] = useState({});
const customFormValue = customValueKey in customFormValueRecord ? customFormValueRecord[customValueKey] : null;
const setCustomFormValue = (value) => setCustomFormValueRecord({ [customValueKey]: value });
const operatorForm = propertyStep && propertyStep.property.getValueFormRenderer(propertyStep.operator);
const isEnumValue = (propertyStep === null || propertyStep === void 0 ? void 0 : propertyStep.property.getTokenType(propertyStep.operator)) === 'enum';
const searchResultsId = useUniqueId('property-filter-search-results');
const constraintTextId = useUniqueId('property-filter-constraint');
const textboxAriaDescribedBy = filteringConstraintText
? joinStrings(rest.ariaDescribedby, constraintTextId)
: rest.ariaDescribedby;
const showResults = !!((_b = internalQuery.tokens) === null || _b === void 0 ? void 0 : _b.length) && !disabled && !!countText;
return (React.createElement("div", Object.assign({}, baseProps, { className: clsx(baseProps.className, styles.root), ref: mergedRef }),
React.createElement("div", { className: clsx(styles['search-field'], analyticsSelectors['search-field']) },
customControl && React.createElement("div", { className: styles['custom-control'] }, customControl),
React.createElement("div", { className: styles['input-wrapper'] },
React.createElement(PropertyFilterAutosuggest, Object.assign({ ref: inputRef, virtualScroll: virtualScroll, enteredTextLabel: i18nStrings.enteredTextLabel, ariaLabel: filteringAriaLabel !== null && filteringAriaLabel !== void 0 ? filteringAriaLabel : i18nStrings.filteringAriaLabel, placeholder: filteringPlaceholder !== null && filteringPlaceholder !== void 0 ? filteringPlaceholder : i18nStrings.filteringPlaceholder, ariaLabelledby: rest.ariaLabelledby, ariaDescribedby: textboxAriaDescribedBy, controlId: rest.controlId, value: filteringText, disabled: disabled }, autosuggestOptions, { onChange: event => setFilteringText(event.detail.value), empty: filteringEmpty }, asyncAutosuggestProps, { expandToViewport: expandToViewport, onOptionClick: handleSelected, customForm: operatorForm || isEnumValue
? {
content: operatorForm ? (React.createElement(PropertyEditorContentCustom, { key: customValueKey, property: propertyStep.property, operator: propertyStep.operator, filter: propertyStep.value, operatorForm: operatorForm, value: customFormValue, onChange: setCustomFormValue })) : (React.createElement(PropertyEditorContentEnum, { key: customValueKey, property: propertyStep.property, filter: propertyStep.value, value: customFormValue, onChange: setCustomFormValue, asyncProps: asyncProps, filteringOptions: internalOptions, onLoadItems: inputLoadItemsHandlers.onLoadItems })),
footer: (React.createElement(PropertyEditorFooter, { key: customValueKey, property: propertyStep.property, operator: propertyStep.operator, value: customFormValue, i18nStrings: i18nStrings, onCancel: () => {
var _a, _b;
setFilteringText('');
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.close();
(_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.focus({ preventDropdown: true });
}, onSubmit: token => {
var _a, _b;
addToken(token);
setFilteringText('');
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventDropdown: true });
(_b = inputRef.current) === null || _b === void 0 ? void 0 : _b.close();
} })),
}
: undefined, onCloseDropdown: () => setCustomFormValueRecord({}), hideEnteredTextOption: internalFreeText.disabled && parsedText.step !== 'property', clearAriaLabel: i18nStrings.clearAriaLabel, searchResultsId: showResults ? searchResultsId : undefined })),
showResults ? (React.createElement("div", { className: styles.results },
React.createElement(SearchResults, { id: searchResultsId, renderLiveRegion: !loading, ref: searchResultsRef }, countText))) : null)),
filteringConstraintText && (React.createElement("div", { id: constraintTextId, className: styles.constraint }, filteringConstraintText)),
internalQuery.tokens && internalQuery.tokens.length > 0 && (React.createElement("div", { className: styles.tokens },
React.createElement(InternalSpaceBetween, { size: "xs", direction: "horizontal" },
React.createElement(TokenList, { alignment: "inline", limit: tokenLimit, items: internalQuery.tokens, limitShowFewerAriaLabel: tokenLimitShowFewerAriaLabel, limitShowMoreAriaLabel: tokenLimitShowMoreAriaLabel, renderItem: (_, tokenIndex) => (React.createElement(TokenButton, { query: internalQuery, tokenIndex: tokenIndex, onUpdateToken: (token, releasedTokens) => {
updateToken(tokenIndex, token, releasedTokens);
}, onUpdateOperation: updateOperation, onRemoveToken: () => {
removeToken(tokenIndex);
setNextFocusIndex(tokenIndex);
}, filteringProperties: internalProperties, filteringOptions: internalOptions, asyncProps: asyncProps, onLoadItems: onLoadItems, i18nStrings: i18nStrings, asyncProperties: asyncProperties, hideOperations: hideOperations, customGroupsText: customGroupsText, freeTextFiltering: internalFreeText, disabled: disabled, expandToViewport: expandToViewport, enableTokenGroups: enableTokenGroups })), i18nStrings: {
limitShowFewer: i18nStrings.tokenLimitShowFewer,
limitShowMore: i18nStrings.tokenLimitShowMore,
}, after: customFilterActions ? (React.createElement("div", { className: styles['custom-filter-actions'] }, customFilterActions)) : (React.createElement("span", Object.assign({}, getAnalyticsMetadataAttribute({
action: 'clearFilters',
})),
React.createElement(InternalButton, { formAction: "none", onClick: () => {
var _a;
removeAllTokens();
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventDropdown: true });
}, className: styles['remove-all'], disabled: disabled }, i18nStrings.clearFiltersText))) }))))));
});
export default PropertyFilterInternal;
//# sourceMappingURL=internal.js.map