@tiptap/extension-node-range 3.22.1 → 3.22.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -59,9 +59,17 @@ function getNodeRangeDecorations(ranges) {
59
59
  // src/helpers/getSelectionRanges.ts
60
60
  var import_model = require("@tiptap/pm/model");
61
61
  var import_state = require("@tiptap/pm/state");
62
- function getSelectionRanges($from, $to, depth) {
62
+ function getNodeContentBounds(nodeStart, nodeSize, node) {
63
+ const contentOffset = node.isText || node.isAtom ? 0 : 1;
64
+ return {
65
+ start: nodeStart + contentOffset,
66
+ end: nodeStart + nodeSize - contentOffset
67
+ };
68
+ }
69
+ function getSelectionRanges($from, $to, depth, options = {}) {
63
70
  const ranges = [];
64
71
  const doc = $from.node(0);
72
+ const { extendOnBoundaryOverlap = true } = options;
65
73
  if (typeof depth === "number" && depth >= 0) {
66
74
  } else if ($from.sameParent($to)) {
67
75
  depth = Math.max(0, $from.sharedDepth($to.pos) - 1);
@@ -73,9 +81,14 @@ function getSelectionRanges($from, $to, depth) {
73
81
  nodeRange.parent.forEach((node, pos) => {
74
82
  const from = offset + pos;
75
83
  const to = from + node.nodeSize;
84
+ const contentBounds = getNodeContentBounds(from, node.nodeSize, node);
85
+ const overlapsNodeContent = extendOnBoundaryOverlap ? $to.pos >= contentBounds.start && $from.pos <= contentBounds.end : $to.pos > contentBounds.start && $from.pos < contentBounds.end;
76
86
  if (from < nodeRange.start || from >= nodeRange.end) {
77
87
  return;
78
88
  }
89
+ if (!overlapsNodeContent) {
90
+ return;
91
+ }
79
92
  const selectionRange = new import_state.SelectionRange(doc.resolve(from), doc.resolve(to));
80
93
  ranges.push(selectionRange);
81
94
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/node-range.ts","../src/helpers/getNodeRangeDecorations.ts","../src/helpers/getSelectionRanges.ts","../src/helpers/NodeRangeSelection.ts","../src/helpers/NodeRangeBookmark.ts","../src/helpers/isNodeRangeSelection.ts"],"sourcesContent":["import { NodeRange } from './node-range.js'\n\nexport * from './helpers/getNodeRangeDecorations.js'\nexport * from './helpers/getSelectionRanges.js'\nexport * from './helpers/isNodeRangeSelection.js'\nexport * from './helpers/NodeRangeSelection.js'\nexport * from './node-range.js'\n\nexport default NodeRange\n","import { Extension } from '@tiptap/core'\nimport type { SelectionRange } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { getNodeRangeDecorations } from './helpers/getNodeRangeDecorations.js'\nimport { getSelectionRanges } from './helpers/getSelectionRanges.js'\nimport { isNodeRangeSelection } from './helpers/isNodeRangeSelection.js'\nimport { NodeRangeSelection } from './helpers/NodeRangeSelection.js'\n\nexport interface NodeRangeOptions {\n depth: number | undefined\n key: 'Shift' | 'Control' | 'Alt' | 'Meta' | 'Mod' | null | undefined\n}\n\nexport const NodeRange = Extension.create<NodeRangeOptions>({\n name: 'nodeRange',\n\n addOptions() {\n return {\n depth: undefined,\n key: 'Mod',\n }\n },\n\n addKeyboardShortcuts() {\n return {\n // extend NodeRangeSelection upwards\n 'Shift-ArrowUp': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth, -1)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendBackwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // extend NodeRangeSelection downwards\n 'Shift-ArrowDown': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendForwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // add `NodeRangeSelection` to all nodes\n 'Mod-a': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, tr } = state\n const nodeRangeSelection = NodeRangeSelection.create(doc, 0, doc.content.size, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n }\n },\n\n onSelectionUpdate() {\n const { selection } = this.editor.state\n\n if (isNodeRangeSelection(selection)) {\n this.editor.view.dom.classList.add('ProseMirror-noderangeselection')\n }\n },\n\n addProseMirrorPlugins() {\n let hideTextSelection = false\n let activeMouseSelection = false\n\n return [\n new Plugin({\n key: new PluginKey('nodeRange'),\n\n props: {\n attributes: () => {\n if (hideTextSelection) {\n return {\n class: 'ProseMirror-noderangeselection',\n }\n }\n\n return { class: '' }\n },\n\n handleDOMEvents: {\n mousedown: (view, event) => {\n const { key } = this.options\n const isMac = /Mac/.test(navigator.platform)\n const isShift = !!event.shiftKey\n const isControl = !!event.ctrlKey\n const isAlt = !!event.altKey\n const isMeta = !!event.metaKey\n const isMod = isMac ? isMeta : isControl\n\n if (\n key === null ||\n key === undefined ||\n (key === 'Shift' && isShift) ||\n (key === 'Control' && isControl) ||\n (key === 'Alt' && isAlt) ||\n (key === 'Meta' && isMeta) ||\n (key === 'Mod' && isMod)\n ) {\n activeMouseSelection = true\n }\n\n if (!activeMouseSelection) {\n return false\n }\n\n document.addEventListener(\n 'mouseup',\n () => {\n activeMouseSelection = false\n\n const { state } = view\n const { doc, selection, tr } = state\n const { $anchor, $head } = selection\n\n if ($anchor.sameParent($head)) {\n return\n }\n\n const nodeRangeSelection = NodeRangeSelection.create(doc, $anchor.pos, $head.pos, this.options.depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n },\n { once: true },\n )\n\n return false\n },\n },\n\n // when selecting some text we want to render some decorations\n // to preview a `NodeRangeSelection`\n decorations: state => {\n const { selection } = state\n const isNodeRange = isNodeRangeSelection(selection)\n\n hideTextSelection = false\n\n if (!activeMouseSelection) {\n if (!isNodeRange) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(selection.ranges as SelectionRange[])\n }\n\n const { $from, $to } = selection\n\n // selection is probably in the same node like a paragraph\n // so we don’t render decorations and show\n // a simple text selection instead\n if (!isNodeRange && $from.sameParent($to)) {\n return null\n }\n\n // try to calculate some node ranges\n const nodeRanges = getSelectionRanges($from, $to, this.options.depth)\n\n if (!nodeRanges.length) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(nodeRanges)\n },\n },\n }),\n ]\n },\n})\n","import type { SelectionRange } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport function getNodeRangeDecorations(ranges: SelectionRange[]): DecorationSet {\n if (!ranges.length) {\n return DecorationSet.empty\n }\n\n const decorations: Decoration[] = []\n const doc = ranges[0].$from.node(0)\n\n ranges.forEach(range => {\n const pos = range.$from.pos\n const node = range.$from.nodeAfter\n\n if (!node) {\n return\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: 'ProseMirror-selectednoderange',\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n}\n","import { type ResolvedPos, NodeRange } from '@tiptap/pm/model'\nimport { SelectionRange } from '@tiptap/pm/state'\n\nexport function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?: number): SelectionRange[] {\n const ranges: SelectionRange[] = []\n const doc = $from.node(0)\n\n // Determine the appropriate depth\n if (typeof depth === 'number' && depth >= 0) {\n // Use the provided depth\n } else if ($from.sameParent($to)) {\n depth = Math.max(0, $from.sharedDepth($to.pos) - 1)\n } else {\n depth = $from.sharedDepth($to.pos)\n }\n\n const nodeRange = new NodeRange($from, $to, depth)\n const offset = nodeRange.depth === 0 ? 0 : doc.resolve(nodeRange.start).posAtIndex(0)\n\n nodeRange.parent.forEach((node, pos) => {\n const from = offset + pos\n const to = from + node.nodeSize\n\n if (from < nodeRange.start || from >= nodeRange.end) {\n return\n }\n\n const selectionRange = new SelectionRange(doc.resolve(from), doc.resolve(to))\n\n ranges.push(selectionRange)\n })\n\n return ranges\n}\n","import type { Node as ProseMirrorNode, ResolvedPos } from '@tiptap/pm/model'\nimport { Selection } from '@tiptap/pm/state'\nimport type { Mapping } from '@tiptap/pm/transform'\n\nimport { getSelectionRanges } from './getSelectionRanges.js'\nimport { NodeRangeBookmark } from './NodeRangeBookmark.js'\n\nexport class NodeRangeSelection extends Selection {\n depth: number | undefined\n\n constructor($anchor: ResolvedPos, $head: ResolvedPos, depth?: number, bias = 1) {\n // if there is only a cursor we can’t calculate a direction of the selection\n // that’s why we adjust the head position by 1 in the desired direction\n const { doc } = $anchor\n const isCursor = $anchor === $head\n const isCursorAtEnd = $anchor.pos === doc.content.size && $head.pos === doc.content.size\n const $correctedHead = isCursor && !isCursorAtEnd ? doc.resolve($head.pos + (bias > 0 ? 1 : -1)) : $head\n const $correctedAnchor = isCursor && isCursorAtEnd ? doc.resolve($anchor.pos - (bias > 0 ? 1 : -1)) : $anchor\n\n const ranges = getSelectionRanges($correctedAnchor.min($correctedHead), $correctedAnchor.max($correctedHead), depth)\n\n // get the smallest range start position\n // this will become the $anchor\n const $rangeFrom = $correctedHead.pos >= $anchor.pos ? ranges[0].$from : ranges[ranges.length - 1].$to\n\n // get the biggest range end position\n // this will become the $head\n const $rangeTo = $correctedHead.pos >= $anchor.pos ? ranges[ranges.length - 1].$to : ranges[0].$from\n\n super($rangeFrom, $rangeTo, ranges)\n\n this.depth = depth\n }\n\n // we can safely ignore this TypeScript error: https://github.com/Microsoft/TypeScript/issues/338\n // @ts-ignore\n get $to() {\n return this.ranges[this.ranges.length - 1].$to\n }\n\n eq(other: Selection): boolean {\n return other instanceof NodeRangeSelection && other.$from.pos === this.$from.pos && other.$to.pos === this.$to.pos\n }\n\n map(doc: ProseMirrorNode, mapping: Mapping): NodeRangeSelection {\n const $anchor = doc.resolve(mapping.map(this.anchor))\n const $head = doc.resolve(mapping.map(this.head))\n\n return new NodeRangeSelection($anchor, $head)\n }\n\n toJSON() {\n return {\n type: 'nodeRange',\n anchor: this.anchor,\n head: this.head,\n }\n }\n\n get isForwards(): boolean {\n return this.head >= this.anchor\n }\n\n get isBackwards(): boolean {\n return !this.isForwards\n }\n\n extendBackwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isForwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(0, -1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($from, $to, this.depth)\n }\n\n const firstRange = this.ranges[0]\n const $from = doc.resolve(Math.max(0, firstRange.$from.pos - 1))\n\n return new NodeRangeSelection(this.$anchor, $from, this.depth)\n }\n\n extendForwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isBackwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($to, $from, this.depth)\n }\n\n const lastRange = this.ranges[this.ranges.length - 1]\n const $to = doc.resolve(Math.min(doc.content.size, lastRange.$to.pos + 1))\n\n return new NodeRangeSelection(this.$anchor, $to, this.depth)\n }\n\n static fromJSON(doc: ProseMirrorNode, json: any): NodeRangeSelection {\n return new NodeRangeSelection(doc.resolve(json.anchor), doc.resolve(json.head))\n }\n\n static create(doc: ProseMirrorNode, anchor: number, head: number, depth?: number, bias = 1): NodeRangeSelection {\n return new this(doc.resolve(anchor), doc.resolve(head), depth, bias)\n }\n\n getBookmark(): NodeRangeBookmark {\n return new NodeRangeBookmark(this.anchor, this.head)\n }\n}\n\nNodeRangeSelection.prototype.visible = false\n","import type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport type { Mappable } from '@tiptap/pm/transform'\n\nimport { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport class NodeRangeBookmark {\n anchor!: number\n\n head!: number\n\n constructor(anchor: number, head: number) {\n this.anchor = anchor\n this.head = head\n }\n\n map(mapping: Mappable) {\n return new NodeRangeBookmark(mapping.map(this.anchor), mapping.map(this.head))\n }\n\n resolve(doc: ProseMirrorNode) {\n const $anchor = doc.resolve(this.anchor)\n const $head = doc.resolve(this.head)\n\n return new NodeRangeSelection($anchor, $head)\n }\n}\n","import { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport function isNodeRangeSelection(value: unknown): value is NodeRangeSelection {\n return value instanceof NodeRangeSelection\n}\n"],"mappings":"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","names":["NodeRange","import_state","import_state","$from","$to","NodeRange","nodeRangeSelection","NodeRange"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/node-range.ts","../src/helpers/getNodeRangeDecorations.ts","../src/helpers/getSelectionRanges.ts","../src/helpers/NodeRangeSelection.ts","../src/helpers/NodeRangeBookmark.ts","../src/helpers/isNodeRangeSelection.ts"],"sourcesContent":["import { NodeRange } from './node-range.js'\n\nexport * from './helpers/getNodeRangeDecorations.js'\nexport * from './helpers/getSelectionRanges.js'\nexport * from './helpers/isNodeRangeSelection.js'\nexport * from './helpers/NodeRangeSelection.js'\nexport * from './node-range.js'\n\nexport default NodeRange\n","import { Extension } from '@tiptap/core'\nimport type { SelectionRange } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { getNodeRangeDecorations } from './helpers/getNodeRangeDecorations.js'\nimport { getSelectionRanges } from './helpers/getSelectionRanges.js'\nimport { isNodeRangeSelection } from './helpers/isNodeRangeSelection.js'\nimport { NodeRangeSelection } from './helpers/NodeRangeSelection.js'\n\nexport interface NodeRangeOptions {\n depth: number | undefined\n key: 'Shift' | 'Control' | 'Alt' | 'Meta' | 'Mod' | null | undefined\n}\n\nexport const NodeRange = Extension.create<NodeRangeOptions>({\n name: 'nodeRange',\n\n addOptions() {\n return {\n depth: undefined,\n key: 'Mod',\n }\n },\n\n addKeyboardShortcuts() {\n return {\n // extend NodeRangeSelection upwards\n 'Shift-ArrowUp': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth, -1)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendBackwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // extend NodeRangeSelection downwards\n 'Shift-ArrowDown': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendForwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // add `NodeRangeSelection` to all nodes\n 'Mod-a': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, tr } = state\n const nodeRangeSelection = NodeRangeSelection.create(doc, 0, doc.content.size, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n }\n },\n\n onSelectionUpdate() {\n const { selection } = this.editor.state\n\n if (isNodeRangeSelection(selection)) {\n this.editor.view.dom.classList.add('ProseMirror-noderangeselection')\n }\n },\n\n addProseMirrorPlugins() {\n let hideTextSelection = false\n let activeMouseSelection = false\n\n return [\n new Plugin({\n key: new PluginKey('nodeRange'),\n\n props: {\n attributes: () => {\n if (hideTextSelection) {\n return {\n class: 'ProseMirror-noderangeselection',\n }\n }\n\n return { class: '' }\n },\n\n handleDOMEvents: {\n mousedown: (view, event) => {\n const { key } = this.options\n const isMac = /Mac/.test(navigator.platform)\n const isShift = !!event.shiftKey\n const isControl = !!event.ctrlKey\n const isAlt = !!event.altKey\n const isMeta = !!event.metaKey\n const isMod = isMac ? isMeta : isControl\n\n if (\n key === null ||\n key === undefined ||\n (key === 'Shift' && isShift) ||\n (key === 'Control' && isControl) ||\n (key === 'Alt' && isAlt) ||\n (key === 'Meta' && isMeta) ||\n (key === 'Mod' && isMod)\n ) {\n activeMouseSelection = true\n }\n\n if (!activeMouseSelection) {\n return false\n }\n\n document.addEventListener(\n 'mouseup',\n () => {\n activeMouseSelection = false\n\n const { state } = view\n const { doc, selection, tr } = state\n const { $anchor, $head } = selection\n\n if ($anchor.sameParent($head)) {\n return\n }\n\n const nodeRangeSelection = NodeRangeSelection.create(doc, $anchor.pos, $head.pos, this.options.depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n },\n { once: true },\n )\n\n return false\n },\n },\n\n // when selecting some text we want to render some decorations\n // to preview a `NodeRangeSelection`\n decorations: state => {\n const { selection } = state\n const isNodeRange = isNodeRangeSelection(selection)\n\n hideTextSelection = false\n\n if (!activeMouseSelection) {\n if (!isNodeRange) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(selection.ranges as SelectionRange[])\n }\n\n const { $from, $to } = selection\n\n // selection is probably in the same node like a paragraph\n // so we don’t render decorations and show\n // a simple text selection instead\n if (!isNodeRange && $from.sameParent($to)) {\n return null\n }\n\n // try to calculate some node ranges\n const nodeRanges = getSelectionRanges($from, $to, this.options.depth)\n\n if (!nodeRanges.length) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(nodeRanges)\n },\n },\n }),\n ]\n },\n})\n","import type { SelectionRange } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport function getNodeRangeDecorations(ranges: SelectionRange[]): DecorationSet {\n if (!ranges.length) {\n return DecorationSet.empty\n }\n\n const decorations: Decoration[] = []\n const doc = ranges[0].$from.node(0)\n\n ranges.forEach(range => {\n const pos = range.$from.pos\n const node = range.$from.nodeAfter\n\n if (!node) {\n return\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: 'ProseMirror-selectednoderange',\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n}\n","import { type ResolvedPos, NodeRange } from '@tiptap/pm/model'\nimport { SelectionRange } from '@tiptap/pm/state'\n\nexport interface GetSelectionRangesOptions {\n /**\n * Whether nodes should be included when the selection only overlaps their\n * start or end content boundary.\n * @default true\n */\n extendOnBoundaryOverlap?: boolean\n}\n\nfunction getNodeContentBounds(nodeStart: number, nodeSize: number, node: { isText: boolean; isAtom: boolean }) {\n const contentOffset = node.isText || node.isAtom ? 0 : 1\n\n return {\n start: nodeStart + contentOffset,\n end: nodeStart + nodeSize - contentOffset,\n }\n}\n\n/**\n * Calculates node-aligned selection ranges between two resolved positions.\n *\n * The helper derives a suitable depth when none is provided and returns a\n * `SelectionRange` for each matching child node in the computed `NodeRange`.\n * Each returned range exposes `$from` as the resolved start position of the\n * node selection and `$to` as the resolved end position.\n *\n * @param $from The resolved anchor position where the selection starts.\n * @param $to The resolved head position where the selection ends.\n * @param depth An optional depth to force when creating the ProseMirror `NodeRange`.\n * When omitted, the depth is inferred from the shared depth of `$from` and `$to`.\n * @param options Optional behavior flags for how boundary nodes are handled.\n * @param options.extendOnBoundaryOverlap Whether touching only a node's start\n * or end content boundary should still include that node in the returned ranges.\n * @returns An array of `SelectionRange` objects for the nodes covered at the\n * computed depth.\n * @example\n * ```ts\n * const { $from, $to } = editor.state.selection\n * const ranges = getSelectionRanges($from, $to, undefined, {\n * extendOnBoundaryOverlap: false,\n * })\n *\n * ranges.forEach(range => {\n * console.log(range.$from.pos, range.$to.pos)\n * })\n * ```\n */\nexport function getSelectionRanges(\n $from: ResolvedPos,\n $to: ResolvedPos,\n depth?: number,\n options: GetSelectionRangesOptions = {},\n): SelectionRange[] {\n const ranges: SelectionRange[] = []\n const doc = $from.node(0)\n const { extendOnBoundaryOverlap = true } = options\n\n // Determine the appropriate depth\n if (typeof depth === 'number' && depth >= 0) {\n // Use the provided depth\n } else if ($from.sameParent($to)) {\n depth = Math.max(0, $from.sharedDepth($to.pos) - 1)\n } else {\n depth = $from.sharedDepth($to.pos)\n }\n\n const nodeRange = new NodeRange($from, $to, depth)\n const offset = nodeRange.depth === 0 ? 0 : doc.resolve(nodeRange.start).posAtIndex(0)\n\n nodeRange.parent.forEach((node, pos) => {\n const from = offset + pos\n const to = from + node.nodeSize\n const contentBounds = getNodeContentBounds(from, node.nodeSize, node)\n const overlapsNodeContent = extendOnBoundaryOverlap\n ? $to.pos >= contentBounds.start && $from.pos <= contentBounds.end\n : $to.pos > contentBounds.start && $from.pos < contentBounds.end\n\n if (from < nodeRange.start || from >= nodeRange.end) {\n return\n }\n\n if (!overlapsNodeContent) {\n return\n }\n\n const selectionRange = new SelectionRange(doc.resolve(from), doc.resolve(to))\n\n ranges.push(selectionRange)\n })\n\n return ranges\n}\n","import type { Node as ProseMirrorNode, ResolvedPos } from '@tiptap/pm/model'\nimport { Selection } from '@tiptap/pm/state'\nimport type { Mapping } from '@tiptap/pm/transform'\n\nimport { getSelectionRanges } from './getSelectionRanges.js'\nimport { NodeRangeBookmark } from './NodeRangeBookmark.js'\n\nexport class NodeRangeSelection extends Selection {\n depth: number | undefined\n\n constructor($anchor: ResolvedPos, $head: ResolvedPos, depth?: number, bias = 1) {\n // if there is only a cursor we can’t calculate a direction of the selection\n // that’s why we adjust the head position by 1 in the desired direction\n const { doc } = $anchor\n const isCursor = $anchor === $head\n const isCursorAtEnd = $anchor.pos === doc.content.size && $head.pos === doc.content.size\n const $correctedHead = isCursor && !isCursorAtEnd ? doc.resolve($head.pos + (bias > 0 ? 1 : -1)) : $head\n const $correctedAnchor = isCursor && isCursorAtEnd ? doc.resolve($anchor.pos - (bias > 0 ? 1 : -1)) : $anchor\n\n const ranges = getSelectionRanges($correctedAnchor.min($correctedHead), $correctedAnchor.max($correctedHead), depth)\n\n // get the smallest range start position\n // this will become the $anchor\n const $rangeFrom = $correctedHead.pos >= $anchor.pos ? ranges[0].$from : ranges[ranges.length - 1].$to\n\n // get the biggest range end position\n // this will become the $head\n const $rangeTo = $correctedHead.pos >= $anchor.pos ? ranges[ranges.length - 1].$to : ranges[0].$from\n\n super($rangeFrom, $rangeTo, ranges)\n\n this.depth = depth\n }\n\n // we can safely ignore this TypeScript error: https://github.com/Microsoft/TypeScript/issues/338\n // @ts-ignore\n get $to() {\n return this.ranges[this.ranges.length - 1].$to\n }\n\n eq(other: Selection): boolean {\n return other instanceof NodeRangeSelection && other.$from.pos === this.$from.pos && other.$to.pos === this.$to.pos\n }\n\n map(doc: ProseMirrorNode, mapping: Mapping): NodeRangeSelection {\n const $anchor = doc.resolve(mapping.map(this.anchor))\n const $head = doc.resolve(mapping.map(this.head))\n\n return new NodeRangeSelection($anchor, $head)\n }\n\n toJSON() {\n return {\n type: 'nodeRange',\n anchor: this.anchor,\n head: this.head,\n }\n }\n\n get isForwards(): boolean {\n return this.head >= this.anchor\n }\n\n get isBackwards(): boolean {\n return !this.isForwards\n }\n\n extendBackwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isForwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(0, -1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($from, $to, this.depth)\n }\n\n const firstRange = this.ranges[0]\n const $from = doc.resolve(Math.max(0, firstRange.$from.pos - 1))\n\n return new NodeRangeSelection(this.$anchor, $from, this.depth)\n }\n\n extendForwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isBackwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($to, $from, this.depth)\n }\n\n const lastRange = this.ranges[this.ranges.length - 1]\n const $to = doc.resolve(Math.min(doc.content.size, lastRange.$to.pos + 1))\n\n return new NodeRangeSelection(this.$anchor, $to, this.depth)\n }\n\n static fromJSON(doc: ProseMirrorNode, json: any): NodeRangeSelection {\n return new NodeRangeSelection(doc.resolve(json.anchor), doc.resolve(json.head))\n }\n\n static create(doc: ProseMirrorNode, anchor: number, head: number, depth?: number, bias = 1): NodeRangeSelection {\n return new this(doc.resolve(anchor), doc.resolve(head), depth, bias)\n }\n\n getBookmark(): NodeRangeBookmark {\n return new NodeRangeBookmark(this.anchor, this.head)\n }\n}\n\nNodeRangeSelection.prototype.visible = false\n","import type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport type { Mappable } from '@tiptap/pm/transform'\n\nimport { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport class NodeRangeBookmark {\n anchor!: number\n\n head!: number\n\n constructor(anchor: number, head: number) {\n this.anchor = anchor\n this.head = head\n }\n\n map(mapping: Mappable) {\n return new NodeRangeBookmark(mapping.map(this.anchor), mapping.map(this.head))\n }\n\n resolve(doc: ProseMirrorNode) {\n const $anchor = doc.resolve(this.anchor)\n const $head = doc.resolve(this.head)\n\n return new NodeRangeSelection($anchor, $head)\n }\n}\n","import { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport function isNodeRangeSelection(value: unknown): value is NodeRangeSelection {\n return value instanceof NodeRangeSelection\n}\n"],"mappings":"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","names":["NodeRange","import_state","import_state","$from","$to","NodeRange","nodeRangeSelection","NodeRange"]}
package/dist/index.d.cts CHANGED
@@ -12,7 +12,44 @@ declare const NodeRange: Extension<NodeRangeOptions, any>;
12
12
 
13
13
  declare function getNodeRangeDecorations(ranges: SelectionRange[]): DecorationSet;
14
14
 
15
- declare function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?: number): SelectionRange[];
15
+ interface GetSelectionRangesOptions {
16
+ /**
17
+ * Whether nodes should be included when the selection only overlaps their
18
+ * start or end content boundary.
19
+ * @default true
20
+ */
21
+ extendOnBoundaryOverlap?: boolean;
22
+ }
23
+ /**
24
+ * Calculates node-aligned selection ranges between two resolved positions.
25
+ *
26
+ * The helper derives a suitable depth when none is provided and returns a
27
+ * `SelectionRange` for each matching child node in the computed `NodeRange`.
28
+ * Each returned range exposes `$from` as the resolved start position of the
29
+ * node selection and `$to` as the resolved end position.
30
+ *
31
+ * @param $from The resolved anchor position where the selection starts.
32
+ * @param $to The resolved head position where the selection ends.
33
+ * @param depth An optional depth to force when creating the ProseMirror `NodeRange`.
34
+ * When omitted, the depth is inferred from the shared depth of `$from` and `$to`.
35
+ * @param options Optional behavior flags for how boundary nodes are handled.
36
+ * @param options.extendOnBoundaryOverlap Whether touching only a node's start
37
+ * or end content boundary should still include that node in the returned ranges.
38
+ * @returns An array of `SelectionRange` objects for the nodes covered at the
39
+ * computed depth.
40
+ * @example
41
+ * ```ts
42
+ * const { $from, $to } = editor.state.selection
43
+ * const ranges = getSelectionRanges($from, $to, undefined, {
44
+ * extendOnBoundaryOverlap: false,
45
+ * })
46
+ *
47
+ * ranges.forEach(range => {
48
+ * console.log(range.$from.pos, range.$to.pos)
49
+ * })
50
+ * ```
51
+ */
52
+ declare function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?: number, options?: GetSelectionRangesOptions): SelectionRange[];
16
53
 
17
54
  declare class NodeRangeBookmark {
18
55
  anchor: number;
@@ -44,4 +81,4 @@ declare class NodeRangeSelection extends Selection {
44
81
 
45
82
  declare function isNodeRangeSelection(value: unknown): value is NodeRangeSelection;
46
83
 
47
- export { NodeRange, type NodeRangeOptions, NodeRangeSelection, NodeRange as default, getNodeRangeDecorations, getSelectionRanges, isNodeRangeSelection };
84
+ export { type GetSelectionRangesOptions, NodeRange, type NodeRangeOptions, NodeRangeSelection, NodeRange as default, getNodeRangeDecorations, getSelectionRanges, isNodeRangeSelection };
package/dist/index.d.ts CHANGED
@@ -12,7 +12,44 @@ declare const NodeRange: Extension<NodeRangeOptions, any>;
12
12
 
13
13
  declare function getNodeRangeDecorations(ranges: SelectionRange[]): DecorationSet;
14
14
 
15
- declare function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?: number): SelectionRange[];
15
+ interface GetSelectionRangesOptions {
16
+ /**
17
+ * Whether nodes should be included when the selection only overlaps their
18
+ * start or end content boundary.
19
+ * @default true
20
+ */
21
+ extendOnBoundaryOverlap?: boolean;
22
+ }
23
+ /**
24
+ * Calculates node-aligned selection ranges between two resolved positions.
25
+ *
26
+ * The helper derives a suitable depth when none is provided and returns a
27
+ * `SelectionRange` for each matching child node in the computed `NodeRange`.
28
+ * Each returned range exposes `$from` as the resolved start position of the
29
+ * node selection and `$to` as the resolved end position.
30
+ *
31
+ * @param $from The resolved anchor position where the selection starts.
32
+ * @param $to The resolved head position where the selection ends.
33
+ * @param depth An optional depth to force when creating the ProseMirror `NodeRange`.
34
+ * When omitted, the depth is inferred from the shared depth of `$from` and `$to`.
35
+ * @param options Optional behavior flags for how boundary nodes are handled.
36
+ * @param options.extendOnBoundaryOverlap Whether touching only a node's start
37
+ * or end content boundary should still include that node in the returned ranges.
38
+ * @returns An array of `SelectionRange` objects for the nodes covered at the
39
+ * computed depth.
40
+ * @example
41
+ * ```ts
42
+ * const { $from, $to } = editor.state.selection
43
+ * const ranges = getSelectionRanges($from, $to, undefined, {
44
+ * extendOnBoundaryOverlap: false,
45
+ * })
46
+ *
47
+ * ranges.forEach(range => {
48
+ * console.log(range.$from.pos, range.$to.pos)
49
+ * })
50
+ * ```
51
+ */
52
+ declare function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?: number, options?: GetSelectionRangesOptions): SelectionRange[];
16
53
 
17
54
  declare class NodeRangeBookmark {
18
55
  anchor: number;
@@ -44,4 +81,4 @@ declare class NodeRangeSelection extends Selection {
44
81
 
45
82
  declare function isNodeRangeSelection(value: unknown): value is NodeRangeSelection;
46
83
 
47
- export { NodeRange, type NodeRangeOptions, NodeRangeSelection, NodeRange as default, getNodeRangeDecorations, getSelectionRanges, isNodeRangeSelection };
84
+ export { type GetSelectionRangesOptions, NodeRange, type NodeRangeOptions, NodeRangeSelection, NodeRange as default, getNodeRangeDecorations, getSelectionRanges, isNodeRangeSelection };
package/dist/index.js CHANGED
@@ -28,9 +28,17 @@ function getNodeRangeDecorations(ranges) {
28
28
  // src/helpers/getSelectionRanges.ts
29
29
  import { NodeRange } from "@tiptap/pm/model";
30
30
  import { SelectionRange } from "@tiptap/pm/state";
31
- function getSelectionRanges($from, $to, depth) {
31
+ function getNodeContentBounds(nodeStart, nodeSize, node) {
32
+ const contentOffset = node.isText || node.isAtom ? 0 : 1;
33
+ return {
34
+ start: nodeStart + contentOffset,
35
+ end: nodeStart + nodeSize - contentOffset
36
+ };
37
+ }
38
+ function getSelectionRanges($from, $to, depth, options = {}) {
32
39
  const ranges = [];
33
40
  const doc = $from.node(0);
41
+ const { extendOnBoundaryOverlap = true } = options;
34
42
  if (typeof depth === "number" && depth >= 0) {
35
43
  } else if ($from.sameParent($to)) {
36
44
  depth = Math.max(0, $from.sharedDepth($to.pos) - 1);
@@ -42,9 +50,14 @@ function getSelectionRanges($from, $to, depth) {
42
50
  nodeRange.parent.forEach((node, pos) => {
43
51
  const from = offset + pos;
44
52
  const to = from + node.nodeSize;
53
+ const contentBounds = getNodeContentBounds(from, node.nodeSize, node);
54
+ const overlapsNodeContent = extendOnBoundaryOverlap ? $to.pos >= contentBounds.start && $from.pos <= contentBounds.end : $to.pos > contentBounds.start && $from.pos < contentBounds.end;
45
55
  if (from < nodeRange.start || from >= nodeRange.end) {
46
56
  return;
47
57
  }
58
+ if (!overlapsNodeContent) {
59
+ return;
60
+ }
48
61
  const selectionRange = new SelectionRange(doc.resolve(from), doc.resolve(to));
49
62
  ranges.push(selectionRange);
50
63
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/node-range.ts","../src/helpers/getNodeRangeDecorations.ts","../src/helpers/getSelectionRanges.ts","../src/helpers/NodeRangeSelection.ts","../src/helpers/NodeRangeBookmark.ts","../src/helpers/isNodeRangeSelection.ts","../src/index.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { SelectionRange } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { getNodeRangeDecorations } from './helpers/getNodeRangeDecorations.js'\nimport { getSelectionRanges } from './helpers/getSelectionRanges.js'\nimport { isNodeRangeSelection } from './helpers/isNodeRangeSelection.js'\nimport { NodeRangeSelection } from './helpers/NodeRangeSelection.js'\n\nexport interface NodeRangeOptions {\n depth: number | undefined\n key: 'Shift' | 'Control' | 'Alt' | 'Meta' | 'Mod' | null | undefined\n}\n\nexport const NodeRange = Extension.create<NodeRangeOptions>({\n name: 'nodeRange',\n\n addOptions() {\n return {\n depth: undefined,\n key: 'Mod',\n }\n },\n\n addKeyboardShortcuts() {\n return {\n // extend NodeRangeSelection upwards\n 'Shift-ArrowUp': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth, -1)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendBackwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // extend NodeRangeSelection downwards\n 'Shift-ArrowDown': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendForwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // add `NodeRangeSelection` to all nodes\n 'Mod-a': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, tr } = state\n const nodeRangeSelection = NodeRangeSelection.create(doc, 0, doc.content.size, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n }\n },\n\n onSelectionUpdate() {\n const { selection } = this.editor.state\n\n if (isNodeRangeSelection(selection)) {\n this.editor.view.dom.classList.add('ProseMirror-noderangeselection')\n }\n },\n\n addProseMirrorPlugins() {\n let hideTextSelection = false\n let activeMouseSelection = false\n\n return [\n new Plugin({\n key: new PluginKey('nodeRange'),\n\n props: {\n attributes: () => {\n if (hideTextSelection) {\n return {\n class: 'ProseMirror-noderangeselection',\n }\n }\n\n return { class: '' }\n },\n\n handleDOMEvents: {\n mousedown: (view, event) => {\n const { key } = this.options\n const isMac = /Mac/.test(navigator.platform)\n const isShift = !!event.shiftKey\n const isControl = !!event.ctrlKey\n const isAlt = !!event.altKey\n const isMeta = !!event.metaKey\n const isMod = isMac ? isMeta : isControl\n\n if (\n key === null ||\n key === undefined ||\n (key === 'Shift' && isShift) ||\n (key === 'Control' && isControl) ||\n (key === 'Alt' && isAlt) ||\n (key === 'Meta' && isMeta) ||\n (key === 'Mod' && isMod)\n ) {\n activeMouseSelection = true\n }\n\n if (!activeMouseSelection) {\n return false\n }\n\n document.addEventListener(\n 'mouseup',\n () => {\n activeMouseSelection = false\n\n const { state } = view\n const { doc, selection, tr } = state\n const { $anchor, $head } = selection\n\n if ($anchor.sameParent($head)) {\n return\n }\n\n const nodeRangeSelection = NodeRangeSelection.create(doc, $anchor.pos, $head.pos, this.options.depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n },\n { once: true },\n )\n\n return false\n },\n },\n\n // when selecting some text we want to render some decorations\n // to preview a `NodeRangeSelection`\n decorations: state => {\n const { selection } = state\n const isNodeRange = isNodeRangeSelection(selection)\n\n hideTextSelection = false\n\n if (!activeMouseSelection) {\n if (!isNodeRange) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(selection.ranges as SelectionRange[])\n }\n\n const { $from, $to } = selection\n\n // selection is probably in the same node like a paragraph\n // so we don’t render decorations and show\n // a simple text selection instead\n if (!isNodeRange && $from.sameParent($to)) {\n return null\n }\n\n // try to calculate some node ranges\n const nodeRanges = getSelectionRanges($from, $to, this.options.depth)\n\n if (!nodeRanges.length) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(nodeRanges)\n },\n },\n }),\n ]\n },\n})\n","import type { SelectionRange } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport function getNodeRangeDecorations(ranges: SelectionRange[]): DecorationSet {\n if (!ranges.length) {\n return DecorationSet.empty\n }\n\n const decorations: Decoration[] = []\n const doc = ranges[0].$from.node(0)\n\n ranges.forEach(range => {\n const pos = range.$from.pos\n const node = range.$from.nodeAfter\n\n if (!node) {\n return\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: 'ProseMirror-selectednoderange',\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n}\n","import { type ResolvedPos, NodeRange } from '@tiptap/pm/model'\nimport { SelectionRange } from '@tiptap/pm/state'\n\nexport function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?: number): SelectionRange[] {\n const ranges: SelectionRange[] = []\n const doc = $from.node(0)\n\n // Determine the appropriate depth\n if (typeof depth === 'number' && depth >= 0) {\n // Use the provided depth\n } else if ($from.sameParent($to)) {\n depth = Math.max(0, $from.sharedDepth($to.pos) - 1)\n } else {\n depth = $from.sharedDepth($to.pos)\n }\n\n const nodeRange = new NodeRange($from, $to, depth)\n const offset = nodeRange.depth === 0 ? 0 : doc.resolve(nodeRange.start).posAtIndex(0)\n\n nodeRange.parent.forEach((node, pos) => {\n const from = offset + pos\n const to = from + node.nodeSize\n\n if (from < nodeRange.start || from >= nodeRange.end) {\n return\n }\n\n const selectionRange = new SelectionRange(doc.resolve(from), doc.resolve(to))\n\n ranges.push(selectionRange)\n })\n\n return ranges\n}\n","import type { Node as ProseMirrorNode, ResolvedPos } from '@tiptap/pm/model'\nimport { Selection } from '@tiptap/pm/state'\nimport type { Mapping } from '@tiptap/pm/transform'\n\nimport { getSelectionRanges } from './getSelectionRanges.js'\nimport { NodeRangeBookmark } from './NodeRangeBookmark.js'\n\nexport class NodeRangeSelection extends Selection {\n depth: number | undefined\n\n constructor($anchor: ResolvedPos, $head: ResolvedPos, depth?: number, bias = 1) {\n // if there is only a cursor we can’t calculate a direction of the selection\n // that’s why we adjust the head position by 1 in the desired direction\n const { doc } = $anchor\n const isCursor = $anchor === $head\n const isCursorAtEnd = $anchor.pos === doc.content.size && $head.pos === doc.content.size\n const $correctedHead = isCursor && !isCursorAtEnd ? doc.resolve($head.pos + (bias > 0 ? 1 : -1)) : $head\n const $correctedAnchor = isCursor && isCursorAtEnd ? doc.resolve($anchor.pos - (bias > 0 ? 1 : -1)) : $anchor\n\n const ranges = getSelectionRanges($correctedAnchor.min($correctedHead), $correctedAnchor.max($correctedHead), depth)\n\n // get the smallest range start position\n // this will become the $anchor\n const $rangeFrom = $correctedHead.pos >= $anchor.pos ? ranges[0].$from : ranges[ranges.length - 1].$to\n\n // get the biggest range end position\n // this will become the $head\n const $rangeTo = $correctedHead.pos >= $anchor.pos ? ranges[ranges.length - 1].$to : ranges[0].$from\n\n super($rangeFrom, $rangeTo, ranges)\n\n this.depth = depth\n }\n\n // we can safely ignore this TypeScript error: https://github.com/Microsoft/TypeScript/issues/338\n // @ts-ignore\n get $to() {\n return this.ranges[this.ranges.length - 1].$to\n }\n\n eq(other: Selection): boolean {\n return other instanceof NodeRangeSelection && other.$from.pos === this.$from.pos && other.$to.pos === this.$to.pos\n }\n\n map(doc: ProseMirrorNode, mapping: Mapping): NodeRangeSelection {\n const $anchor = doc.resolve(mapping.map(this.anchor))\n const $head = doc.resolve(mapping.map(this.head))\n\n return new NodeRangeSelection($anchor, $head)\n }\n\n toJSON() {\n return {\n type: 'nodeRange',\n anchor: this.anchor,\n head: this.head,\n }\n }\n\n get isForwards(): boolean {\n return this.head >= this.anchor\n }\n\n get isBackwards(): boolean {\n return !this.isForwards\n }\n\n extendBackwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isForwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(0, -1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($from, $to, this.depth)\n }\n\n const firstRange = this.ranges[0]\n const $from = doc.resolve(Math.max(0, firstRange.$from.pos - 1))\n\n return new NodeRangeSelection(this.$anchor, $from, this.depth)\n }\n\n extendForwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isBackwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($to, $from, this.depth)\n }\n\n const lastRange = this.ranges[this.ranges.length - 1]\n const $to = doc.resolve(Math.min(doc.content.size, lastRange.$to.pos + 1))\n\n return new NodeRangeSelection(this.$anchor, $to, this.depth)\n }\n\n static fromJSON(doc: ProseMirrorNode, json: any): NodeRangeSelection {\n return new NodeRangeSelection(doc.resolve(json.anchor), doc.resolve(json.head))\n }\n\n static create(doc: ProseMirrorNode, anchor: number, head: number, depth?: number, bias = 1): NodeRangeSelection {\n return new this(doc.resolve(anchor), doc.resolve(head), depth, bias)\n }\n\n getBookmark(): NodeRangeBookmark {\n return new NodeRangeBookmark(this.anchor, this.head)\n }\n}\n\nNodeRangeSelection.prototype.visible = false\n","import type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport type { Mappable } from '@tiptap/pm/transform'\n\nimport { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport class NodeRangeBookmark {\n anchor!: number\n\n head!: number\n\n constructor(anchor: number, head: number) {\n this.anchor = anchor\n this.head = head\n }\n\n map(mapping: Mappable) {\n return new NodeRangeBookmark(mapping.map(this.anchor), mapping.map(this.head))\n }\n\n resolve(doc: ProseMirrorNode) {\n const $anchor = doc.resolve(this.anchor)\n const $head = doc.resolve(this.head)\n\n return new NodeRangeSelection($anchor, $head)\n }\n}\n","import { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport function isNodeRangeSelection(value: unknown): value is NodeRangeSelection {\n return value instanceof NodeRangeSelection\n}\n","import { NodeRange } from './node-range.js'\n\nexport * from './helpers/getNodeRangeDecorations.js'\nexport * from './helpers/getSelectionRanges.js'\nexport * from './helpers/isNodeRangeSelection.js'\nexport * from './helpers/NodeRangeSelection.js'\nexport * from './node-range.js'\n\nexport default NodeRange\n"],"mappings":"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","names":["$from","$to","NodeRange","nodeRangeSelection","NodeRange"]}
1
+ {"version":3,"sources":["../src/node-range.ts","../src/helpers/getNodeRangeDecorations.ts","../src/helpers/getSelectionRanges.ts","../src/helpers/NodeRangeSelection.ts","../src/helpers/NodeRangeBookmark.ts","../src/helpers/isNodeRangeSelection.ts","../src/index.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { SelectionRange } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\nimport { getNodeRangeDecorations } from './helpers/getNodeRangeDecorations.js'\nimport { getSelectionRanges } from './helpers/getSelectionRanges.js'\nimport { isNodeRangeSelection } from './helpers/isNodeRangeSelection.js'\nimport { NodeRangeSelection } from './helpers/NodeRangeSelection.js'\n\nexport interface NodeRangeOptions {\n depth: number | undefined\n key: 'Shift' | 'Control' | 'Alt' | 'Meta' | 'Mod' | null | undefined\n}\n\nexport const NodeRange = Extension.create<NodeRangeOptions>({\n name: 'nodeRange',\n\n addOptions() {\n return {\n depth: undefined,\n key: 'Mod',\n }\n },\n\n addKeyboardShortcuts() {\n return {\n // extend NodeRangeSelection upwards\n 'Shift-ArrowUp': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth, -1)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendBackwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // extend NodeRangeSelection downwards\n 'Shift-ArrowDown': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, selection, tr } = state\n const { anchor, head } = selection\n\n if (!isNodeRangeSelection(selection)) {\n const nodeRangeSelection = NodeRangeSelection.create(doc, anchor, head, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n }\n\n const nodeRangeSelection = selection.extendForwards()\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n\n // add `NodeRangeSelection` to all nodes\n 'Mod-a': ({ editor }) => {\n const { depth } = this.options\n const { view, state } = editor\n const { doc, tr } = state\n const nodeRangeSelection = NodeRangeSelection.create(doc, 0, doc.content.size, depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n\n return true\n },\n }\n },\n\n onSelectionUpdate() {\n const { selection } = this.editor.state\n\n if (isNodeRangeSelection(selection)) {\n this.editor.view.dom.classList.add('ProseMirror-noderangeselection')\n }\n },\n\n addProseMirrorPlugins() {\n let hideTextSelection = false\n let activeMouseSelection = false\n\n return [\n new Plugin({\n key: new PluginKey('nodeRange'),\n\n props: {\n attributes: () => {\n if (hideTextSelection) {\n return {\n class: 'ProseMirror-noderangeselection',\n }\n }\n\n return { class: '' }\n },\n\n handleDOMEvents: {\n mousedown: (view, event) => {\n const { key } = this.options\n const isMac = /Mac/.test(navigator.platform)\n const isShift = !!event.shiftKey\n const isControl = !!event.ctrlKey\n const isAlt = !!event.altKey\n const isMeta = !!event.metaKey\n const isMod = isMac ? isMeta : isControl\n\n if (\n key === null ||\n key === undefined ||\n (key === 'Shift' && isShift) ||\n (key === 'Control' && isControl) ||\n (key === 'Alt' && isAlt) ||\n (key === 'Meta' && isMeta) ||\n (key === 'Mod' && isMod)\n ) {\n activeMouseSelection = true\n }\n\n if (!activeMouseSelection) {\n return false\n }\n\n document.addEventListener(\n 'mouseup',\n () => {\n activeMouseSelection = false\n\n const { state } = view\n const { doc, selection, tr } = state\n const { $anchor, $head } = selection\n\n if ($anchor.sameParent($head)) {\n return\n }\n\n const nodeRangeSelection = NodeRangeSelection.create(doc, $anchor.pos, $head.pos, this.options.depth)\n\n tr.setSelection(nodeRangeSelection)\n view.dispatch(tr)\n },\n { once: true },\n )\n\n return false\n },\n },\n\n // when selecting some text we want to render some decorations\n // to preview a `NodeRangeSelection`\n decorations: state => {\n const { selection } = state\n const isNodeRange = isNodeRangeSelection(selection)\n\n hideTextSelection = false\n\n if (!activeMouseSelection) {\n if (!isNodeRange) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(selection.ranges as SelectionRange[])\n }\n\n const { $from, $to } = selection\n\n // selection is probably in the same node like a paragraph\n // so we don’t render decorations and show\n // a simple text selection instead\n if (!isNodeRange && $from.sameParent($to)) {\n return null\n }\n\n // try to calculate some node ranges\n const nodeRanges = getSelectionRanges($from, $to, this.options.depth)\n\n if (!nodeRanges.length) {\n return null\n }\n\n hideTextSelection = true\n\n return getNodeRangeDecorations(nodeRanges)\n },\n },\n }),\n ]\n },\n})\n","import type { SelectionRange } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\nexport function getNodeRangeDecorations(ranges: SelectionRange[]): DecorationSet {\n if (!ranges.length) {\n return DecorationSet.empty\n }\n\n const decorations: Decoration[] = []\n const doc = ranges[0].$from.node(0)\n\n ranges.forEach(range => {\n const pos = range.$from.pos\n const node = range.$from.nodeAfter\n\n if (!node) {\n return\n }\n\n decorations.push(\n Decoration.node(pos, pos + node.nodeSize, {\n class: 'ProseMirror-selectednoderange',\n }),\n )\n })\n\n return DecorationSet.create(doc, decorations)\n}\n","import { type ResolvedPos, NodeRange } from '@tiptap/pm/model'\nimport { SelectionRange } from '@tiptap/pm/state'\n\nexport interface GetSelectionRangesOptions {\n /**\n * Whether nodes should be included when the selection only overlaps their\n * start or end content boundary.\n * @default true\n */\n extendOnBoundaryOverlap?: boolean\n}\n\nfunction getNodeContentBounds(nodeStart: number, nodeSize: number, node: { isText: boolean; isAtom: boolean }) {\n const contentOffset = node.isText || node.isAtom ? 0 : 1\n\n return {\n start: nodeStart + contentOffset,\n end: nodeStart + nodeSize - contentOffset,\n }\n}\n\n/**\n * Calculates node-aligned selection ranges between two resolved positions.\n *\n * The helper derives a suitable depth when none is provided and returns a\n * `SelectionRange` for each matching child node in the computed `NodeRange`.\n * Each returned range exposes `$from` as the resolved start position of the\n * node selection and `$to` as the resolved end position.\n *\n * @param $from The resolved anchor position where the selection starts.\n * @param $to The resolved head position where the selection ends.\n * @param depth An optional depth to force when creating the ProseMirror `NodeRange`.\n * When omitted, the depth is inferred from the shared depth of `$from` and `$to`.\n * @param options Optional behavior flags for how boundary nodes are handled.\n * @param options.extendOnBoundaryOverlap Whether touching only a node's start\n * or end content boundary should still include that node in the returned ranges.\n * @returns An array of `SelectionRange` objects for the nodes covered at the\n * computed depth.\n * @example\n * ```ts\n * const { $from, $to } = editor.state.selection\n * const ranges = getSelectionRanges($from, $to, undefined, {\n * extendOnBoundaryOverlap: false,\n * })\n *\n * ranges.forEach(range => {\n * console.log(range.$from.pos, range.$to.pos)\n * })\n * ```\n */\nexport function getSelectionRanges(\n $from: ResolvedPos,\n $to: ResolvedPos,\n depth?: number,\n options: GetSelectionRangesOptions = {},\n): SelectionRange[] {\n const ranges: SelectionRange[] = []\n const doc = $from.node(0)\n const { extendOnBoundaryOverlap = true } = options\n\n // Determine the appropriate depth\n if (typeof depth === 'number' && depth >= 0) {\n // Use the provided depth\n } else if ($from.sameParent($to)) {\n depth = Math.max(0, $from.sharedDepth($to.pos) - 1)\n } else {\n depth = $from.sharedDepth($to.pos)\n }\n\n const nodeRange = new NodeRange($from, $to, depth)\n const offset = nodeRange.depth === 0 ? 0 : doc.resolve(nodeRange.start).posAtIndex(0)\n\n nodeRange.parent.forEach((node, pos) => {\n const from = offset + pos\n const to = from + node.nodeSize\n const contentBounds = getNodeContentBounds(from, node.nodeSize, node)\n const overlapsNodeContent = extendOnBoundaryOverlap\n ? $to.pos >= contentBounds.start && $from.pos <= contentBounds.end\n : $to.pos > contentBounds.start && $from.pos < contentBounds.end\n\n if (from < nodeRange.start || from >= nodeRange.end) {\n return\n }\n\n if (!overlapsNodeContent) {\n return\n }\n\n const selectionRange = new SelectionRange(doc.resolve(from), doc.resolve(to))\n\n ranges.push(selectionRange)\n })\n\n return ranges\n}\n","import type { Node as ProseMirrorNode, ResolvedPos } from '@tiptap/pm/model'\nimport { Selection } from '@tiptap/pm/state'\nimport type { Mapping } from '@tiptap/pm/transform'\n\nimport { getSelectionRanges } from './getSelectionRanges.js'\nimport { NodeRangeBookmark } from './NodeRangeBookmark.js'\n\nexport class NodeRangeSelection extends Selection {\n depth: number | undefined\n\n constructor($anchor: ResolvedPos, $head: ResolvedPos, depth?: number, bias = 1) {\n // if there is only a cursor we can’t calculate a direction of the selection\n // that’s why we adjust the head position by 1 in the desired direction\n const { doc } = $anchor\n const isCursor = $anchor === $head\n const isCursorAtEnd = $anchor.pos === doc.content.size && $head.pos === doc.content.size\n const $correctedHead = isCursor && !isCursorAtEnd ? doc.resolve($head.pos + (bias > 0 ? 1 : -1)) : $head\n const $correctedAnchor = isCursor && isCursorAtEnd ? doc.resolve($anchor.pos - (bias > 0 ? 1 : -1)) : $anchor\n\n const ranges = getSelectionRanges($correctedAnchor.min($correctedHead), $correctedAnchor.max($correctedHead), depth)\n\n // get the smallest range start position\n // this will become the $anchor\n const $rangeFrom = $correctedHead.pos >= $anchor.pos ? ranges[0].$from : ranges[ranges.length - 1].$to\n\n // get the biggest range end position\n // this will become the $head\n const $rangeTo = $correctedHead.pos >= $anchor.pos ? ranges[ranges.length - 1].$to : ranges[0].$from\n\n super($rangeFrom, $rangeTo, ranges)\n\n this.depth = depth\n }\n\n // we can safely ignore this TypeScript error: https://github.com/Microsoft/TypeScript/issues/338\n // @ts-ignore\n get $to() {\n return this.ranges[this.ranges.length - 1].$to\n }\n\n eq(other: Selection): boolean {\n return other instanceof NodeRangeSelection && other.$from.pos === this.$from.pos && other.$to.pos === this.$to.pos\n }\n\n map(doc: ProseMirrorNode, mapping: Mapping): NodeRangeSelection {\n const $anchor = doc.resolve(mapping.map(this.anchor))\n const $head = doc.resolve(mapping.map(this.head))\n\n return new NodeRangeSelection($anchor, $head)\n }\n\n toJSON() {\n return {\n type: 'nodeRange',\n anchor: this.anchor,\n head: this.head,\n }\n }\n\n get isForwards(): boolean {\n return this.head >= this.anchor\n }\n\n get isBackwards(): boolean {\n return !this.isForwards\n }\n\n extendBackwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isForwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(0, -1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($from, $to, this.depth)\n }\n\n const firstRange = this.ranges[0]\n const $from = doc.resolve(Math.max(0, firstRange.$from.pos - 1))\n\n return new NodeRangeSelection(this.$anchor, $from, this.depth)\n }\n\n extendForwards(): NodeRangeSelection {\n const { doc } = this.$from\n\n if (this.isBackwards && this.ranges.length > 1) {\n const ranges = this.ranges.slice(1)\n const $from = ranges[0].$from\n const $to = ranges[ranges.length - 1].$to\n\n return new NodeRangeSelection($to, $from, this.depth)\n }\n\n const lastRange = this.ranges[this.ranges.length - 1]\n const $to = doc.resolve(Math.min(doc.content.size, lastRange.$to.pos + 1))\n\n return new NodeRangeSelection(this.$anchor, $to, this.depth)\n }\n\n static fromJSON(doc: ProseMirrorNode, json: any): NodeRangeSelection {\n return new NodeRangeSelection(doc.resolve(json.anchor), doc.resolve(json.head))\n }\n\n static create(doc: ProseMirrorNode, anchor: number, head: number, depth?: number, bias = 1): NodeRangeSelection {\n return new this(doc.resolve(anchor), doc.resolve(head), depth, bias)\n }\n\n getBookmark(): NodeRangeBookmark {\n return new NodeRangeBookmark(this.anchor, this.head)\n }\n}\n\nNodeRangeSelection.prototype.visible = false\n","import type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport type { Mappable } from '@tiptap/pm/transform'\n\nimport { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport class NodeRangeBookmark {\n anchor!: number\n\n head!: number\n\n constructor(anchor: number, head: number) {\n this.anchor = anchor\n this.head = head\n }\n\n map(mapping: Mappable) {\n return new NodeRangeBookmark(mapping.map(this.anchor), mapping.map(this.head))\n }\n\n resolve(doc: ProseMirrorNode) {\n const $anchor = doc.resolve(this.anchor)\n const $head = doc.resolve(this.head)\n\n return new NodeRangeSelection($anchor, $head)\n }\n}\n","import { NodeRangeSelection } from './NodeRangeSelection.js'\n\nexport function isNodeRangeSelection(value: unknown): value is NodeRangeSelection {\n return value instanceof NodeRangeSelection\n}\n","import { NodeRange } from './node-range.js'\n\nexport * from './helpers/getNodeRangeDecorations.js'\nexport * from './helpers/getSelectionRanges.js'\nexport * from './helpers/isNodeRangeSelection.js'\nexport * from './helpers/NodeRangeSelection.js'\nexport * from './node-range.js'\n\nexport default NodeRange\n"],"mappings":"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","names":["$from","$to","NodeRange","nodeRangeSelection","NodeRange"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/extension-node-range",
3
3
  "description": "node range extension for tiptap",
4
- "version": "3.22.1",
4
+ "version": "3.22.3",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -36,12 +36,12 @@
36
36
  "dist"
37
37
  ],
38
38
  "peerDependencies": {
39
- "@tiptap/core": "^3.22.1",
40
- "@tiptap/pm": "^3.22.1"
39
+ "@tiptap/core": "^3.22.3",
40
+ "@tiptap/pm": "^3.22.3"
41
41
  },
42
42
  "devDependencies": {
43
- "@tiptap/core": "^3.22.1",
44
- "@tiptap/pm": "^3.22.1"
43
+ "@tiptap/core": "^3.22.3",
44
+ "@tiptap/pm": "^3.22.3"
45
45
  },
46
46
  "scripts": {
47
47
  "build": "tsup",
@@ -1,9 +1,62 @@
1
1
  import { type ResolvedPos, NodeRange } from '@tiptap/pm/model'
2
2
  import { SelectionRange } from '@tiptap/pm/state'
3
3
 
4
- export function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?: number): SelectionRange[] {
4
+ export interface GetSelectionRangesOptions {
5
+ /**
6
+ * Whether nodes should be included when the selection only overlaps their
7
+ * start or end content boundary.
8
+ * @default true
9
+ */
10
+ extendOnBoundaryOverlap?: boolean
11
+ }
12
+
13
+ function getNodeContentBounds(nodeStart: number, nodeSize: number, node: { isText: boolean; isAtom: boolean }) {
14
+ const contentOffset = node.isText || node.isAtom ? 0 : 1
15
+
16
+ return {
17
+ start: nodeStart + contentOffset,
18
+ end: nodeStart + nodeSize - contentOffset,
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Calculates node-aligned selection ranges between two resolved positions.
24
+ *
25
+ * The helper derives a suitable depth when none is provided and returns a
26
+ * `SelectionRange` for each matching child node in the computed `NodeRange`.
27
+ * Each returned range exposes `$from` as the resolved start position of the
28
+ * node selection and `$to` as the resolved end position.
29
+ *
30
+ * @param $from The resolved anchor position where the selection starts.
31
+ * @param $to The resolved head position where the selection ends.
32
+ * @param depth An optional depth to force when creating the ProseMirror `NodeRange`.
33
+ * When omitted, the depth is inferred from the shared depth of `$from` and `$to`.
34
+ * @param options Optional behavior flags for how boundary nodes are handled.
35
+ * @param options.extendOnBoundaryOverlap Whether touching only a node's start
36
+ * or end content boundary should still include that node in the returned ranges.
37
+ * @returns An array of `SelectionRange` objects for the nodes covered at the
38
+ * computed depth.
39
+ * @example
40
+ * ```ts
41
+ * const { $from, $to } = editor.state.selection
42
+ * const ranges = getSelectionRanges($from, $to, undefined, {
43
+ * extendOnBoundaryOverlap: false,
44
+ * })
45
+ *
46
+ * ranges.forEach(range => {
47
+ * console.log(range.$from.pos, range.$to.pos)
48
+ * })
49
+ * ```
50
+ */
51
+ export function getSelectionRanges(
52
+ $from: ResolvedPos,
53
+ $to: ResolvedPos,
54
+ depth?: number,
55
+ options: GetSelectionRangesOptions = {},
56
+ ): SelectionRange[] {
5
57
  const ranges: SelectionRange[] = []
6
58
  const doc = $from.node(0)
59
+ const { extendOnBoundaryOverlap = true } = options
7
60
 
8
61
  // Determine the appropriate depth
9
62
  if (typeof depth === 'number' && depth >= 0) {
@@ -20,11 +73,19 @@ export function getSelectionRanges($from: ResolvedPos, $to: ResolvedPos, depth?:
20
73
  nodeRange.parent.forEach((node, pos) => {
21
74
  const from = offset + pos
22
75
  const to = from + node.nodeSize
76
+ const contentBounds = getNodeContentBounds(from, node.nodeSize, node)
77
+ const overlapsNodeContent = extendOnBoundaryOverlap
78
+ ? $to.pos >= contentBounds.start && $from.pos <= contentBounds.end
79
+ : $to.pos > contentBounds.start && $from.pos < contentBounds.end
23
80
 
24
81
  if (from < nodeRange.start || from >= nodeRange.end) {
25
82
  return
26
83
  }
27
84
 
85
+ if (!overlapsNodeContent) {
86
+ return
87
+ }
88
+
28
89
  const selectionRange = new SelectionRange(doc.resolve(from), doc.resolve(to))
29
90
 
30
91
  ranges.push(selectionRange)