@atlaskit/editor-plugin-floating-toolbar
Version:
Floating toolbar plugin for @atlaskit/editor-core
733 lines (723 loc) • 32.5 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
/* eslint-disable @atlaskit/design-system/no-css-tagged-template-expression -- needs manual remediation */
/**
* @jsxRuntime classic
* @jsx jsx
*/
import React, { Component } 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 ButtonGroup from '@atlaskit/button/button-group';
import { areSameItems, messages } from '@atlaskit/editor-common/floating-toolbar';
import commonMessages from '@atlaskit/editor-common/messages';
import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
import { Announcer, FloatingToolbarButton as Button, FloatingToolbarSeparator as Separator } from '@atlaskit/editor-common/ui';
import { backgroundPaletteTooltipMessages } from '@atlaskit/editor-common/ui-color';
import { ColorPickerButton, ToolbarArrowKeyNavigationProvider } from '@atlaskit/editor-common/ui-menu';
import { hexToEditorBackgroundPaletteColor } from '@atlaskit/editor-palette';
import ShowMoreHorizontalIcon from '@atlaskit/icon/core/show-more-horizontal';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { checkShouldForceFocusAndApply, forceFocusSelector } from '../pm-plugins/force-focus';
import { showConfirmDialog } from '../pm-plugins/toolbar-data/commands';
import Dropdown from './Dropdown';
import { EmojiPickerButton } from './EmojiPickerButton';
import { ExtensionsPlaceholder } from './ExtensionsPlaceholder';
import { Input } from './Input';
import { ScrollButton } from './ScrollButton';
import { ScrollButtons } from './ScrollButtons';
import Select from './Select';
// eslint-disable-next-line jsdoc/require-jsdoc
export function groupItems(items, areAnyNewToolbarFlagsEnabled) {
const groupItems = items.reduce((accumulator, item, i) => {
const {
finalOutput,
buttonGroup
} = accumulator;
if (item.type === 'button') {
const notLastItem = i < items.length - 1;
const nextItemIsButton = items[i + 1] && items[i + 1].type === 'button';
const wasPreviousButton = items[i - 1] && items[i - 1].type === 'button';
const shouldBeRadioButton = notLastItem && nextItemIsButton || wasPreviousButton;
// Only group as radio button if not explicitly set to false
const isRadioButton = !expValEquals('platform_editor_august_a11y', 'isEnabled', true) ? shouldBeRadioButton : shouldBeRadioButton && item.isRadioButton !== false;
if (isRadioButton) {
item.isRadioButton = true;
buttonGroup.push(item);
if (!nextItemIsButton && wasPreviousButton) {
finalOutput.push(buttonGroup);
accumulator.buttonGroup = [];
}
} else {
finalOutput.push(item);
}
} else if (item.type === 'separator' && areAnyNewToolbarFlagsEnabled) {
var _items;
const isLeadingSeparator = i === 0;
const isTrailingSeparator = i === items.length - 1;
const isDuplicateSeparator = ((_items = items[i - 1]) === null || _items === void 0 ? void 0 : _items.type) === 'separator';
!isLeadingSeparator && !isTrailingSeparator && !isDuplicateSeparator && finalOutput.push(item);
} else {
finalOutput.push(item);
}
return accumulator;
}, {
buttonGroup: [],
finalOutput: []
});
return groupItems.finalOutput;
}
const ToolbarItems = /*#__PURE__*/React.memo(({
items,
groupLabel,
dispatchCommand,
popupsMountPoint,
popupsBoundariesElement,
editorView,
dispatchAnalyticsEvent,
popupsScrollableElement,
scrollable,
providerFactory,
extensionsProvider,
node,
setDisableScroll,
mountRef,
api,
intl
}) => {
const emojiAndColourPickerMountPoint = scrollable ? popupsMountPoint || (editorView === null || editorView === void 0 ? void 0 : editorView.dom.closest('.fabric-editor-popup-scroll-parent')) || (editorView === null || editorView === void 0 ? void 0 : editorView.dom.closest('.ak-editor-content-area')) || undefined : popupsMountPoint;
const areAnyNewToolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar));
const renderItem = (item, idx) => {
var _api$contextPanel, _api$extension;
switch (item.type) {
case 'button':
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ButtonIcon = item.icon;
const onClickHandler = () => {
if (item.confirmDialog) {
dispatchCommand(showConfirmDialog(idx));
} else {
dispatchCommand(item.onClick);
if (item.focusEditoronEnter && !(editorView !== null && editorView !== void 0 && editorView.hasFocus())) {
editorView === null || editorView === void 0 ? void 0 : editorView.focus();
}
}
};
const getIconColor = (disabled, selected) => {
if (disabled) {
return "var(--ds-icon-disabled, #080F214A)";
}
if (selected) {
return "var(--ds-icon-selected, #1868DB)";
}
return 'currentColor';
};
return jsx(Button
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop, @atlaskit/design-system/no-unsafe-style-overrides -- Ignored via go/DSP-18766
, {
className: item.className,
key: idx,
title: item.title,
href: item.href,
icon: item.icon ? jsx(ButtonIcon, {
color: getIconColor(item.disabled, item.selected),
spacing: "spacious",
label: undefined,
"aria-hidden": true // Icon is described by the button for screen readers
}) : undefined,
iconAfter: item.iconAfter ? jsx(item.iconAfter, {
label: ""
}) : undefined,
appearance: item.appearance,
target: item.target,
onClick: onClickHandler
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onMouseEnter: () => dispatchCommand(item.onMouseEnter)
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onMouseLeave: () => dispatchCommand(item.onMouseLeave)
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onFocus: () => dispatchCommand(item.onFocus)
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onBlur: () => dispatchCommand(item.onBlur),
onMount: item.onMount,
onUnmount: item.onUnmount,
selected: item.selected,
disabled: item.disabled,
tooltipContent: item.tooltipContent,
testId: item.testId,
hideTooltipOnClick: item.hideTooltipOnClick,
ariaHasPopup: item.ariaHasPopup,
tabIndex: item.tabIndex,
isRadioButton: item.isRadioButton,
ariaLabel: expValEquals('platform_editor_floating_toolbar_button_aria_label', 'isEnabled', true) ? item === null || item === void 0 ? void 0 : item.ariaLabel : undefined,
pulse: item.pulse,
interactionName: item.interactionName,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}, item.showTitle && item.title);
case 'input':
return jsx(Input, {
key: idx,
mountPoint: popupsMountPoint,
boundariesElement: popupsBoundariesElement,
defaultValue: item.defaultValue,
placeholder: item.placeholder
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onSubmit: value => dispatchCommand(item.onSubmit(value))
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onBlur: value => dispatchCommand(item.onBlur(value))
});
case 'custom':
{
return item.render(editorView, idx, dispatchAnalyticsEvent);
}
case 'overflow-dropdown':
// if an option has a confirmDialog, we need to replace its onClick handler
// to set the state to show the confirm dialog
// crudely done here to avoid greater coupling with DropdownMenuItem from `floating-toolbar`
// which would need knowledge of indexes, showConfirmDialog etc.
const options = item.options.map((option, optionIndex) => {
if (!('type' in option) && option.confirmDialog) {
const onClick = option.confirmDialog ? showConfirmDialog(idx, optionIndex) : option.onClick;
return {
...option,
onClick
};
}
return option;
});
return jsx(Dropdown, {
alignX: areAnyNewToolbarFlagsEnabled ? 'right' : undefined,
key: idx,
title: intl.formatMessage(commonMessages.viewMore),
icon: jsx(ShowMoreHorizontalIcon, {
label: "",
spacing: "spacious"
}),
dispatchCommand: dispatchCommand,
options: options,
disabled: item.disabled,
tooltip: item.tooltip,
hideExpandIcon: true,
mountPoint: popupsMountPoint,
boundariesElement: popupsBoundariesElement,
scrollableElement: popupsScrollableElement,
dropdownWidth: item.dropdownWidth,
showSelected: item.showSelected,
buttonTestId: item.testId,
editorView: editorView,
setDisableParentScroll: scrollable ? setDisableScroll : undefined,
dropdownListId: (item === null || item === void 0 ? void 0 : item.id) && `${item.id}-dropdownList`,
alignDropdownWithToolbar: items.length === 1,
onClick: item.onClick,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
});
case 'dropdown':
const DropdownIcon = item.icon;
const BeforeIcon = item.iconBefore;
return jsx(Dropdown, {
key: idx,
title: item.title,
icon: DropdownIcon && jsx(DropdownIcon, {
label: item.title
}),
iconBefore: BeforeIcon && jsx(BeforeIcon, {
label: ""
}),
dispatchCommand: dispatchCommand,
options: item.options,
disabled: item.disabled,
tooltip: item.tooltip,
hideExpandIcon: item.hideExpandIcon,
mountPoint: popupsMountPoint,
boundariesElement: popupsBoundariesElement,
scrollableElement: popupsScrollableElement,
dropdownWidth: item.dropdownWidth,
showSelected: item.showSelected,
buttonTestId: item.testId,
editorView: editorView,
setDisableParentScroll: scrollable ? setDisableScroll : undefined,
dropdownListId: (item === null || item === void 0 ? void 0 : item.id) && `${item.id}-dropdownList`,
alignDropdownWithToolbar: items.length === 1,
onToggle: item.onToggle,
footer: item.footer,
onMount: item.onMount,
onClick: item.onClick,
pulse: item.pulse,
shouldFitContainer: item.shouldFitContainer,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
});
case 'select':
if (item.selectType === 'list') {
const ariaLabel = item.title || item.placeholder;
return jsx(Select, {
key: idx,
dispatchCommand: dispatchCommand,
options: item.options,
hideExpandIcon: item.hideExpandIcon
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
,
mountPoint: scrollable ? mountRef.current : undefined,
boundariesElement: popupsBoundariesElement,
scrollableElement: popupsScrollableElement,
defaultValue: item.defaultValue,
placeholder: item.placeholder
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onChange: selected => dispatchCommand(item.onChange(selected)),
ariaLabel: ariaLabel,
filterOption: item.filterOption,
setDisableParentScroll: scrollable ? setDisableScroll : undefined,
classNamePrefix: 'floating-toolbar-select'
});
}
if (item.selectType === 'color') {
return jsx(ColorPickerButton, {
skipFocusButtonAfterPick: true,
key: idx,
isAriaExpanded: item.isAriaExpanded,
title: item.title
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onChange: selected => {
dispatchCommand(item.onChange(selected));
},
colorPalette: item.options,
currentColor: item.defaultValue ? item.defaultValue.value : undefined,
placement: "Panels",
mountPoint: emojiAndColourPickerMountPoint,
setDisableParentScroll: scrollable ? setDisableScroll : undefined
// Currently in floating toolbar, color picker is only
// used in panel and table cell background color.
// Both uses same color palette.
// That's why hard-coding hexToEditorBackgroundPaletteColor
// and paletteColorTooltipMessages.
// When we need to support different color palette
// in floating toolbar, we need to set hexToPaletteColor
// and paletteColorTooltipMessages in item options.
,
hexToPaletteColor: hexToEditorBackgroundPaletteColor,
paletteColorTooltipMessages: backgroundPaletteTooltipMessages,
returnEscToButton: item.returnEscToButton
});
}
if (item.selectType === 'emoji') {
return jsx(EmojiPickerButton, {
key: idx,
editorView: editorView,
title: item.title,
providerFactory: providerFactory,
isSelected: item.selected
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onChange: selected => dispatchCommand(item.onChange(selected)),
mountPoint: emojiAndColourPickerMountPoint,
popupsBoundariesElement: popupsBoundariesElement,
setDisableParentScroll: scrollable ? setDisableScroll : undefined,
pluginInjectionApi: api
});
}
return null;
case 'extensions-placeholder':
if (!editorView || !extensionsProvider) {
return null;
}
return jsx(ExtensionsPlaceholder, {
key: idx,
node: node,
editorView: editorView,
extensionProvider: extensionsProvider,
separator: item.separator,
applyChangeToContextPanel: api === null || api === void 0 ? void 0 : (_api$contextPanel = api.contextPanel) === null || _api$contextPanel === void 0 ? void 0 : _api$contextPanel.actions.applyChange,
extensionApi: api === null || api === void 0 ? void 0 : (_api$extension = api.extension) === null || _api$extension === void 0 ? void 0 : _api$extension.actions.api(),
dispatchCommand: dispatchCommand,
popupsMountPoint: popupsMountPoint,
popupsBoundariesElement: popupsBoundariesElement,
popupsScrollableElement: popupsScrollableElement,
alignDropdownWithToolbar: items.length === 1,
scrollable: scrollable,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
});
case 'separator':
if (areAnyNewToolbarFlagsEnabled) {
return item.fullHeight ? jsx(Separator, {
key: idx,
fullHeight: true,
areAnyNewToolbarFlagsEnabled: true
}) : null;
}
return jsx(Separator, {
key: idx,
fullHeight: item.fullHeight,
areAnyNewToolbarFlagsEnabled: false
});
}
};
const groupedItems = groupItems(
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
items.filter(item => !item.hidden), areAnyNewToolbarFlagsEnabled);
return jsx(ButtonGroup, {
testId: "editor-floating-toolbar-items"
}, groupedItems.map((element, index) => {
const isGroup = Array.isArray(element);
if (isGroup) {
return jsx("div", {
// Ignored via go/ees005
// eslint-disable-next-line react/no-array-index-key
key: index,
css: areAnyNewToolbarFlagsEnabled ? buttonGroupStylesNew : buttonGroupStyles,
role: "radiogroup",
"aria-label": groupLabel !== null && groupLabel !== void 0 ? groupLabel : undefined,
"data-testid": "editor-floating-toolbar-grouped-buttons"
}, element.map(element => {
const indexInAllItems = items.findIndex(item => item === element);
return renderItem(element, indexInAllItems);
}));
} else {
const indexInAllItems = items.findIndex(item => item === element);
return renderItem(element, indexInAllItems);
}
}));
}, (prevProps, nextProps) => {
if (!nextProps.node) {
return false;
}
// only rerender toolbar items if the node is different
// otherwise it causes an issue where multiple popups stays open
return !(prevProps.node.type !== nextProps.node.type || prevProps.node.attrs.localId !== nextProps.node.attrs.localId || !areSameItems(prevProps.items, nextProps.items) || !prevProps.mounted !== !nextProps.mounted);
});
const buttonGroupStyles = css({
display: 'flex',
gap: "var(--ds-space-050, 4px)"
});
const buttonGroupStylesNew = css({
display: 'flex',
gap: "var(--ds-space-075, 6px)"
});
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
const toolbarContainer = (areAnyNewToolbarFlagsEnabled, scrollable, hasSelect, firstElementIsSelect) => css({
backgroundColor: "var(--ds-surface-overlay, #FFFFFF)",
borderRadius: "var(--ds-radius-small, 3px)",
boxShadow: "var(--ds-shadow-overlay, 0px 8px 12px #1E1F2126, 0px 0px 1px #1E1F214f)",
display: 'flex',
// eslint-disable-next-line @atlaskit/design-system/use-tokens-typography
lineHeight: 1,
boxSizing: 'border-box',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'& > div > div': {
alignItems: 'center'
}
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
scrollable ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css(
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
hasSelect ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
height: '40px'
}) :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
height: '32px'
}), {
overflow: 'hidden'
}) :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
areAnyNewToolbarFlagsEnabled ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values
css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
padding: `${"var(--ds-space-0, 0px)"} 4px ${"var(--ds-space-0, 0px)"} 4px`
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
firstElementIsSelect &&
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
paddingLeft: "var(--ds-space-050, 4px)"
})) :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
padding: `${"var(--ds-space-050, 4px)"} ${"var(--ds-space-100, 8px)"}`
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
firstElementIsSelect &&
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
paddingLeft: "var(--ds-space-050, 4px)"
})),
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
areAnyNewToolbarFlagsEnabled ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values
css({
minHeight: "var(--ds-space-500, 40px)"
}) : undefined);
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
const toolbarOverflow = ({
scrollable,
scrollDisabled,
firstElementIsSelect,
areAnyNewToolbarFlagsEnabled
}) => css(
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
scrollable ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css(
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
scrollDisabled ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
overflow: 'hidden'
}) :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values
css({
overflowX: 'auto',
overflowY: 'hidden',
// When scrollable is true, ScrollButtons will be shown, hence we want to hide show default horizontal scrollbar
scrollbarWidth: 'none'
}), {
WebkitOverflowScrolling: 'touch',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
padding: `${"var(--ds-space-050, 4px)"} 0 ${"var(--ds-space-050, 4px)"}`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors -- Ignored via go/DSP-18766
'> div': {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
'> div:first-child': firstElementIsSelect ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
marginLeft: "var(--ds-space-050, 4px)"
}) :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
marginLeft: "var(--ds-space-100, 8px)"
}),
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
'> div:last-child': {
marginRight: "var(--ds-space-100, 8px)"
}
}
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
areAnyNewToolbarFlagsEnabled ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values
css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
padding: `${"var(--ds-space-0, 0px)"} 4px ${"var(--ds-space-600, 48px)"} 4px`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
'> div': {
minHeight: "var(--ds-space-500, 40px)",
gap: "var(--ds-space-075, 6px)",
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
'> div:first-child': {
marginLeft: 0
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
'> div:last-child': {
marginRight: 0
}
}
}) : undefined) :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
display: 'flex'
}));
// eslint-disable-next-line @repo/internal/react/no-class-components
class Toolbar extends Component {
constructor(props) {
super(props);
_defineProperty(this, "shouldHandleArrowKeys", () => {
var _this$props$items;
//To prevent the keydown handling of arrow keys for custom toolbar items with 'disableArrowNavigation' prop enabled,
//Usually the button which has menus or popups
return !((_this$props$items = this.props.items) !== null && _this$props$items !== void 0 && _this$props$items.find(item => item.type === 'custom' && item.disableArrowNavigation));
});
_defineProperty(this, "handleEscape", event => {
var _this$props$editorVie;
// If any menu is open inside the floating toolbar 'Esc' key should not
// focus the editorview.
// Event can't be stopped as they are not childnodes of floating toolbar
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
const isDropdownOpen = !!document.querySelector('[data-role="droplistContent"]');
// eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
const isSelectMenuOpen = !!document.querySelector('.floating-toolbar-select__menu');
if (isDropdownOpen || isSelectMenuOpen) {
return;
}
(_this$props$editorVie = this.props.editorView) === null || _this$props$editorVie === void 0 ? void 0 : _this$props$editorVie.focus();
event.preventDefault();
event.stopPropagation();
});
_defineProperty(this, "captureMouseEvent", event => {
var _this$props$items2;
// Don't capture mouse event for custom toolbars e.g. insert hyperlink
if (((_this$props$items2 = this.props.items) === null || _this$props$items2 === void 0 ? void 0 : _this$props$items2.length) === 1 && this.props.items[0].type === 'custom') {
return;
}
// Prevents toolbar from closing when clicking on the toolbar itself and not on the buttons
event.stopPropagation();
event.preventDefault();
});
_defineProperty(this, "isShortcutToFocusToolbar", event => {
//Alt + F10 to reach first element in this floating toolbar
return event.altKey && (event.key === 'F10' || event.keyCode === 121);
});
_defineProperty(this, "doesNodeRequireAssitiveMessage", node => {
// Code blocks have an assistive message to announce the content of the code block to screen readers, so we don't need to announce the floating toolbar for those nodes
const nodesWithAlternativeRoles = ['codeBlock'];
if (nodesWithAlternativeRoles.includes(node.type.name)) {
return false;
}
return true;
});
this.scrollContainerRef = /*#__PURE__*/React.createRef();
this.mountRef = /*#__PURE__*/React.createRef();
this.toolbarContainerRef = /*#__PURE__*/React.createRef();
this.state = {
scrollDisabled: false,
mounted: false
};
}
// remove any decorations added by toolbar buttons i.e danger and selected styling
// this prevents https://product-fabric.atlassian.net/browse/ED-10207
resetStyling() {
if (this.props.editorView) {
var _this$props$api, _this$props$api$decor;
const {
state,
dispatch
} = this.props.editorView;
(_this$props$api = this.props.api) === null || _this$props$api === void 0 ? void 0 : (_this$props$api$decor = _this$props$api.decorations) === null || _this$props$api$decor === void 0 ? void 0 : _this$props$api$decor.actions.removeDecoration(state, dispatch);
}
}
setDisableScroll(disabled) {
// wait before setting disabled state incase users jumping from one popup to another
if (disabled) {
requestAnimationFrame(() => {
this.setState({
scrollDisabled: disabled
});
});
} else {
this.setState({
scrollDisabled: disabled
});
}
}
componentDidMount() {
this.setState({
mounted: true
});
}
componentDidUpdate(prevProps) {
var _this$props;
checkShouldForceFocusAndApply((_this$props = this.props) === null || _this$props === void 0 ? void 0 : _this$props.editorView);
if (this.props.node !== prevProps.node) {
this.resetStyling();
}
}
componentWillUnmount() {
const {
editorView
} = this.props;
if (editorView) {
const {
state: {
tr
},
dispatch
} = editorView;
dispatch(forceFocusSelector(null)(tr));
}
this.resetStyling();
}
render() {
var _this$props$api2;
const {
items,
className,
node,
intl,
scrollable,
mediaAssistiveMessage
} = this.props;
const areAnyNewToolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean((_this$props$api2 = this.props.api) === null || _this$props$api2 === void 0 ? void 0 : _this$props$api2.toolbar));
if (!items || !items.length) {
return null;
}
// Select has left padding of 4px to the border, everything else 8px
const firstElementIsSelect = items[0].type === 'select';
const hasSelect = items.find(item => item.type === 'select' && item.selectType === 'list');
const shouldRenderAssistiveAnnouncer = expValEquals('editor_a11y_role_textbox', 'isEnabled', true) ? this.doesNodeRequireAssitiveMessage(node) : true;
return jsx(React.Fragment, null, jsx(ToolbarArrowKeyNavigationProvider, {
editorView: this.props.editorView,
handleEscape: this.handleEscape,
disableArrowKeyNavigation: !this.shouldHandleArrowKeys(),
childComponentSelector: "[data-testid='editor-floating-toolbar']",
isShortcutToFocusToolbar: this.isShortcutToFocusToolbar,
intl: intl
}, jsx("div", {
ref: this.toolbarContainerRef,
css: () => [toolbarContainer(areAnyNewToolbarFlagsEnabled, scrollable, hasSelect !== undefined, firstElementIsSelect)],
"aria-label": intl.formatMessage(messages.floatingToolbarAriaLabel),
role: "toolbar",
"data-testid": "editor-floating-toolbar"
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: className,
onMouseDown: areAnyNewToolbarFlagsEnabled ? this.captureMouseEvent : undefined
}, shouldRenderAssistiveAnnouncer && jsx(Announcer, {
text: mediaAssistiveMessage ? `${mediaAssistiveMessage}, ${intl.formatMessage(messages.floatingToolbarAnnouncer)}` : intl.formatMessage(messages.floatingToolbarAnnouncer),
delay: 250
}), scrollable && areAnyNewToolbarFlagsEnabled && jsx(ScrollButton, {
intl: intl,
scrollContainerRef: this.scrollContainerRef,
node: node,
disabled: this.state.scrollDisabled,
side: "left"
}), jsx("div", {
"data-testid": "floating-toolbar-items",
ref: this.scrollContainerRef
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
,
css: toolbarOverflow({
areAnyNewToolbarFlagsEnabled,
scrollable,
scrollDisabled: this.state.scrollDisabled,
firstElementIsSelect
})
}, jsx(ToolbarItems
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
, _extends({}, this.props, {
setDisableScroll: this.setDisableScroll.bind(this),
mountRef: this.mountRef,
mounted: this.state.mounted
}))), scrollable && (areAnyNewToolbarFlagsEnabled ? jsx(ScrollButton, {
intl: intl,
scrollContainerRef: this.scrollContainerRef,
node: node,
disabled: this.state.scrollDisabled,
side: "right"
}) : jsx(ScrollButtons, {
intl: intl,
scrollContainerRef: this.scrollContainerRef,
node: node,
disabled: this.state.scrollDisabled,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}))), jsx("div", {
ref: this.mountRef
})));
}
}
// eslint-disable-next-line @typescript-eslint/ban-types
const _default_1 = injectIntl(Toolbar);
export default _default_1;