UNPKG

@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
/* 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;