@atlaskit/editor-plugin-table 5.2.2 → 5.3.1

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 (112) hide show
  1. package/CHANGELOG.md +13 -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/commands.js +36 -7
  5. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/handlers.js +24 -0
  6. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/plugin-factory.js +29 -4
  7. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/plugin.js +83 -9
  8. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/reducer.js +2 -0
  9. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/utils/index.js +12 -0
  10. package/dist/cjs/plugins/table/pm-plugins/drag-and-drop/utils/monitor.js +59 -0
  11. package/dist/cjs/plugins/table/types.js +12 -1
  12. package/dist/cjs/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.js +106 -0
  13. package/dist/cjs/plugins/table/ui/TableFloatingColumnControls/index.js +90 -0
  14. package/dist/cjs/plugins/table/ui/common-styles.js +1 -1
  15. package/dist/cjs/plugins/table/ui/consts.js +2 -1
  16. package/dist/cjs/plugins/table/ui/ui-styles.js +15 -9
  17. package/dist/cjs/plugins/table/utils/decoration.js +67 -1
  18. package/dist/cjs/plugins/table/utils/index.js +26 -1
  19. package/dist/cjs/plugins/table/utils/merged-cells.js +66 -0
  20. package/dist/es2019/plugins/table/nodeviews/TableComponent.js +20 -2
  21. package/dist/es2019/plugins/table/nodeviews/table.js +1 -0
  22. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/commands.js +35 -7
  23. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/handlers.js +10 -0
  24. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/plugin-factory.js +28 -4
  25. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/plugin.js +82 -6
  26. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/reducer.js +2 -0
  27. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/utils/index.js +1 -0
  28. package/dist/es2019/plugins/table/pm-plugins/drag-and-drop/utils/monitor.js +56 -0
  29. package/dist/es2019/plugins/table/types.js +12 -1
  30. package/dist/es2019/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.js +101 -0
  31. package/dist/es2019/plugins/table/ui/TableFloatingColumnControls/index.js +60 -0
  32. package/dist/es2019/plugins/table/ui/common-styles.js +14 -1
  33. package/dist/es2019/plugins/table/ui/consts.js +1 -0
  34. package/dist/es2019/plugins/table/ui/ui-styles.js +127 -1
  35. package/dist/es2019/plugins/table/utils/decoration.js +62 -0
  36. package/dist/es2019/plugins/table/utils/index.js +3 -2
  37. package/dist/es2019/plugins/table/utils/merged-cells.js +48 -0
  38. package/dist/esm/plugins/table/nodeviews/TableComponent.js +20 -2
  39. package/dist/esm/plugins/table/nodeviews/table.js +1 -0
  40. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/commands.js +36 -7
  41. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/handlers.js +18 -0
  42. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/plugin-factory.js +28 -3
  43. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/plugin.js +82 -9
  44. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/reducer.js +2 -0
  45. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/utils/index.js +1 -0
  46. package/dist/esm/plugins/table/pm-plugins/drag-and-drop/utils/monitor.js +53 -0
  47. package/dist/esm/plugins/table/types.js +12 -1
  48. package/dist/esm/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.js +97 -0
  49. package/dist/esm/plugins/table/ui/TableFloatingColumnControls/index.js +80 -0
  50. package/dist/esm/plugins/table/ui/common-styles.js +2 -2
  51. package/dist/esm/plugins/table/ui/consts.js +1 -0
  52. package/dist/esm/plugins/table/ui/ui-styles.js +15 -9
  53. package/dist/esm/plugins/table/utils/decoration.js +66 -0
  54. package/dist/esm/plugins/table/utils/index.js +3 -2
  55. package/dist/esm/plugins/table/utils/merged-cells.js +60 -0
  56. package/dist/types/plugins/table/index.d.ts +1 -1
  57. package/dist/types/plugins/table/nodeviews/TableComponent.d.ts +1 -0
  58. package/dist/types/plugins/table/pm-plugins/drag-and-drop/actions.d.ts +5 -1
  59. package/dist/types/plugins/table/pm-plugins/drag-and-drop/commands.d.ts +6 -1
  60. package/dist/types/plugins/table/pm-plugins/drag-and-drop/handlers.d.ts +3 -0
  61. package/dist/types/plugins/table/pm-plugins/drag-and-drop/plugin-factory.d.ts +1 -2
  62. package/dist/types/plugins/table/pm-plugins/drag-and-drop/types.d.ts +2 -0
  63. package/dist/types/plugins/table/pm-plugins/drag-and-drop/utils/index.d.ts +1 -0
  64. package/dist/types/plugins/table/pm-plugins/drag-and-drop/utils/monitor.d.ts +3 -0
  65. package/dist/types/plugins/table/types.d.ts +23 -1
  66. package/dist/types/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.d.ts +11 -0
  67. package/dist/types/plugins/table/ui/TableFloatingColumnControls/index.d.ts +20 -0
  68. package/dist/types/plugins/table/ui/consts.d.ts +1 -0
  69. package/dist/types/plugins/table/ui/ui-styles.d.ts +2 -0
  70. package/dist/types/plugins/table/utils/decoration.d.ts +2 -0
  71. package/dist/types/plugins/table/utils/index.d.ts +2 -1
  72. package/dist/types/plugins/table/utils/merged-cells.d.ts +3 -0
  73. package/dist/types-ts4.5/plugins/table/index.d.ts +1 -1
  74. package/dist/types-ts4.5/plugins/table/nodeviews/TableComponent.d.ts +1 -0
  75. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/actions.d.ts +5 -1
  76. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/commands.d.ts +6 -1
  77. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/handlers.d.ts +3 -0
  78. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/plugin-factory.d.ts +1 -2
  79. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/types.d.ts +2 -0
  80. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/utils/index.d.ts +1 -0
  81. package/dist/types-ts4.5/plugins/table/pm-plugins/drag-and-drop/utils/monitor.d.ts +3 -0
  82. package/dist/types-ts4.5/plugins/table/types.d.ts +23 -1
  83. package/dist/types-ts4.5/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.d.ts +11 -0
  84. package/dist/types-ts4.5/plugins/table/ui/TableFloatingColumnControls/index.d.ts +20 -0
  85. package/dist/types-ts4.5/plugins/table/ui/consts.d.ts +1 -0
  86. package/dist/types-ts4.5/plugins/table/ui/ui-styles.d.ts +2 -0
  87. package/dist/types-ts4.5/plugins/table/utils/decoration.d.ts +2 -0
  88. package/dist/types-ts4.5/plugins/table/utils/index.d.ts +2 -1
  89. package/dist/types-ts4.5/plugins/table/utils/merged-cells.d.ts +3 -0
  90. package/package.json +4 -1
  91. package/src/__tests__/unit/ui/TableFloatingColumnControls.tsx +139 -0
  92. package/src/plugins/table/index.tsx +1 -1
  93. package/src/plugins/table/nodeviews/TableComponent.tsx +25 -0
  94. package/src/plugins/table/nodeviews/table.tsx +1 -0
  95. package/src/plugins/table/pm-plugins/drag-and-drop/actions.ts +6 -1
  96. package/src/plugins/table/pm-plugins/drag-and-drop/commands.ts +58 -8
  97. package/src/plugins/table/pm-plugins/drag-and-drop/handlers.ts +35 -0
  98. package/src/plugins/table/pm-plugins/drag-and-drop/plugin-factory.ts +27 -2
  99. package/src/plugins/table/pm-plugins/drag-and-drop/plugin.ts +88 -6
  100. package/src/plugins/table/pm-plugins/drag-and-drop/reducer.ts +2 -0
  101. package/src/plugins/table/pm-plugins/drag-and-drop/types.ts +3 -0
  102. package/src/plugins/table/pm-plugins/drag-and-drop/utils/index.ts +1 -0
  103. package/src/plugins/table/pm-plugins/drag-and-drop/utils/monitor.ts +72 -0
  104. package/src/plugins/table/types.ts +28 -1
  105. package/src/plugins/table/ui/TableFloatingColumnControls/ColumnDropTargets/index.tsx +128 -0
  106. package/src/plugins/table/ui/TableFloatingColumnControls/index.tsx +101 -0
  107. package/src/plugins/table/ui/common-styles.ts +15 -0
  108. package/src/plugins/table/ui/consts.ts +1 -0
  109. package/src/plugins/table/ui/ui-styles.ts +129 -0
  110. package/src/plugins/table/utils/decoration.ts +101 -0
  111. package/src/plugins/table/utils/index.ts +3 -0
  112. package/src/plugins/table/utils/merged-cells.ts +67 -0
@@ -1,22 +1,69 @@
1
- import type { Transaction } from '@atlaskit/editor-prosemirror/state';
1
+ import type {
2
+ EditorState,
3
+ Transaction,
4
+ } from '@atlaskit/editor-prosemirror/state';
5
+ import type { Decoration } from '@atlaskit/editor-prosemirror/view';
6
+ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
+
8
+ import { TableDecorations } from '../../types';
9
+ import {
10
+ createColumnInsertLine,
11
+ createRowInsertLine,
12
+ updateDecorations,
13
+ } from '../../utils';
2
14
 
3
15
  import { DragAndDropActionType } from './actions';
4
16
  import type { DropTargetType } from './consts';
5
- import { createCommand } from './plugin-factory';
17
+ import { createCommand, getPluginState } from './plugin-factory';
18
+ import { pluginKey } from './plugin-key';
6
19
 
7
20
  // TODO: This command is a placeholder example. Please replace this if required.
21
+ export const getDecorations = (state: EditorState): DecorationSet => {
22
+ return pluginKey.getState(state)?.decorationSet || DecorationSet.empty;
23
+ };
24
+
25
+ export const updatePluginStateDecorations = (
26
+ state: EditorState,
27
+ decorations: Decoration[],
28
+ key: TableDecorations,
29
+ ): DecorationSet =>
30
+ updateDecorations(state.doc, getDecorations(state), decorations, key);
31
+
8
32
  export const setDropTarget = (
9
33
  type: DropTargetType,
10
34
  index: number,
11
35
  tr?: Transaction,
12
36
  ) =>
13
37
  createCommand(
14
- {
15
- type: DragAndDropActionType.SET_DROP_TARGET,
16
- data: {
17
- type,
18
- index,
19
- },
38
+ (state) => {
39
+ const { dropTargetType, dropTargetIndex } = getPluginState(state);
40
+ if (dropTargetType === type && dropTargetIndex === index) {
41
+ return false;
42
+ }
43
+
44
+ let decorationSet = DecorationSet.empty;
45
+ if (type === 'column') {
46
+ decorationSet = updatePluginStateDecorations(
47
+ state,
48
+ createColumnInsertLine(index, state.selection),
49
+ TableDecorations.COLUMN_INSERT_LINE,
50
+ );
51
+ } else if (type === 'row') {
52
+ decorationSet = updatePluginStateDecorations(
53
+ state,
54
+ createRowInsertLine(index, state.selection),
55
+ TableDecorations.ROW_INSERT_LINE,
56
+ );
57
+ }
58
+
59
+ return {
60
+ type: DragAndDropActionType.SET_DROP_TARGET,
61
+ data: {
62
+ decorationSet,
63
+ type,
64
+ index,
65
+ },
66
+ };
20
67
  },
21
68
  (originalTr: Transaction) =>
22
69
  (tr || originalTr).setMeta('addToHistory', false),
@@ -26,6 +73,9 @@ export const clearDropTarget = (tr?: Transaction) =>
26
73
  createCommand(
27
74
  {
28
75
  type: DragAndDropActionType.CLEAR_DROP_TARGET,
76
+ data: {
77
+ decorationSet: DecorationSet.empty,
78
+ },
29
79
  },
30
80
  (originalTr: Transaction) =>
31
81
  (tr || originalTr).setMeta('addToHistory', false),
@@ -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,29 +4,111 @@ 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';
8
+ import { moveColumn, moveRow } from '@atlaskit/editor-tables/utils';
9
+ import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/adapter/element';
10
+
11
+ import type { DraggableSourceData } from '../../types';
12
+ import {
13
+ hasMergedCellsInColumn,
14
+ hasMergedCellsInRow,
15
+ } from '../../utils/merged-cells';
16
+ import { getPluginState as getTablePluginState } from '../plugin-factory';
7
17
 
8
18
  import { DropTargetType } from './consts';
9
- import { createPluginState } from './plugin-factory';
19
+ import { createPluginState, getPluginState } from './plugin-factory';
10
20
  import { pluginKey } from './plugin-key';
21
+ import { getDraggableDataFromEvent } from './utils/monitor';
11
22
 
12
23
  export const createPlugin = (
13
24
  dispatch: Dispatch,
14
25
  eventDispatcher: EventDispatcher,
15
26
  ) => {
16
27
  return new SafePlugin({
17
- state: createPluginState(dispatch, {
28
+ state: createPluginState(dispatch, (state) => ({
29
+ decorationSet: DecorationSet.empty,
18
30
  // TODO: This is example placeholder state. We could use this to track which row/col is currently set as the drop target
19
31
  // This would result in a blue highlight being displayed on the corrisponding row/column to single the drop target location.
20
32
  dropTargetType: DropTargetType.NONE,
21
33
  dropTargetIndex: 0,
22
- }),
34
+ })),
23
35
  key: pluginKey,
24
36
  view: (editorView: EditorView) => {
25
- // TODO: Add Pragmatic DnD monitor when the view is constructed.
26
37
  return {
27
- // TODO: Cleanup monitor instance
28
- // destroy: cleanup,
38
+ destroy: monitorForElements({
39
+ canMonitor({ source }) {
40
+ const { type, localId, indexes } =
41
+ source.data as Partial<DraggableSourceData>;
42
+
43
+ // First; Perform any quick checks so we can abort early.
44
+ if (
45
+ !indexes ||
46
+ !localId ||
47
+ // FIXME: We currently don't support DragNDrop of multiple elements. For now we will not bother to monitor drags
48
+ // of more then 1 item.
49
+ indexes.length !== 1 ||
50
+ !(type === 'table-row' || type === 'table-column')
51
+ ) {
52
+ return false;
53
+ }
54
+
55
+ const { tableNode } = getTablePluginState(editorView.state);
56
+ // If the draggable localId is the same as the current selected table localId then we will allow the monitor
57
+ // watch for changes
58
+ return localId === tableNode?.attrs.localId;
59
+ },
60
+ onDrag(event) {
61
+ const data = getDraggableDataFromEvent(event);
62
+
63
+ // If no data can be found then it's most like we do not want to perform any drag actions
64
+ if (!data) {
65
+ return;
66
+ }
67
+
68
+ // TODO: as we drag an element around we are going to want to update the state to acurately reflect the current
69
+ // insert location as to where the draggable will most likely be go. For example;
70
+ // const { sourceType, targetAdjustedIndex } = data;
71
+ // const highlight = sourceType === 'table-row' ? highlightRow : highlightColumn;
72
+ // return editorView.dispatch(
73
+ // highlight(targetAdjustedIndex)(editorView.state.tr),
74
+ // );
75
+ },
76
+ onDrop(event) {
77
+ const data = getDraggableDataFromEvent(event);
78
+
79
+ // If no data can be found then it's most like we do not want to perform any drop action
80
+ if (!data) {
81
+ return;
82
+ }
83
+
84
+ const { sourceType, sourceIndexes, targetAdjustedIndex } = data;
85
+
86
+ // If the drop target index contains merged cells then we should not allow the drop to occur.
87
+ const hasMergedCells =
88
+ sourceType === 'table-row'
89
+ ? hasMergedCellsInRow
90
+ : hasMergedCellsInColumn;
91
+ if (
92
+ hasMergedCells(targetAdjustedIndex)(editorView.state.selection)
93
+ ) {
94
+ return;
95
+ }
96
+
97
+ const move = sourceType === 'table-row' ? moveRow : moveColumn;
98
+
99
+ const [sourceIndex] = sourceIndexes;
100
+ return editorView.dispatch(
101
+ move(sourceIndex, targetAdjustedIndex)(editorView.state.tr),
102
+ );
103
+ },
104
+ }),
29
105
  };
30
106
  },
107
+ props: {
108
+ decorations: (state) => {
109
+ const { decorationSet } = getPluginState(state);
110
+ return decorationSet;
111
+ },
112
+ },
31
113
  });
32
114
  };
@@ -11,12 +11,14 @@ export default (
11
11
  case DragAndDropActionType.SET_DROP_TARGET:
12
12
  return {
13
13
  ...pluginState,
14
+ decorationSet: action.data.decorationSet,
14
15
  dropTargetType: action.data.type,
15
16
  dropTargetIndex: action.data.index,
16
17
  };
17
18
  case DragAndDropActionType.CLEAR_DROP_TARGET:
18
19
  return {
19
20
  ...pluginState,
21
+ decorationSet: action.data.decorationSet,
20
22
  dropTargetType: DropTargetType.NONE,
21
23
  dropTargetIndex: 0,
22
24
  };
@@ -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
  }
@@ -0,0 +1 @@
1
+ export { getDraggableDataFromEvent } from './monitor';
@@ -0,0 +1,72 @@
1
+ import { extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/addon/closest-edge';
2
+ import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/addon/closest-edge';
3
+ import type { ElementEventBasePayload } from '@atlaskit/pragmatic-drag-and-drop/adapter/element';
4
+
5
+ import type {
6
+ DraggableData,
7
+ DraggableSourceData,
8
+ DraggableTargetData,
9
+ } from '../../../types';
10
+
11
+ export const getDraggableDataFromEvent = ({
12
+ location,
13
+ source,
14
+ }: ElementEventBasePayload): DraggableData | undefined => {
15
+ const destination = location.current.dropTargets.at(0);
16
+ // If no target exists at the current location, then the current draggable is not over a target or the target doesn't support
17
+ // the current draggable.
18
+ if (!destination) {
19
+ return undefined;
20
+ }
21
+
22
+ // This is the draggable elements data
23
+ const {
24
+ indexes: sourceIndexes,
25
+ type: sourceType,
26
+ localId: sourceLocalId,
27
+ } = source.data as DraggableSourceData;
28
+
29
+ // This is the drop target's data
30
+ const {
31
+ targetIndex,
32
+ type: targetType,
33
+ localId: targetLocalId,
34
+ } = destination.data as DraggableTargetData;
35
+
36
+ // Some basic check to abort early with...
37
+ if (
38
+ !sourceIndexes ||
39
+ targetIndex < 0 ||
40
+ // abort if the type of the draggable is different to the target, for eg. rows cannot be dropped onto column targets.
41
+ sourceType !== targetType ||
42
+ // abort if the draggable is coming from a different table that the target is on.
43
+ sourceLocalId !== targetLocalId
44
+ ) {
45
+ return undefined;
46
+ }
47
+
48
+ // FIXME: currently we only support a single row/col index being moved, remove this clause when this is no longer the case.
49
+ if (sourceIndexes.length > 1) {
50
+ return undefined;
51
+ }
52
+
53
+ const targetClosestEdge =
54
+ extractClosestEdge(destination.data) ??
55
+ ((targetType === 'table-row' ? 'top' : 'left') as Edge);
56
+ // NOTE: By default we always insert row/cols at the target index to the top/left (retrospectively of row/cols).
57
+ // This introduces an offset in the event the drop occured closer to the bottom/right of the target. We want
58
+ // the new target index to be 1 index higher.
59
+ const targetOffset =
60
+ targetClosestEdge === 'right' || targetClosestEdge === 'bottom' ? 1 : 0;
61
+
62
+ return {
63
+ sourceType,
64
+ sourceLocalId,
65
+ sourceIndexes,
66
+ targetType,
67
+ targetLocalId,
68
+ targetIndex,
69
+ targetAdjustedIndex: targetIndex + targetOffset,
70
+ targetClosestEdge,
71
+ };
72
+ };
@@ -14,6 +14,7 @@ import type { Node as PmNode } from '@atlaskit/editor-prosemirror/model';
14
14
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
15
15
  import type { DecorationSet } from '@atlaskit/editor-prosemirror/view';
16
16
  import type { Rect } from '@atlaskit/editor-tables/table-map';
17
+ import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/addon/closest-edge';
17
18
 
18
19
  import type tablePlugin from './index';
19
20
 
@@ -239,11 +240,15 @@ export enum TableDecorations {
239
240
  CELL_CONTROLS_HOVER = 'CELL_CONTROLS_HOVER',
240
241
 
241
242
  COLUMN_CONTROLS_DECORATIONS = 'COLUMN_CONTROLS_DECORATIONS',
243
+ COLUMN_DROP_TARGET_DECORATIONS = 'COLUMN_DROP_TARGET_DECORATIONS',
242
244
  COLUMN_SELECTED = 'COLUMN_SELECTED',
243
245
  COLUMN_RESIZING_HANDLE = 'COLUMN_RESIZING_HANDLE',
244
246
  COLUMN_RESIZING_HANDLE_WIDGET = 'COLUMN_RESIZING_HANDLE_WIDGET',
245
247
  COLUMN_RESIZING_HANDLE_LINE = 'COLUMN_RESIZING_HANDLE_LINE',
246
248
 
249
+ COLUMN_INSERT_LINE = 'COLUMN_INSERT_LINE',
250
+ ROW_INSERT_LINE = 'ROW_INSERT_LINE',
251
+
247
252
  LAST_CELL_ELEMENT = 'LAST_CELL_ELEMENT',
248
253
  }
249
254
 
@@ -254,6 +259,10 @@ export const TableCssClassName = {
254
259
  COLUMN_CONTROLS_DECORATIONS: `${tablePrefixSelector}-column-controls-decoration`,
255
260
  COLUMN_SELECTED: `${tablePrefixSelector}-column__selected`,
256
261
 
262
+ COLUMN_CONTROLS_WRAPPER: `${tablePrefixSelector}-col-controls-wrapper`,
263
+ COLUMN_DROP_TARGET_CONTROLS: `${tablePrefixSelector}-col-drop-target-controls`,
264
+ COLUMN_CONTROLS_INNER: `${tablePrefixSelector}-col-controls__inner`,
265
+
257
266
  ROW_CONTROLS_WRAPPER: `${tablePrefixSelector}-row-controls-wrapper`,
258
267
  ROW_CONTROLS: `${tablePrefixSelector}-row-controls`,
259
268
  ROW_CONTROLS_INNER: `${tablePrefixSelector}-row-controls__inner`,
@@ -324,8 +333,15 @@ export const TableCssClassName = {
324
333
  TOP_LEFT_CELL: 'table > tbody > tr:nth-child(2) > td:nth-child(1)',
325
334
  LAST_ITEM_IN_CELL: `${tablePrefixSelector}-last-item-in-cell`,
326
335
 
336
+ WITH_COLUMN_INSERT_LINE: `${tablePrefixSelector}-column-insert-line`,
337
+ WITH_FIRST_COLUMN_INSERT_LINE: `${tablePrefixSelector}-first-column-insert-line`,
338
+ WITH_LAST_COLUMN_INSERT_LINE: `${tablePrefixSelector}-last-column-insert-line`,
339
+
327
340
  WITH_RESIZE_LINE: `${tablePrefixSelector}-column-resize-line`,
328
341
  WITH_RESIZE_LINE_LAST_COLUMN: `${tablePrefixSelector}-column-resize-line-last-column`,
342
+
343
+ WITH_ROW_INSERT_LINE: `${tablePrefixSelector}-row-insert-line`,
344
+ WITH_LAST_ROW_INSERT_LINE: `${tablePrefixSelector}-last-row-insert-line`,
329
345
  };
330
346
 
331
347
  export interface ToolbarMenuConfig {
@@ -378,8 +394,19 @@ export interface DraggableSourceData extends Record<string, unknown> {
378
394
  indexes: number[];
379
395
  }
380
396
 
381
- export interface DraggableTargetData extends Record<string, unknown> {
397
+ export interface DraggableTargetData extends Record<string | symbol, unknown> {
382
398
  type: DraggableType;
383
399
  localId: string;
384
400
  targetIndex: number;
385
401
  }
402
+
403
+ export interface DraggableData {
404
+ sourceType: DraggableType;
405
+ sourceLocalId: string;
406
+ sourceIndexes: number[];
407
+ targetType: DraggableType;
408
+ targetLocalId: string;
409
+ targetIndex: number;
410
+ targetAdjustedIndex: number;
411
+ targetClosestEdge: Edge;
412
+ }
@@ -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;