@atlaskit/inline-edit
Version:
An inline edit displays a custom input component that switches between reading and editing on the same page.
245 lines (242 loc) • 8.93 kB
JavaScript
/* inline-edit.tsx generated by @compiled/babel-plugin v3.0.2 */
import "./inline-edit.compiled.css";
import { ax, ix } from "@compiled/react/runtime";
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';
const fieldStyles = null;
const analyticsAttributes = {
componentName: 'inlineEdit',
packageName: "@atlaskit/inline-edit",
packageVersion: "16.4.2"
};
const InnerInlineEdit = props => {
const {
startWithEditViewOpen = false,
keepEditViewOpenOnBlur = false,
hideActionButtons = false,
isRequired = false,
readViewFitContainerWidth = false,
editButtonLabel = 'Edit',
editLabel = 'edit',
confirmButtonLabel = 'Confirm',
cancelButtonLabel = 'Cancel',
defaultValue,
isEditing,
label,
validate,
readView,
editView,
analyticsContext,
onConfirm: providedOnConfirm,
onCancel: providedOnCancel,
onEdit: providedOnEdit,
testId
} = props;
const wasFocusReceivedSinceLastBlurRef = useRef(false);
const isControlled = typeof isEditing === 'undefined';
const [isEditingState, setEditingState] = useState(startWithEditViewOpen);
const timerRef = useRef();
const {
editButtonRef,
editViewRef,
shouldBeEditing,
doNotFocusOnEditButton
} = useButtonFocusHook(isEditing, isEditingState);
const onCancel = useCallback(() => {
if (isControlled) {
setEditingState(false);
}
providedOnCancel === null || providedOnCancel === void 0 ? void 0 : providedOnCancel();
}, [isControlled, providedOnCancel]);
const onEditRequested = useCallback(() => {
if (isControlled) {
setEditingState(true);
}
providedOnEdit === null || providedOnEdit === void 0 ? void 0 : providedOnEdit();
if (shouldBeEditing && editViewRef.current) {
editViewRef.current.focus();
}
}, [isControlled, shouldBeEditing, editViewRef, providedOnEdit]);
const onConfirm = usePlatformLeafEventHandler({
fn: (value, analyticsEvent) => {
if (isControlled) {
setEditingState(false);
}
providedOnConfirm(value, analyticsEvent);
},
action: 'confirmed',
analyticsData: analyticsContext,
...analyticsAttributes
});
const onCancelClick = useCallback(event => {
event.preventDefault();
onCancel();
}, [onCancel]);
const tryAutoSubmitWhenBlur = useCallback((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).
*/
const onEditViewWrapperBlur = useCallback((isFieldInvalid, onSubmit, formRef) => {
if (!keepEditViewOpenOnBlur) {
wasFocusReceivedSinceLastBlurRef.current = false;
timerRef.current = setTimeout(() => 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.
*/
const onEditViewWrapperFocus = useCallback(() => {
wasFocusReceivedSinceLastBlurRef.current = true;
}, []);
const concatenatedEditButtonLabel = () => {
if (label) {
const labelText = typeof label === 'string' ? label : getTextFromReactNode(label);
return `${editButtonLabel}, ${labelText}, ${editLabel}`;
}
return `${editButtonLabel}, ${editLabel}`;
};
const 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: data => onConfirm(data.inlineEdit)
}, ({
formProps: {
onKeyDown,
onSubmit,
ref: formRef
},
reset
}) => /*#__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: 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
}, ({
fieldProps,
error
}) => /*#__PURE__*/React.createElement("div", {
onBlur: e => {
if (!e.currentTarget.contains(e.relatedTarget)) {
onEditViewWrapperBlur(fieldProps.isInvalid, onSubmit, formRef);
}
},
onFocus: onEditViewWrapperFocus,
className: ax(["_p12f1osq _kqswh2mm"])
}, editView({
...fieldProps,
errorMessage: error
}, editViewRef), !hideActionButtons ? /*#__PURE__*/React.createElement(Buttons, {
testId: testId,
cancelButtonLabel: cancelButtonLabel,
confirmButtonLabel: confirmButtonLabel,
onMouseDown: () => {
/**
* Prevents focus on edit button only if mouse is used to click button, but not when keyboard is used
*/
doNotFocusOnEditButton();
},
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)));
};
const 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;