@atlaskit/editor-plugin-table 5.2.2 → 5.3.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 (63) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/plugins/table/nodeviews/TableComponent.js +20 -2
  3. package/dist/cjs/plugins/table/nodeviews/table.js +1 -0
  4. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/handlers.js +24 -0
  5. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/plugin-factory.js +29 -4
  6. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/plugin.js +17 -5
  7. package/dist/cjs/plugins/table/types.js +4 -0
  8. package/dist/cjs/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.js +106 -0
  9. package/dist/cjs/plugins/table/ui/TableFloatingColumnControls/index.js +90 -0
  10. package/dist/cjs/plugins/table/ui/common-styles.js +1 -1
  11. package/dist/cjs/plugins/table/ui/ui-styles.js +12 -9
  12. package/dist/es2019/plugins/table/nodeviews/TableComponent.js +20 -2
  13. package/dist/es2019/plugins/table/nodeviews/table.js +1 -0
  14. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/handlers.js +10 -0
  15. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/plugin-factory.js +28 -4
  16. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/plugin.js +14 -3
  17. package/dist/es2019/plugins/table/types.js +4 -0
  18. package/dist/es2019/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.js +101 -0
  19. package/dist/es2019/plugins/table/ui/TableFloatingColumnControls/index.js +60 -0
  20. package/dist/es2019/plugins/table/ui/common-styles.js +13 -1
  21. package/dist/es2019/plugins/table/ui/ui-styles.js +13 -0
  22. package/dist/esm/plugins/table/nodeviews/TableComponent.js +20 -2
  23. package/dist/esm/plugins/table/nodeviews/table.js +1 -0
  24. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/handlers.js +18 -0
  25. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/plugin-factory.js +28 -3
  26. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/plugin.js +18 -6
  27. package/dist/esm/plugins/table/types.js +4 -0
  28. package/dist/esm/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.js +97 -0
  29. package/dist/esm/plugins/table/ui/TableFloatingColumnControls/index.js +80 -0
  30. package/dist/esm/plugins/table/ui/common-styles.js +2 -2
  31. package/dist/esm/plugins/table/ui/ui-styles.js +11 -8
  32. package/dist/types/plugins/table/index.d.ts +1 -1
  33. package/dist/types/plugins/table/nodeviews/TableComponent.d.ts +1 -0
  34. package/dist/types/plugins/table/pm-plugins/drag-and-drop/handlers.d.ts +3 -0
  35. package/dist/types/plugins/table/pm-plugins/drag-and-drop/plugin-factory.d.ts +1 -2
  36. package/dist/types/plugins/table/pm-plugins/drag-and-drop/types.d.ts +2 -0
  37. package/dist/types/plugins/table/types.d.ts +4 -0
  38. package/dist/types/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.d.ts +11 -0
  39. package/dist/types/plugins/table/ui/TableFloatingColumnControls/index.d.ts +20 -0
  40. package/dist/types/plugins/table/ui/ui-styles.d.ts +1 -0
  41. package/dist/types-ts4.5/plugins/table/index.d.ts +1 -1
  42. package/dist/types-ts4.5/plugins/table/nodeviews/TableComponent.d.ts +1 -0
  43. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/handlers.d.ts +3 -0
  44. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/plugin-factory.d.ts +1 -2
  45. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/types.d.ts +2 -0
  46. package/dist/types-ts4.5/plugins/table/types.d.ts +4 -0
  47. package/dist/types-ts4.5/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.d.ts +11 -0
  48. package/dist/types-ts4.5/plugins/table/ui/TableFloatingColumnControls/index.d.ts +20 -0
  49. package/dist/types-ts4.5/plugins/table/ui/ui-styles.d.ts +1 -0
  50. package/package.json +1 -1
  51. package/src/__tests__/unit/ui/TableFloatingColumnControls.tsx +139 -0
  52. package/src/plugins/table/index.tsx +1 -1
  53. package/src/plugins/table/nodeviews/TableComponent.tsx +25 -0
  54. package/src/plugins/table/nodeviews/table.tsx +1 -0
  55. package/src/plugins/table/pm-plugins/drag-and-drop/handlers.ts +35 -0
  56. package/src/plugins/table/pm-plugins/drag-and-drop/plugin-factory.ts +27 -2
  57. package/src/plugins/table/pm-plugins/drag-and-drop/plugin.ts +12 -3
  58. package/src/plugins/table/pm-plugins/drag-and-drop/types.ts +3 -0
  59. package/src/plugins/table/types.ts +5 -0
  60. package/src/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.tsx +128 -0
  61. package/src/plugins/table/ui/TableFloatingColumnControls/index.tsx +101 -0
  62. package/src/plugins/table/ui/common-styles.ts +13 -0
  63. package/src/plugins/table/ui/ui-styles.ts +14 -0
@@ -225,6 +225,7 @@ export declare enum TableDecorations {
225
225
  TABLE_CONTROLS_HOVER = "TABLE_CONTROLS_HOVER",
226
226
  CELL_CONTROLS_HOVER = "CELL_CONTROLS_HOVER",
227
227
  COLUMN_CONTROLS_DECORATIONS = "COLUMN_CONTROLS_DECORATIONS",
228
+ COLUMN_DROP_TARGET_DECORATIONS = "COLUMN_DROP_TARGET_DECORATIONS",
228
229
  COLUMN_SELECTED = "COLUMN_SELECTED",
229
230
  COLUMN_RESIZING_HANDLE = "COLUMN_RESIZING_HANDLE",
230
231
  COLUMN_RESIZING_HANDLE_WIDGET = "COLUMN_RESIZING_HANDLE_WIDGET",
@@ -235,6 +236,9 @@ export declare const TableCssClassName: {
235
236
  COLUMN_CONTROLS: string;
236
237
  COLUMN_CONTROLS_DECORATIONS: string;
237
238
  COLUMN_SELECTED: string;
239
+ COLUMN_CONTROLS_WRAPPER: string;
240
+ COLUMN_DROP_TARGET_CONTROLS: string;
241
+ COLUMN_CONTROLS_INNER: string;
238
242
  ROW_CONTROLS_WRAPPER: string;
239
243
  ROW_CONTROLS: string;
240
244
  ROW_CONTROLS_INNER: string;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
3
+ export interface Props {
4
+ editorView: EditorView;
5
+ tableRef: HTMLTableElement;
6
+ stickyTop?: number;
7
+ tableHeight?: number;
8
+ localId?: string;
9
+ }
10
+ export declare const ColumnDropTargets: React.FC<Props>;
11
+ export default ColumnDropTargets;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import type { TableColumnOrdering } from '@atlaskit/custom-steps';
3
+ import type { GetEditorFeatureFlags } from '@atlaskit/editor-common/types';
4
+ import type { Selection } from '@atlaskit/editor-prosemirror/state';
5
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
6
+ import type { RowStickyState } from '../../pm-plugins/sticky-headers';
7
+ export interface Props {
8
+ editorView: EditorView;
9
+ getEditorFeatureFlags: GetEditorFeatureFlags;
10
+ selection?: Selection;
11
+ tableRef?: HTMLTableElement;
12
+ tableActive?: boolean;
13
+ hasHeaderRow?: boolean;
14
+ headerRowHeight?: number;
15
+ hoveredRows?: number[];
16
+ ordering?: TableColumnOrdering;
17
+ stickyHeader?: RowStickyState;
18
+ }
19
+ export declare const TableFloatingColumnControls: React.FC<Props>;
20
+ export default TableFloatingColumnControls;
@@ -8,6 +8,7 @@ export declare const insertRowButtonWrapper: (props: ThemeProps) => import("@emo
8
8
  export declare const columnControlsLineMarker: () => import("@emotion/react").SerializedStyles;
9
9
  export declare const DeleteButton: (props: ThemeProps) => import("@emotion/react").SerializedStyles;
10
10
  export declare const OverflowShadow: (props: ThemeProps) => import("@emotion/react").SerializedStyles;
11
+ export declare const floatingColumnControls: (props: ThemeProps) => import("@emotion/react").SerializedStyles;
11
12
  export declare const columnControlsDecoration: (props: ThemeProps) => import("@emotion/react").SerializedStyles;
12
13
  export declare const hoveredDeleteButton: (props: ThemeProps) => import("@emotion/react").SerializedStyles;
13
14
  export declare const hoveredCell: (props: ThemeProps) => import("@emotion/react").SerializedStyles;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-table",
3
- "version": "5.2.2",
3
+ "version": "5.3.0",
4
4
  "description": "Table plugin for the @atlaskit/editor",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+
3
+ import { render, screen } from '@testing-library/react';
4
+ import { IntlProvider } from 'react-intl-next';
5
+
6
+ import type { DocBuilder } from '@atlaskit/editor-common/types';
7
+ // eslint-disable-next-line import/no-extraneous-dependencies -- Removed import for fixing circular dependencies
8
+ import { analyticsPlugin } from '@atlaskit/editor-plugin-analytics';
9
+ import { contentInsertionPlugin } from '@atlaskit/editor-plugin-content-insertion';
10
+ import { featureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
11
+ import { guidelinePlugin } from '@atlaskit/editor-plugin-guideline';
12
+ import { selectionPlugin } from '@atlaskit/editor-plugin-selection';
13
+ import { widthPlugin } from '@atlaskit/editor-plugin-width';
14
+ import type { PluginKey } from '@atlaskit/editor-prosemirror/state';
15
+ import type { LightEditorPlugin } from '@atlaskit/editor-test-helpers/create-prosemirror-editor';
16
+ // eslint-disable-next-line import/no-extraneous-dependencies -- Removed import for fixing circular dependencies
17
+ import {
18
+ createProsemirrorEditorFactory,
19
+ Preset,
20
+ } from '@atlaskit/editor-test-helpers/create-prosemirror-editor';
21
+ // eslint-disable-next-line import/no-extraneous-dependencies -- Removed import for fixing circular dependencies
22
+ import {
23
+ doc,
24
+ p,
25
+ table,
26
+ tdEmpty,
27
+ tr,
28
+ } from '@atlaskit/editor-test-helpers/doc-builder';
29
+
30
+ import tablePlugin from '../../../plugins/table-plugin';
31
+ import { pluginKey } from '../../../plugins/table/pm-plugins/plugin-key';
32
+ import type { TablePluginState } from '../../../plugins/table/types';
33
+ import TableFloatingColumnControls from '../../../plugins/table/ui/TableFloatingColumnControls';
34
+
35
+ describe('TableFloatingColumnControls', () => {
36
+ const createEditor = createProsemirrorEditorFactory();
37
+ const fakeGetEditorFeatureFlags = () => ({});
38
+ const editor = (
39
+ doc: DocBuilder,
40
+ options?: {
41
+ dragAndDropEnabled?: boolean;
42
+ },
43
+ ) => {
44
+ const preset = new Preset<LightEditorPlugin>()
45
+ .add([featureFlagsPlugin, {}])
46
+ .add([analyticsPlugin, {}])
47
+ .add(contentInsertionPlugin)
48
+ .add(widthPlugin)
49
+ .add(guidelinePlugin)
50
+ .add(selectionPlugin)
51
+ .add([tablePlugin, { ...options, tableOptions: {} }]);
52
+ return createEditor<TablePluginState, PluginKey, typeof preset>({
53
+ doc,
54
+ preset,
55
+ pluginKey: pluginKey,
56
+ });
57
+ };
58
+
59
+ it('should not render floating column controls when tableRef undefined and drag and drop is not enabled', () => {
60
+ const { editorView } = editor(
61
+ doc(p('text'), table()(tr(tdEmpty, tdEmpty, tdEmpty))),
62
+ );
63
+ const { container } = render(
64
+ <TableFloatingColumnControls
65
+ editorView={editorView}
66
+ getEditorFeatureFlags={fakeGetEditorFeatureFlags}
67
+ />,
68
+ );
69
+ expect(container.innerHTML).toEqual('');
70
+ });
71
+
72
+ it('should not render floating column controls when tableRef undefined and drag and drop is enabled', () => {
73
+ const { editorView } = editor(
74
+ doc(p('text'), table()(tr(tdEmpty, tdEmpty, tdEmpty))),
75
+ {
76
+ dragAndDropEnabled: true,
77
+ },
78
+ );
79
+ const { container } = render(
80
+ <TableFloatingColumnControls
81
+ editorView={editorView}
82
+ getEditorFeatureFlags={fakeGetEditorFeatureFlags}
83
+ />,
84
+ );
85
+ expect(container.innerHTML).toEqual('');
86
+ });
87
+
88
+ it('should render a drop target per column', () => {
89
+ const { editorView } = editor(
90
+ doc(p('text'), table()(tr(tdEmpty, tdEmpty, tdEmpty, tdEmpty, tdEmpty))),
91
+ {
92
+ dragAndDropEnabled: true,
93
+ },
94
+ );
95
+ const ref = editorView.dom.querySelector('table') || undefined;
96
+
97
+ render(
98
+ <IntlProvider locale="en">
99
+ <TableFloatingColumnControls
100
+ tableRef={ref}
101
+ tableActive={true}
102
+ editorView={editorView}
103
+ getEditorFeatureFlags={fakeGetEditorFeatureFlags}
104
+ />
105
+ </IntlProvider>,
106
+ );
107
+
108
+ const dropTargets = screen.getAllByTestId(
109
+ 'table-floating-column-controls-drop-target',
110
+ );
111
+ expect(dropTargets).toHaveLength(5);
112
+ });
113
+
114
+ it('should render a drop target per column regardless of row count', () => {
115
+ const { editorView } = editor(
116
+ doc(p('text'), table()(tr(tdEmpty), tr(tdEmpty), tr(tdEmpty))),
117
+ {
118
+ dragAndDropEnabled: true,
119
+ },
120
+ );
121
+ const ref = editorView.dom.querySelector('table') || undefined;
122
+
123
+ render(
124
+ <IntlProvider locale="en">
125
+ <TableFloatingColumnControls
126
+ tableRef={ref}
127
+ tableActive={true}
128
+ editorView={editorView}
129
+ getEditorFeatureFlags={fakeGetEditorFeatureFlags}
130
+ />
131
+ </IntlProvider>,
132
+ );
133
+
134
+ const dropTargets = screen.getAllByTestId(
135
+ 'table-floating-column-controls-drop-target',
136
+ );
137
+ expect(dropTargets).toHaveLength(1);
138
+ });
139
+ });
@@ -78,7 +78,7 @@ import FloatingDeleteButton from './ui/FloatingDeleteButton';
78
78
  import FloatingInsertButton from './ui/FloatingInsertButton';
79
79
  import LayoutButton from './ui/LayoutButton';
80
80
  import { isLayoutSupported } from './utils';
81
- interface TablePluginOptions {
81
+ export interface TablePluginOptions {
82
82
  tableOptions: PluginConfig;
83
83
  // experimental custom table resizing experience, set inside editor-core behind a feature flag
84
84
  // will eventually replace breakoutEnabled
@@ -58,6 +58,7 @@ import {
58
58
  tableOverflowShadowWidth,
59
59
  tableOverflowShadowWidthWide,
60
60
  } from '../ui/consts';
61
+ import TableFloatingColumnControls from '../ui/TableFloatingColumnControls';
61
62
  import TableFloatingControls from '../ui/TableFloatingControls';
62
63
  import {
63
64
  containsHeaderRow,
@@ -91,6 +92,7 @@ export interface ComponentProps {
91
92
  isHeaderRowEnabled: boolean;
92
93
  isHeaderColumnEnabled: boolean;
93
94
  isMediaFullscreen?: boolean;
95
+ isDragAndDropEnabled?: boolean;
94
96
  tableActive: boolean;
95
97
  ordering: TableColumnOrdering;
96
98
  isResizing?: boolean;
@@ -396,6 +398,7 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
396
398
  options,
397
399
  getPos,
398
400
  pluginInjectionApi,
401
+ isDragAndDropEnabled,
399
402
  } = this.props;
400
403
  const { showBeforeShadow, showAfterShadow } = this.state;
401
404
  const node = getNode();
@@ -431,6 +434,24 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
431
434
  </div>
432
435
  );
433
436
 
437
+ const colControls = (
438
+ <div className={ClassName.COLUMN_CONTROLS_WRAPPER}>
439
+ <TableFloatingColumnControls
440
+ editorView={view}
441
+ tableRef={tableRef}
442
+ tableActive={tableActive}
443
+ hoveredRows={hoveredRows}
444
+ ordering={ordering}
445
+ hasHeaderRow={hasHeaderRow}
446
+ // pass `selection` and `tableHeight` to control re-render
447
+ selection={view.state.selection}
448
+ headerRowHeight={headerRow ? headerRow.offsetHeight : undefined}
449
+ stickyHeader={this.state.stickyHeader}
450
+ getEditorFeatureFlags={this.props.getEditorFeatureFlags}
451
+ />
452
+ </div>
453
+ );
454
+
434
455
  const shadowPadding =
435
456
  allowControls && tableActive ? -tableToolbarSize : tableMarginSides;
436
457
 
@@ -482,7 +503,11 @@ class TableComponent extends React.Component<ComponentProps, TableState> {
482
503
  data-testid="sticky-scrollbar-sentinel-top"
483
504
  />
484
505
  )}
506
+
485
507
  {allowControls && rowControls}
508
+
509
+ {isDragAndDropEnabled && allowControls && colControls}
510
+
486
511
  <div
487
512
  style={shadowStyle(showBeforeShadow)}
488
513
  className={ClassName.TABLE_LEFT_SHADOW}
@@ -248,6 +248,7 @@ export default class TableView extends ReactNodeView<Props> {
248
248
  allowControls={pluginState!.pluginConfig.allowControls!}
249
249
  isHeaderRowEnabled={pluginState!.isHeaderRowEnabled}
250
250
  isHeaderColumnEnabled={pluginState!.isHeaderColumnEnabled}
251
+ isDragAndDropEnabled={pluginState!.isDragAndDropEnabled}
251
252
  tableActive={tableActive}
252
253
  ordering={pluginState!.ordering as TableColumnOrdering}
253
254
  isResizing={isResizing}
@@ -0,0 +1,35 @@
1
+ // @ts-ignore -- ReadonlyTransaction is a local declaration and will cause a TS2305 error in CCFE typecheck
2
+ import type {
3
+ ReadonlyTransaction,
4
+ Transaction,
5
+ } from '@atlaskit/editor-prosemirror/state';
6
+ import type { ContentNodeWithPos } from '@atlaskit/editor-prosemirror/utils';
7
+ import { findTable } from '@atlaskit/editor-tables/utils';
8
+
9
+ import type { DragAndDropPluginState } from './types';
10
+
11
+ type BuilderDragAndDropPluginState = (props: {
12
+ tr: Transaction | ReadonlyTransaction;
13
+ table?: ContentNodeWithPos;
14
+ }) => (pluginState: DragAndDropPluginState) => DragAndDropPluginState;
15
+
16
+ const buildPluginState =
17
+ (
18
+ builders: Array<BuilderDragAndDropPluginState>,
19
+ ): BuilderDragAndDropPluginState =>
20
+ (props) =>
21
+ (pluginState) => {
22
+ return builders.reduce(
23
+ (_pluginState, transform) => transform(props)(_pluginState),
24
+ pluginState,
25
+ );
26
+ };
27
+
28
+ export const handleDocOrSelectionChanged = (
29
+ tr: Transaction | ReadonlyTransaction,
30
+ pluginState: DragAndDropPluginState,
31
+ ): DragAndDropPluginState =>
32
+ buildPluginState([])({
33
+ tr,
34
+ table: findTable(tr.selection),
35
+ })(pluginState);
@@ -1,8 +1,33 @@
1
1
  import { pluginFactory } from '@atlaskit/editor-common/utils';
2
2
 
3
+ import { pluginKey as tablePluginKey } from '../plugin-key';
4
+
5
+ import { handleDocOrSelectionChanged } from './handlers';
3
6
  import { pluginKey } from './plugin-key';
4
7
  import reducer from './reducer';
5
8
 
6
- const { createPluginState, createCommand } = pluginFactory(pluginKey, reducer);
9
+ export const { createPluginState, createCommand, getPluginState } =
10
+ pluginFactory(pluginKey, reducer, {
11
+ mapping: (tr, pluginState) => {
12
+ if (tr.docChanged) {
13
+ let decorationSet = pluginState.decorationSet;
14
+
15
+ const meta = tr.getMeta(tablePluginKey);
16
+ if (meta && meta.data && meta.data.decorationSet) {
17
+ decorationSet = meta.data.decorationSet;
18
+ }
19
+
20
+ if (decorationSet) {
21
+ decorationSet = decorationSet.map(tr.mapping, tr.doc);
22
+ }
7
23
 
8
- export { createPluginState, createCommand };
24
+ return {
25
+ ...pluginState,
26
+ ...{ decorationSet },
27
+ };
28
+ }
29
+ return pluginState;
30
+ },
31
+ onDocChanged: handleDocOrSelectionChanged,
32
+ onSelectionChanged: handleDocOrSelectionChanged,
33
+ });
@@ -4,9 +4,10 @@ import type {
4
4
  } from '@atlaskit/editor-common/event-dispatcher';
5
5
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
6
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
7
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
8
 
8
9
  import { DropTargetType } from './consts';
9
- import { createPluginState } from './plugin-factory';
10
+ import { createPluginState, getPluginState } from './plugin-factory';
10
11
  import { pluginKey } from './plugin-key';
11
12
 
12
13
  export const createPlugin = (
@@ -14,19 +15,27 @@ export const createPlugin = (
14
15
  eventDispatcher: EventDispatcher,
15
16
  ) => {
16
17
  return new SafePlugin({
17
- state: createPluginState(dispatch, {
18
+ state: createPluginState(dispatch, (state) => ({
19
+ decorationSet: DecorationSet.empty,
18
20
  // TODO: This is example placeholder state. We could use this to track which row/col is currently set as the drop target
19
21
  // This would result in a blue highlight being displayed on the corrisponding row/column to single the drop target location.
20
22
  dropTargetType: DropTargetType.NONE,
21
23
  dropTargetIndex: 0,
22
- }),
24
+ })),
23
25
  key: pluginKey,
24
26
  view: (editorView: EditorView) => {
25
27
  // TODO: Add Pragmatic DnD monitor when the view is constructed.
28
+
26
29
  return {
27
30
  // TODO: Cleanup monitor instance
28
31
  // destroy: cleanup,
29
32
  };
30
33
  },
34
+ props: {
35
+ decorations: (state) => {
36
+ const { decorationSet } = getPluginState(state);
37
+ return decorationSet;
38
+ },
39
+ },
31
40
  });
32
41
  };
@@ -1,6 +1,9 @@
1
+ import type { DecorationSet } from '@atlaskit/editor-prosemirror/view';
2
+
1
3
  import type { DropTargetType } from './consts';
2
4
 
3
5
  export interface DragAndDropPluginState {
6
+ decorationSet: DecorationSet;
4
7
  dropTargetType: DropTargetType;
5
8
  dropTargetIndex: number;
6
9
  }
@@ -239,6 +239,7 @@ export enum TableDecorations {
239
239
  CELL_CONTROLS_HOVER = 'CELL_CONTROLS_HOVER',
240
240
 
241
241
  COLUMN_CONTROLS_DECORATIONS = 'COLUMN_CONTROLS_DECORATIONS',
242
+ COLUMN_DROP_TARGET_DECORATIONS = 'COLUMN_DROP_TARGET_DECORATIONS',
242
243
  COLUMN_SELECTED = 'COLUMN_SELECTED',
243
244
  COLUMN_RESIZING_HANDLE = 'COLUMN_RESIZING_HANDLE',
244
245
  COLUMN_RESIZING_HANDLE_WIDGET = 'COLUMN_RESIZING_HANDLE_WIDGET',
@@ -254,6 +255,10 @@ export const TableCssClassName = {
254
255
  COLUMN_CONTROLS_DECORATIONS: `${tablePrefixSelector}-column-controls-decoration`,
255
256
  COLUMN_SELECTED: `${tablePrefixSelector}-column__selected`,
256
257
 
258
+ COLUMN_CONTROLS_WRAPPER: `${tablePrefixSelector}-col-controls-wrapper`,
259
+ COLUMN_DROP_TARGET_CONTROLS: `${tablePrefixSelector}-col-drop-target-controls`,
260
+ COLUMN_CONTROLS_INNER: `${tablePrefixSelector}-col-controls__inner`,
261
+
257
262
  ROW_CONTROLS_WRAPPER: `${tablePrefixSelector}-row-controls-wrapper`,
258
263
  ROW_CONTROLS: `${tablePrefixSelector}-row-controls`,
259
264
  ROW_CONTROLS_INNER: `${tablePrefixSelector}-row-controls__inner`,
@@ -0,0 +1,128 @@
1
+ import React, { useEffect, useMemo, useRef } from 'react';
2
+
3
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
+ import { attachClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/addon/closest-edge';
5
+ import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/adapter/element';
6
+
7
+ import type { DraggableSourceData } from '../../../types';
8
+ import { TableCssClassName as ClassName } from '../../../types';
9
+ import { getColumnsWidths, getRowHeights } from '../../../utils';
10
+
11
+ export interface Props {
12
+ editorView: EditorView;
13
+ tableRef: HTMLTableElement;
14
+ stickyTop?: number;
15
+ tableHeight?: number;
16
+ localId?: string;
17
+ }
18
+
19
+ export const ColumnDropTargets: React.FC<Props> = ({
20
+ editorView,
21
+ tableRef,
22
+ tableHeight,
23
+ stickyTop,
24
+ localId,
25
+ }) => {
26
+ const colWidths = getColumnsWidths(editorView);
27
+ const rowHeights = useMemo(() => {
28
+ // NOTE: we don't care so much as to what tableHeight is, we only care that it changed and is a sane value.
29
+ if (tableRef && !!tableHeight) {
30
+ return getRowHeights(tableRef);
31
+ }
32
+ return [0];
33
+ }, [tableRef, tableHeight]);
34
+
35
+ if (!tableRef) {
36
+ return null;
37
+ }
38
+
39
+ const firstRow = tableRef.querySelector('tr');
40
+ const hasHeaderRow = firstRow
41
+ ? firstRow.getAttribute('data-header-row')
42
+ : false;
43
+
44
+ const marginTop =
45
+ hasHeaderRow && stickyTop !== undefined ? rowHeights?.[0] ?? 0 : 0;
46
+
47
+ return (
48
+ <div className={ClassName.COLUMN_DROP_TARGET_CONTROLS}>
49
+ <div
50
+ className={ClassName.COLUMN_CONTROLS_INNER}
51
+ data-testid="table-floating-column-controls-drop-targets"
52
+ >
53
+ {colWidths.map((width, index) => {
54
+ return (
55
+ <ColumnDropTarget
56
+ key={index}
57
+ index={index}
58
+ localId={localId}
59
+ width={width}
60
+ height={tableHeight}
61
+ marginTop={marginTop}
62
+ />
63
+ );
64
+ })}
65
+ </div>
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export default ColumnDropTargets;
71
+
72
+ const ColumnDropTarget: React.FC<{
73
+ index: number;
74
+ localId?: string;
75
+ width?: number;
76
+ height?: number;
77
+ marginTop?: number;
78
+ }> = ({ index, localId, width, height, marginTop }) => {
79
+ const dropTargetRef = useRef<HTMLDivElement | null>(null);
80
+
81
+ useEffect(() => {
82
+ if (!dropTargetRef.current) {
83
+ return;
84
+ }
85
+
86
+ return dropTargetForElements({
87
+ element: dropTargetRef.current,
88
+ canDrop({ source }) {
89
+ const data = source.data as DraggableSourceData;
90
+ return (
91
+ // Only draggables of row type can be dropped on this target
92
+ data.type === 'table-column' &&
93
+ // Only draggables which came from the same table can be dropped on this target
94
+ data.localId === localId &&
95
+ // Only draggables which DO NOT include this drop targets index can be dropped
96
+ !!data.indexes?.length &&
97
+ data.indexes?.indexOf(index) === -1
98
+ );
99
+ },
100
+ getData({ input, element }) {
101
+ const data = {
102
+ localId,
103
+ type: 'table-column',
104
+ targetIndex: index,
105
+ };
106
+ return attachClosestEdge(data, {
107
+ input,
108
+ element,
109
+ allowedEdges: ['left', 'right'],
110
+ });
111
+ },
112
+ });
113
+ }, [index, localId]);
114
+
115
+ return (
116
+ <div
117
+ ref={dropTargetRef}
118
+ style={{
119
+ width: width && `${width - 1}px`,
120
+ height: height && `${height}px`,
121
+ marginTop: marginTop && `${marginTop}px`,
122
+ }}
123
+ data-drop-target-index={index}
124
+ data-drop-target-localid={localId}
125
+ data-testid="table-floating-column-controls-drop-target"
126
+ ></div>
127
+ );
128
+ };
@@ -0,0 +1,101 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+
3
+ import type { TableColumnOrdering } from '@atlaskit/custom-steps';
4
+ import type { GetEditorFeatureFlags } from '@atlaskit/editor-common/types';
5
+ import type { Selection } from '@atlaskit/editor-prosemirror/state';
6
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
7
+ import { findTable } from '@atlaskit/editor-tables';
8
+
9
+ import type { RowStickyState } from '../../pm-plugins/sticky-headers';
10
+
11
+ import { ColumnDropTargets } from './ColumnDropTargets';
12
+
13
+ export interface Props {
14
+ editorView: EditorView;
15
+ getEditorFeatureFlags: GetEditorFeatureFlags;
16
+ selection?: Selection;
17
+ tableRef?: HTMLTableElement;
18
+ tableActive?: boolean;
19
+ hasHeaderRow?: boolean;
20
+ headerRowHeight?: number;
21
+ hoveredRows?: number[];
22
+ ordering?: TableColumnOrdering;
23
+ stickyHeader?: RowStickyState;
24
+ }
25
+
26
+ export const TableFloatingColumnControls: React.FC<Props> = ({
27
+ editorView,
28
+ tableRef,
29
+ tableActive,
30
+ hasHeaderRow,
31
+ stickyHeader,
32
+ selection,
33
+ }) => {
34
+ const [tableRect, setTableRect] = useState<{ width: number; height: number }>(
35
+ { width: 0, height: 0 },
36
+ );
37
+
38
+ useEffect(() => {
39
+ if (tableRef && window?.ResizeObserver) {
40
+ const resizeObserver = new ResizeObserver((entries) => {
41
+ for (let entry of entries) {
42
+ setTableRect((prev) => {
43
+ if (
44
+ prev.width !== entry.contentRect.width ||
45
+ prev.height !== entry.contentRect.height
46
+ ) {
47
+ return entry.contentRect;
48
+ }
49
+ return prev;
50
+ });
51
+ }
52
+ });
53
+ resizeObserver.observe(tableRef);
54
+
55
+ return () => {
56
+ resizeObserver.disconnect();
57
+ };
58
+ }
59
+ }, [tableRef]);
60
+
61
+ const selectedLocalId = useMemo(() => {
62
+ if (!selection) {
63
+ return undefined;
64
+ }
65
+
66
+ const tableNode = findTable(selection);
67
+ if (!tableNode) {
68
+ return undefined;
69
+ }
70
+
71
+ return tableNode.node.attrs.localId;
72
+ }, [selection]);
73
+
74
+ if (!tableRef) {
75
+ return null;
76
+ }
77
+
78
+ const stickyTop =
79
+ stickyHeader && stickyHeader.sticky && hasHeaderRow
80
+ ? stickyHeader.top
81
+ : undefined;
82
+
83
+ return (
84
+ <div
85
+ onMouseDown={(e) => e.preventDefault()}
86
+ data-testid="table-floating-column-controls-wrapper"
87
+ >
88
+ {tableActive && (
89
+ <ColumnDropTargets
90
+ editorView={editorView}
91
+ tableRef={tableRef}
92
+ stickyTop={tableActive ? stickyTop : undefined}
93
+ tableHeight={tableRect.height}
94
+ localId={selectedLocalId}
95
+ />
96
+ )}
97
+ </div>
98
+ );
99
+ };
100
+
101
+ export default TableFloatingColumnControls;