UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

236 lines (235 loc) 9.73 kB
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;