@carbon/ibm-products
Version:
Carbon for IBM Products
320 lines (314 loc) • 9.64 kB
JavaScript
/**
* 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;