@atlaskit/editor-plugin-table 1.6.1 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/plugins/table/index.js +2 -1
- package/dist/cjs/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.js +3 -1
- package/dist/cjs/plugins/table/types.js +1 -0
- package/dist/cjs/plugins/table/ui/FloatingContextualButton/FixedButton.js +133 -0
- package/dist/cjs/plugins/table/ui/FloatingContextualButton/index.js +73 -128
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/plugins/table/index.js +2 -1
- package/dist/es2019/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.js +3 -1
- package/dist/es2019/plugins/table/types.js +1 -0
- package/dist/es2019/plugins/table/ui/FloatingContextualButton/FixedButton.js +120 -0
- package/dist/es2019/plugins/table/ui/FloatingContextualButton/index.js +76 -108
- package/dist/es2019/version.json +1 -1
- package/dist/esm/plugins/table/index.js +2 -1
- package/dist/esm/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.js +3 -1
- package/dist/esm/plugins/table/types.js +1 -0
- package/dist/esm/plugins/table/ui/FloatingContextualButton/FixedButton.js +118 -0
- package/dist/esm/plugins/table/ui/FloatingContextualButton/index.js +73 -129
- package/dist/esm/version.json +1 -1
- package/dist/types/plugins/table/types.d.ts +1 -0
- package/dist/types/plugins/table/ui/FloatingContextualButton/FixedButton.d.ts +23 -0
- package/dist/types/plugins/table/ui/FloatingContextualButton/index.d.ts +1 -9
- package/dist/types-ts4.5/plugins/table/types.d.ts +1 -0
- package/dist/types-ts4.5/plugins/table/ui/FloatingContextualButton/FixedButton.d.ts +23 -0
- package/dist/types-ts4.5/plugins/table/ui/FloatingContextualButton/index.d.ts +1 -9
- package/package.json +5 -4
- package/src/__tests__/playwright/__fixtures__/base-adfs.ts +1486 -0
- package/src/__tests__/playwright/extensions.spec.ts +67 -0
- package/src/__tests__/unit/nodeviews/cell.ts +0 -14
- package/src/__tests__/unit/ui/FixedButton.tsx +214 -0
- package/src/plugins/table/index.tsx +1 -0
- package/src/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.ts +2 -1
- package/src/plugins/table/types.ts +1 -0
- package/src/plugins/table/ui/FloatingContextualButton/FixedButton.tsx +175 -0
- package/src/plugins/table/ui/FloatingContextualButton/index.tsx +41 -95
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EditorTableModel,
|
|
3
|
+
EditorNodeContainerModel,
|
|
4
|
+
EditorExtensionModel,
|
|
5
|
+
editorTestCase as test,
|
|
6
|
+
expect,
|
|
7
|
+
} from '@af/editor-libra';
|
|
8
|
+
import { tableInsideLayoutAfterExtension } from './__fixtures__/base-adfs';
|
|
9
|
+
|
|
10
|
+
test.use({
|
|
11
|
+
editorProps: {
|
|
12
|
+
appearance: 'full-page',
|
|
13
|
+
allowTables: {
|
|
14
|
+
advanced: true,
|
|
15
|
+
},
|
|
16
|
+
allowLayouts: true,
|
|
17
|
+
allowExtension: true,
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
editorMountOptions: {
|
|
21
|
+
withConfluenceMacrosExtensionProvider: true,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
adf: tableInsideLayoutAfterExtension,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test.describe('when the extension context panel opens', () => {
|
|
28
|
+
test('should not overflow the table', async ({ editor }) => {
|
|
29
|
+
const nodes = EditorNodeContainerModel.from(editor);
|
|
30
|
+
const extensionModel = EditorExtensionModel.from(nodes.bodiedExtension);
|
|
31
|
+
const tableModel = EditorTableModel.from(nodes.table);
|
|
32
|
+
|
|
33
|
+
await editor.selection.set({ anchor: 1, head: 1 });
|
|
34
|
+
|
|
35
|
+
await test.step('make sure the table is not overflowed already', async () => {
|
|
36
|
+
expect(await tableModel.hasOverflowed()).toBeFalsy();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await extensionModel.configuration(editor).openContextPanel();
|
|
40
|
+
|
|
41
|
+
expect(await tableModel.hasOverflowed()).toBeFalsy();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('should not overflow the table when changing viewport', async ({
|
|
46
|
+
editor,
|
|
47
|
+
}) => {
|
|
48
|
+
const nodes = EditorNodeContainerModel.from(editor);
|
|
49
|
+
const tableModel = EditorTableModel.from(nodes.table);
|
|
50
|
+
await editor.page.setViewportSize({ width: 1000, height: 1024 });
|
|
51
|
+
await editor.selection.set({ anchor: 1, head: 1 });
|
|
52
|
+
await tableModel.isSelected();
|
|
53
|
+
const cell = await tableModel.cell(0);
|
|
54
|
+
await cell.click();
|
|
55
|
+
await cell.resize({
|
|
56
|
+
mouse: editor.page.mouse,
|
|
57
|
+
cellSide: 'right',
|
|
58
|
+
moveDirection: 'right',
|
|
59
|
+
moveDistance: 100,
|
|
60
|
+
});
|
|
61
|
+
await test.step('make sure the table is not overflowed already', async () => {
|
|
62
|
+
expect(await tableModel.hasOverflowed()).toBeFalsy();
|
|
63
|
+
});
|
|
64
|
+
await editor.page.setViewportSize({ width: 1050, height: 1024 });
|
|
65
|
+
await tableModel.isSelected();
|
|
66
|
+
expect(await tableModel.hasOverflowed()).toBeFalsy();
|
|
67
|
+
});
|
|
@@ -43,20 +43,6 @@ jest.mock('@atlaskit/editor-common/utils', () => ({
|
|
|
43
43
|
},
|
|
44
44
|
}));
|
|
45
45
|
|
|
46
|
-
jest.mock('@atlaskit/editor-palette', () => ({
|
|
47
|
-
...jest.requireActual<Object>('@atlaskit/editor-palette'),
|
|
48
|
-
hexToEditorBackgroundPaletteColorTokenName: jest.fn((hexColor: string) => {
|
|
49
|
-
return hexColor;
|
|
50
|
-
}),
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
jest.mock('@atlaskit/tokens', () => ({
|
|
54
|
-
...jest.requireActual<Object>('@atlaskit/tokens'),
|
|
55
|
-
getTokenValue: jest.fn((tokenId: string, fallback: string = '') => {
|
|
56
|
-
return tokenId || fallback;
|
|
57
|
-
}),
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
46
|
describe('table -> nodeviews -> tableCell.tsx', () => {
|
|
61
47
|
const TABLE_LOCAL_ID = 'test-table-local-id';
|
|
62
48
|
const createEditor = createProsemirrorEditorFactory();
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { render, screen, cleanup } from '@testing-library/react';
|
|
4
|
+
import { akEditorFloatingPanelZIndex } from '@atlaskit/editor-shared-styles';
|
|
5
|
+
import FixedButton, {
|
|
6
|
+
BUTTON_WIDTH,
|
|
7
|
+
calcLeftPos,
|
|
8
|
+
calcObserverTargetMargin,
|
|
9
|
+
} from '../../../plugins/table/ui/FloatingContextualButton/FixedButton';
|
|
10
|
+
|
|
11
|
+
jest.mock('react-dom', () => ({
|
|
12
|
+
createPortal: jest.fn((node: React.ReactNode) => node),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
describe('calcLeftPos()', () => {
|
|
16
|
+
it('should calculate left position', () => {
|
|
17
|
+
let result = calcLeftPos({
|
|
18
|
+
buttonWidth: 0,
|
|
19
|
+
cellRectLeft: 0,
|
|
20
|
+
cellRefWidth: 0,
|
|
21
|
+
offset: 0,
|
|
22
|
+
});
|
|
23
|
+
expect(result).toEqual(0);
|
|
24
|
+
result = calcLeftPos({
|
|
25
|
+
buttonWidth: 20,
|
|
26
|
+
cellRectLeft: 400,
|
|
27
|
+
cellRefWidth: 250,
|
|
28
|
+
offset: 3,
|
|
29
|
+
});
|
|
30
|
+
expect(result).toEqual(627);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('calcObserverTargetMargin()', () => {
|
|
35
|
+
it('should calculate the margin', () => {
|
|
36
|
+
const mockTableWrapper = document.createElement('div');
|
|
37
|
+
mockTableWrapper.scrollLeft = 50;
|
|
38
|
+
const tableSpy = jest.spyOn(mockTableWrapper, 'getBoundingClientRect');
|
|
39
|
+
tableSpy.mockImplementation(() => ({ left: 800 } as DOMRect));
|
|
40
|
+
|
|
41
|
+
const mockButton = document.createElement('div');
|
|
42
|
+
const buttonSpy = jest.spyOn(mockButton, 'getBoundingClientRect');
|
|
43
|
+
buttonSpy.mockImplementation(() => ({ left: 1200 } as DOMRect));
|
|
44
|
+
|
|
45
|
+
const result = calcObserverTargetMargin(mockTableWrapper, mockButton);
|
|
46
|
+
expect(result).toEqual(450);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('<FixedButton />', () => {
|
|
51
|
+
const targetCellRef = document.createElement('div');
|
|
52
|
+
const fixedButtonRef = {
|
|
53
|
+
current: document.createElement('div'),
|
|
54
|
+
};
|
|
55
|
+
const observerTargetRef = {
|
|
56
|
+
current: document.createElement('div'),
|
|
57
|
+
};
|
|
58
|
+
const props = {
|
|
59
|
+
offset: 10,
|
|
60
|
+
stickyHeader: {
|
|
61
|
+
pos: 0,
|
|
62
|
+
top: 0,
|
|
63
|
+
padding: 0,
|
|
64
|
+
sticky: true,
|
|
65
|
+
},
|
|
66
|
+
targetCellPosition: 0,
|
|
67
|
+
targetCellRef,
|
|
68
|
+
mountTo: document.createElement('div'),
|
|
69
|
+
tableWrapper: document.createElement('div'),
|
|
70
|
+
fixedButtonRef,
|
|
71
|
+
observerTargetRef,
|
|
72
|
+
isContextualMenuOpen: false,
|
|
73
|
+
};
|
|
74
|
+
const MockChildren = () => <div data-testid="mock-children" />;
|
|
75
|
+
|
|
76
|
+
it('should render children', () => {
|
|
77
|
+
render(
|
|
78
|
+
<FixedButton {...props}>
|
|
79
|
+
<MockChildren />
|
|
80
|
+
</FixedButton>,
|
|
81
|
+
);
|
|
82
|
+
expect(screen.getByTestId('mock-children')).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should render using createPortal', () => {
|
|
86
|
+
render(
|
|
87
|
+
<FixedButton {...props}>
|
|
88
|
+
<MockChildren />
|
|
89
|
+
</FixedButton>,
|
|
90
|
+
);
|
|
91
|
+
expect(createPortal).toBeCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('observerTargetRef', () => {
|
|
95
|
+
it('should have correct inital styles', () => {
|
|
96
|
+
const { container } = render(
|
|
97
|
+
<FixedButton {...props}>
|
|
98
|
+
<MockChildren />
|
|
99
|
+
</FixedButton>,
|
|
100
|
+
);
|
|
101
|
+
expect(container.firstChild).toHaveStyle(
|
|
102
|
+
`top: 0px; left: 0px; position: absolute; width: ${BUTTON_WIDTH}px; height: ${BUTTON_WIDTH}px`,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should have correct "margin-left" style', () => {
|
|
107
|
+
const mockTableWrapper = document.createElement('div');
|
|
108
|
+
mockTableWrapper.scrollLeft = 123;
|
|
109
|
+
const { container } = render(
|
|
110
|
+
<FixedButton {...props} tableWrapper={mockTableWrapper}>
|
|
111
|
+
<MockChildren />
|
|
112
|
+
</FixedButton>,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(container.firstChild).toHaveStyle(`margin-left: 123px;`);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('fixedButtonRef', () => {
|
|
120
|
+
it('should have correct "top" style', () => {
|
|
121
|
+
const { container, rerender } = render(
|
|
122
|
+
<FixedButton {...props} offset={10}>
|
|
123
|
+
<MockChildren />
|
|
124
|
+
</FixedButton>,
|
|
125
|
+
);
|
|
126
|
+
expect(container.firstChild?.firstChild).toHaveStyle(`top: 20px;`);
|
|
127
|
+
|
|
128
|
+
rerender(
|
|
129
|
+
<FixedButton
|
|
130
|
+
{...props}
|
|
131
|
+
stickyHeader={{ pos: 0, top: 3, padding: 0, sticky: true }}
|
|
132
|
+
>
|
|
133
|
+
<MockChildren />
|
|
134
|
+
</FixedButton>,
|
|
135
|
+
);
|
|
136
|
+
expect(container.firstChild?.firstChild).toHaveStyle(`top: 23px;`);
|
|
137
|
+
|
|
138
|
+
rerender(
|
|
139
|
+
<FixedButton
|
|
140
|
+
{...props}
|
|
141
|
+
stickyHeader={{ pos: 0, top: 0, padding: 2, sticky: true }}
|
|
142
|
+
>
|
|
143
|
+
<MockChildren />
|
|
144
|
+
</FixedButton>,
|
|
145
|
+
);
|
|
146
|
+
expect(container.firstChild?.firstChild).toHaveStyle(`top: 22px;`);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should have correct "left" style', () => {
|
|
150
|
+
const mockRef = document.createElement('div');
|
|
151
|
+
const mockRefSpy = jest.spyOn(mockRef, 'getBoundingClientRect');
|
|
152
|
+
mockRefSpy.mockImplementation(() => ({ left: 3 } as DOMRect));
|
|
153
|
+
|
|
154
|
+
const { container } = render(
|
|
155
|
+
<FixedButton {...props} targetCellRef={mockRef}>
|
|
156
|
+
<MockChildren />
|
|
157
|
+
</FixedButton>,
|
|
158
|
+
);
|
|
159
|
+
expect(container.firstChild?.firstChild).toHaveStyle(`left: -27px;`);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should have correct "z-index" style', () => {
|
|
163
|
+
const { container } = render(
|
|
164
|
+
<FixedButton {...props}>
|
|
165
|
+
<MockChildren />
|
|
166
|
+
</FixedButton>,
|
|
167
|
+
);
|
|
168
|
+
expect(container.firstChild?.firstChild).toHaveStyle(
|
|
169
|
+
`z-index: ${akEditorFloatingPanelZIndex}`,
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('useEffect()', () => {
|
|
175
|
+
const observe = jest.fn();
|
|
176
|
+
const unobserve = jest.fn();
|
|
177
|
+
|
|
178
|
+
window.IntersectionObserver = jest.fn((callback) => {
|
|
179
|
+
callback([{ isIntersecting: true }]);
|
|
180
|
+
return {
|
|
181
|
+
observe,
|
|
182
|
+
unobserve,
|
|
183
|
+
};
|
|
184
|
+
}) as any;
|
|
185
|
+
|
|
186
|
+
it('should add / remove event listener to the table wrapper', () => {
|
|
187
|
+
const mockTableWrapper = document.createElement('div');
|
|
188
|
+
const addSpy = jest.spyOn(mockTableWrapper, 'addEventListener');
|
|
189
|
+
const removeSpy = jest.spyOn(mockTableWrapper, 'removeEventListener');
|
|
190
|
+
|
|
191
|
+
render(
|
|
192
|
+
<FixedButton {...props} tableWrapper={mockTableWrapper}>
|
|
193
|
+
<MockChildren />
|
|
194
|
+
</FixedButton>,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(addSpy).toBeCalled();
|
|
198
|
+
cleanup();
|
|
199
|
+
expect(removeSpy).toBeCalled();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should observe / unobserve an InterSectionObserver', () => {
|
|
203
|
+
render(
|
|
204
|
+
<FixedButton {...props}>
|
|
205
|
+
<MockChildren />
|
|
206
|
+
</FixedButton>,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(observe).toHaveBeenCalled();
|
|
210
|
+
cleanup();
|
|
211
|
+
expect(unobserve).toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -611,7 +611,7 @@ export class TableRowNodeView implements NodeView {
|
|
|
611
611
|
return;
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
-
const { table } = tree;
|
|
614
|
+
const { table, wrapper } = tree;
|
|
615
615
|
|
|
616
616
|
// ED-16035 Make sure sticky header is only applied to first row
|
|
617
617
|
const tbody = this.dom.parentElement;
|
|
@@ -639,6 +639,7 @@ export class TableRowNodeView implements NodeView {
|
|
|
639
639
|
|
|
640
640
|
this.dom.style.top = `${domTop}px`;
|
|
641
641
|
updateTableMargin(table);
|
|
642
|
+
this.dom.scrollLeft = wrapper.scrollLeft;
|
|
642
643
|
|
|
643
644
|
this.emitOn(domTop, this.colControlsOffset);
|
|
644
645
|
};
|
|
@@ -289,6 +289,7 @@ export const TableCssClassName = {
|
|
|
289
289
|
CONTEXTUAL_SUBMENU: `${tablePrefixSelector}-contextual-submenu`,
|
|
290
290
|
CONTEXTUAL_MENU_BUTTON_WRAP: `${tablePrefixSelector}-contextual-menu-button-wrap`,
|
|
291
291
|
CONTEXTUAL_MENU_BUTTON: `${tablePrefixSelector}-contextual-menu-button`,
|
|
292
|
+
CONTEXTUAL_MENU_BUTTON_FIXED: `${tablePrefixSelector}-contextual-menu-button-fixed`,
|
|
292
293
|
CONTEXTUAL_MENU_ICON: `${tablePrefixSelector}-contextual-submenu-icon`,
|
|
293
294
|
|
|
294
295
|
// come from prosemirror-table
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import rafSchedule from 'raf-schd';
|
|
4
|
+
|
|
5
|
+
import { akEditorFloatingPanelZIndex } from '@atlaskit/editor-shared-styles';
|
|
6
|
+
import { RowStickyState } from '../../pm-plugins/sticky-headers';
|
|
7
|
+
import { insertColumnButtonOffset } from '../common-styles';
|
|
8
|
+
import { TableCssClassName as ClassName } from '../../types';
|
|
9
|
+
|
|
10
|
+
export const BUTTON_WIDTH = 20;
|
|
11
|
+
|
|
12
|
+
export interface Props {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
mountTo: HTMLElement;
|
|
15
|
+
offset: number;
|
|
16
|
+
stickyHeader: RowStickyState;
|
|
17
|
+
targetCellPosition: number;
|
|
18
|
+
targetCellRef: HTMLElement;
|
|
19
|
+
tableWrapper: HTMLElement;
|
|
20
|
+
isContextualMenuOpen: boolean | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CalcLeftPosData {
|
|
24
|
+
buttonWidth: number;
|
|
25
|
+
cellRectLeft: number;
|
|
26
|
+
cellRefWidth: number;
|
|
27
|
+
offset: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const calcLeftPos = ({
|
|
31
|
+
buttonWidth,
|
|
32
|
+
cellRectLeft,
|
|
33
|
+
cellRefWidth,
|
|
34
|
+
offset,
|
|
35
|
+
}: CalcLeftPosData) => {
|
|
36
|
+
return cellRectLeft + cellRefWidth - buttonWidth - offset;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const calcObserverTargetMargin = (
|
|
40
|
+
tableWrapper: HTMLElement,
|
|
41
|
+
fixedButtonRefCurrent: HTMLElement,
|
|
42
|
+
) => {
|
|
43
|
+
const tableWrapperRect = tableWrapper.getBoundingClientRect();
|
|
44
|
+
const fixedButtonRect = fixedButtonRefCurrent.getBoundingClientRect();
|
|
45
|
+
const scrollLeft = tableWrapper.scrollLeft;
|
|
46
|
+
return fixedButtonRect.left - tableWrapperRect.left + scrollLeft;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const FixedButton = ({
|
|
50
|
+
children,
|
|
51
|
+
isContextualMenuOpen,
|
|
52
|
+
mountTo,
|
|
53
|
+
offset,
|
|
54
|
+
stickyHeader,
|
|
55
|
+
tableWrapper,
|
|
56
|
+
targetCellPosition,
|
|
57
|
+
targetCellRef,
|
|
58
|
+
}: Props) => {
|
|
59
|
+
const fixedButtonRef = useRef<HTMLDivElement | null>(null);
|
|
60
|
+
const observerTargetRef = useRef<HTMLDivElement | null>(null);
|
|
61
|
+
|
|
62
|
+
// Using refs here rather than state to prevent heaps of renders on scroll
|
|
63
|
+
const scrollDataRef = useRef(0);
|
|
64
|
+
const leftPosDataRef = useRef(0);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const observerTargetRefCurrent = observerTargetRef.current;
|
|
68
|
+
const fixedButtonRefCurrent = fixedButtonRef.current;
|
|
69
|
+
|
|
70
|
+
if (fixedButtonRefCurrent && observerTargetRefCurrent) {
|
|
71
|
+
scrollDataRef.current = tableWrapper.scrollLeft;
|
|
72
|
+
leftPosDataRef.current = 0;
|
|
73
|
+
// Hide the button initially in case there's a flash of the button being
|
|
74
|
+
// outside the table before the Intersection Observer fires
|
|
75
|
+
fixedButtonRefCurrent.style.visibility = 'hidden';
|
|
76
|
+
|
|
77
|
+
const margin = calcObserverTargetMargin(
|
|
78
|
+
tableWrapper,
|
|
79
|
+
fixedButtonRefCurrent,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Much more simple and predictable to add this margin to the observer target
|
|
83
|
+
// rather than using it to calculate the rootMargin values
|
|
84
|
+
observerTargetRefCurrent.style.marginLeft = `${margin}px`;
|
|
85
|
+
|
|
86
|
+
const observer = new IntersectionObserver(
|
|
87
|
+
(entries) => {
|
|
88
|
+
entries.forEach((entry) => {
|
|
89
|
+
if (entry.isIntersecting) {
|
|
90
|
+
fixedButtonRefCurrent.style.visibility = 'visible';
|
|
91
|
+
} else {
|
|
92
|
+
fixedButtonRefCurrent.style.visibility = 'hidden';
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
root: tableWrapper,
|
|
98
|
+
rootMargin: `0px ${insertColumnButtonOffset}px 0px 0px`,
|
|
99
|
+
threshold: 1,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const handleScroll = rafSchedule((event) => {
|
|
104
|
+
if (fixedButtonRef.current) {
|
|
105
|
+
const delta = event.target.scrollLeft - scrollDataRef.current;
|
|
106
|
+
const style = `translateX(${leftPosDataRef.current - delta}px)`;
|
|
107
|
+
fixedButtonRef.current.style.transform = style;
|
|
108
|
+
|
|
109
|
+
scrollDataRef.current = event.target.scrollLeft;
|
|
110
|
+
leftPosDataRef.current = leftPosDataRef.current - delta;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
observer.observe(observerTargetRefCurrent);
|
|
115
|
+
tableWrapper.addEventListener('scroll', handleScroll);
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
tableWrapper.removeEventListener('scroll', handleScroll);
|
|
119
|
+
fixedButtonRefCurrent.style.transform = '';
|
|
120
|
+
observer.unobserve(observerTargetRefCurrent);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}, [
|
|
124
|
+
fixedButtonRef,
|
|
125
|
+
observerTargetRef,
|
|
126
|
+
tableWrapper,
|
|
127
|
+
targetCellPosition,
|
|
128
|
+
targetCellRef,
|
|
129
|
+
isContextualMenuOpen,
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const targetCellRect = targetCellRef.getBoundingClientRect();
|
|
133
|
+
|
|
134
|
+
// Using a portal here to ensure wrapperRef has the tableWrapper as an
|
|
135
|
+
// ancestor. This is required to make the Intersection Observer work.
|
|
136
|
+
return createPortal(
|
|
137
|
+
// Using observerTargetRef here for our Intersection Observer. There is issues
|
|
138
|
+
// getting the observer to work just using the fixedButtonRef, possible due
|
|
139
|
+
// to using position fixed on this Element, or possibly due to its position
|
|
140
|
+
// being changed on scroll.
|
|
141
|
+
<div
|
|
142
|
+
ref={observerTargetRef}
|
|
143
|
+
style={{
|
|
144
|
+
position: 'absolute',
|
|
145
|
+
top: '0px',
|
|
146
|
+
left: '0px',
|
|
147
|
+
width: `${BUTTON_WIDTH}px`,
|
|
148
|
+
height: `${BUTTON_WIDTH}px`,
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<div
|
|
152
|
+
ref={fixedButtonRef}
|
|
153
|
+
style={{
|
|
154
|
+
position: 'fixed',
|
|
155
|
+
top: stickyHeader.top + stickyHeader.padding + offset * 2,
|
|
156
|
+
zIndex: akEditorFloatingPanelZIndex,
|
|
157
|
+
left: calcLeftPos({
|
|
158
|
+
buttonWidth: BUTTON_WIDTH,
|
|
159
|
+
cellRectLeft: targetCellRect.left,
|
|
160
|
+
cellRefWidth: targetCellRef.clientWidth,
|
|
161
|
+
offset,
|
|
162
|
+
}),
|
|
163
|
+
width: `${BUTTON_WIDTH}px`,
|
|
164
|
+
height: `${BUTTON_WIDTH}px`,
|
|
165
|
+
}}
|
|
166
|
+
className={ClassName.CONTEXTUAL_MENU_BUTTON_FIXED}
|
|
167
|
+
>
|
|
168
|
+
{children}
|
|
169
|
+
</div>
|
|
170
|
+
</div>,
|
|
171
|
+
mountTo,
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export default FixedButton;
|