@atlaskit/inline-edit
Version:
An inline edit displays a custom input component that switches between reading and editing on the same page.
261 lines (258 loc) • 11.9 kB
JavaScript
/* inline-edit.tsx generated by @compiled/babel-plugin v3.0.2 */
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import "./inline-edit.compiled.css";
import { ax, ix } from "@compiled/react/runtime";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React, { useCallback, useRef, useState } from 'react';
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next/usePlatformLeafEventHandler';
import Field from '@atlaskit/form/Field';
import Form from '@atlaskit/form/Form';
import { fg } from '@atlaskit/platform-feature-flags/fg';
import { Pressable } from '@atlaskit/primitives/compiled';
import VisuallyHidden from '@atlaskit/visually-hidden/visually-hidden';
import Buttons from './internal/buttons';
import getTextFromReactNode from './internal/get-text-from-react-node';
import useButtonFocusHook from './internal/hooks/use-button-focus-hook';
import ReadView from './internal/read-view';
var fieldStyles = null;
var analyticsAttributes = {
componentName: 'inlineEdit',
packageName: "@atlaskit/inline-edit",
packageVersion: "16.4.2"
};
var InnerInlineEdit = function InnerInlineEdit(props) {
var _props$startWithEditV = props.startWithEditViewOpen,
startWithEditViewOpen = _props$startWithEditV === void 0 ? false : _props$startWithEditV,
_props$keepEditViewOp = props.keepEditViewOpenOnBlur,
keepEditViewOpenOnBlur = _props$keepEditViewOp === void 0 ? false : _props$keepEditViewOp,
_props$hideActionButt = props.hideActionButtons,
hideActionButtons = _props$hideActionButt === void 0 ? false : _props$hideActionButt,
_props$isRequired = props.isRequired,
isRequired = _props$isRequired === void 0 ? false : _props$isRequired,
_props$readViewFitCon = props.readViewFitContainerWidth,
readViewFitContainerWidth = _props$readViewFitCon === void 0 ? false : _props$readViewFitCon,
_props$editButtonLabe = props.editButtonLabel,
editButtonLabel = _props$editButtonLabe === void 0 ? 'Edit' : _props$editButtonLabe,
_props$editLabel = props.editLabel,
editLabel = _props$editLabel === void 0 ? 'edit' : _props$editLabel,
_props$confirmButtonL = props.confirmButtonLabel,
confirmButtonLabel = _props$confirmButtonL === void 0 ? 'Confirm' : _props$confirmButtonL,
_props$cancelButtonLa = props.cancelButtonLabel,
cancelButtonLabel = _props$cancelButtonLa === void 0 ? 'Cancel' : _props$cancelButtonLa,
defaultValue = props.defaultValue,
isEditing = props.isEditing,
label = props.label,
validate = props.validate,
readView = props.readView,
editView = props.editView,
analyticsContext = props.analyticsContext,
providedOnConfirm = props.onConfirm,
providedOnCancel = props.onCancel,
providedOnEdit = props.onEdit,
testId = props.testId;
var wasFocusReceivedSinceLastBlurRef = useRef(false);
var isControlled = typeof isEditing === 'undefined';
var _useState = useState(startWithEditViewOpen),
_useState2 = _slicedToArray(_useState, 2),
isEditingState = _useState2[0],
setEditingState = _useState2[1];
var timerRef = useRef();
var _useButtonFocusHook = useButtonFocusHook(isEditing, isEditingState),
editButtonRef = _useButtonFocusHook.editButtonRef,
editViewRef = _useButtonFocusHook.editViewRef,
shouldBeEditing = _useButtonFocusHook.shouldBeEditing,
doNotFocusOnEditButton = _useButtonFocusHook.doNotFocusOnEditButton;
var onCancel = useCallback(function () {
if (isControlled) {
setEditingState(false);
}
providedOnCancel === null || providedOnCancel === void 0 || providedOnCancel();
}, [isControlled, providedOnCancel]);
var onEditRequested = useCallback(function () {
if (isControlled) {
setEditingState(true);
}
providedOnEdit === null || providedOnEdit === void 0 || providedOnEdit();
if (shouldBeEditing && editViewRef.current) {
editViewRef.current.focus();
}
}, [isControlled, shouldBeEditing, editViewRef, providedOnEdit]);
var onConfirm = usePlatformLeafEventHandler(_objectSpread({
fn: function fn(value, analyticsEvent) {
if (isControlled) {
setEditingState(false);
}
providedOnConfirm(value, analyticsEvent);
},
action: 'confirmed',
analyticsData: analyticsContext
}, analyticsAttributes));
var _onCancelClick = useCallback(function (event) {
event.preventDefault();
onCancel();
}, [onCancel]);
var tryAutoSubmitWhenBlur = useCallback(function (isFieldInvalid, onSubmit, formRef) {
// Ignore if the provided ref is a function. This is used to make the
// typechecking accurate. Inline edit only uses one type of ref, so this
// will never trigger.
if (typeof formRef === 'function') {
return;
}
if (!isFieldInvalid && !wasFocusReceivedSinceLastBlurRef.current && formRef.current) {
doNotFocusOnEditButton();
if (formRef.current.checkValidity()) {
onSubmit();
}
}
}, [doNotFocusOnEditButton]);
/**
* If keepEditViewOpenOnBlur prop is set to false, will call confirmIfUnfocused() which
* confirms the value, if the focus is not transferred to the action buttons.
*
* When you're in `editing` state, the focus will be on the input field. And if you use keyboard
* to navigate to `submit` button, this function will be invoked. Then function `onEditViewWrapperFocus`
* will be called, the timeout used here is making sure `onEditViewWrapperFocus` is always called before
* `autoSubmitWhenBlur`.
*
* There are two paths here the function can be triggered:
*
* - focus on input first, and then use keyboard to `submit`
* - focus on input first, and then click anywhere else on the page (outside of edit view wrapper) to `submit` (auto save).
*/
var onEditViewWrapperBlur = useCallback(function (isFieldInvalid, onSubmit, formRef) {
if (!keepEditViewOpenOnBlur) {
wasFocusReceivedSinceLastBlurRef.current = false;
timerRef.current = setTimeout(function () {
return tryAutoSubmitWhenBlur(isFieldInvalid, onSubmit, formRef);
}, 0);
}
}, [keepEditViewOpenOnBlur, tryAutoSubmitWhenBlur]);
/**
* Gets called when focus is transferred to the editView, or action buttons.
*
* There are three paths here the function can be called:
*
* - when a user click the `editView`
* - when a user use keyboard to tab into `editView`
* - when a user use keyboard to tab into `submit` when they were on input field.
*/
var onEditViewWrapperFocus = useCallback(function () {
wasFocusReceivedSinceLastBlurRef.current = true;
}, []);
var concatenatedEditButtonLabel = function concatenatedEditButtonLabel() {
if (label) {
var labelText = typeof label === 'string' ? label : getTextFromReactNode(label);
return "".concat(editButtonLabel, ", ").concat(labelText, ", ").concat(editLabel);
}
return "".concat(editButtonLabel, ", ").concat(editLabel);
};
var renderReadView = function renderReadView() {
return /*#__PURE__*/React.createElement(ReadView, {
editButtonLabel: concatenatedEditButtonLabel(),
onEditRequested: onEditRequested,
postReadViewClick: doNotFocusOnEditButton,
editButtonRef: editButtonRef,
readViewFitContainerWidth: readViewFitContainerWidth,
readView: readView,
testId: testId
});
};
return /*#__PURE__*/React.createElement(Form, {
onSubmit: function onSubmit(data) {
return onConfirm(data.inlineEdit);
}
}, function (_ref) {
var _ref$formProps = _ref.formProps,
_onKeyDown = _ref$formProps.onKeyDown,
onSubmit = _ref$formProps.onSubmit,
formRef = _ref$formProps.ref,
reset = _ref.reset;
return /*#__PURE__*/React.createElement("form", {
/**
* It is not normally acceptable to add key handlers to non-interactive elements
* as this is an accessibility anti-pattern. However, because this instance is
* to add support for keyboard functionality instead of creating an inaccessible
* custom element, we can add role="presentation" so that there is no negative
* impacts to assistive technologies.
*/
role: "presentation",
onKeyDown: function onKeyDown(e) {
_onKeyDown(e);
if (e.key === 'Esc' || e.key === 'Escape') {
reset();
onCancel();
}
},
onSubmit: onSubmit,
ref: formRef
// Below we have added a class name to the form element to prevent the default focus on the form element
// This is because due to some default focus being applied to the form element, the inline edit component
// was being focused automatically and causing scroll issues in issue view
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
,
className: fg('platform_design_system_inline_edit_dont_focus') ? 'dont-default-focus' : undefined
}, shouldBeEditing ? /*#__PURE__*/React.createElement(Field, {
name: "inlineEdit",
label: label,
defaultValue: defaultValue,
validate: validate,
isRequired: isRequired,
key: "edit-view" // used for reset to default value
}, function (_ref2) {
var fieldProps = _ref2.fieldProps,
error = _ref2.error;
return /*#__PURE__*/React.createElement("div", {
onBlur: function onBlur(e) {
if (!e.currentTarget.contains(e.relatedTarget)) {
onEditViewWrapperBlur(fieldProps.isInvalid, onSubmit, formRef);
}
},
onFocus: onEditViewWrapperFocus,
className: ax(["_p12f1osq _kqswh2mm"])
}, editView(_objectSpread(_objectSpread({}, fieldProps), {}, {
errorMessage: error
}), editViewRef), !hideActionButtons ? /*#__PURE__*/React.createElement(Buttons, {
testId: testId,
cancelButtonLabel: cancelButtonLabel,
confirmButtonLabel: confirmButtonLabel,
onMouseDown: function onMouseDown() {
/**
* Prevents focus on edit button only if mouse is used to click button, but not when keyboard is used
*/
doNotFocusOnEditButton();
},
onCancelClick: function onCancelClick(e) {
reset();
_onCancelClick(e);
}
}) :
/*#__PURE__*/
/**
* This is to allow Ctrl + Enter to submit without action buttons
*/
React.createElement(Pressable, {
hidden: true,
type: "submit"
}, /*#__PURE__*/React.createElement(VisuallyHidden, null, "Submit")));
}) :
/*#__PURE__*/
/**
* Field is used here only for the label and spacing
*/
React.createElement(Field, {
name: "inlineEdit",
label: label,
defaultValue: "",
isRequired: isRequired,
key: "read-view" // used for reset to default value
}, renderReadView));
});
};
var InlineEdit = function InlineEdit(props) {
// eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props
return /*#__PURE__*/React.createElement(InnerInlineEdit, props);
};
// eslint-disable-next-line @repo/internal/react/require-jsdoc
export default InlineEdit;