Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support rectangular selection when selecting a merged cell #271

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export { tableEditingKey };
*/
export type TableEditingOptions = {
allowTableNodeSelection?: boolean;
supportRectangularSelection?: boolean;
};

/**
Expand All @@ -86,6 +87,7 @@ export type TableEditingOptions = {
*/
export function tableEditing({
allowTableNodeSelection = false,
supportRectangularSelection = false,
}: TableEditingOptions = {}): Plugin {
return new Plugin({
key: tableEditingKey,
Expand All @@ -110,7 +112,7 @@ export function tableEditing({
decorations: drawCellSelection,

handleDOMEvents: {
mousedown: handleMouseDown,
mousedown: (view, event) => handleMouseDown(view, event, supportRectangularSelection),
},

createSelectionBetween(view) {
Expand Down
73 changes: 72 additions & 1 deletion src/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ import {

type Axis = 'horiz' | 'vert';

interface Rect {
left: number;
top: number;
right: number;
bottom: number;
}

/**
* @public
*/
Expand Down Expand Up @@ -127,6 +134,51 @@ export function handleTripleClick(view: EditorView, pos: number): boolean {
return true;
}

// judge rect is rectangle
export function judgeRectangle(tableMap: TableMap, rect: Rect) {
const { width, map, height } = tableMap;
const mergedCellsIndices = []
let indexTop = rect.top * width + rect.left; let indexLeft = indexTop;
let indexBottom = (rect.bottom - 1) * width + rect.left; let indexRight = indexTop + (rect.right - rect.left - 1);
for (let i = rect.top; i < rect.bottom; i++) {
if (rect.left > 0 && map[indexLeft] === map[indexLeft - 1] || rect.right < width && map[indexRight] === map[indexRight + 1]) {
if (map[indexLeft] === map[indexLeft - 1]) mergedCellsIndices.push(indexLeft - 1)
if (map[indexRight] === map[indexRight + 1]) mergedCellsIndices.push(indexRight + 1)
}
indexLeft += width; indexRight += width;
}
for (let i = rect.left; i < rect.right; i++) {
if (rect.top > 0 && map[indexTop] === map[indexTop - width] || rect.bottom < height && map[indexBottom] === map[indexBottom + width]) {
if (map[indexTop] === map[indexTop - width]) mergedCellsIndices.push(indexTop - width)
if (map[indexBottom] === map[indexBottom + width]) mergedCellsIndices.push(indexBottom + width)
}
indexTop++; indexBottom++;
}
return Array.from(new Set(mergedCellsIndices))
}

// get rectangular
export function getRectangularRect (rect: Rect, tableMap: TableMap) {
let mergedCellsIndices = []
const rectangle = JSON.parse(JSON.stringify(rect))
while ((mergedCellsIndices = judgeRectangle(tableMap, rectangle)).length) {
let maxRow = 0, minRow = Infinity, maxCol = 0, minCol = Infinity
mergedCellsIndices.forEach((index: number) => {
const rowIndex = Math.floor(index / tableMap.width)
const colIndex = index % tableMap.width
maxRow = Math.max(rowIndex, rectangle.bottom - 1, maxRow)
minRow = Math.min(rowIndex, rectangle.top, minRow)
maxCol = Math.max(colIndex, rectangle.right - 1, maxCol)
minCol = Math.min(colIndex, rectangle.left, minCol)
})
rectangle.left = minCol
rectangle.right = maxCol + 1
rectangle.top = minRow
rectangle.bottom = maxRow + 1
}
return rectangle
}

/**
* @public
*/
Expand Down Expand Up @@ -177,6 +229,7 @@ export function handlePaste(
export function handleMouseDown(
view: EditorView,
startEvent: MouseEvent,
supportRectangularSelection?: boolean
): void {
if (startEvent.ctrlKey || startEvent.metaKey) return;

Expand Down Expand Up @@ -210,14 +263,32 @@ export function handleMouseDown(
if (starting) $head = $anchor;
else return;
}
const selection = new CellSelection($anchor, $head);
const selection = supportRectangularSelection
? getRectangularSelection($anchor, $head)
: new CellSelection($anchor, $head);

if (starting || !view.state.selection.eq(selection)) {
const tr = view.state.tr.setSelection(selection);
if (starting) tr.setMeta(tableEditingKey, $anchor.pos);
view.dispatch(tr);
}
}

// get Rectangular cell Selection
function getRectangularSelection ($anchor: ResolvedPos, $head: ResolvedPos) {
const tableNode = $anchor.node(-1)
const tableMap = TableMap.get(tableNode)
const tableStart = $anchor.start(-1)
const rect = tableMap.rectBetween($anchor.pos - tableStart , $head.pos - tableStart)
const rectangle = getRectangularRect(rect, tableMap)
const { left, right, top, bottom } = rectangle
const { map, width } = tableMap
const { tr } = view.state
const $anchorCell = tr.doc.resolve(map[top * width + left] + tableStart)
const $headCell = tr.doc.resolve(map[(bottom - 1) * width + right - 1] + tableStart)
return new CellSelection($anchorCell, $headCell)
}

// Stop listening to mouse motion events.
function stop(): void {
view.root.removeEventListener('mouseup', stop);
Expand Down