UNPKG

@atlaskit/editor-plugin-floating-toolbar

Version:

Floating toolbar plugin for @atlaskit/editor-core

274 lines (268 loc) 12.1 kB
import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn"; import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf"; import _inherits from "@babel/runtime/helpers/inherits"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; 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; } function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /** * @jsxRuntime classic * @jsx jsx * @jsxFrag */ import React, { Component } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports import { css, jsx } from '@emotion/react'; import { FloatingToolbarButton as Button } from '@atlaskit/editor-common/ui'; import { ArrowKeyNavigationType, DropdownContainer as UiDropdown } from '@atlaskit/editor-common/ui-menu'; import ChevronDownIcon from '@atlaskit/icon/core/chevron-down'; import { Divider } from './Divider'; import DropdownMenu, { itemSpacing, menuItemDimensions } from './DropdownMenu'; // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage var dropdownExpandContainer = css({ margin: "0px ".concat("var(--ds-space-negative-050, -4px)") }); // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage var iconGroup = css({ display: 'flex' }); var CompositeIcon = function CompositeIcon(_ref) { var icon = _ref.icon; return jsx("div", { css: iconGroup }, icon, jsx("span", { css: dropdownExpandContainer }, jsx(ChevronDownIcon, { color: "currentColor", spacing: "spacious", label: "Expand dropdown menu", size: "small" }))); }; // eslint-disable-next-line @repo/internal/react/no-class-components var Dropdown = /*#__PURE__*/function (_Component) { function Dropdown() { var _this; _classCallCheck(this, Dropdown); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _callSuper(this, Dropdown, [].concat(args)); _defineProperty(_this, "state", { isOpen: false, isOpenedByKeyboard: false }); _defineProperty(_this, "triggerRef", /*#__PURE__*/React.createRef()); _defineProperty(_this, "makeArrayOptionsFromCallback", function (makeOptions) { var options = makeOptions(); return options; }); _defineProperty(_this, "renderArrayOptions", function (options) { var _this$props = _this.props, showSelected = _this$props.showSelected, dispatchCommand = _this$props.dispatchCommand, editorView = _this$props.editorView, areAnyNewToolbarFlagsEnabled = _this$props.areAnyNewToolbarFlagsEnabled; return jsx(DropdownMenu, { hide: _this.hide, dispatchCommand: dispatchCommand, items: options, showSelected: showSelected, editorView: editorView, areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled }); }); _defineProperty(_this, "toggleOpen", function () { var onClick = _this.props.onClick; if (onClick) { onClick(); } _this.setState({ isOpen: !_this.state.isOpen, isOpenedByKeyboard: false }); var onToggle = _this.props.onToggle; if (!onToggle) { return; } requestAnimationFrame(function () { _this.props.dispatchCommand(onToggle); }); }); _defineProperty(_this, "toggleOpenByKeyboard", function (event) { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); _this.setState({ isOpen: !_this.state.isOpen, isOpenedByKeyboard: true }); } }); _defineProperty(_this, "hide", function () { _this.setState(_objectSpread(_objectSpread({}, _this.state), {}, { isOpen: false })); }); _defineProperty(_this, "hideOnEsc", function () { var _document$querySelect; // Focus the trigger button only on Escape // Focus is done before hiding to ensure onBlur is called // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting (_document$querySelect = document.querySelector("[data-testid=".concat(_this.props.buttonTestId, "]"))) === null || _document$querySelect === void 0 || _document$querySelect.focus(); _this.hide(); }); _defineProperty(_this, "onOpenChanged", function (openChangedEvent) { if (!openChangedEvent.isOpen && openChangedEvent.event instanceof KeyboardEvent) { var _openChangedEvent$eve; ((_openChangedEvent$eve = openChangedEvent.event) === null || _openChangedEvent$eve === void 0 ? void 0 : _openChangedEvent$eve.key) === 'Escape' ? _this.hideOnEsc() : _this.hide(); } }); return _this; } _inherits(Dropdown, _Component); return _createClass(Dropdown, [{ key: "render", value: function render() { var isOpen = this.state.isOpen; var _this$props2 = this.props, title = _this$props2.title, icon = _this$props2.icon, iconBefore = _this$props2.iconBefore, options = _this$props2.options, dispatchCommand = _this$props2.dispatchCommand, mountPoint = _this$props2.mountPoint, boundariesElement = _this$props2.boundariesElement, scrollableElement = _this$props2.scrollableElement, hideExpandIcon = _this$props2.hideExpandIcon, disabled = _this$props2.disabled, tooltip = _this$props2.tooltip, buttonTestId = _this$props2.buttonTestId, dropdownWidth = _this$props2.dropdownWidth, dropdownListId = _this$props2.dropdownListId, alignDropdownWithToolbar = _this$props2.alignDropdownWithToolbar, footer = _this$props2.footer, onMount = _this$props2.onMount, pulse = _this$props2.pulse, shouldFitContainer = _this$props2.shouldFitContainer, alignX = _this$props2.alignX, areAnyNewToolbarFlagsEnabled = _this$props2.areAnyNewToolbarFlagsEnabled; var trigger; if (icon) { var TriggerIcon = hideExpandIcon ? icon : jsx(CompositeIcon, { icon: icon }); trigger = jsx(Button, { testId: buttonTestId, title: title, icon: TriggerIcon, onClick: this.toggleOpen, onKeyDown: this.toggleOpenByKeyboard, selected: isOpen, disabled: disabled, tooltipContent: tooltip, ariaHasPopup: areAnyNewToolbarFlagsEnabled ? true : undefined, onMount: onMount, pulse: pulse, areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled }); } else { trigger = jsx(Button, { testId: buttonTestId, iconAfter: jsx("span", { css: dropdownExpandContainer }, jsx(ChevronDownIcon, { color: "currentColor", spacing: "spacious", label: "Expand dropdown menu", size: "small" })), icon: iconBefore, onClick: this.toggleOpen, onKeyDown: this.toggleOpenByKeyboard, selected: isOpen, disabled: disabled, tooltipContent: tooltip, ariaHasPopup: true, areaControls: dropdownListId, onMount: onMount, pulse: pulse, areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled }, title); } /** * We want to change direction of our dropdowns a bit early, * not exactly when it hits the boundary. */ var fitTolerance = 10; var fitWidth = Array.isArray(options) || typeof options === 'function' ? dropdownWidth || menuItemDimensions.width : options.width; var fitHeight = Array.isArray(options) ? options.length * menuItemDimensions.height + itemSpacing * 2 : typeof options === 'function' ? this.makeArrayOptionsFromCallback(options).length : options.height; return ( /** * At the moment footer diver is rendered along with footer, if it's provided * This is to provide some level of consistency * Refer to the PR for more details: * https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/pull-requests/137394/overview?commentId=8130003 */ jsx(UiDropdown, { alignX: alignX, mountTo: mountPoint, boundariesElement: boundariesElement, scrollableElement: scrollableElement, isOpen: isOpen, handleClickOutside: this.hide, handleEscapeKeydown: this.hideOnEsc, onOpenChange: this.onOpenChanged, fitWidth: fitWidth + fitTolerance, fitHeight: fitHeight + fitTolerance, trigger: trigger, dropdownListId: dropdownListId, alignDropdownWithParentElement: alignDropdownWithToolbar // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , arrowKeyNavigationProviderOptions: { type: ArrowKeyNavigationType.MENU }, shouldFitContainer: shouldFitContainer }, Array.isArray(options) ? this.renderArrayOptions(options) : typeof options === 'function' ? this.renderArrayOptions(this.makeArrayOptionsFromCallback(options)) : options.render({ hide: this.hide, dispatchCommand: dispatchCommand }), footer && jsx(React.Fragment, null, jsx(Divider, null), footer)) ); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { if (prevState.isOpen !== this.state.isOpen) { if (this.props.setDisableParentScroll) { this.props.setDisableParentScroll(this.state.isOpen); } // ECA11Y-235: no sense in sending keyboard event since the menu popup mounted to the custom element, // we will ensure first element focused asap as 'MenuArrowKeyNavigationProvider' is mounted if (this.props.mountPoint) { return; } if (this.state.isOpen && this.state.isOpenedByKeyboard) { var dropList = document.querySelector('[data-role="droplistContent"]'); if (dropList) { // Add setTimeout so that if a dropdown item has tooltip, // the tooltip won't be rendered until next render cycle // when the droplist is correctly positioned. // This makes tooltip appears at the correct position for the first dropdown item. setTimeout(function () { var keyboardEvent = new KeyboardEvent('keydown', { bubbles: true, key: 'ArrowDown' }); dropList.dispatchEvent(keyboardEvent); }, 0); } } } } }]); }(Component); export { Dropdown as default };