@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
236 lines (235 loc) • 9.73 kB
TypeScript
import type { IntlShape } from 'react-intl';
import type { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import type { PortalProviderAPI } from '@atlaskit/editor-common/portal';
import type { DIRECTION, EditorCommand, NextEditorPlugin, OptionalPlugin, PublicPluginAPI } from '@atlaskit/editor-common/types';
import type { AccessibilityUtilsPlugin } from '@atlaskit/editor-plugin-accessibility-utils';
import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
import type { EditorDisabledPlugin } from '@atlaskit/editor-plugin-editor-disabled';
import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmode';
import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
import type { InteractionPlugin } from '@atlaskit/editor-plugin-interaction';
import type { LimitedModePlugin } from '@atlaskit/editor-plugin-limited-mode';
import type { MetricsPlugin } from '@atlaskit/editor-plugin-metrics';
import type { QuickInsertPlugin } from '@atlaskit/editor-plugin-quick-insert';
import type { SelectionPlugin } from '@atlaskit/editor-plugin-selection';
import type { ToolbarPlugin } from '@atlaskit/editor-plugin-toolbar';
import type { TypeAheadPlugin } from '@atlaskit/editor-plugin-type-ahead';
import type { UserIntentPlugin } from '@atlaskit/editor-plugin-user-intent';
import type { WidthPlugin } from '@atlaskit/editor-plugin-width';
import type { EditorState, Selection } from '@atlaskit/editor-prosemirror/state';
import type { Mapping } from '@atlaskit/editor-prosemirror/transform';
import type { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
export type ActiveNode = {
anchorName: string;
handleOptions?: HandleOptions;
nodeType: string;
pos: number;
rootAnchorName?: string;
rootNodeType?: string;
rootPos?: number;
};
export type TriggerByNode = {
nodeType: string;
pos: number;
rootPos?: number;
};
export type ActiveDropTargetNode = {
nodeTypeName: string | null;
pos: number;
};
export type MultiSelectDnD = {
anchor: number;
head: number;
textAnchor: number;
textHead: number;
userAnchor: number;
userHead: number;
};
export interface PluginState {
activeDropTargetNode?: ActiveDropTargetNode;
activeNode?: ActiveNode;
blockMenuOptions?: {
canMoveDown?: boolean;
canMoveUp?: boolean;
openedViaKeyboard?: boolean;
};
decorations: DecorationSet;
editorHeight: number;
editorWidthLeft: number;
editorWidthRight: number;
/**
* @private
* @deprecated Doc size limits no longer supported
*/
isDocSizeLimitEnabled: boolean | null;
isDragging: boolean;
isMenuOpen?: boolean;
/**
* is dragging the node without using drag handle, i,e, native prosemirror DnD
*/
isPMDragging: boolean;
isResizerResizing: boolean;
isSelectedViaDragHandle?: boolean;
isShiftDown?: boolean;
lastDragCancelled: boolean;
menuTriggerBy?: string;
menuTriggerByNode?: TriggerByNode;
multiSelectDnD?: MultiSelectDnD;
preservedSelection?: Selection;
}
export type ReleaseHiddenDecoration = () => boolean | undefined;
export type BlockControlsPluginConfig = {
/** Enable the quick insert plus button icon on the left of the drag handle */
quickInsertButtonEnabled?: boolean;
/** Enable left/right hover split: show left controls when hovering left, right controls when hovering right */
rightSideControlsEnabled?: boolean;
};
export type BlockControlsSharedState = {
activeDropTargetNode?: ActiveDropTargetNode;
activeNode?: ActiveNode;
blockMenuOptions?: {
canMoveDown?: boolean;
canMoveUp?: boolean;
openedViaKeyboard?: boolean;
};
hoverSide?: 'left' | 'right';
isDragging: boolean;
isEditing?: boolean;
isMenuOpen: boolean;
isMouseOut?: boolean;
isPMDragging: boolean;
isSelectedViaDragHandle?: boolean;
isShiftDown?: boolean;
lastDragCancelled: boolean;
menuTriggerBy?: string;
menuTriggerByNode?: TriggerByNode;
multiSelectDnD?: MultiSelectDnD;
preservedSelection?: Selection;
/** Whether left/right hover split is enabled (from plugin config) */
rightSideControlsEnabled?: boolean;
} | undefined;
export type HandleOptions = {
isFocused: boolean;
} | undefined;
/**
* Props passed to custom right-edge button components (e.g. config.rightEdgeButton).
*/
export type RightEdgeButtonProps = {
api: PublicPluginAPI<[
BlockControlsPlugin
]>;
getPos: () => number | undefined;
};
export type NodeDecorationFactoryParams = {
anchorName: string;
editorState: EditorState;
nodeType: string;
nodeViewPortalProviderAPI: PortalProviderAPI;
rootAnchorName?: string;
rootNodeType?: string;
rootPos: number;
};
/**
* When true, this factory's decorations are shown in view mode on block hover
* (without drag handle or quick insert). Used for right-edge controls.
*/
export type NodeDecorationFactory = {
create: (params: NodeDecorationFactoryParams) => Decoration;
/**
* Optional filter: when false, the decoration is not created.
* Use for node-type-specific visibility (e.g. Remix button only on remixable blocks).
*/
shouldCreate?: (params: NodeDecorationFactoryParams) => boolean;
/** Show this decoration in view mode when hovering over a block */
showInViewMode?: boolean;
type: string;
};
export type MoveNode = (start: number, to: number, inputMethod?: MoveNodeMethod, formatMessage?: IntlShape['formatMessage']) => EditorCommand;
export type BlockControlsPluginDependencies = [
OptionalPlugin<LimitedModePlugin>,
OptionalPlugin<EditorDisabledPlugin>,
OptionalPlugin<EditorViewModePlugin>,
OptionalPlugin<WidthPlugin>,
OptionalPlugin<FeatureFlagsPlugin>,
OptionalPlugin<AnalyticsPlugin>,
OptionalPlugin<AccessibilityUtilsPlugin>,
OptionalPlugin<QuickInsertPlugin>,
OptionalPlugin<TypeAheadPlugin>,
OptionalPlugin<SelectionPlugin>,
OptionalPlugin<MetricsPlugin>,
OptionalPlugin<InteractionPlugin>,
OptionalPlugin<UserIntentPlugin>,
OptionalPlugin<ToolbarPlugin>
];
export type BlockControlsPlugin = NextEditorPlugin<'blockControls', {
actions: {
registerNodeDecoration: (factory: NodeDecorationFactory) => void;
unregisterNodeDecoration: (type: string) => void;
};
commands: {
/**
* Updates the transaction's selection based on the clicked drag handle position.
*
* - If the clicked handle is within an existing multi-block selection range, the selection
* is expanded to cover both the existing range and the clicked node's range.
* - For tables, a table cell selection is used.
* - Otherwise, selects the single node at the clicked handle position.
*/
expandAndUpdateSelection: (options: {
isShiftPressed: boolean;
nodeType: string;
selection: Selection;
startPos: number;
}) => EditorCommand;
handleKeyDownWithPreservedSelection: (event: KeyboardEvent) => EditorCommand;
mapPreservedSelection: (mapping: Mapping) => EditorCommand;
moveNode: MoveNode;
moveNodeWithBlockMenu: (direction: DIRECTION.UP | DIRECTION.DOWN) => EditorCommand;
/**
* Move a node before (unless `moveToEnd` is set) another node to expand a layout or create a new layout
* @param from position of the node to be moved
* @param to position of the layout/layout column/node to move the node to
* @param options moveToEnd: move the node to after the layout/layout column/another node
* @param options selectMovedNode: select the moved node after moving it
*/
moveToLayout: (start: number, to: number, options?: {
moveNodeAtCursorPos?: boolean;
moveToEnd?: boolean;
selectMovedNode?: boolean;
}) => EditorCommand;
setMultiSelectPositions: (anchor?: number, head?: number) => EditorCommand;
setNodeDragged: (getPos: () => number | undefined, anchorName: string, nodeType: string) => EditorCommand;
setSelectedViaDragHandle: (isSelectedViaDragHandle?: boolean) => EditorCommand;
showDragHandleAt: (pos: number, anchorName: string, nodeType: string, handleOptions?: HandleOptions, rootPos?: number, rootAnchorName?: string, rootNodeType?: string) => EditorCommand;
/**
* Starts preserving the current selection across transactions.
* Used when opening block menus, dragging, or other interactions
* where multi-node selections should remain stable.
*/
startPreservingSelection: () => EditorCommand;
/**
* Stops preserving the selection, allowing it to change freely.
* Called when block menus close or drag operations end.
*/
stopPreservingSelection: () => EditorCommand;
toggleBlockMenu: (options?: {
anchorName?: string;
closeMenu?: boolean;
openedViaKeyboard?: boolean;
triggerByNode?: TriggerByNode;
}) => EditorCommand;
};
dependencies: BlockControlsPluginDependencies;
pluginConfiguration?: BlockControlsPluginConfig;
sharedState: BlockControlsSharedState;
}>;
export type BlockControlsMeta = {
activeNode: ActiveNode;
dom: HTMLElement;
editorBlurred: boolean;
editorHeight: number;
nodeMoved: boolean;
preservedSelectionMapping: Mapping;
type: string;
};
export type MoveNodeMethod = INPUT_METHOD.DRAG_AND_DROP | INPUT_METHOD.SHORTCUT | INPUT_METHOD.BLOCK_MENU;