UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

320 lines (314 loc) 9.64 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. */ 'use strict'; var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var icons = require('@carbon/react/icons'); var React = require('react'); var settings = require('../../settings.js'); var react = require('@carbon/react'); var index = require('../../_virtual/index.js'); var cx = require('classnames'); var devtools = require('../../global/js/utils/devtools.js'); var _Close, _Checkmark, _Edit; const componentName = 'EditInPlace'; const blockClass = `${settings.pkg.prefix}--edit-in-place`; const defaults = { tooltipAlignment: 'top'}; exports.EditInPlace = /*#__PURE__*/React.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] = React.useState(false); const [initialValue, setInitialValue] = React.useState(''); const [dirtyInput, setDirtyInput] = React.useState(false); const inputRef = React.useRef(null); const canSave = value !== initialValue && !invalid; const escaping = React.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; }, {}); React.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.createElement("div", _rollupPluginBabelHelpers.extends({}, rest, { ref: ref }, devtools.getDevtoolsProps(componentName)), /*#__PURE__*/React.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.createElement("input", { id: id, className: cx(`${blockClass}__text-input`, `${settings.carbon.prefix}--text-input`, `${settings.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.createElement("div", { className: `${blockClass}__toolbar` }, invalid && /*#__PURE__*/React.createElement(icons.WarningFilled, { size: 16, className: `${blockClass}__warning-icon` }), focused ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(react.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.createElement(icons.Close, { size: 16 }))), /*#__PURE__*/React.createElement(react.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.createElement(icons.Checkmark, { size: 16 })))) : /*#__PURE__*/React.createElement(react.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.createElement(icons.Edit, { size: 16 }))))), invalid && /*#__PURE__*/React.createElement("p", { className: `${blockClass}__warning-text` }, invalidText ?? deprecated_invalidLabel)); }); exports.EditInPlace = settings.pkg.checkComponentEnabled(exports.EditInPlace, componentName); exports.EditInPlace.displayName = componentName; const deprecatedProps = { /** * **Deprecated** * invalidLabel was misnamed, using invalidText to match Carbon */ invalidText: index.default.string }; const alignPropType = index.default.oneOf(['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'right']); exports.EditInPlace.propTypes = { /** * label for cancel button */ cancelLabel: index.default.string.isRequired, /** * By default the edit icon is shown on hover only. */ editAlwaysVisible: index.default.bool, /** * label for edit button */ editLabel: index.default.string.isRequired, /** * Specify a custom id for the input */ id: index.default.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: index.default.bool, /** * determines if the input is invalid */ invalid: index.default.bool, /** * text that is displayed if the input is invalid */ /**@ts-ignore*/ invalidText: index.default.string, /** * Provide the text that will be read by a screen reader when visiting this control */ labelText: index.default.string.isRequired, /** * handler to add custom onBlur event */ onBlur: index.default.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: index.default.func.isRequired, /** * handler that is called when the input is updated */ onChange: index.default.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: index.default.func.isRequired, /** * Placeholder for text input */ placeholder: index.default.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: index.default.string.isRequired, /** * vertical size of control */ size: index.default.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: index.default.oneOfType([alignPropType, index.default.shape({ cancel: alignPropType, edit: alignPropType, save: alignPropType })]), /** * current value of the input */ value: index.default.string.isRequired, ...deprecatedProps }; exports.deprecatedProps = deprecatedProps;