@atlaskit/editor-plugin-floating-toolbar
Version:
Floating toolbar plugin for @atlaskit/editor-core
741 lines (730 loc) • 33.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 Announcer from '@atlaskit/editor-common/announcer';
import { areSameItems } from '@atlaskit/editor-common/floating-toolbar';
import messages from '@atlaskit/editor-common/floating-toolbar/messages';
import Button from '@atlaskit/editor-common/FloatingToolbar/Button';
import Separator from '@atlaskit/editor-common/FloatingToolbar/Separator';
import commonMessages from '@atlaskit/editor-common/messages';
import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
import { backgroundPaletteTooltipMessages } from '@atlaskit/editor-common/ui-color/ColorPalette/paletteMessagesTokenModeNames';
import ColorPickerButton from '@atlaskit/editor-common/ui-menu/ColorPickerButton';
import { ToolbarArrowKeyNavigationProvider } from '@atlaskit/editor-common/ui-menu/ToolbarArrowKeyNavigationProvider';
import { hexToEditorBackgroundPaletteColor } from '@atlaskit/editor-palette/background';
import ShowMoreHorizontalIcon from '@atlaskit/icon/core/show-more-horizontal';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
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 = 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,
hasNewContentOnTriggerClick: item.hasNewContentOnTriggerClick,
ariaHasPopup: item.ariaHasPopup,
tabIndex: item.tabIndex,
isRadioButton: item.isRadioButton,
ariaLabel: item === null || item === void 0 ? void 0 : item.ariaLabel,
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, {
popupsBoundariesElement,
popupsMountPoint,
popupsScrollableElement,
scrollable,
setDisableParentScroll: scrollable ? setDisableScroll : undefined
});
}
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') {
var _item$hexToPaletteCol, _item$paletteColorToo;
return jsx(ColorPickerButton, {
skipFocusButtonAfterPick: true,
key: idx,
isAriaExpanded: item.isAriaExpanded,
hideExpandIcon: item.hideExpandIcon,
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));
},
cols: item.cols,
colorPalette: item.options,
currentColor: item.defaultValue ? item.defaultValue.value : undefined,
placement: "Panels",
mountPoint: emojiAndColourPickerMountPoint,
setDisableParentScroll: scrollable ? setDisableScroll : undefined,
hexToPaletteColor: isExperimentEnabled('platform_editor_lovability_dividers') ? (_item$hexToPaletteCol = item.hexToPaletteColor) !== null && _item$hexToPaletteCol !== void 0 ? _item$hexToPaletteCol : hexToEditorBackgroundPaletteColor : hexToEditorBackgroundPaletteColor,
paletteColorTooltipMessages: isExperimentEnabled('platform_editor_lovability_dividers') ? (_item$paletteColorToo = item.paletteColorTooltipMessages) !== null && _item$paletteColorToo !== void 0 ? _item$paletteColorToo : backgroundPaletteTooltipMessages : 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 || prevProps.popupsBoundariesElement !== nextProps.popupsBoundariesElement || prevProps.popupsMountPoint !== nextProps.popupsMountPoint || prevProps.popupsScrollableElement !== nextProps.popupsScrollableElement || prevProps.scrollable !== nextProps.scrollable || prevProps.setDisableScroll !== nextProps.setDisableScroll || !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)"
});
const toolbarContainerBase = css({
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/design-system/consistent-css-prop-usage
const toolbarContainer = (areAnyNewToolbarFlagsEnabled, scrollable, hasSelect, firstElementIsSelect,
// See `FloatingToolbarConfig.containerSurface`: 'none' is for a single custom item that
// already renders its own complete surface, so this wrapper's own surface is omitted.
containerSurface = 'default') => css(
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
containerSurface === 'none' ? undefined : {
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)"
},
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
containerSurface === 'none' ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
css({
padding: "var(--ds-space-0, 0px)"
}) : 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:nth-last-child(1 of :not(:where([popover], dialog)))': {
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:nth-last-child(1 of :not(:where([popover], dialog)))': {
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, "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
});
}
});
_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);
});
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);
}
}
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');
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: () => [toolbarContainerBase, toolbarContainer(areAnyNewToolbarFlagsEnabled, scrollable, hasSelect !== undefined, firstElementIsSelect, this.props.containerSurface)],
"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
}, 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,
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/no-restricted-types
const _default_1 = injectIntl(Toolbar);
export default _default_1;