Skip to content

Commit 0e14dce

Browse files
author
ilimei
committed
✨ feat(table): enhance table controller visibility with focus and hover states
1 parent 63de413 commit 0e14dce

3 files changed

Lines changed: 65 additions & 12 deletions

File tree

src/plugins/table/react/TableColController.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ const TableColController = memo<TableColControllerProps>(({ editor, node }) => {
5858
[editor, node],
5959
);
6060
const [colWidths, setColWidths] = useState(initialColWidths);
61-
const { isTableSelected, selectedColumns } = useTableControllerSelection(editor, node);
61+
const { isTableFocused, isTableSelected, selectedColumns } = useTableControllerSelection(
62+
editor,
63+
node,
64+
);
6265
const anchorColumnIndexRef = useRef<number | null>(null);
6366
const colTopRef = useRef<HTMLDivElement | null>(null);
6467
const deletePreviewElementsRef = useRef<HTMLElement[]>([]);
@@ -73,6 +76,7 @@ const TableColController = memo<TableColControllerProps>(({ editor, node }) => {
7376
index: number;
7477
insertAfter: boolean;
7578
} | null>(null);
79+
const [isControllerHovered, setControllerHovered] = useState(false);
7680
const [isInsertButtonHovered, setInsertButtonHovered] = useState(false);
7781
const insertColumnOffset = insertTarget
7882
? sum(colWidths.slice(0, insertTarget.index)) +
@@ -201,12 +205,31 @@ const TableColController = memo<TableColControllerProps>(({ editor, node }) => {
201205
};
202206
}, [clearDeletePreview]);
203207

208+
const shouldShowController = isTableFocused || isControllerHovered;
209+
210+
useEffect(() => {
211+
if (!shouldShowController) {
212+
clearInsertButtonHideTimer();
213+
clearDeletePreview();
214+
setInsertTarget(null);
215+
setInsertButtonHovered(false);
216+
}
217+
}, [clearDeletePreview, shouldShowController]);
218+
219+
if (!shouldShowController) {
220+
return null;
221+
}
222+
204223
return (
205224
<LexicalPortalContainer editor={editor} node={node}>
206225
<div className="table-controller-col" contentEditable={false}>
207226
<div
208227
className={cx('top', styles.colTop)}
228+
onMouseEnter={() => {
229+
setControllerHovered(true);
230+
}}
209231
onMouseLeave={() => {
232+
setControllerHovered(false);
210233
scheduleHideInsertButton();
211234
}}
212235
ref={colTopRef}

src/plugins/table/react/TableRowController.tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ const TableRowController = memo<TableRowControllerProps>(({ editor, node }) => {
6464
[editor, node],
6565
);
6666
const [rowHeights, setRowHeights] = useState(initialRowHeights);
67-
const { isTableSelected, selectedRows } = useTableControllerSelection(editor, node);
67+
const { isTableFocused, isTableSelected, selectedRows } = useTableControllerSelection(
68+
editor,
69+
node,
70+
);
6871
const anchorRowIndexRef = useRef<number | null>(null);
6972
const deletePreviewElementsRef = useRef<HTMLElement[]>([]);
7073
const insertButtonHoveredRef = useRef(false);
@@ -79,6 +82,7 @@ const TableRowController = memo<TableRowControllerProps>(({ editor, node }) => {
7982
index: number;
8083
insertAfter: boolean;
8184
} | null>(null);
85+
const [isControllerHovered, setControllerHovered] = useState(false);
8286
const [isInsertButtonHovered, setInsertButtonHovered] = useState(false);
8387
const insertRowOffset = insertTarget
8488
? sum(rowHeights.slice(0, insertTarget.index)) +
@@ -213,15 +217,34 @@ const TableRowController = memo<TableRowControllerProps>(({ editor, node }) => {
213217
};
214218
}, [editor, node]);
215219

220+
const shouldShowController = isTableFocused || isControllerHovered;
221+
222+
useEffect(() => {
223+
if (!shouldShowController) {
224+
clearInsertButtonHideTimer();
225+
clearDeletePreview();
226+
setInsertTarget(null);
227+
setInsertButtonHovered(false);
228+
}
229+
}, [clearDeletePreview, shouldShowController]);
230+
231+
if (!shouldShowController) {
232+
return null;
233+
}
234+
216235
return (
217-
<div className="table-controller-row" contentEditable={false}>
218-
<div
219-
className={cx('left', styles.rowLeft)}
220-
onMouseLeave={() => {
221-
scheduleHideInsertButton();
222-
}}
223-
ref={rowLeftRef}
224-
>
236+
<div
237+
className="table-controller-row"
238+
contentEditable={false}
239+
onMouseEnter={() => {
240+
setControllerHovered(true);
241+
}}
242+
onMouseLeave={() => {
243+
setControllerHovered(false);
244+
scheduleHideInsertButton();
245+
}}
246+
>
247+
<div className={cx('left', styles.rowLeft)} ref={rowLeftRef}>
225248
<TableDeleteButton
226249
ariaLabel="Delete selected rows"
227250
offset={selectedRowTop + selectedRowHeight / 2}

src/plugins/table/react/hooks.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
* LICENSE file in the root directory of this source tree.
66
*
77
*/
8-
import { TableNode } from '@lexical/table';
8+
import { $findTableNode, $isTableSelection, TableNode } from '@lexical/table';
99
import { debounce } from 'es-toolkit/compat';
10-
import { $getSelection, LexicalEditor } from 'lexical';
10+
import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical';
1111
import { useEffect, useMemo, useRef, useState } from 'react';
1212

1313
import { getTableSelectionIndexes, isTableFullySelected } from '../utils';
1414

1515
export interface TableControllerSelection {
16+
isTableFocused: boolean;
1617
isTableSelected: boolean;
1718
selectedColumns: number[];
1819
selectedRows: number[];
@@ -23,6 +24,7 @@ const isSameSelection = (
2324
next: TableControllerSelection,
2425
): boolean => {
2526
return (
27+
current.isTableFocused === next.isTableFocused &&
2628
current.isTableSelected === next.isTableSelected &&
2729
current.selectedColumns.length === next.selectedColumns.length &&
2830
current.selectedRows.length === next.selectedRows.length &&
@@ -43,8 +45,13 @@ const readTableControllerSelection = (
4345
const tableKey = latestNode.getKey();
4446

4547
const selectionIndexes = getTableSelectionIndexes(selection, tableKey, columnCount, rowCount);
48+
const isTableFocused =
49+
($isTableSelection(selection) && selection.tableKey === tableKey) ||
50+
($isRangeSelection(selection) &&
51+
Boolean($findTableNode(selection.anchor.getNode())?.is(latestNode)));
4652

4753
return {
54+
isTableFocused,
4855
isTableSelected: isTableFullySelected(selection, tableKey, columnCount, rowCount),
4956
...selectionIndexes,
5057
};

0 commit comments

Comments
 (0)