Skip to content

Commit 9fb0524

Browse files
committed
fix(YfmTable): fixed position of floating row and column controls when table scrolling horizontally
1 parent 38f3b57 commit 9fb0524

File tree

3 files changed

+100
-9
lines changed

3 files changed

+100
-9
lines changed

src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingMenu/FloatingMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212

1313
import {useBooleanState} from 'src/react-utils';
1414

15-
import {FloatingPopup, type FloatingPopupProps} from '../FloatingPopup';
15+
import {FloatingPopup, type FloatingPopupProps, type ReferenceType} from '../FloatingPopup';
1616

1717
const popupOffset: FloatingPopupProps['offset'] = {
1818
mainAxis: -9.5,
@@ -21,7 +21,7 @@ const popupOffset: FloatingPopupProps['offset'] = {
2121
export type FloatingMenuProps = {
2222
dirtype: 'row' | 'column';
2323
canDrag: boolean;
24-
anchorElement: Element;
24+
anchorElement: ReferenceType;
2525
dropdownItems: DropdownMenuProps<unknown>['items'];
2626
switcherMouseProps?: Pick<
2727
ButtonButtonProps,

src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingMenuControl/FloatingMenuControl.tsx

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {useMemo} from 'react';
22

3+
// eslint-disable-next-line import/no-extraneous-dependencies
4+
import type {ClientRectObject, VirtualElement} from '@floating-ui/dom';
35
import {
46
ArrowDown,
57
ArrowLeft,
@@ -16,10 +18,13 @@ import {i18n} from 'src/i18n/yfm-table';
1618
import type {DnDControlHandler} from '../../dnd/dnd';
1719
import {FloatingMenu, type FloatingMenuProps} from '../FloatingMenu/FloatingMenu';
1820

21+
type ControlType = FloatingMenuProps['dirtype'];
22+
1923
export type FloatingMenuControlProps = {
20-
acnhorElement: Element;
24+
cellElement: Element;
25+
tableElement: Element;
2126
multiple: boolean;
22-
type: FloatingMenuProps['dirtype'];
27+
type: ControlType;
2328
dndHandler?: DnDControlHandler;
2429
onMenuOpenToggle: FloatingMenuProps['onOpenToggle'];
2530
onClearCellsClick: () => void;
@@ -34,7 +39,8 @@ export const FloatingMenuControl: React.FC<FloatingMenuControlProps> =
3439
type,
3540
multiple,
3641
dndHandler,
37-
acnhorElement,
42+
cellElement,
43+
tableElement,
3844
onMenuOpenToggle,
3945
onClearCellsClick,
4046
onInsertBeforeClick,
@@ -94,12 +100,17 @@ export const FloatingMenuControl: React.FC<FloatingMenuControlProps> =
94100
],
95101
);
96102

103+
const anchor = useMemo(
104+
() => getVirtualAnchor(type, tableElement, cellElement),
105+
[type, tableElement, cellElement],
106+
);
107+
97108
return (
98109
<FloatingMenu
99110
dirtype={type}
100111
canDrag={dndHandler ? dndHandler.canDrag() : false}
101112
onOpenToggle={onMenuOpenToggle}
102-
anchorElement={acnhorElement}
113+
anchorElement={anchor}
103114
switcherMouseProps={
104115
dndHandler
105116
? {
@@ -114,3 +125,76 @@ export const FloatingMenuControl: React.FC<FloatingMenuControlProps> =
114125
/>
115126
);
116127
};
128+
129+
function getVirtualAnchor(
130+
type: ControlType,
131+
tableElem: Element,
132+
cellElem: Element,
133+
): VirtualElement {
134+
if (type === 'row') {
135+
return {
136+
contextElement: cellElem,
137+
getBoundingClientRect() {
138+
const cellRect = cellElem.getBoundingClientRect();
139+
const tableRect: ClientRectObject = tableElem.getBoundingClientRect().toJSON();
140+
141+
{
142+
// fix table rect
143+
tableRect.x += 1;
144+
tableRect.width -= 2;
145+
tableRect.left += 1;
146+
tableRect.right -= 1;
147+
}
148+
149+
return {
150+
// from table
151+
x: tableRect.x,
152+
width: tableRect.width,
153+
left: tableRect.left,
154+
right: tableRect.right,
155+
// from cell
156+
y: cellRect.y,
157+
height: cellRect.height,
158+
top: cellRect.top,
159+
bottom: cellRect.top,
160+
};
161+
},
162+
};
163+
}
164+
165+
if (type === 'column') {
166+
return {
167+
contextElement: cellElem,
168+
getBoundingClientRect() {
169+
const cellRect: ClientRectObject = cellElem.getBoundingClientRect().toJSON();
170+
const tableRect = tableElem.getBoundingClientRect();
171+
172+
// I don't know why it's 32.
173+
// It might have something to do with control width (20 px)
174+
// and table border radius (8 px).
175+
const MAGIC_NUMBER = 32;
176+
177+
const cellMiddle = cellRect.x + cellRect.width / 2;
178+
179+
// left border of table
180+
if (cellMiddle - MAGIC_NUMBER / 2 <= tableRect.left) {
181+
const visible = cellRect.right - tableRect.left;
182+
cellRect.width = visible * 2 - MAGIC_NUMBER;
183+
cellRect.left = cellRect.right - cellRect.width;
184+
cellRect.x = cellRect.left;
185+
}
186+
187+
// right border of table
188+
if (cellMiddle + MAGIC_NUMBER / 2 >= tableRect.right) {
189+
const visible = tableRect.right - cellRect.left;
190+
cellRect.width = visible * 2 - MAGIC_NUMBER;
191+
cellRect.right = cellRect.left + cellRect.width;
192+
}
193+
194+
return cellRect;
195+
},
196+
};
197+
}
198+
199+
throw new Error(`Unknown control type: ${type}`);
200+
}

src/extensions/yfm/YfmTable/plugins/YfmTableControls/nodeviews/yfm-table-cell-view.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class YfmTableCellView implements NodeView {
6666
private _decoRowUniqKey: number | null = null;
6767
private _decoColumnUniqKey: number | null = null;
6868
private _cellInfo: null | {
69+
tablePos: number;
6970
rowIndex: number;
7071
columnIndex: number;
7172
rowRange: Readonly<TableRowRange>;
@@ -103,14 +104,18 @@ class YfmTableCellView implements NodeView {
103104
() => {
104105
if (!this._cellInfo) return null;
105106

106-
const {showRowControl, showColumnControl, rowRange, columnRange} = this._cellInfo;
107+
const {showRowControl, showColumnControl, rowRange, columnRange, tablePos} =
108+
this._cellInfo;
109+
110+
const tableElem = this._view.domAtPos(tablePos + 1).node as Element;
107111

108112
return (
109113
<ErrorLoggerBoundary>
110114
{showRowControl && (
111115
<FloatingMenuControl
112116
type="row"
113-
acnhorElement={this.dom}
117+
cellElement={this.dom}
118+
tableElement={tableElem}
114119
dndHandler={this._dndHandler?.row}
115120
multiple={rowRange.rowsCount > 1}
116121
onMenuOpenToggle={this._onRowControlOpenToggle}
@@ -124,7 +129,8 @@ class YfmTableCellView implements NodeView {
124129
{showColumnControl && (
125130
<FloatingMenuControl
126131
type="column"
127-
acnhorElement={this.dom}
132+
cellElement={this.dom}
133+
tableElement={tableElem}
128134
dndHandler={this._dndHandler?.column}
129135
multiple={columnRange.columnsCount > 1}
130136
onMenuOpenToggle={this._onColumnControlOpenToggle}
@@ -154,6 +160,7 @@ class YfmTableCellView implements NodeView {
154160

155161
if (cellInfo && (cellInfo.cell.row === 0 || cellInfo.cell.column === 0)) {
156162
const info = (this._cellInfo = {
163+
tablePos: cellInfo.table.pos,
157164
rowIndex: cellInfo.cell.row,
158165
columnIndex: cellInfo.cell.column,
159166
showRowControl: false as boolean,

0 commit comments

Comments
 (0)