@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.
Files changed (33) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/plugins/table/nodeviews/TableComponent.js +36 -2
  3. package/dist/cjs/plugins/table/nodeviews/TableResizer.js +7 -1
  4. package/dist/cjs/plugins/table/nodeviews/TableStickyScrollbar.js +154 -0
  5. package/dist/cjs/plugins/table/toolbar.js +6 -0
  6. package/dist/cjs/plugins/table/ui/common-styles.js +9 -2
  7. package/dist/es2019/plugins/table/nodeviews/TableComponent.js +37 -3
  8. package/dist/es2019/plugins/table/nodeviews/TableResizer.js +11 -1
  9. package/dist/es2019/plugins/table/nodeviews/TableStickyScrollbar.js +112 -0
  10. package/dist/es2019/plugins/table/toolbar.js +6 -0
  11. package/dist/es2019/plugins/table/ui/common-styles.js +33 -1
  12. package/dist/esm/plugins/table/nodeviews/TableComponent.js +37 -3
  13. package/dist/esm/plugins/table/nodeviews/TableResizer.js +7 -1
  14. package/dist/esm/plugins/table/nodeviews/TableStickyScrollbar.js +146 -0
  15. package/dist/esm/plugins/table/toolbar.js +6 -0
  16. package/dist/esm/plugins/table/ui/common-styles.js +8 -2
  17. package/dist/types/plugins/table/nodeviews/TableComponent.d.ts +1 -0
  18. package/dist/types/plugins/table/nodeviews/TableStickyScrollbar.d.ts +24 -0
  19. package/dist/types/plugins/table/toolbar.d.ts +4 -4
  20. package/dist/types/plugins/table/types.d.ts +3 -0
  21. package/dist/types/plugins/table/ui/common-styles.d.ts +1 -0
  22. package/dist/types-ts4.5/plugins/table/nodeviews/TableComponent.d.ts +1 -0
  23. package/dist/types-ts4.5/plugins/table/nodeviews/TableStickyScrollbar.d.ts +24 -0
  24. package/dist/types-ts4.5/plugins/table/toolbar.d.ts +4 -4
  25. package/dist/types-ts4.5/plugins/table/types.d.ts +3 -0
  26. package/dist/types-ts4.5/plugins/table/ui/common-styles.d.ts +1 -0
  27. package/package.json +6 -3
  28. package/src/__tests__/unit/nodeviews/TableContainer.tsx +43 -18
  29. package/src/plugins/table/nodeviews/TableComponent.tsx +54 -3
  30. package/src/plugins/table/nodeviews/TableResizer.tsx +7 -1
  31. package/src/plugins/table/nodeviews/TableStickyScrollbar.ts +204 -0
  32. package/src/plugins/table/toolbar.tsx +10 -6
  33. 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.1.2",
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.6.0",
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.7.0",
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: [tablePlugin({ config: undefined })],
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).toHaveBeenCalledWith({
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).toHaveBeenCalledWith({
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 { container, unmount, selectionActionMock, actualGuidelineMock };
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 { akEditorTableToolbarSize as tableToolbarSize } from '@atlaskit/editor-shared-styles';
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,