@atlaskit/editor-plugin-table 3.1.2 → 3.2.0
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 +16 -0
- package/dist/cjs/plugins/table/nodeviews/TableComponent.js +36 -2
- package/dist/cjs/plugins/table/nodeviews/TableResizer.js +7 -1
- package/dist/cjs/plugins/table/nodeviews/TableStickyScrollbar.js +154 -0
- package/dist/cjs/plugins/table/toolbar.js +6 -0
- package/dist/cjs/plugins/table/ui/common-styles.js +9 -2
- package/dist/es2019/plugins/table/nodeviews/TableComponent.js +37 -3
- package/dist/es2019/plugins/table/nodeviews/TableResizer.js +11 -1
- package/dist/es2019/plugins/table/nodeviews/TableStickyScrollbar.js +112 -0
- package/dist/es2019/plugins/table/toolbar.js +6 -0
- package/dist/es2019/plugins/table/ui/common-styles.js +33 -1
- package/dist/esm/plugins/table/nodeviews/TableComponent.js +37 -3
- package/dist/esm/plugins/table/nodeviews/TableResizer.js +7 -1
- package/dist/esm/plugins/table/nodeviews/TableStickyScrollbar.js +146 -0
- package/dist/esm/plugins/table/toolbar.js +6 -0
- package/dist/esm/plugins/table/ui/common-styles.js +8 -2
- package/dist/types/plugins/table/nodeviews/TableComponent.d.ts +1 -0
- package/dist/types/plugins/table/nodeviews/TableStickyScrollbar.d.ts +24 -0
- package/dist/types/plugins/table/toolbar.d.ts +4 -4
- package/dist/types/plugins/table/types.d.ts +3 -0
- package/dist/types/plugins/table/ui/common-styles.d.ts +1 -0
- package/dist/types-ts4.5/plugins/table/nodeviews/TableComponent.d.ts +1 -0
- package/dist/types-ts4.5/plugins/table/nodeviews/TableStickyScrollbar.d.ts +24 -0
- package/dist/types-ts4.5/plugins/table/toolbar.d.ts +4 -4
- package/dist/types-ts4.5/plugins/table/types.d.ts +3 -0
- package/dist/types-ts4.5/plugins/table/ui/common-styles.d.ts +1 -0
- package/package.json +6 -3
- package/src/__tests__/unit/nodeviews/TableContainer.tsx +43 -18
- package/src/plugins/table/nodeviews/TableComponent.tsx +54 -3
- package/src/plugins/table/nodeviews/TableResizer.tsx +7 -1
- package/src/plugins/table/nodeviews/TableStickyScrollbar.ts +204 -0
- package/src/plugins/table/toolbar.tsx +10 -6
- package/src/plugins/table/ui/common-styles.ts +38 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
2
|
+
export declare class TableStickyScrollbar {
|
|
3
|
+
private wrapper;
|
|
4
|
+
private view;
|
|
5
|
+
private editorScrollableElement?;
|
|
6
|
+
private intersectionObserver?;
|
|
7
|
+
private stickyScrollbarContainerElement?;
|
|
8
|
+
private sentinels;
|
|
9
|
+
private topSentinelState?;
|
|
10
|
+
private bottomSentinelState?;
|
|
11
|
+
constructor(wrapper: HTMLDivElement, view: EditorView);
|
|
12
|
+
dispose(): void;
|
|
13
|
+
scrollLeft(left: number): void;
|
|
14
|
+
private init;
|
|
15
|
+
private createIntersectionObserver;
|
|
16
|
+
private deleteIntesactionObserver;
|
|
17
|
+
private sentenialBottomCallback;
|
|
18
|
+
private sentenialTopCallback;
|
|
19
|
+
private toggle;
|
|
20
|
+
private hide;
|
|
21
|
+
private show;
|
|
22
|
+
private handleScroll;
|
|
23
|
+
private handleScrollDebounced;
|
|
24
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
|
|
2
2
|
import type { Command, FloatingToolbarDropdown, FloatingToolbarHandler, FloatingToolbarItem, GetEditorContainerWidth, GetEditorFeatureFlags } from '@atlaskit/editor-common/types';
|
|
3
|
-
import { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
-
import { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
-
import { Rect } from '@atlaskit/editor-tables/table-map';
|
|
6
|
-
import { PluginConfig, ToolbarMenuConfig, ToolbarMenuContext, ToolbarMenuState } from './types';
|
|
3
|
+
import type { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
+
import type { Rect } from '@atlaskit/editor-tables/table-map';
|
|
6
|
+
import type { PluginConfig, ToolbarMenuConfig, ToolbarMenuContext, ToolbarMenuState } from './types';
|
|
7
7
|
export declare const messages: {
|
|
8
8
|
tableOptions: {
|
|
9
9
|
id: string;
|
|
@@ -295,8 +295,11 @@ export declare const TableCssClassName: {
|
|
|
295
295
|
TABLE_RIGHT_SHADOW: string;
|
|
296
296
|
TABLE_STICKY_SHADOW: string;
|
|
297
297
|
TABLE_STICKY_WRAPPER: string;
|
|
298
|
+
TABLE_STICKY_SCROLLBAR_CONTAINER: string;
|
|
298
299
|
TABLE_STICKY_SENTINEL_TOP: string;
|
|
299
300
|
TABLE_STICKY_SENTINEL_BOTTOM: string;
|
|
301
|
+
TABLE_STICKY_SCROLLBAR_SENTINEL_TOP: string;
|
|
302
|
+
TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM: string;
|
|
300
303
|
TABLE_SHADOW_SENTINEL_LEFT: string;
|
|
301
304
|
TABLE_SHADOW_SENTINEL_RIGHT: string;
|
|
302
305
|
TABLE_CELL_NODEVIEW_CONTENT_DOM: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FeatureFlags } from '@atlaskit/editor-common/types';
|
|
2
2
|
import type { ThemeProps } from '@atlaskit/theme/types';
|
|
3
3
|
export declare const insertColumnButtonOffset: number;
|
|
4
|
+
export declare const tableRowHeight = 44;
|
|
4
5
|
export declare const tableStyles: (props: ThemeProps & {
|
|
5
6
|
featureFlags?: FeatureFlags;
|
|
6
7
|
}) => import("@emotion/react").SerializedStyles;
|
|
@@ -43,6 +43,7 @@ declare class TableComponent extends React.Component<ComponentProps, TableState>
|
|
|
43
43
|
private containerWidth?;
|
|
44
44
|
private layoutSize?;
|
|
45
45
|
private overflowShadowsObserver?;
|
|
46
|
+
private stickyScrollbar?;
|
|
46
47
|
private isInitialOverflowSent;
|
|
47
48
|
private initialOverflowCaptureTimerId?;
|
|
48
49
|
constructor(props: ComponentProps);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
2
|
+
export declare class TableStickyScrollbar {
|
|
3
|
+
private wrapper;
|
|
4
|
+
private view;
|
|
5
|
+
private editorScrollableElement?;
|
|
6
|
+
private intersectionObserver?;
|
|
7
|
+
private stickyScrollbarContainerElement?;
|
|
8
|
+
private sentinels;
|
|
9
|
+
private topSentinelState?;
|
|
10
|
+
private bottomSentinelState?;
|
|
11
|
+
constructor(wrapper: HTMLDivElement, view: EditorView);
|
|
12
|
+
dispose(): void;
|
|
13
|
+
scrollLeft(left: number): void;
|
|
14
|
+
private init;
|
|
15
|
+
private createIntersectionObserver;
|
|
16
|
+
private deleteIntesactionObserver;
|
|
17
|
+
private sentenialBottomCallback;
|
|
18
|
+
private sentenialTopCallback;
|
|
19
|
+
private toggle;
|
|
20
|
+
private hide;
|
|
21
|
+
private show;
|
|
22
|
+
private handleScroll;
|
|
23
|
+
private handleScrollDebounced;
|
|
24
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
|
|
2
2
|
import type { Command, FloatingToolbarDropdown, FloatingToolbarHandler, FloatingToolbarItem, GetEditorContainerWidth, GetEditorFeatureFlags } from '@atlaskit/editor-common/types';
|
|
3
|
-
import { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
-
import { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
-
import { Rect } from '@atlaskit/editor-tables/table-map';
|
|
6
|
-
import { PluginConfig, ToolbarMenuConfig, ToolbarMenuContext, ToolbarMenuState } from './types';
|
|
3
|
+
import type { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
+
import type { Rect } from '@atlaskit/editor-tables/table-map';
|
|
6
|
+
import type { PluginConfig, ToolbarMenuConfig, ToolbarMenuContext, ToolbarMenuState } from './types';
|
|
7
7
|
export declare const messages: {
|
|
8
8
|
tableOptions: {
|
|
9
9
|
id: string;
|
|
@@ -295,8 +295,11 @@ export declare const TableCssClassName: {
|
|
|
295
295
|
TABLE_RIGHT_SHADOW: string;
|
|
296
296
|
TABLE_STICKY_SHADOW: string;
|
|
297
297
|
TABLE_STICKY_WRAPPER: string;
|
|
298
|
+
TABLE_STICKY_SCROLLBAR_CONTAINER: string;
|
|
298
299
|
TABLE_STICKY_SENTINEL_TOP: string;
|
|
299
300
|
TABLE_STICKY_SENTINEL_BOTTOM: string;
|
|
301
|
+
TABLE_STICKY_SCROLLBAR_SENTINEL_TOP: string;
|
|
302
|
+
TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM: string;
|
|
300
303
|
TABLE_SHADOW_SENTINEL_LEFT: string;
|
|
301
304
|
TABLE_SHADOW_SENTINEL_RIGHT: string;
|
|
302
305
|
TABLE_CELL_NODEVIEW_CONTENT_DOM: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FeatureFlags } from '@atlaskit/editor-common/types';
|
|
2
2
|
import type { ThemeProps } from '@atlaskit/theme/types';
|
|
3
3
|
export declare const insertColumnButtonOffset: number;
|
|
4
|
+
export declare const tableRowHeight = 44;
|
|
4
5
|
export declare const tableStyles: (props: ThemeProps & {
|
|
5
6
|
featureFlags?: FeatureFlags;
|
|
6
7
|
}) => import("@emotion/react").SerializedStyles;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-table",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Table plugin for the @atlaskit/editor",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@atlaskit/adf-schema": "^29.1.0",
|
|
31
|
-
"@atlaskit/editor-common": "^75.
|
|
31
|
+
"@atlaskit/editor-common": "^75.8.0",
|
|
32
32
|
"@atlaskit/editor-palette": "1.5.1",
|
|
33
33
|
"@atlaskit/editor-plugin-analytics": "^0.2.0",
|
|
34
34
|
"@atlaskit/editor-plugin-content-insertion": "^0.1.0",
|
|
35
35
|
"@atlaskit/editor-prosemirror": "1.1.0",
|
|
36
|
-
"@atlaskit/editor-shared-styles": "^2.
|
|
36
|
+
"@atlaskit/editor-shared-styles": "^2.8.0",
|
|
37
37
|
"@atlaskit/editor-tables": "^2.3.0",
|
|
38
38
|
"@atlaskit/icon": "^21.12.0",
|
|
39
39
|
"@atlaskit/platform-feature-flags": "^0.2.1",
|
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"platform.editor.custom-table-width": {
|
|
103
103
|
"type": "boolean"
|
|
104
104
|
},
|
|
105
|
+
"platform.editor.table-sticky-scrollbar": {
|
|
106
|
+
"type": "boolean"
|
|
107
|
+
},
|
|
105
108
|
"platform.editor.update-table-cell-width-via-step": {
|
|
106
109
|
"type": "boolean"
|
|
107
110
|
},
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
TableContainer,
|
|
30
30
|
} from '../../../plugins/table/nodeviews/TableContainer';
|
|
31
31
|
import { pluginKey } from '../../../plugins/table/pm-plugins/plugin-key';
|
|
32
|
+
import { pluginKey as tableResizingPluginKey } from '../../../plugins/table/pm-plugins/table-width';
|
|
32
33
|
import type { TablePluginState } from '../../../plugins/table/types';
|
|
33
34
|
|
|
34
35
|
const mockStartMeasure = jest.fn();
|
|
@@ -66,9 +67,14 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
66
67
|
return createEditor({
|
|
67
68
|
doc,
|
|
68
69
|
editorProps: {
|
|
70
|
+
appearance: 'full-page',
|
|
69
71
|
allowTables: false,
|
|
70
72
|
dangerouslyAppendPlugins: {
|
|
71
|
-
__plugins: [
|
|
73
|
+
__plugins: [
|
|
74
|
+
tablePlugin({
|
|
75
|
+
config: { tableResizingEnabled: true, tableOptions: {} },
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
72
78
|
},
|
|
73
79
|
featureFlags,
|
|
74
80
|
},
|
|
@@ -228,21 +234,7 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
228
234
|
fireEvent.mouseMove(container.querySelector('.resizer-handle.right')!);
|
|
229
235
|
fireEvent.mouseUp(container.querySelector('.resizer-handle.right')!);
|
|
230
236
|
|
|
231
|
-
expect(analyticsMock).
|
|
232
|
-
action: TABLE_ACTION.RESIZED,
|
|
233
|
-
actionSubject: ACTION_SUBJECT.TABLE,
|
|
234
|
-
eventType: EVENT_TYPE.TRACK,
|
|
235
|
-
attributes: {
|
|
236
|
-
width: undefined, // Can't get the events right to trigger re-resizeable
|
|
237
|
-
prevWidth: null,
|
|
238
|
-
nodeSize: 20,
|
|
239
|
-
totalTableWidth: null,
|
|
240
|
-
totalRowCount: 1,
|
|
241
|
-
totalColumnCount: 3,
|
|
242
|
-
},
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
expect(analyticsMock).toHaveBeenCalledWith({
|
|
237
|
+
expect(analyticsMock).toHaveBeenNthCalledWith(1, {
|
|
246
238
|
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
|
|
247
239
|
actionSubject: ACTION_SUBJECT.TABLE,
|
|
248
240
|
eventType: EVENT_TYPE.OPERATIONAL,
|
|
@@ -254,7 +246,7 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
254
246
|
},
|
|
255
247
|
});
|
|
256
248
|
|
|
257
|
-
expect(analyticsMock).
|
|
249
|
+
expect(analyticsMock).toHaveBeenNthCalledWith(2, {
|
|
258
250
|
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
|
|
259
251
|
actionSubject: ACTION_SUBJECT.TABLE,
|
|
260
252
|
eventType: EVENT_TYPE.OPERATIONAL,
|
|
@@ -266,6 +258,20 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
266
258
|
},
|
|
267
259
|
});
|
|
268
260
|
|
|
261
|
+
expect(analyticsMock).toHaveBeenNthCalledWith(3, {
|
|
262
|
+
action: TABLE_ACTION.RESIZED,
|
|
263
|
+
actionSubject: ACTION_SUBJECT.TABLE,
|
|
264
|
+
eventType: EVENT_TYPE.TRACK,
|
|
265
|
+
attributes: {
|
|
266
|
+
newWidth: 0, // Can't get the events right to trigger re-resizeable
|
|
267
|
+
prevWidth: 960,
|
|
268
|
+
nodeSize: 20,
|
|
269
|
+
totalTableWidth: null,
|
|
270
|
+
totalRowCount: 1,
|
|
271
|
+
totalColumnCount: 3,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
269
275
|
analyticsMock.mockReset();
|
|
270
276
|
});
|
|
271
277
|
|
|
@@ -378,7 +384,13 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
378
384
|
/>,
|
|
379
385
|
);
|
|
380
386
|
|
|
381
|
-
return {
|
|
387
|
+
return {
|
|
388
|
+
container,
|
|
389
|
+
unmount,
|
|
390
|
+
editorView,
|
|
391
|
+
selectionActionMock,
|
|
392
|
+
actualGuidelineMock,
|
|
393
|
+
};
|
|
382
394
|
};
|
|
383
395
|
|
|
384
396
|
afterEach(() => {
|
|
@@ -408,5 +420,18 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
408
420
|
unmount();
|
|
409
421
|
expect(actualGuidelineMock).toHaveBeenCalledWith({ guidelines: [] });
|
|
410
422
|
});
|
|
423
|
+
|
|
424
|
+
// this is testing logic inside TableResizer, targeting the clean up in the useEffect
|
|
425
|
+
it('should call restore resizing plugin state when removed', () => {
|
|
426
|
+
const { container, unmount, editorView } = buildContainer({});
|
|
427
|
+
|
|
428
|
+
fireEvent.mouseDown(container.querySelector('.resizer-handle.right')!);
|
|
429
|
+
fireEvent.mouseMove(container.querySelector('.resizer-handle.right')!);
|
|
430
|
+
|
|
431
|
+
unmount();
|
|
432
|
+
expect(tableResizingPluginKey.getState(editorView.state)).toStrictEqual({
|
|
433
|
+
resizing: false,
|
|
434
|
+
});
|
|
435
|
+
});
|
|
411
436
|
});
|
|
412
437
|
});
|
|
@@ -27,7 +27,10 @@ import {
|
|
|
27
27
|
} from '@atlaskit/editor-common/utils';
|
|
28
28
|
import type { Node as PmNode } from '@atlaskit/editor-prosemirror/model';
|
|
29
29
|
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
MAX_BROWSER_SCROLLBAR_HEIGHT,
|
|
32
|
+
akEditorTableToolbarSize as tableToolbarSize,
|
|
33
|
+
} from '@atlaskit/editor-shared-styles';
|
|
31
34
|
import { findTable, isTableSelected } from '@atlaskit/editor-tables/utils';
|
|
32
35
|
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
33
36
|
|
|
@@ -65,6 +68,7 @@ import {
|
|
|
65
68
|
|
|
66
69
|
import { OverflowShadowsObserver } from './OverflowShadowsObserver';
|
|
67
70
|
import { TableContainer } from './TableContainer';
|
|
71
|
+
import { TableStickyScrollbar } from './TableStickyScrollbar';
|
|
68
72
|
import type { TableOptions } from './types';
|
|
69
73
|
|
|
70
74
|
const isIE11 = browser.ie_version === 11;
|
|
@@ -72,6 +76,7 @@ const isIE11 = browser.ie_version === 11;
|
|
|
72
76
|
// componentDidUpdate is called multiple times. The isOverflowing value is correct only on the last update.
|
|
73
77
|
// To make sure we capture the last update, we use setTimeout.
|
|
74
78
|
const initialOverflowCaptureTimeroutDelay = 300;
|
|
79
|
+
|
|
75
80
|
export interface ComponentProps {
|
|
76
81
|
view: EditorView;
|
|
77
82
|
getNode: () => PmNode;
|
|
@@ -117,6 +122,7 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
117
122
|
private containerWidth?: EditorContainerWidth;
|
|
118
123
|
private layoutSize?: number;
|
|
119
124
|
private overflowShadowsObserver?: OverflowShadowsObserver;
|
|
125
|
+
private stickyScrollbar?: TableStickyScrollbar;
|
|
120
126
|
|
|
121
127
|
private isInitialOverflowSent: boolean;
|
|
122
128
|
private initialOverflowCaptureTimerId?: ReturnType<typeof setTimeout>;
|
|
@@ -154,7 +160,18 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
154
160
|
componentDidMount() {
|
|
155
161
|
const { allowColumnResizing, eventDispatcher, options } = this.props;
|
|
156
162
|
if (allowColumnResizing && this.wrapper && !isIE11) {
|
|
157
|
-
this.wrapper.addEventListener('scroll', this.handleScrollDebounced
|
|
163
|
+
this.wrapper.addEventListener('scroll', this.handleScrollDebounced, {
|
|
164
|
+
passive: true,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (getBooleanFF('platform.editor.table-sticky-scrollbar')) {
|
|
168
|
+
if (this.table) {
|
|
169
|
+
this.stickyScrollbar = new TableStickyScrollbar(
|
|
170
|
+
this.wrapper,
|
|
171
|
+
this.props.view,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
158
175
|
}
|
|
159
176
|
|
|
160
177
|
if (allowColumnResizing) {
|
|
@@ -193,6 +210,12 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
193
210
|
this.wrapper.removeEventListener('scroll', this.handleScrollDebounced);
|
|
194
211
|
}
|
|
195
212
|
|
|
213
|
+
if (getBooleanFF('platform.editor.table-sticky-scrollbar')) {
|
|
214
|
+
if (this.stickyScrollbar) {
|
|
215
|
+
this.stickyScrollbar.dispose();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
196
219
|
this.handleScrollDebounced.cancel();
|
|
197
220
|
this.scaleTableDebounced.cancel();
|
|
198
221
|
this.handleTableResizingDebounced.cancel();
|
|
@@ -453,6 +476,12 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
453
476
|
className={ClassName.TABLE_STICKY_SENTINEL_TOP}
|
|
454
477
|
data-testid="sticky-sentinel-top"
|
|
455
478
|
/>
|
|
479
|
+
{getBooleanFF('platform.editor.table-sticky-scrollbar') && (
|
|
480
|
+
<div
|
|
481
|
+
className={ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_TOP}
|
|
482
|
+
data-testid="sticky-scrollbar-sentinel-top"
|
|
483
|
+
/>
|
|
484
|
+
)}
|
|
456
485
|
{allowControls && rowControls}
|
|
457
486
|
<div
|
|
458
487
|
style={shadowStyle(showBeforeShadow)}
|
|
@@ -473,7 +502,6 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
473
502
|
}}
|
|
474
503
|
/>
|
|
475
504
|
)}
|
|
476
|
-
|
|
477
505
|
<div
|
|
478
506
|
className={classnames(ClassName.TABLE_NODE_WRAPPER)}
|
|
479
507
|
ref={(elem) => {
|
|
@@ -488,6 +516,17 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
488
516
|
}
|
|
489
517
|
}}
|
|
490
518
|
/>
|
|
519
|
+
{getBooleanFF('platform.editor.table-sticky-scrollbar') && (
|
|
520
|
+
<div
|
|
521
|
+
className={ClassName.TABLE_STICKY_SCROLLBAR_CONTAINER}
|
|
522
|
+
style={{
|
|
523
|
+
height: MAX_BROWSER_SCROLLBAR_HEIGHT,
|
|
524
|
+
display: 'none',
|
|
525
|
+
}}
|
|
526
|
+
>
|
|
527
|
+
<div style={{ width: tableRef?.clientWidth }}></div>
|
|
528
|
+
</div>
|
|
529
|
+
)}
|
|
491
530
|
<div
|
|
492
531
|
style={shadowStyle(showAfterShadow)}
|
|
493
532
|
className={ClassName.TABLE_RIGHT_SHADOW}
|
|
@@ -526,6 +565,12 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
526
565
|
className={ClassName.TABLE_STICKY_SENTINEL_BOTTOM}
|
|
527
566
|
data-testid="sticky-sentinel-bottom"
|
|
528
567
|
/>
|
|
568
|
+
{getBooleanFF('platform.editor.table-sticky-scrollbar') && (
|
|
569
|
+
<div
|
|
570
|
+
className={ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM}
|
|
571
|
+
data-testid="sticky-scrollbar-sentinel-bottom"
|
|
572
|
+
/>
|
|
573
|
+
)}
|
|
529
574
|
</TableContainer>
|
|
530
575
|
);
|
|
531
576
|
}
|
|
@@ -535,6 +580,12 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
|
|
|
535
580
|
return;
|
|
536
581
|
}
|
|
537
582
|
|
|
583
|
+
if (getBooleanFF('platform.editor.table-sticky-scrollbar')) {
|
|
584
|
+
if (this.stickyScrollbar) {
|
|
585
|
+
this.stickyScrollbar.scrollLeft(this.wrapper.scrollLeft);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
538
589
|
if (this.table) {
|
|
539
590
|
// sync sticky header row to table scroll
|
|
540
591
|
const headers = this.table.querySelectorAll('tr[data-header-row]');
|
|
@@ -201,11 +201,17 @@ export const TableResizer = ({
|
|
|
201
201
|
// only bring back the cursor if this table was deleted - i.e. if a user was resizing, then another
|
|
202
202
|
// deleted this table
|
|
203
203
|
if (isResizing.current) {
|
|
204
|
+
const {
|
|
205
|
+
dispatch,
|
|
206
|
+
state: { tr },
|
|
207
|
+
} = editorView;
|
|
204
208
|
displayGapCursor(true);
|
|
205
209
|
displayGuideline([]);
|
|
210
|
+
tr.setMeta(tableWidthPluginKey, { resizing: false });
|
|
211
|
+
dispatch(tr);
|
|
206
212
|
}
|
|
207
213
|
};
|
|
208
|
-
}, [displayGuideline, displayGapCursor]);
|
|
214
|
+
}, [editorView, displayGuideline, displayGapCursor]);
|
|
209
215
|
|
|
210
216
|
const handleResizeStart = useCallback(() => {
|
|
211
217
|
startMeasure();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import rafSchedule from 'raf-schd';
|
|
2
|
+
|
|
3
|
+
import { findOverflowScrollParent } from '@atlaskit/editor-common/ui';
|
|
4
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
+
|
|
6
|
+
import { TableCssClassName as ClassName } from '../types';
|
|
7
|
+
|
|
8
|
+
type SentinelState = 'above' | 'visible' | 'below';
|
|
9
|
+
|
|
10
|
+
export class TableStickyScrollbar {
|
|
11
|
+
private wrapper: HTMLDivElement;
|
|
12
|
+
private view: EditorView;
|
|
13
|
+
private editorScrollableElement?: HTMLElement | Document;
|
|
14
|
+
private intersectionObserver?: IntersectionObserver;
|
|
15
|
+
private stickyScrollbarContainerElement?: HTMLDivElement | null;
|
|
16
|
+
|
|
17
|
+
private sentinels: {
|
|
18
|
+
bottom?: HTMLElement | null;
|
|
19
|
+
top?: HTMLElement | null;
|
|
20
|
+
} = {};
|
|
21
|
+
|
|
22
|
+
private topSentinelState?: SentinelState;
|
|
23
|
+
private bottomSentinelState?: SentinelState;
|
|
24
|
+
|
|
25
|
+
constructor(wrapper: HTMLDivElement, view: EditorView) {
|
|
26
|
+
this.wrapper = wrapper;
|
|
27
|
+
this.view = view;
|
|
28
|
+
|
|
29
|
+
this.init();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
dispose() {
|
|
33
|
+
if (this.stickyScrollbarContainerElement) {
|
|
34
|
+
this.stickyScrollbarContainerElement.removeEventListener(
|
|
35
|
+
'scroll',
|
|
36
|
+
this.handleScrollDebounced,
|
|
37
|
+
);
|
|
38
|
+
this.handleScrollDebounced.cancel();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.deleteIntesactionObserver();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
scrollLeft(left: number) {
|
|
45
|
+
if (this.stickyScrollbarContainerElement) {
|
|
46
|
+
this.stickyScrollbarContainerElement.scrollLeft = left;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private init() {
|
|
51
|
+
if (!this.wrapper) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.stickyScrollbarContainerElement =
|
|
56
|
+
this.wrapper.parentElement?.querySelector(
|
|
57
|
+
`.${ClassName.TABLE_STICKY_SCROLLBAR_CONTAINER}`,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (this.stickyScrollbarContainerElement) {
|
|
61
|
+
this.stickyScrollbarContainerElement.addEventListener(
|
|
62
|
+
'scroll',
|
|
63
|
+
this.handleScrollDebounced,
|
|
64
|
+
{ passive: true },
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.createIntersectionObserver();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private createIntersectionObserver() {
|
|
72
|
+
this.editorScrollableElement =
|
|
73
|
+
(findOverflowScrollParent(this.view.dom) as HTMLElement) ||
|
|
74
|
+
window.document;
|
|
75
|
+
|
|
76
|
+
if (!this.editorScrollableElement || !this.wrapper) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.intersectionObserver = new IntersectionObserver(
|
|
81
|
+
(entries: IntersectionObserverEntry[], _: IntersectionObserver) => {
|
|
82
|
+
if (!this.stickyScrollbarContainerElement) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
entries.forEach((entry) => {
|
|
87
|
+
const target = entry.target as HTMLElement;
|
|
88
|
+
// if the rootBounds has 0 height, e.g. confluence preview mode, we do nothing.
|
|
89
|
+
if (entry.rootBounds?.height === 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
target.classList.contains(
|
|
95
|
+
ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM,
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
this.sentenialBottomCallback(entry);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
target.classList.contains(
|
|
103
|
+
ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_TOP,
|
|
104
|
+
)
|
|
105
|
+
) {
|
|
106
|
+
this.sentenialTopCallback(entry);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
{ root: this.editorScrollableElement },
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
this.sentinels.bottom = this.wrapper?.parentElement
|
|
114
|
+
?.getElementsByClassName(ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM)
|
|
115
|
+
?.item(0) as HTMLElement;
|
|
116
|
+
|
|
117
|
+
this.sentinels.top = this.wrapper?.parentElement
|
|
118
|
+
?.getElementsByClassName(ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_TOP)
|
|
119
|
+
?.item(0) as HTMLElement;
|
|
120
|
+
|
|
121
|
+
[this.sentinels.bottom, this.sentinels.top].forEach((el) =>
|
|
122
|
+
this.intersectionObserver!.observe(el),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private deleteIntesactionObserver() {
|
|
127
|
+
if (this.intersectionObserver) {
|
|
128
|
+
if (this.sentinels.bottom) {
|
|
129
|
+
this.intersectionObserver.unobserve(this.sentinels.bottom);
|
|
130
|
+
}
|
|
131
|
+
this.intersectionObserver.disconnect();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private sentenialBottomCallback(entry: IntersectionObserverEntry) {
|
|
136
|
+
const sentinelIsAboveScrollArea =
|
|
137
|
+
entry.boundingClientRect.top < (entry.rootBounds?.top || 0);
|
|
138
|
+
|
|
139
|
+
this.bottomSentinelState = sentinelIsAboveScrollArea
|
|
140
|
+
? 'above'
|
|
141
|
+
: entry.isIntersecting
|
|
142
|
+
? 'visible'
|
|
143
|
+
: 'below';
|
|
144
|
+
|
|
145
|
+
this.toggle();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private sentenialTopCallback(entry: IntersectionObserverEntry) {
|
|
149
|
+
const sentinelIsBelowScrollArea =
|
|
150
|
+
(entry.rootBounds?.bottom || 0) < entry.boundingClientRect.top;
|
|
151
|
+
|
|
152
|
+
this.topSentinelState = sentinelIsBelowScrollArea
|
|
153
|
+
? 'below'
|
|
154
|
+
: entry.isIntersecting
|
|
155
|
+
? 'visible'
|
|
156
|
+
: 'above';
|
|
157
|
+
|
|
158
|
+
this.toggle();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private toggle() {
|
|
162
|
+
if (
|
|
163
|
+
(this.topSentinelState === 'visible' ||
|
|
164
|
+
this.topSentinelState === 'above') &&
|
|
165
|
+
this.bottomSentinelState === 'below'
|
|
166
|
+
) {
|
|
167
|
+
this.show();
|
|
168
|
+
} else {
|
|
169
|
+
this.hide();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private hide() {
|
|
174
|
+
if (
|
|
175
|
+
this.stickyScrollbarContainerElement &&
|
|
176
|
+
this.stickyScrollbarContainerElement.style.display !== 'none'
|
|
177
|
+
) {
|
|
178
|
+
this.stickyScrollbarContainerElement.style.display = 'none';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private show() {
|
|
183
|
+
if (
|
|
184
|
+
this.stickyScrollbarContainerElement &&
|
|
185
|
+
this.stickyScrollbarContainerElement.style.display !== 'block'
|
|
186
|
+
) {
|
|
187
|
+
this.stickyScrollbarContainerElement.style.display = 'block';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private handleScroll = (event: Event) => {
|
|
192
|
+
if (
|
|
193
|
+
!this.stickyScrollbarContainerElement ||
|
|
194
|
+
!this.wrapper ||
|
|
195
|
+
event.target !== this.stickyScrollbarContainerElement
|
|
196
|
+
) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.wrapper.scrollLeft = this.stickyScrollbarContainerElement.scrollLeft;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
private handleScrollDebounced = rafSchedule(this.handleScroll);
|
|
204
|
+
}
|
|
@@ -33,13 +33,13 @@ import {
|
|
|
33
33
|
getNodeName,
|
|
34
34
|
isReferencedSource,
|
|
35
35
|
} from '@atlaskit/editor-common/utils';
|
|
36
|
-
import { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
37
|
-
import { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
36
|
+
import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
37
|
+
import type { EditorState } from '@atlaskit/editor-prosemirror/state';
|
|
38
38
|
import { findParentDomRefOfType } from '@atlaskit/editor-prosemirror/utils';
|
|
39
|
-
import { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
39
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
40
40
|
import { akEditorFloatingPanelZIndex } from '@atlaskit/editor-shared-styles';
|
|
41
41
|
import { shortcutStyle } from '@atlaskit/editor-shared-styles/shortcut';
|
|
42
|
-
import { Rect } from '@atlaskit/editor-tables/table-map';
|
|
42
|
+
import type { Rect } from '@atlaskit/editor-tables/table-map';
|
|
43
43
|
import {
|
|
44
44
|
findCellRectClosestToPos,
|
|
45
45
|
findTable,
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
splitCell,
|
|
49
49
|
} from '@atlaskit/editor-tables/utils';
|
|
50
50
|
import RemoveIcon from '@atlaskit/icon/glyph/editor/remove';
|
|
51
|
+
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
51
52
|
|
|
52
53
|
import {
|
|
53
54
|
clearHoverSelection,
|
|
@@ -79,13 +80,13 @@ import { pluginKey as tableResizingPluginKey } from './pm-plugins/table-resizing
|
|
|
79
80
|
import { getNewResizeStateFromSelectedColumns } from './pm-plugins/table-resizing/utils/resize-state';
|
|
80
81
|
import { pluginKey as tableWidthPluginKey } from './pm-plugins/table-width';
|
|
81
82
|
import { canMergeCells } from './transforms';
|
|
82
|
-
import {
|
|
83
|
+
import type {
|
|
83
84
|
PluginConfig,
|
|
84
|
-
TableCssClassName,
|
|
85
85
|
ToolbarMenuConfig,
|
|
86
86
|
ToolbarMenuContext,
|
|
87
87
|
ToolbarMenuState,
|
|
88
88
|
} from './types';
|
|
89
|
+
import { TableCssClassName } from './types';
|
|
89
90
|
import { messages as ContextualMenuMessages } from './ui/FloatingContextualMenu/ContextualMenu';
|
|
90
91
|
import tableMessages from './ui/messages';
|
|
91
92
|
import {
|
|
@@ -520,6 +521,9 @@ export const getToolbarConfig =
|
|
|
520
521
|
getDomRef,
|
|
521
522
|
nodeType,
|
|
522
523
|
offset: [0, 18],
|
|
524
|
+
absoluteOffset: getBooleanFF('platform.editor.table-sticky-scrollbar')
|
|
525
|
+
? { top: -6 }
|
|
526
|
+
: { top: 0 },
|
|
523
527
|
zIndex: akEditorFloatingPanelZIndex + 1, // Place the context menu slightly above the others
|
|
524
528
|
items: [
|
|
525
529
|
menu,
|