Files
docmost/packages/editor-ext/src/lib/table/utils/query.ts
T
Mirone 7d1e5bce0d feat: table row/column drag and drop (#1467)
* chore: add dev container

* feat: add drag handle when hovering cell

* feat: add column drag and drop

* feat: add support for row drag and drop

* refactor: extract preview controllers

* fix: hover issue

* refactor: add handle controller

* chore: f

* chore: remove log

* chore: remove dev files

* feat: hide other drop indicators when table dnd working

* feat: add auto scroll and bug fix

* chore: f

* fix: firefox
2025-08-31 18:53:27 +01:00

123 lines
2.6 KiB
TypeScript

import type {
Node,
ResolvedPos,
} from '@tiptap/pm/model'
import type { Selection } from '@tiptap/pm/state'
import {
cellAround,
cellNear,
CellSelection,
inSameTable,
} from '@tiptap/pm/tables'
/**
* Checks if the given object is a `CellSelection` instance.
*
* @public
*/
export function isCellSelection(value: unknown): value is CellSelection {
return value instanceof CellSelection
}
/**
* Find the closest table node.
*
* @internal
*/
export function findTable($pos: ResolvedPos): FindParentNodeResult | undefined {
return findParentNode((node) => node.type.spec.tableRole === 'table', $pos)
}
/**
* Try to find the anchor and head cell in the same table by using the given
* anchor and head as hit points, or fallback to the selection's anchor and
* head.
*
* @internal
*/
export function findCellRange(
selection: Selection,
anchorHit?: number,
headHit?: number,
): [ResolvedPos, ResolvedPos] | undefined {
if (anchorHit == null && headHit == null && isCellSelection(selection)) {
return [selection.$anchorCell, selection.$headCell]
}
const anchor: number = anchorHit ?? headHit ?? selection.anchor
const head: number = headHit ?? anchorHit ?? selection.head
const doc = selection.$head.doc
const $anchorCell = findCellPos(doc, anchor)
const $headCell = findCellPos(doc, head)
if ($anchorCell && $headCell && inSameTable($anchorCell, $headCell)) {
return [$anchorCell, $headCell]
}
}
/**
* Try to find a resolved pos of a cell by using the given pos as a hit point.
*
* @internal
*/
export function findCellPos(
doc: Node,
pos: number,
): ResolvedPos | undefined {
const $pos = doc.resolve(pos)
return cellAround($pos) || cellNear($pos)
}
/**
* @internal
*/
export interface FindParentNodeResult {
/**
* The closest parent node that satisfies the predicate.
*/
node: Node
/**
* The position directly before the node.
*/
pos: number
/**
* The position at the start of the node.
*/
start: number
/**
* The depth of the node.
*/
depth: number
}
/**
* Find the closest parent node that satisfies the predicate.
*
* @internal
*/
export function findParentNode(
/**
* The predicate to test the parent node.
*/
predicate: (node: Node) => boolean,
/**
* The position to start searching from.
*/
$pos: ResolvedPos,
): FindParentNodeResult | undefined {
for (let depth = $pos.depth; depth >= 0; depth -= 1) {
const node = $pos.node(depth)
if (predicate(node)) {
const pos = depth === 0 ? 0 : $pos.before(depth)
const start = $pos.start(depth)
return { node, pos, start, depth }
}
}
}