diff --git a/.changeset/spotty-mirrors-matter.md b/.changeset/spotty-mirrors-matter.md new file mode 100644 index 000000000..fb00b720c --- /dev/null +++ b/.changeset/spotty-mirrors-matter.md @@ -0,0 +1,5 @@ +--- +'slate': patch +--- + +Expose getDirtyPaths method on Editor object to allow for customization diff --git a/packages/slate/src/create-editor.ts b/packages/slate/src/create-editor.ts index 8b8051478..346488b2d 100644 --- a/packages/slate/src/create-editor.ts +++ b/packages/slate/src/create-editor.ts @@ -71,7 +71,7 @@ export const createEditor = (): Editor => { dirtyPathKeys = oldDirtyPathKeys } - const newDirtyPaths = getDirtyPaths(op) + const newDirtyPaths = editor.getDirtyPaths(op) for (const path of newDirtyPaths) { add(path) } @@ -296,83 +296,83 @@ export const createEditor = (): Editor => { } } }, + + /** + * Get the "dirty" paths generated from an operation. + */ + + getDirtyPaths: (op: Operation): Path[] => { + switch (op.type) { + case 'insert_text': + case 'remove_text': + case 'set_node': { + const { path } = op + return Path.levels(path) + } + + case 'insert_node': { + const { node, path } = op + const levels = Path.levels(path) + const descendants = Text.isText(node) + ? [] + : Array.from(Node.nodes(node), ([, p]) => path.concat(p)) + + return [...levels, ...descendants] + } + + case 'merge_node': { + const { path } = op + const ancestors = Path.ancestors(path) + const previousPath = Path.previous(path) + return [...ancestors, previousPath] + } + + case 'move_node': { + const { path, newPath } = op + + if (Path.equals(path, newPath)) { + return [] + } + + const oldAncestors: Path[] = [] + const newAncestors: Path[] = [] + + for (const ancestor of Path.ancestors(path)) { + const p = Path.transform(ancestor, op) + oldAncestors.push(p!) + } + + for (const ancestor of Path.ancestors(newPath)) { + const p = Path.transform(ancestor, op) + newAncestors.push(p!) + } + + const newParent = newAncestors[newAncestors.length - 1] + const newIndex = newPath[newPath.length - 1] + const resultPath = newParent.concat(newIndex) + + return [...oldAncestors, ...newAncestors, resultPath] + } + + case 'remove_node': { + const { path } = op + const ancestors = Path.ancestors(path) + return [...ancestors] + } + + case 'split_node': { + const { path } = op + const levels = Path.levels(path) + const nextPath = Path.next(path) + return [...levels, nextPath] + } + + default: { + return [] + } + } + }, } return editor } - -/** - * Get the "dirty" paths generated from an operation. - */ - -const getDirtyPaths = (op: Operation): Path[] => { - switch (op.type) { - case 'insert_text': - case 'remove_text': - case 'set_node': { - const { path } = op - return Path.levels(path) - } - - case 'insert_node': { - const { node, path } = op - const levels = Path.levels(path) - const descendants = Text.isText(node) - ? [] - : Array.from(Node.nodes(node), ([, p]) => path.concat(p)) - - return [...levels, ...descendants] - } - - case 'merge_node': { - const { path } = op - const ancestors = Path.ancestors(path) - const previousPath = Path.previous(path) - return [...ancestors, previousPath] - } - - case 'move_node': { - const { path, newPath } = op - - if (Path.equals(path, newPath)) { - return [] - } - - const oldAncestors: Path[] = [] - const newAncestors: Path[] = [] - - for (const ancestor of Path.ancestors(path)) { - const p = Path.transform(ancestor, op) - oldAncestors.push(p!) - } - - for (const ancestor of Path.ancestors(newPath)) { - const p = Path.transform(ancestor, op) - newAncestors.push(p!) - } - - const newParent = newAncestors[newAncestors.length - 1] - const newIndex = newPath[newPath.length - 1] - const resultPath = newParent.concat(newIndex) - - return [...oldAncestors, ...newAncestors, resultPath] - } - - case 'remove_node': { - const { path } = op - const ancestors = Path.ancestors(path) - return [...ancestors] - } - - case 'split_node': { - const { path } = op - const levels = Path.levels(path) - const nextPath = Path.next(path) - return [...levels, nextPath] - } - - default: { - return [] - } - } -} diff --git a/packages/slate/src/interfaces/editor.ts b/packages/slate/src/interfaces/editor.ts index 6bb87b4cb..5d4f16f87 100644 --- a/packages/slate/src/interfaces/editor.ts +++ b/packages/slate/src/interfaces/editor.ts @@ -77,6 +77,7 @@ export interface BaseEditor { insertNode: (node: Node) => void insertText: (text: string) => void removeMark: (key: string) => void + getDirtyPaths: (op: Operation) => Path[] } export type Editor = ExtendedType<'Editor', BaseEditor> @@ -626,6 +627,7 @@ export const Editor: EditorInterface = { typeof value.normalizeNode === 'function' && typeof value.onChange === 'function' && typeof value.removeMark === 'function' && + typeof value.getDirtyPaths === 'function' && (value.marks === null || isPlainObject(value.marks)) && (value.selection === null || Range.isRange(value.selection)) && Node.isNodeList(value.children) && diff --git a/packages/slate/test/interfaces/Element/isElement/editor.tsx b/packages/slate/test/interfaces/Element/isElement/editor.tsx index 1c091056e..443ddb5df 100644 --- a/packages/slate/test/interfaces/Element/isElement/editor.tsx +++ b/packages/slate/test/interfaces/Element/isElement/editor.tsx @@ -20,6 +20,7 @@ export const input = { normalizeNode() {}, onChange() {}, removeMark() {}, + getDirtyPaths() {}, } export const test = value => { return Element.isElement(value) diff --git a/packages/slate/test/interfaces/Element/isElementList/full-editor.tsx b/packages/slate/test/interfaces/Element/isElementList/full-editor.tsx index 747463aba..cebb36e17 100644 --- a/packages/slate/test/interfaces/Element/isElementList/full-editor.tsx +++ b/packages/slate/test/interfaces/Element/isElementList/full-editor.tsx @@ -21,6 +21,7 @@ export const input = [ normalizeNode() {}, onChange() {}, removeMark() {}, + getDirtyPaths() {}, }, ] export const test = value => {