UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

318 lines (313 loc) 9.57 kB
/** * Copyright IBM Corp. 2020, 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import { WarningFilled, Close, Checkmark, Edit } from '@carbon/react/icons'; import React__default, { forwardRef, useState, useRef, useEffect } from 'react'; import { pkg, carbon } from '../../settings.js'; import { IconButton } from '@carbon/react'; import PropTypes from '../../_virtual/index.js'; import cx from 'classnames'; import { getDevtoolsProps } from '../../global/js/utils/devtools.js'; var _Close, _Checkmark, _Edit; const componentName = 'EditInPlace'; const blockClass = `${pkg.prefix}--edit-in-place`; const defaults = { tooltipAlignment: 'top'}; let EditInPlace = /*#__PURE__*/forwardRef((_ref, ref) => { let { cancelLabel, editAlwaysVisible, editLabel, id, inheritTypography, invalid, invalidLabel: deprecated_invalidLabel = ' ', invalidText, labelText, onCancel, onChange, onSave, onBlur, // readOnly, // readOnlyLabel, saveLabel, size = 'sm', tooltipAlignment, value, placeholder, ...rest } = _ref; const [focused, setFocused] = useState(false); const [initialValue, setInitialValue] = useState(''); const [dirtyInput, setDirtyInput] = useState(false); const inputRef = useRef(null); const canSave = value !== initialValue && !invalid; const escaping = useRef(false); const tipAlignIsObject = typeof tooltipAlignment === 'object'; const tipAlignments = ['edit', 'save', 'cancel'].reduce((acc, tips) => { acc[tips] = (tipAlignIsObject ? tooltipAlignment[tips] : tooltipAlignment) ?? defaults.tooltipAlignment; return acc; }, {}); useEffect(() => { if (!initialValue && !dirtyInput) { setInitialValue(value); } }, [initialValue, dirtyInput, value]); const isTargetingChild = _ref2 => { let { currentTarget, relatedTarget } = _ref2; return currentTarget.contains(relatedTarget); }; const onChangeHandler = _ref3 => { let { target } = _ref3; if (!dirtyInput) { setDirtyInput(true); } onChange(target.value); }; const onFocusHandler = e => { // if (readOnly) { // return; // } if (!isTargetingChild(e)) { inputRef.current?.focus(); setFocused(true); } }; const onSaveHandler = () => { setInitialValue(value); setDirtyInput(false); onSave(); setFocused(false); }; const onCancelHandler = () => { setDirtyInput(false); onCancel(initialValue); }; const onBlurHandler = e => { // Use custom function provided if passed through if (typeof onBlur === 'function' && !isTargetingChild(e)) { onBlur(initialValue); setFocused(false); } else { // Use Default behavior if no custom function provided if (escaping.current) { return; } if (!isTargetingChild(e)) { if (canSave) { onSaveHandler(); } else { onCancelHandler(); setFocused(false); } } } }; const returnHandler = () => { if (canSave) { onSaveHandler(); } }; const escapeHandler = () => { onCancelHandler(); }; const removeFocus = () => { inputRef.current?.blur(); setFocused(false); }; const onKeyHandler = e => { // to prevent blur handler from being called twice add additional state to check if escape is being used escaping.current = true; switch (e.key) { case 'Escape': removeFocus(); escapeHandler(); break; case 'Enter': removeFocus(); returnHandler(); break; } escaping.current = false; }; return /*#__PURE__*/React__default.createElement("div", _extends({}, rest, { ref: ref }, getDevtoolsProps(componentName)), /*#__PURE__*/React__default.createElement("div", { className: cx(blockClass, `${blockClass}--${size}`, { [`${blockClass}--focused`]: focused, [`${blockClass}--invalid`]: invalid, [`${blockClass}--inherit-type`]: inheritTypography, [`${blockClass}--overflows`]: inputRef.current && inputRef.current.scrollWidth > inputRef.current.offsetWidth // [`${blockClass}--readonly`]: readOnly, }), onFocus: onFocusHandler, onBlur: onBlurHandler }, /*#__PURE__*/React__default.createElement("input", { id: id, className: cx(`${blockClass}__text-input`, `${carbon.prefix}--text-input`, `${carbon.prefix}--text-input--${size}`), type: "text", placeholder: placeholder, value: value, onChange: onChangeHandler, ref: inputRef // readOnly={readOnly} , onKeyDown: onKeyHandler, "aria-label": labelText, "aria-invalid": invalid }), /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__toolbar` }, invalid && /*#__PURE__*/React__default.createElement(WarningFilled, { size: 16, className: `${blockClass}__warning-icon` }), focused ? /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(IconButton, { align: tipAlignments.cancel, size: size, label: cancelLabel, onClick: onCancelHandler, kind: "ghost", tabIndex: 0, key: "cancel", className: `${blockClass}__btn ${blockClass}__btn-cancel` }, _Close || (_Close = /*#__PURE__*/React__default.createElement(Close, { size: 16 }))), /*#__PURE__*/React__default.createElement(IconButton, { align: tipAlignments.save, size: size, label: saveLabel, onClick: onSaveHandler, kind: "ghost", tabIndex: 0, key: "save", className: `${blockClass}__btn ${blockClass}__btn-save`, disabled: !canSave }, _Checkmark || (_Checkmark = /*#__PURE__*/React__default.createElement(Checkmark, { size: 16 })))) : /*#__PURE__*/React__default.createElement(IconButton, { align: tipAlignments.edit, className: cx(`${blockClass}__btn`, `${blockClass}__btn-edit`, { [`${blockClass}__btn-edit--always-visible`]: editAlwaysVisible }), size: size, label: editLabel, onClick: onFocusHandler, kind: "ghost", tabIndex: 0, key: "edit" }, _Edit || (_Edit = /*#__PURE__*/React__default.createElement(Edit, { size: 16 }))))), invalid && /*#__PURE__*/React__default.createElement("p", { className: `${blockClass}__warning-text` }, invalidText ?? deprecated_invalidLabel)); }); EditInPlace = pkg.checkComponentEnabled(EditInPlace, componentName); EditInPlace.displayName = componentName; const deprecatedProps = { /** * **Deprecated** * invalidLabel was misnamed, using invalidText to match Carbon */ invalidText: PropTypes.string }; const alignPropType = PropTypes.oneOf(['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'right']); EditInPlace.propTypes = { /** * label for cancel button */ cancelLabel: PropTypes.string.isRequired, /** * By default the edit icon is shown on hover only. */ editAlwaysVisible: PropTypes.bool, /** * label for edit button */ editLabel: PropTypes.string.isRequired, /** * Specify a custom id for the input */ id: PropTypes.string.isRequired, /** * inheritTypography - causes the text entry field to inherit typography settings * assigned to the container. This is useful when editing titles for instance. * * NOTE: The size property limits the vertical size of the input element. * Inherited font's should be selected to fit within the size selected. */ inheritTypography: PropTypes.bool, /** * determines if the input is invalid */ invalid: PropTypes.bool, /** * text that is displayed if the input is invalid */ /**@ts-ignore*/ invalidText: PropTypes.string, /** * Provide the text that will be read by a screen reader when visiting this control */ labelText: PropTypes.string.isRequired, /** * handler to add custom onBlur event */ onBlur: PropTypes.func, /** * handler that is called when the cancel button is pressed or when the user removes focus from the input and there is no new value */ onCancel: PropTypes.func.isRequired, /** * handler that is called when the input is updated */ onChange: PropTypes.func.isRequired, /** * handler that is called when the save button is pressed or when the user removes focus from the input if it has a new value */ onSave: PropTypes.func.isRequired, /** * Placeholder for text input */ placeholder: PropTypes.string, /** * determines if the input is in readOnly mode */ // readOnly: PropTypes.bool, /** * label for the edit button that displays when in read only mode */ // readOnlyLabel: PropTypes.string, /** * label for save button */ saveLabel: PropTypes.string.isRequired, /** * vertical size of control */ size: PropTypes.oneOf(['sm', 'md', 'lg']), /** * tooltipAlignment from the standard tooltip. Default center. * * Can be passed either as one of tooltip options or as an object specifying cancel, edit and save separately */ /**@ts-ignore*/ tooltipAlignment: PropTypes.oneOfType([alignPropType, PropTypes.shape({ cancel: alignPropType, edit: alignPropType, save: alignPropType })]), /** * current value of the input */ value: PropTypes.string.isRequired, ...deprecatedProps }; export { EditInPlace, deprecatedProps };