UNPKG

@atlaskit/editor-plugin-status

Version:

Status plugin for @atlaskit/editor-core

343 lines (339 loc) 12.5 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; /** * @jsxRuntime classic * @jsx jsx */ import React from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766 import { css, jsx } from '@emotion/react'; import { injectIntl } from 'react-intl'; import { withAnalyticsEvents } from '@atlaskit/analytics-next'; import { getDocument } from '@atlaskit/browser-apis'; import { statusMessages as messages } from '@atlaskit/editor-common/messages'; import { Popup } from '@atlaskit/editor-common/ui'; import { OutsideClickTargetRefContext, withReactEditorViewOuterListeners as withOuterListeners } from '@atlaskit/editor-common/ui-react'; import { UserIntentPopupWrapper } from '@atlaskit/editor-common/user-intent'; import { akEditorFloatingDialogZIndex } from '@atlaskit/editor-shared-styles'; import { fg } from '@atlaskit/platform-feature-flags'; import { StatusPicker as AkStatusPicker } from '@atlaskit/status/picker'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import VisuallyHidden from '@atlaskit/visually-hidden'; import { DEFAULT_STATUS } from '../pm-plugins/actions'; import { analyticsState, createStatusAnalyticsAndFire } from './analytics'; const PopupWithListeners = withOuterListeners(Popup); export let InputMethod = /*#__PURE__*/function (InputMethod) { InputMethod["blur"] = "blur"; InputMethod["escKey"] = "escKey"; InputMethod["enterKey"] = "enterKey"; return InputMethod; }({}); export let closingMethods = /*#__PURE__*/function (closingMethods) { closingMethods["ArrowLeft"] = "arrowLeft"; closingMethods["ArrowRight"] = "arrowRight"; return closingMethods; }({}); const pickerContainerStyles = css({ background: "var(--ds-surface-overlay, #FFFFFF)", padding: `${"var(--ds-space-100, 8px)"} 0`, borderRadius: "var(--ds-radius-small, 3px)", boxShadow: "var(--ds-shadow-overlay, 0px 8px 12px #1E1F2126, 0px 0px 1px #1E1F214f)", // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766 ':focus': { outline: 'none' }, // eslint-disable-next-line @atlaskit/design-system/no-nested-styles, @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766 input: { // eslint-disable-next-line @atlaskit/design-system/use-tokens-typography textTransform: 'uppercase' } }); const pickerContainerStylesTeam26 = css({ background: "var(--ds-surface-overlay, #FFFFFF)", padding: `${"var(--ds-space-100, 8px)"} 0`, borderRadius: "var(--ds-radius-small, 3px)", boxShadow: "var(--ds-shadow-overlay, 0 0 1px rgba(9, 30, 66, 0.31), 0 4px 8px -2px rgba(9, 30, 66, 0.25))", // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-selectors ':focus': { outline: 'none' } }); // eslint-disable-next-line @repo/internal/react/no-class-components class StatusPickerWithIntl extends React.Component { constructor(props) { super(props); _defineProperty(this, "handleClickOutside", event => { event.preventDefault(); this.inputMethod = InputMethod.blur; const selectedText = window.getSelection(); if (!selectedText) { this.props.closeStatusPicker(); } }); _defineProperty(this, "handleEscapeKeydown", event => { event.preventDefault(); this.inputMethod = InputMethod.escKey; this.props.onEnter(this.state); }); _defineProperty(this, "handleTabPress", event => { var _getDocument, _document; const colorButtons = event.currentTarget.querySelectorAll('button'); const inputField = event.currentTarget.querySelector('input'); const activeElement = expValEquals('platform_editor_a11y_eslint_fix', 'isEnabled', true) ? (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement : (_document = document) === null || _document === void 0 ? void 0 : _document.activeElement; const isInputFocussed = activeElement === inputField; const isButtonFocussed = Array.from(colorButtons).some(buttonElement => { return activeElement === buttonElement; }); if (event !== null && event !== void 0 && event.shiftKey) { /* shift + tab */ if (isInputFocussed) { colorButtons[0].focus(); event.preventDefault(); } /* After the user presses shift + tab the color-palette component updates tab index for the first color to be 0. To correctly set focus to the input field instead of the first color button we need to set focus manually */ if (isButtonFocussed) { inputField === null || inputField === void 0 ? void 0 : inputField.focus(); event.preventDefault(); } } else { /* tab */ if (isButtonFocussed) { inputField === null || inputField === void 0 ? void 0 : inputField.focus(); event.preventDefault(); } } }); _defineProperty(this, "handleArrow", (event, closingMethod) => { var _getDocument2, _document2; const activeElement = expValEquals('platform_editor_a11y_eslint_fix', 'isEnabled', true) ? (_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement : (_document2 = document) === null || _document2 === void 0 ? void 0 : _document2.activeElement; if (activeElement === this.popupBodyWrapper.current) { var _this$popupBodyWrappe, _this$popupBodyWrappe2; event.preventDefault(); (_this$popupBodyWrappe = this.popupBodyWrapper) === null || _this$popupBodyWrappe === void 0 ? void 0 : (_this$popupBodyWrappe2 = _this$popupBodyWrappe.current) === null || _this$popupBodyWrappe2 === void 0 ? void 0 : _this$popupBodyWrappe2.blur(); this.props.closeStatusPicker({ closingMethod }); } }); _defineProperty(this, "onKeyDown", event => { const isTabPressed = event.key === 'Tab'; if (isTabPressed) { return this.handleTabPress(event); } if (event.key in closingMethods) { return this.handleArrow(event, closingMethods[event.key]); } }); _defineProperty(this, "onColorHover", color => { this.createStatusAnalyticsAndFireFunc({ action: 'hovered', actionSubject: 'statusColorPicker', attributes: { color, localId: this.state.localId, state: analyticsState(this.props.isNew) } }); }); _defineProperty(this, "onColorClick", color => { const { text, localId } = this.state; if (color === this.state.color) { this.createStatusAnalyticsAndFireFunc({ action: 'clicked', actionSubject: 'statusColorPicker', attributes: { color, localId, state: analyticsState(this.props.isNew) } }); // closes status box and commits colour this.onEnter(); } else { this.setState({ color }); this.props.onSelect({ text, color, localId }); } }); _defineProperty(this, "onTextChanged", text => { const { color, localId } = this.state; this.setState({ text }); this.props.onTextChanged({ text, color, localId }, !!this.props.isNew); }); _defineProperty(this, "onEnter", () => { this.inputMethod = InputMethod.enterKey; this.props.onEnter(this.state); }); // cancel bubbling to fix clickOutside logic: // popup re-renders its content before the click event bubbles up to the document // therefore click target element would be different from the popup content _defineProperty(this, "handlePopupClick", event => event.nativeEvent.stopImmediatePropagation()); this.state = this.extractStateFromProps(props); this.createStatusAnalyticsAndFireFunc = createStatusAnalyticsAndFire(props.createAnalyticsEvent); this.popupBodyWrapper = /*#__PURE__*/React.createRef(); } fireStatusPopupOpenedAnalytics(state) { const { color, text, localId, isNew } = state; this.startTime = Date.now(); this.createStatusAnalyticsAndFireFunc({ action: 'opened', actionSubject: 'statusPopup', attributes: { textLength: text ? text.length : 0, selectedColor: color, localId, state: analyticsState(isNew) } }); } fireStatusPopupClosedAnalytics(state) { const { color, text, localId, isNew } = state; this.createStatusAnalyticsAndFireFunc({ action: 'closed', actionSubject: 'statusPopup', attributes: { inputMethod: this.inputMethod, duration: Date.now() - this.startTime, textLength: text ? text.length : 0, selectedColor: color, localId, state: analyticsState(isNew) } }); } reset() { this.startTime = Date.now(); this.inputMethod = InputMethod.blur; } componentDidMount() { this.reset(); this.fireStatusPopupOpenedAnalytics(this.state); } componentWillUnmount() { this.focusTimeout && cancelAnimationFrame(this.focusTimeout); this.fireStatusPopupClosedAnalytics(this.state); this.startTime = 0; } componentDidUpdate(prevProps, prevState) { const element = this.props.target; if (prevProps.target !== element) { const newState = this.extractStateFromProps(this.props); this.setState(newState); this.fireStatusPopupClosedAnalytics(prevState); this.reset(); this.fireStatusPopupOpenedAnalytics(newState); } } extractStateFromProps(props) { const { defaultColor, defaultText, defaultLocalId, isNew } = props; return { color: defaultColor || DEFAULT_STATUS.color, text: defaultText || DEFAULT_STATUS.text, localId: defaultLocalId, isNew }; } setRef(setOutsideClickTargetRef) { return ref => { setOutsideClickTargetRef(ref); this.popupBodyWrapper.current = ref; }; } renderWithSetOutsideClickTargetRef(setOutsideClickTargetRef) { const { isNew, focusStatusInput, api } = this.props; const { color, text } = this.state; return jsx(UserIntentPopupWrapper, { api: api, userIntent: "statusPickerOpen" }, jsx("div", { css: fg('platform-dst-lozenge-tag-badge-visual-uplifts') ? pickerContainerStylesTeam26 : pickerContainerStyles, role: "none", ref: this.setRef(setOutsideClickTargetRef), onClick: this.handlePopupClick, onKeyDown: this.onKeyDown }, jsx(AkStatusPicker, { autoFocus: isNew || focusStatusInput, selectedColor: color, text: text, onColorClick: this.onColorClick, onColorHover: this.onColorHover, onTextChanged: this.onTextChanged, onEnter: this.onEnter }))); } render() { const { target, mountTo, boundariesElement, scrollableElement, editorView, intl } = this.props; if (!(editorView !== null && editorView !== void 0 && editorView.editable)) { return null; } return target && jsx(PopupWithListeners, { ariaLabel: fg('_editor_a11y_aria_label_removal_popup') ? intl.formatMessage(messages.statusEditorLabel) : undefined, target: target // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , offset: [0, 8], handleClickOutside: this.handleClickOutside, handleEscapeKeydown: this.handleEscapeKeydown, zIndex: akEditorFloatingDialogZIndex, fitHeight: 40, mountTo: mountTo, boundariesElement: boundariesElement, scrollableElement: scrollableElement, closeOnTab: false }, jsx(VisuallyHidden, { "aria-atomic": true, role: "alert" }, intl.formatMessage(messages.statusPickerOpenedAlert)), jsx(OutsideClickTargetRefContext.Consumer, null, this.renderWithSetOutsideClickTargetRef.bind(this))); } } // eslint-disable-next-line @typescript-eslint/ban-types export const StatusPickerWithoutAnalytcs = injectIntl(StatusPickerWithIntl); const _default_1 = withAnalyticsEvents()(StatusPickerWithoutAnalytcs); export default _default_1;