UNPKG

@awsui/components-react

Version:

AWS UI is a collection of [React](https://reactjs.org/) components that help create intuitive, responsive, and accessible user experiences for web applications. It is developed by Amazon Web Services (AWS). This work is available under the terms of the [A

148 lines (147 loc) • 9.97 kB
import { __assign, __rest, __spreadArrays } from "tslib"; import React, { useCallback, useImperativeHandle, useLayoutEffect, useMemo, useRef } from 'react'; import clsx from 'clsx'; import { getBaseProps } from '../internal/base-component'; import { fireNonCancelableEvent } from '../internal/events'; import { useStableEventHandler } from '../internal/hooks/use-stable-event-handler'; import { useTelemetry } from '../internal/hooks/use-telemetry'; import AttributeEditor from '../attribute-editor'; import StatusIndicator from '../status-indicator'; import Box from '../box'; import { FormFieldError } from '../form-field/internal'; import { TagControl, UndoButton } from './internal'; import { validate } from './validation'; import { findIndex, useMemoizedArray } from './utils'; import styles from './styles.css.js'; var isItemRemovable = function (_a) { var tag = _a.tag; return !tag.markedForRemoval; }; var TagEditor = React.forwardRef(function (_a, ref) { var _b, _c; var _d = _a.tags, tags = _d === void 0 ? [] : _d, i18nStrings = _a.i18nStrings, _e = _a.loading, loading = _e === void 0 ? false : _e, _f = _a.tagLimit, tagLimit = _f === void 0 ? 50 : _f, allowedCharacterPattern = _a.allowedCharacterPattern, keysRequest = _a.keysRequest, valuesRequest = _a.valuesRequest, onChange = _a.onChange, restProps = __rest(_a, ["tags", "i18nStrings", "loading", "tagLimit", "allowedCharacterPattern", "keysRequest", "valuesRequest", "onChange"]); useTelemetry('TagEditor'); var remainingTags = tagLimit - tags.filter(function (tag) { return !tag.markedForRemoval; }).length; var attributeEditorRef = useRef(null); var keyInputRefs = useRef([]); var valueInputRefs = useRef([]); var undoButtonRefs = useRef([]); var initialKeyOptionsRef = useRef([]); var keyDirtyStateRef = useRef([]); var focusEventRef = useRef(); useLayoutEffect(function () { var _a; (_a = focusEventRef.current) === null || _a === void 0 ? void 0 : _a.apply(undefined); focusEventRef.current = undefined; }); var errors = validate(tags, keyDirtyStateRef.current, i18nStrings, allowedCharacterPattern ? new RegExp(allowedCharacterPattern) : undefined); var internalTags = useMemoizedArray(tags.map(function (tag, i) { return ({ tag: tag, error: errors[i] }); }), function (prev, next) { var _a, _b, _c, _d; return prev.tag === next.tag && ((_a = prev.error) === null || _a === void 0 ? void 0 : _a.key) === ((_b = next.error) === null || _b === void 0 ? void 0 : _b.key) && ((_c = prev.error) === null || _c === void 0 ? void 0 : _c.value) === ((_d = next.error) === null || _d === void 0 ? void 0 : _d.value); }); useImperativeHandle(ref, function () { return ({ focus: function () { var _a, _b; var errorIndex = findIndex(internalTags, function (_a) { var error = _a.error; return (error === null || error === void 0 ? void 0 : error.key) || (error === null || error === void 0 ? void 0 : error.value); }); if (errorIndex !== -1) { var refArray = ((_a = internalTags[errorIndex].error) === null || _a === void 0 ? void 0 : _a.key) ? keyInputRefs : valueInputRefs; (_b = refArray.current[errorIndex]) === null || _b === void 0 ? void 0 : _b.focus(); } } }); }, [internalTags]); var validateAndFire = useCallback(function (newTags) { fireNonCancelableEvent(onChange, { tags: newTags, valid: !validate(newTags, keyDirtyStateRef.current, i18nStrings).some(function (error) { return error; }) }); }, [onChange, i18nStrings]); var onAddButtonClick = function () { validateAndFire(__spreadArrays(tags, [{ key: '', value: '', existing: false }])); focusEventRef.current = function () { var _a; (_a = keyInputRefs.current[tags.length]) === null || _a === void 0 ? void 0 : _a.focus(); }; }; var onRemoveButtonClick = useStableEventHandler(function (_a) { var _b; var detail = _a.detail; var existing = tags[detail.itemIndex].existing; validateAndFire(__spreadArrays(tags.slice(0, detail.itemIndex), (existing ? [__assign(__assign({}, tags[detail.itemIndex]), { markedForRemoval: true })] : []), tags.slice(detail.itemIndex + 1))); if (existing) { focusEventRef.current = function () { var _a; (_a = undoButtonRefs.current[detail.itemIndex]) === null || _a === void 0 ? void 0 : _a.focus(); }; } else { keyDirtyStateRef.current.splice(detail.itemIndex, 1); (_b = keyInputRefs.current[detail.itemIndex]) === null || _b === void 0 ? void 0 : _b.focus(); } }, [tags, validateAndFire]); var onKeyChange = useStableEventHandler(function (value, row) { keyDirtyStateRef.current[row] = true; validateAndFire(__spreadArrays(tags.slice(0, row), [__assign(__assign({}, tags[row]), { key: value })], tags.slice(row + 1))); }, [tags, validateAndFire]); var onKeyBlur = useStableEventHandler(function (row) { keyDirtyStateRef.current[row] = true; validateAndFire(__spreadArrays(tags)); }, [tags, validateAndFire]); var onValueChange = useStableEventHandler(function (value, row) { validateAndFire(__spreadArrays(tags.slice(0, row), [__assign(__assign({}, tags[row]), { value: value })], tags.slice(row + 1))); }, [tags, validateAndFire]); var onUndoRemoval = useStableEventHandler(function (row) { validateAndFire(__spreadArrays(tags.slice(0, row), [__assign(__assign({}, tags[row]), { markedForRemoval: false })], tags.slice(row + 1))); focusEventRef.current = function () { var _a; (_a = attributeEditorRef.current) === null || _a === void 0 ? void 0 : _a.focusRemoveButton(row); }; }, [tags, validateAndFire]); var definition = useMemo(function () { return [ { label: i18nStrings.keyHeader, control: function (_a, row) { var tag = _a.tag; return (React.createElement(TagControl, { row: row, value: tag.key, readOnly: tag.existing, limit: 200, defaultOptions: [], placeholder: i18nStrings.keyPlaceholder, errorText: i18nStrings.keysSuggestionError, loadingText: i18nStrings.keysSuggestionLoading, suggestionText: i18nStrings.keySuggestion, tooManySuggestionText: i18nStrings.tooManyKeysSuggestion, enteredTextLabel: i18nStrings.enteredKeyLabel, onRequest: keysRequest, onChange: onKeyChange, onBlur: onKeyBlur, initialOptionsRef: initialKeyOptionsRef, ref: function (ref) { keyInputRefs.current[row] = ref; } })); }, errorText: function (_a) { var error = _a.error; return error === null || error === void 0 ? void 0 : error.key; } }, { label: (React.createElement(React.Fragment, null, i18nStrings.valueHeader, " - ", React.createElement("i", null, i18nStrings.optional))), control: function (_a, row) { var _b; var tag = _a.tag; return tag.markedForRemoval ? (React.createElement("div", { role: "alert" }, React.createElement(Box, { margin: { top: 'xxs' } }, i18nStrings.undoPrompt, ' ', React.createElement(UndoButton, { onClick: function () { return onUndoRemoval(row); }, ref: function (elem) { undoButtonRefs.current[row] = elem; } }, i18nStrings.undoButton)))) : (React.createElement(TagControl, { row: row, value: tag.value, readOnly: false, limit: 200, defaultOptions: (_b = tag.valueSuggestionOptions) !== null && _b !== void 0 ? _b : [], placeholder: i18nStrings.valuePlaceholder, errorText: i18nStrings.valuesSuggestionError, loadingText: i18nStrings.valuesSuggestionLoading, suggestionText: i18nStrings.valueSuggestion, tooManySuggestionText: i18nStrings.tooManyValuesSuggestion, enteredTextLabel: i18nStrings.enteredValueLabel, filteringKey: tag.key, onRequest: valuesRequest && (function (value) { return valuesRequest(tag.key, value); }), onChange: onValueChange, ref: function (ref) { valueInputRefs.current[row] = ref; } })); }, errorText: function (_a) { var error = _a.error; return error === null || error === void 0 ? void 0 : error.value; } } ]; }, [i18nStrings, keysRequest, onKeyChange, onKeyBlur, valuesRequest, onValueChange, onUndoRemoval]); if (loading) { return (React.createElement("div", { className: styles.root }, React.createElement(StatusIndicator, { className: styles.loading, type: "loading" }, i18nStrings.loading))); } var baseProps = getBaseProps(restProps); return (React.createElement(AttributeEditor, __assign({}, baseProps, { ref: attributeEditorRef, className: clsx(styles.root, baseProps.className), items: internalTags, isItemRemovable: isItemRemovable, onAddButtonClick: onAddButtonClick, onRemoveButtonClick: onRemoveButtonClick, addButtonText: i18nStrings.addButton, removeButtonText: i18nStrings.removeButton, disableAddButton: remainingTags <= 0, empty: i18nStrings.emptyTags, additionalInfo: React.createElement("div", { "aria-live": "polite" }, remainingTags < 0 ? (React.createElement(FormFieldError, null, (_b = i18nStrings.tagLimitExceeded(tagLimit)) !== null && _b !== void 0 ? _b : '')) : remainingTags === 0 ? ((_c = i18nStrings.tagLimitReached(tagLimit)) !== null && _c !== void 0 ? _c : '') : (i18nStrings.tagLimit(remainingTags))), definition: definition }))); }); export default TagEditor;