@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
119 lines (107 loc) • 3.89 kB
text/typescript
import { isNodeSelection, posToDOMRect } from "@tiptap/core";
import {
getSelection,
getSelectionCutBlocks,
setSelection,
} from "../../api/blockManipulation/selections/selection.js";
import {
getTextCursorPosition,
setTextCursorPosition,
} from "../../api/blockManipulation/selections/textCursorPosition.js";
import {
DefaultBlockSchema,
DefaultInlineContentSchema,
DefaultStyleSchema,
} from "../../blocks/defaultBlocks.js";
import {
BlockIdentifier,
BlockSchema,
InlineContentSchema,
StyleSchema,
} from "../../schema/index.js";
import { BlockNoteEditor } from "../BlockNoteEditor.js";
import { TextCursorPosition } from "../cursorPositionTypes.js";
import { Selection } from "../selectionTypes.js";
export class SelectionManager<
BSchema extends BlockSchema = DefaultBlockSchema,
ISchema extends InlineContentSchema = DefaultInlineContentSchema,
SSchema extends StyleSchema = DefaultStyleSchema,
> {
constructor(private editor: BlockNoteEditor<BSchema, ISchema, SSchema>) {}
/**
* Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
* that the selection spans across.
*
* If the selection starts / ends halfway through a block, the returned data will contain the entire block.
*/
public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {
return this.editor.transact((tr) => getSelection(tr));
}
/**
* Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
* that the selection spans across.
*
* If the selection starts / ends halfway through a block, the returned block will be
* only the part of the block that is included in the selection.
*/
public getSelectionCutBlocks(expandToWords = false) {
return this.editor.transact((tr) => getSelectionCutBlocks(tr, expandToWords));
}
/**
* Sets the selection to a range of blocks.
* @param startBlock The identifier of the block that should be the start of the selection.
* @param endBlock The identifier of the block that should be the end of the selection.
*/
public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {
return this.editor.transact((tr) => setSelection(tr, startBlock, endBlock));
}
/**
* Gets a snapshot of the current text cursor position.
* @returns A snapshot of the current text cursor position.
*/
public getTextCursorPosition(): TextCursorPosition<
BSchema,
ISchema,
SSchema
> {
return this.editor.transact((tr) => getTextCursorPosition(tr));
}
/**
* Sets the text cursor position to the start or end of an existing block. Throws an error if the target block could
* not be found.
* @param targetBlock The identifier of an existing block that the text cursor should be moved to.
* @param placement Whether the text cursor should be placed at the start or end of the block.
*/
public setTextCursorPosition(
targetBlock: BlockIdentifier,
placement: "start" | "end" = "start",
) {
return this.editor.transact((tr) =>
setTextCursorPosition(tr, targetBlock, placement),
);
}
/**
* Gets the bounding box of the current selection.
*/
public getSelectionBoundingBox() {
if (!this.editor.prosemirrorView) {
return undefined;
}
const { selection } = this.editor.prosemirrorState;
// support for CellSelections
const { ranges } = selection;
const from = Math.min(...ranges.map((range) => range.$from.pos));
const to = Math.max(...ranges.map((range) => range.$to.pos));
if (isNodeSelection(selection)) {
const node = this.editor.prosemirrorView.nodeDOM(from) as HTMLElement;
if (node) {
return node.getBoundingClientRect();
}
}
return posToDOMRect(
this.editor.prosemirrorView,
from,
to,
).toJSON() as DOMRect;
}
}