UNPKG

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