Skip to content
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
5 changes: 4 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,8 @@ export function tableEditing({
decorations: drawCellSelection,

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

createSelectionBetween(view) {
Expand Down
95 changes: 94 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 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just import type {Rect} from './tablemap'.

left: number;
top: number;
right: number;
bottom: number;
}

/**
* @public
*/
Expand Down Expand Up @@ -127,6 +134,68 @@ 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));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's copy the properties directly.

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 +246,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 +280,37 @@ 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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this function out of handleMouseDown so that we can write test for it.

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,
);
Comment on lines +306 to +310
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you don't need tr to the doc. You can use const doc = $anchor.doc

return new CellSelection($anchorCell, $headCell);
}

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