jedmund-svelte/src/lib/components/edra/extensions/table/utils.ts

320 lines
7.4 KiB
TypeScript

import { Editor, findParentNode } from '@tiptap/core'
import { CellSelection, type Rect, TableMap } from '@tiptap/pm/tables'
import type { EditorView } from '@tiptap/pm/view'
import Table from './table.js'
export const isRectSelected = (rect: Rect) => (selection: CellSelection) => {
const map = TableMap.get(selection.$anchorCell.node(-1))
const start = selection.$anchorCell.start(-1)
const cells = map.cellsInRect(rect)
const selectedCells = map.cellsInRect(
map.rectBetween(selection.$anchorCell.pos - start, selection.$headCell.pos - start)
)
for (let i = 0, count = cells.length; i < count; i += 1) {
if (selectedCells.indexOf(cells[i]) === -1) {
return false
}
}
return true
}
export const findTable = (selection: Selection) =>
findParentNode((node) => node.type.spec.tableRole && node.type.spec.tableRole === 'table')(
selection
)
export const isCellSelection = (selection: Selection): selection is CellSelection =>
selection instanceof CellSelection
export const isColumnSelected = (columnIndex: number) => (selection: Selection) => {
if (isCellSelection(selection)) {
const map = TableMap.get(selection.$anchorCell.node(-1))
return isRectSelected({
left: columnIndex,
right: columnIndex + 1,
top: 0,
bottom: map.height
})(selection)
}
return false
}
export const isRowSelected = (rowIndex: number) => (selection: Selection) => {
if (isCellSelection(selection)) {
const map = TableMap.get(selection.$anchorCell.node(-1))
return isRectSelected({
left: 0,
right: map.width,
top: rowIndex,
bottom: rowIndex + 1
})(selection)
}
return false
}
export const isTableSelected = (selection: Selection) => {
if (isCellSelection(selection)) {
const map = TableMap.get(selection.$anchorCell.node(-1))
return isRectSelected({
left: 0,
right: map.width,
top: 0,
bottom: map.height
})(selection)
}
return false
}
export const getCellsInColumn = (columnIndex: number | number[]) => (selection: Selection) => {
const table = findTable(selection)
if (table) {
const map = TableMap.get(table.node)
const indexes = Array.isArray(columnIndex) ? columnIndex : Array.from([columnIndex])
return indexes.reduce(
(acc, index) => {
if (index >= 0 && index <= map.width - 1) {
const cells = map.cellsInRect({
left: index,
right: index + 1,
top: 0,
bottom: map.height
})
return acc.concat(
cells.map((nodePos) => {
const node = table.node.nodeAt(nodePos)
const pos = nodePos + table.start
return { pos, start: pos + 1, node }
})
)
}
return acc
},
[] as { pos: number; start: number; node: Node | null | undefined }[]
)
}
return null
}
export const getCellsInRow = (rowIndex: number | number[]) => (selection: Selection) => {
const table = findTable(selection)
if (table) {
const map = TableMap.get(table.node)
const indexes = Array.isArray(rowIndex) ? rowIndex : Array.from([rowIndex])
return indexes.reduce(
(acc, index) => {
if (index >= 0 && index <= map.height - 1) {
const cells = map.cellsInRect({
left: 0,
right: map.width,
top: index,
bottom: index + 1
})
return acc.concat(
cells.map((nodePos) => {
const node = table.node.nodeAt(nodePos)
const pos = nodePos + table.start
return { pos, start: pos + 1, node }
})
)
}
return acc
},
[] as { pos: number; start: number; node: Node | null | undefined }[]
)
}
return null
}
export const getCellsInTable = (selection: Selection) => {
const table = findTable(selection)
if (table) {
const map = TableMap.get(table.node)
const cells = map.cellsInRect({
left: 0,
right: map.width,
top: 0,
bottom: map.height
})
return cells.map((nodePos) => {
const node = table.node.nodeAt(nodePos)
const pos = nodePos + table.start
return { pos, start: pos + 1, node }
})
}
return null
}
export const findParentNodeClosestToPos = (
$pos: ResolvedPos,
predicate: (node: Node) => boolean
) => {
for (let i = $pos.depth; i > 0; i -= 1) {
const node = $pos.node(i)
if (predicate(node)) {
return {
pos: i > 0 ? $pos.before(i) : 0,
start: $pos.start(i),
depth: i,
node
}
}
}
return null
}
export const findCellClosestToPos = ($pos: ResolvedPos) => {
const predicate = (node: Node) =>
node.type.spec.tableRole && /cell/i.test(node.type.spec.tableRole)
return findParentNodeClosestToPos($pos, predicate)
}
const select = (type: 'row' | 'column') => (index: number) => (tr: Transaction) => {
const table = findTable(tr.selection)
const isRowSelection = type === 'row'
if (table) {
const map = TableMap.get(table.node)
// Check if the index is valid
if (index >= 0 && index < (isRowSelection ? map.height : map.width)) {
const left = isRowSelection ? 0 : index
const top = isRowSelection ? index : 0
const right = isRowSelection ? map.width : index + 1
const bottom = isRowSelection ? index + 1 : map.height
const cellsInFirstRow = map.cellsInRect({
left,
top,
right: isRowSelection ? right : left + 1,
bottom: isRowSelection ? top + 1 : bottom
})
const cellsInLastRow =
bottom - top === 1
? cellsInFirstRow
: map.cellsInRect({
left: isRowSelection ? left : right - 1,
top: isRowSelection ? bottom - 1 : top,
right,
bottom
})
const head = table.start + cellsInFirstRow[0]
const anchor = table.start + cellsInLastRow[cellsInLastRow.length - 1]
const $head = tr.doc.resolve(head)
const $anchor = tr.doc.resolve(anchor)
return tr.setSelection(new CellSelection($anchor, $head))
}
}
return tr
}
export const selectColumn = select('column')
export const selectRow = select('row')
export const selectTable = (tr: Transaction) => {
const table = findTable(tr.selection)
if (table) {
const { map } = TableMap.get(table.node)
if (map && map.length) {
const head = table.start + map[0]
const anchor = table.start + map[map.length - 1]
const $head = tr.doc.resolve(head)
const $anchor = tr.doc.resolve(anchor)
return tr.setSelection(new CellSelection($anchor, $head))
}
}
return tr
}
export const isColumnGripSelected = ({
editor,
view,
state,
from
}: {
editor: Editor
view: EditorView
state: EditorState
from: number
}) => {
const domAtPos = view.domAtPos(from).node as HTMLElement
const nodeDOM = view.nodeDOM(from) as HTMLElement
const node = nodeDOM || domAtPos
if (!editor.isActive(Table.name) || !node || isTableSelected(state.selection)) {
return false
}
let container = node
while (container && !['TD', 'TH'].includes(container.tagName)) {
container = container.parentElement!
}
const gripColumn =
container && container.querySelector && container.querySelector('a.grip-column.selected')
return !!gripColumn
}
export const isRowGripSelected = ({
editor,
view,
state,
from
}: {
editor: Editor
view: EditorView
state: EditorState
from: number
}) => {
const domAtPos = view.domAtPos(from).node as HTMLElement
const nodeDOM = view.nodeDOM(from) as HTMLElement
const node = nodeDOM || domAtPos
if (!editor.isActive(Table.name) || !node || isTableSelected(state.selection)) {
return false
}
let container = node
while (container && !['TD', 'TH'].includes(container.tagName)) {
container = container.parentElement!
}
const gripRow =
container && container.querySelector && container.querySelector('a.grip-row.selected')
return !!gripRow
}