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

Implement movement with Tab key inside Table #2529

Merged
merged 9 commits into from
Apr 2, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const Up = 'ArrowUp';
const Down = 'ArrowDown';
const Left = 'ArrowLeft';
const Right = 'ArrowRight';
const Tab = 'Tab';

class SelectionPlugin implements PluginWithState<SelectionPluginState> {
private editor: IEditor | null = null;
Expand Down Expand Up @@ -132,8 +133,7 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {

return;
} else if (selection?.type == 'image' && selection.image !== rawEvent.target) {
this.selectBeforeImage(editor, selection.image);

this.selectBeforeOrAfterElement(editor, selection.image);
Andres-CT98 marked this conversation as resolved.
Show resolved Hide resolved
return;
}

Expand Down Expand Up @@ -265,25 +265,27 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
case 'image':
if (!isModifierKey(rawEvent) && !rawEvent.shiftKey && selection.image.parentNode) {
if (key === 'Escape') {
this.selectBeforeImage(editor, selection.image);
this.selectBeforeOrAfterElement(editor, selection.image);
rawEvent.stopPropagation();
} else if (key !== 'Delete' && key !== 'Backspace') {
this.selectBeforeImage(editor, selection.image);
this.selectBeforeOrAfterElement(editor, selection.image);
}
}
break;

case 'range':
if (key == Up || key == Down || key == Left || key == Right) {
if (key == Up || key == Down || key == Left || key == Right || key == Tab) {
const start = selection.range.startContainer;
this.state.tableSelection = this.parseTableSelection(
start,
start,
editor.getDOMHelper()
);

const rangeKey = key == Tab ? this.getTabKey(rawEvent) : key;

if (this.state.tableSelection) {
win?.requestAnimationFrame(() => this.handleSelectionInTable(key));
win?.requestAnimationFrame(() => this.handleSelectionInTable(rangeKey));
}
}
break;
Expand Down Expand Up @@ -316,7 +318,13 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}
}

private handleSelectionInTable(key: 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight') {
private getTabKey(rawEvent: KeyboardEvent) {
return rawEvent.shiftKey ? 'TabLeft' : 'TabRight';
}

private handleSelectionInTable(
key: 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight' | 'TabLeft' | 'TabRight'
) {
if (!this.editor || !this.state.tableSelection) {
return;
}
Expand All @@ -340,8 +348,8 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
let lastCo = findCoordinate(tableSel?.parsedTable, end, domHelper);
const { parsedTable, firstCo: oldCo, table } = this.state.tableSelection;

if (lastCo && tableSel.table == table && lastCo.col != oldCo.col) {
if (key == Up || key == Down) {
if (lastCo && tableSel.table == table) {
if ((lastCo.col != oldCo.col && key == Up) || key == Down) {
Andres-CT98 marked this conversation as resolved.
Show resolved Hide resolved
const change = key == Up ? -1 : 1;
const originalTd = findTableCellElement(parsedTable, oldCo)?.cell;
let td: HTMLTableCellElement | null = null;
Expand All @@ -359,24 +367,42 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}

if (collapsed && td) {
const { node, offset } = normalizePos(
Andres-CT98 marked this conversation as resolved.
Show resolved Hide resolved
this.setRangeSelectionInTable(
td,
key == Up ? td.childNodes.length : 0
);
const range = this.editor.getDocument().createRange();

range.setStart(node, offset);
range.collapse(true /*toStart*/);

this.setDOMSelection(
{
type: 'range',
range,
isReverted: false,
},
null /*tableSelection*/
key == Up ? td.childNodes.length : 0,
this.editor
);
}
} else if (key == 'TabLeft' || key == 'TabRight') {
const revrse = key == 'TabLeft';
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: just small typo

Copy link
Contributor

Choose a reason for hiding this comment

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

If RTL is enabled, should the behavior be same or should we invert the keys?

Copy link
Contributor Author

@Andres-CT98 Andres-CT98 Mar 27, 2024

Choose a reason for hiding this comment

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

Good question, It works the same both ways. In an HTML Table, the order of the cells is the same in RTL and LTR. This tab change works on a 2D representation of the HTML table where the cursor is.

for (
let step = revrse ? -1 : 1,
row = lastCo.row ?? 0,
col = (lastCo.col ?? 0) + step;
;
col += step
) {
if (col < 0 || col >= parsedTable[row].length) {
row += step;
if (row < 0) {
this.selectBeforeOrAfterElement(this.editor, tableSel.table);
break;
} else if (row >= parsedTable.length) {
this.selectBeforeOrAfterElement(
this.editor,
tableSel.table,
true /*after*/
);
break;
}
col = revrse ? parsedTable[row].length - 1 : 0;
}
const cell = parsedTable[row][col];
if (typeof cell != 'string') {
this.setRangeSelectionInTable(cell, 0, this.editor);
break;
}
}
} else {
this.state.tableSelection = null;
}
Expand All @@ -389,6 +415,23 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
}
}

private setRangeSelectionInTable(cell: Node, nodeOffset: number, editor: IEditor) {
const { node, offset } = normalizePos(cell, nodeOffset);
const range = editor.getDocument().createRange();

range.setStart(node, offset);
range.collapse(true /*toStart*/);

this.setDOMSelection(
{
type: 'range',
range,
isReverted: false,
},
null /*tableSelection*/
);
}

private updateTableSelectionFromKeyboard(rowChange: number, colChange: number) {
if (this.state.tableSelection?.lastCo && this.editor) {
const { lastCo, parsedTable } = this.state.tableSelection;
Expand All @@ -411,14 +454,14 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
);
}

private selectBeforeImage(editor: IEditor, image: HTMLImageElement) {
private selectBeforeOrAfterElement(editor: IEditor, element: HTMLElement, after?: boolean) {
const doc = editor.getDocument();
const parent = image.parentNode;
const index = parent && toArray(parent.childNodes).indexOf(image);
const parent = element.parentNode;
const index = parent && toArray(parent.childNodes).indexOf(element);

if (parent && index !== null && index >= 0) {
const range = doc.createRange();
range.setStart(parent, index);
range.setStart(parent, index + (after ? 1 : 0));
range.collapse();

this.setDOMSelection(
Expand Down
Loading
Loading