@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
377 lines (376 loc) • 13.9 kB
TypeScript
import type React from 'react';
import type { IntlShape } from 'react-intl';
import type { Node, NodeType } from '@atlaskit/editor-prosemirror/model';
import type { EditorState } from '@atlaskit/editor-prosemirror/state';
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
import type { EmojiId } from '@atlaskit/emoji/types';
import type { TooltipProps } from '@atlaskit/tooltip';
import type { DispatchAnalyticsEvent } from '../analytics/types/dispatch-analytics-event';
import type { DropdownMenuItemProps } from '../floating-toolbar';
import type { ProviderFactory } from '../provider-factory';
import type { PaletteColor } from '../ui-color/ColorPalette/Palettes/type';
import type { Command, CommandDispatch } from './command';
import type { MarkOptions, NodeOptions } from './copy-button';
/**
* New dropdown types to support editor controls
*/
export type OverflowDropdownHeading = {
title: string;
type: 'overflow-dropdown-heading';
};
type OverflowDropdownCustom<T extends Object> = {
fallback: Array<FloatingToolbarFallbackItem<T>>;
hidden?: boolean;
render: (view?: EditorView, dropdownOption?: Omit<DropdownMenuItemProps, 'item'>) => React.ReactNode;
type: 'custom';
};
export type OverflowDropdownOption<T extends Object> = DropdownOptionT<T> & {
rank?: number;
};
export type FloatingToolbarOverflowDropdownOptions<T extends Object> = Array<OverflowDropdownOption<T> | FloatingToolbarSeparator | OverflowDropdownHeading | OverflowDropdownCustom<T>>;
export type FloatingToolbarOverflowDropdown<T extends Object> = {
alignDropdownWithToolbar?: boolean;
disabled?: boolean;
dropdownWidth?: number;
hidden?: boolean;
id?: string;
onClick?: () => void;
options: FloatingToolbarOverflowDropdownOptions<T>;
showSelected?: boolean;
supportsViewMode?: boolean;
testId?: string;
tooltip?: string;
type: 'overflow-dropdown';
};
export interface RenderOptionsPropsT<T extends Object> {
dispatchCommand: (command: T) => void;
hide: () => void;
}
export interface DropdownOptionT<T extends Object> {
confirmDialog?: ConfirmDialogOptions | (() => ConfirmDialogOptions);
description?: string;
disabled?: boolean;
domItemOptions?: {
type: typeOption;
};
elemAfter?: React.ReactNode;
hidden?: boolean;
icon?: React.ReactNode;
id?: string;
onBlur?: T;
onClick: T;
onFocus?: T;
onMouseDown?: T;
onMouseEnter?: T;
onMouseLeave?: T;
onMouseOut?: T;
onMouseOver?: T;
selected?: boolean;
testId?: string;
title: string;
tooltip?: string;
}
export type typeOption =
/** Dropdown menu item type
* @default 'item'
*/
'item' | 'item-checkbox';
export type DropdownOptions<T extends Object> = Array<DropdownOptionT<T>> | {
height: number;
render: (props: RenderOptionsPropsT<T>) => React.ReactElement<any> | null;
width: number;
};
export interface SelectOption<T extends Object = Object> {
data?: T;
disabled?: boolean;
hidden?: boolean;
label: string;
selected?: boolean;
value: string;
}
export type ButtonAppearance = 'subtle' | 'danger';
export type Icon = React.ComponentType<React.PropsWithChildren<{
label: string;
}>>;
export type RenderOptionsProps = RenderOptionsPropsT<Command>;
export type AlignType = 'left' | 'center' | 'right';
interface Position {
bottom?: number;
left?: number;
right?: number;
top?: number;
}
type PositionOffset = Position;
export type ConfirmDialogChildInfo = {
amount: number;
id: string;
name: string | null;
};
export interface ConfirmDialogOptions {
cancelButtonLabel?: string;
checkboxLabel?: string;
getChildrenInfo?: () => ConfirmDialogChildInfo[];
isReferentialityDialog?: boolean;
message: string;
messagePrefix?: string;
okButtonLabel?: string;
onConfirm?: (...args: any[]) => Command;
title?: string;
}
export type ConfirmationDialogProps = {
/**
* onClose is called every time when the dialog is closed.
* Either clicking on 'Confirm' button or 'Cancel' button,
* which means it is being called after onConfirm, or by itself when clicking 'Cancel' button.
*/
onClose: () => void;
onConfirm: (isCheck?: boolean) => void;
options?: ConfirmDialogOptions;
testId?: string;
};
export type FloatingToolbarCopyButton = {
hidden?: boolean;
items: Array<FloatingToolbarSeparator | MarkOptions | NodeOptions>;
supportsViewMode?: boolean;
type: 'copy-button';
};
export type FloatingToolbarButton<T extends Object> = {
appearance?: ButtonAppearance;
ariaHasPopup?: boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | undefined;
ariaLabel?: string;
className?: string;
confirmDialog?: ConfirmDialogOptions | (() => ConfirmDialogOptions);
disabled?: boolean;
focusEditoronEnter?: boolean;
hidden?: boolean;
hideTooltipOnClick?: boolean;
href?: string;
icon?: Icon;
iconAfter?: Icon;
iconFallback?: Icon;
id?: string;
interactionName?: string;
isRadioButton?: boolean;
metadata?: {
[key: string]: string;
};
onBlur?: T;
onClick: T;
onFocus?: T;
onMount?: () => void;
onMouseEnter?: T;
onMouseLeave?: T;
onUnmount?: () => void;
/** If true, the component will have pulse onboarding effect around it. */
pulse?: boolean;
selected?: boolean;
showTitle?: boolean;
supportsViewMode?: boolean;
tabIndex?: number | null | undefined;
target?: string;
testId?: string;
title: string;
tooltipContent?: TooltipProps['content'];
type: 'button';
};
export type FloatingToolbarInput<T extends Object> = {
defaultValue?: string;
description?: string;
hidden?: boolean;
id: string;
onBlur: (...args: any[]) => T;
onSubmit: (...args: any[]) => T;
placeholder?: string;
title?: string;
type: 'input';
};
export type FloatingToolbarCustomRenderContext = {
/**
* Element used as the boundary for positioning floating toolbar popups.
* Custom toolbar items should pass this through to editor popup/dropdown primitives
* so their content stays within the same visual constraints as built-in toolbar items.
*/
popupsBoundariesElement?: HTMLElement;
/**
* Element that editor popups should be mounted into.
* Use this when rendering custom dropdown or popup content outside the editor DOM
* to avoid editor stacking-context issues.
*/
popupsMountPoint?: HTMLElement;
/**
* Scroll container that editor popups should track when recalculating position.
* Custom toolbar items should pass this through when they need popup content to
* remain visually attached to the floating toolbar while the editor scrolls.
*/
popupsScrollableElement?: HTMLElement;
/**
* Whether the floating toolbar can scroll its own contents.
*/
scrollable?: boolean;
/**
* Temporarily disables or re-enables parent toolbar scrolling.
* Custom dropdowns should call this while open when they need keyboard or pointer
* interaction to stay inside the dropdown rather than moving the toolbar contents.
*/
setDisableParentScroll?: (disabled: boolean) => void;
};
export type FloatingToolbarCustom<T extends Object> = {
/**
* By default -- the floating toolbar supports navigating between
* items using arrow keys (to meet aria guidelines).
* In some cases the floating toolbar is being used to present
* non toolbar content -- such as the link editing experience.
* In these cases you can opt out of arrow navigation using the
* this property.
*
* @default false
*/
disableArrowNavigation?: boolean;
fallback: Array<FloatingToolbarFallbackItem<T>>;
hidden?: boolean;
render: (view?: EditorView, idx?: number, dispatchAnalyticsEvent?: DispatchAnalyticsEvent, context?: FloatingToolbarCustomRenderContext) => React.ReactNode;
supportsViewMode?: boolean;
type: 'custom';
};
type FloatingToolbarSelectBase<T extends Object, V = SelectOption> = {
defaultValue?: V | null;
filterOption?: ((option: V, rawInput: string) => boolean) | null;
hidden?: boolean;
hideExpandIcon?: boolean;
id: string;
isAriaExpanded?: boolean;
onChange: (selected: V) => T;
options: V[];
placeholder?: string;
returnEscToButton?: boolean;
selectType: 'list' | 'emoji' | 'date' | 'color';
title?: string;
type: 'select';
};
export type FloatingToolbarListPicker<T extends Object> = FloatingToolbarSelectBase<T> & {
selectType: 'list';
};
export type FloatingToolbarColorPicker<T extends Object> = FloatingToolbarSelectBase<T, PaletteColor> & {
selectType: 'color';
};
export type FloatingToolbarEmojiPicker<T extends Object> = FloatingToolbarSelectBase<T, EmojiId> & {
options: never[];
selected?: boolean;
selectType: 'emoji';
};
export type FloatingToolbarDatePicker<T extends Object> = FloatingToolbarSelectBase<T, number> & {
options: never[];
selectType: 'date';
};
export type FloatingToolbarSelect<T extends Object> = FloatingToolbarEmojiPicker<T> | FloatingToolbarColorPicker<T> | FloatingToolbarListPicker<T> | FloatingToolbarDatePicker<T>;
export type FloatingToolbarSeparator = {
fullHeight?: boolean;
hidden?: boolean;
supportsViewMode?: boolean;
type: 'separator';
};
export type ExtensionDropdownOptions = () => DropdownOptions<Function>;
export type FloatingToolbarDropdown<T extends Object> = {
alignDropdownWithToolbar?: boolean;
disabled?: boolean;
dropdownWidth?: number;
footer?: React.ReactNode;
hidden?: boolean;
hideExpandIcon?: boolean;
icon?: Icon;
/**
* Places an icon before the title as a representation
*/
iconBefore?: Icon;
id?: string;
onClick?: () => void;
onMount?: () => void;
onToggle?: (state: EditorState, dispatch: CommandDispatch | undefined) => boolean;
options: DropdownOptions<T> | ExtensionDropdownOptions;
/** If true, the component will have pulse onboarding effect around it. */
pulse?: boolean;
shouldFitContainer?: boolean;
showSelected?: boolean;
testId?: string;
title: string;
tooltip?: string;
type: 'dropdown';
};
type FloatingToolbarExtensionsPlaceholder = {
hidden?: boolean;
separator?: 'start' | 'end' | 'both';
type: 'extensions-placeholder';
};
/**
* This additional type is introduced in order to prevent infinite loop due to
* `extract-react-types-loader`. The issue occurs when custom type `fallback` field
* is an array of FloatingToolbarItem. Since FloatingToolbarItem is a FloatingToolbarCustom
* type, it stucks in an infinite loop. Custom - Item -> Custom .... go on.
*
* This type is restricted with the items that can be used for fallback.
* Make sure that this type is not a FloatingToolbarCustom type.
*/
export type FloatingToolbarFallbackItem<T extends Object> = FloatingToolbarButton<T> | FloatingToolbarCopyButton | FloatingToolbarDropdown<T> | FloatingToolbarSelect<T> | FloatingToolbarInput<T> | FloatingToolbarSeparator;
export type FloatingToolbarItem<T extends Object> = FloatingToolbarButton<T> | FloatingToolbarCopyButton | FloatingToolbarDropdown<T> | FloatingToolbarSelect<T> | FloatingToolbarInput<T> | FloatingToolbarCustom<T> | FloatingToolbarSeparator | FloatingToolbarExtensionsPlaceholder | FloatingToolbarOverflowDropdown<T>;
export interface FloatingToolbarConfig {
/**
* @deprecated To suppress visibility of toolbars, set user intent state in userIntentPlugin instead.
* If any config has __suppressAllToolbars set to true, no floating toolbar will be rendered.
* Use case:
* When a node is nested inside a table and the cursor is inside of the nested node.
* Nested node's toolbar is active. When table's menu opens, we provide table's config with
* __suppressAllToolbars set to true.
* To hide floating toolbar, use user intent API instead.
*/
__suppressAllToolbars?: boolean;
/** Absolute offset of the toolbar */
absoluteOffset?: PositionOffset;
align?: AlignType;
/** Class added to Toolbar wrapper */
className?: string;
/**
* Enable Popup component's focus trap
*/
focusTrap?: boolean;
forcePlacement?: boolean;
/**
* Override the DOM reference used to apply as the target for the
* floating toolbar, if the config matches.
*
* By default, it will find the DOM reference of the node from the
* head of the current selection.
*/
getDomRef?: (view: EditorView) => HTMLElement | undefined;
/** aria-label added to role='radiogroup'element */
groupLabel?: string;
/** Toolbar height */
height?: number;
/** Items that will populate the Toolbar.
*
* See: `FloatingToolbarItem`
*/
items: Array<FloatingToolbarItem<Command>> | ((node: Node) => Array<FloatingToolbarItem<Command>>);
mediaAssistiveMessage?: string;
/**
* nodeType or list of `nodeType`s this floating toolbar should be shown for.
**/
nodeType: NodeType | NodeType[];
/** Offset the position of the toolbar. */
offset?: [
number,
number
];
onPositionCalculated?: (editorView: EditorView, nextPos: Position) => Position;
preventPopupOverflow?: boolean;
scrollable?: boolean;
stick?: boolean;
/** Used for the ariaLabel on the <Popup /> component */
title: string;
/** Can prevent the Toolbar from rendering */
visible?: boolean;
/** Toolbar width */
width?: number;
zIndex?: number;
}
export type FloatingToolbarHandler = (state: EditorState, intl: IntlShape, providerFactory: ProviderFactory, processedConfigs?: Array<FloatingToolbarConfig>) => FloatingToolbarConfig | undefined;
export {};