@atlaskit/editor-plugin-block-controls 1.4.9 → 1.4.10

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 (35) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/plugin.js +34 -0
  3. package/dist/cjs/pm-plugins/decorations.js +60 -25
  4. package/dist/cjs/pm-plugins/main.js +81 -60
  5. package/dist/cjs/ui/drag-handle.js +48 -26
  6. package/dist/cjs/ui/drag-preview.js +4 -4
  7. package/dist/cjs/ui/mouse-move-wrapper.js +92 -0
  8. package/dist/cjs/utils/drag-handle-positions.js +23 -0
  9. package/dist/es2019/plugin.js +32 -0
  10. package/dist/es2019/pm-plugins/decorations.js +60 -25
  11. package/dist/es2019/pm-plugins/main.js +73 -55
  12. package/dist/es2019/ui/drag-handle.js +53 -30
  13. package/dist/es2019/ui/drag-preview.js +4 -4
  14. package/dist/es2019/ui/mouse-move-wrapper.js +77 -0
  15. package/dist/es2019/utils/drag-handle-positions.js +17 -0
  16. package/dist/esm/plugin.js +34 -0
  17. package/dist/esm/pm-plugins/decorations.js +60 -25
  18. package/dist/esm/pm-plugins/main.js +81 -61
  19. package/dist/esm/ui/drag-handle.js +50 -28
  20. package/dist/esm/ui/drag-preview.js +4 -4
  21. package/dist/esm/ui/mouse-move-wrapper.js +84 -0
  22. package/dist/esm/utils/drag-handle-positions.js +17 -0
  23. package/dist/types/pm-plugins/decorations.d.ts +10 -3
  24. package/dist/types/types.d.ts +13 -1
  25. package/dist/types/ui/drag-handle.d.ts +6 -3
  26. package/dist/types/ui/drag-preview.d.ts +1 -1
  27. package/dist/types/ui/mouse-move-wrapper.d.ts +11 -0
  28. package/dist/types/utils/drag-handle-positions.d.ts +2 -0
  29. package/dist/types-ts4.5/pm-plugins/decorations.d.ts +10 -3
  30. package/dist/types-ts4.5/types.d.ts +13 -1
  31. package/dist/types-ts4.5/ui/drag-handle.d.ts +6 -3
  32. package/dist/types-ts4.5/ui/drag-preview.d.ts +1 -1
  33. package/dist/types-ts4.5/ui/mouse-move-wrapper.d.ts +11 -0
  34. package/dist/types-ts4.5/utils/drag-handle-positions.d.ts +2 -0
  35. package/package.json +3 -2
@@ -26,8 +26,40 @@ export const blockControlsPlugin = ({
26
26
  const mappedTo = tr.mapping.map(to);
27
27
  tr.insert(mappedTo, nodeCopy); // insert the content at the new position
28
28
  tr.setSelection(getSelection(tr, mappedTo));
29
+ tr.setMeta(key, {
30
+ nodeMoved: true
31
+ });
29
32
  api === null || api === void 0 ? void 0 : api.core.actions.focus();
30
33
  return tr;
34
+ },
35
+ showDragHandleAt: (pos, anchorName, nodeType) => ({
36
+ tr
37
+ }) => {
38
+ tr.setMeta(key, {
39
+ activeNode: {
40
+ pos,
41
+ anchorName,
42
+ nodeType
43
+ }
44
+ });
45
+ return tr;
46
+ },
47
+ setNodeDragged: (pos, anchorName) => ({
48
+ tr
49
+ }) => {
50
+ const newTr = tr;
51
+ if (pos === undefined) {
52
+ return tr;
53
+ }
54
+ newTr.setSelection(getSelection(newTr, pos));
55
+ newTr.setMeta(key, {
56
+ isDragging: true,
57
+ activeNode: {
58
+ pos,
59
+ anchorName
60
+ }
61
+ });
62
+ return newTr;
31
63
  }
32
64
  },
33
65
  getSharedState(editorState) {
@@ -1,9 +1,9 @@
1
1
  import { createElement } from 'react';
2
2
  import ReactDOM from 'react-dom';
3
- import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
4
- import { DRAG_HANDLE_WIDTH, dragHandleGap } from '../ui/consts';
3
+ import { Decoration } from '@atlaskit/editor-prosemirror/view';
5
4
  import { DragHandle } from '../ui/drag-handle';
6
5
  import { DropTarget } from '../ui/drop-target';
6
+ import { MouseMoveWrapper } from '../ui/mouse-move-wrapper';
7
7
  export const dropTargetDecorations = (oldState, newState, api) => {
8
8
  const decs = [];
9
9
  // Decoration state is used to keep track of the position of the drop targets
@@ -21,6 +21,8 @@ export const dropTargetDecorations = (oldState, newState, api) => {
21
21
  index
22
22
  }), element);
23
23
  return element;
24
+ }, {
25
+ type: 'drop-target-decoration'
24
26
  }));
25
27
  return false;
26
28
  });
@@ -43,39 +45,72 @@ export const dropTargetDecorations = (oldState, newState, api) => {
43
45
  index: decorationState.length
44
46
  }), element);
45
47
  return element;
48
+ }, {
49
+ type: 'drop-target-decoration'
46
50
  }));
47
51
  return {
48
52
  decs,
49
53
  decorationState
50
54
  };
51
55
  };
52
- export const dragHandleDecoration = (oldState, meta, api) => {
53
- return DecorationSet.create(oldState.doc, [Decoration.widget(meta.pos, (view, getPos) => {
56
+ export const nodeDecorations = newState => {
57
+ const decs = [];
58
+ newState.doc.descendants((node, pos, _parent, index) => {
59
+ const anchorName = `--node-anchor-${node.type.name}-${index}`;
60
+ const style = `anchor-name: ${anchorName}; ${pos === 0 ? 'margin-top: 0px;' : ''}`;
61
+ decs.push(Decoration.node(pos, pos + node.nodeSize, {
62
+ style,
63
+ ['data-drag-handler-anchor-name']: anchorName
64
+ }));
65
+ return false;
66
+ });
67
+ return decs;
68
+ };
69
+ /**
70
+ * Setting up decorations around each node to track mousemove events into each node
71
+ * When a mouseenter event is triggered on the node, we will set the activeNode to the node
72
+ * And show the drag handle
73
+ */
74
+ export const mouseMoveWrapperDecorations = (newState, api) => {
75
+ const decs = [];
76
+ newState.doc.descendants((node, pos, _parent, index) => {
77
+ const anchorName = `--node-anchor-${node.type.name}-${index}`;
78
+ decs.push(Decoration.widget(pos, (view, getPos) => {
79
+ const element = document.createElement('div');
80
+ ReactDOM.render( /*#__PURE__*/createElement(MouseMoveWrapper, {
81
+ view,
82
+ api,
83
+ anchorName,
84
+ nodeType: node.type.name,
85
+ getPos
86
+ }), element);
87
+ return element;
88
+ }, {
89
+ type: 'mouse-move-wrapper',
90
+ side: -1,
91
+ ignoreSelection: true,
92
+ stopEvent: e => {
93
+ return true;
94
+ }
95
+ }));
96
+ return false;
97
+ });
98
+ return decs;
99
+ };
100
+ export const dragHandleDecoration = (pos, anchorName, nodeType, api) => {
101
+ return Decoration.widget(pos, (view, getPos) => {
54
102
  const element = document.createElement('div');
103
+ element.setAttribute('data-testid', 'block-ctrl-decorator-widget');
55
104
  ReactDOM.render( /*#__PURE__*/createElement(DragHandle, {
56
- dom: meta.dom,
105
+ view,
57
106
  api,
58
- start: meta.pos
107
+ getPos,
108
+ anchorName,
109
+ nodeType
59
110
  }), element);
60
- element.style.position = 'absolute';
61
- element.style.zIndex = '1';
62
-
63
- // If the selected node is a table or mediaSingle with resizer, we need to adjust the position of the drag handle
64
- const resizer = ['table', 'mediaSingle'].includes(meta.type) ? meta.dom.querySelector('.resizer-item') : null;
65
- if (resizer) {
66
- element.style.left = getComputedStyle(resizer).transform === 'none' ? `${resizer.offsetLeft - dragHandleGap(meta.type) - DRAG_HANDLE_WIDTH}px` : `${resizer.offsetLeft - resizer.offsetWidth / 2 - dragHandleGap(meta.type) - DRAG_HANDLE_WIDTH}px`;
67
- } else {
68
- element.style.left = `${meta.dom.offsetLeft - dragHandleGap(meta.type) - DRAG_HANDLE_WIDTH}px`;
69
- }
70
- if (meta.type === 'table') {
71
- const table = meta.dom.querySelector('table');
72
- element.style.top = `${meta.dom.offsetTop + ((table === null || table === void 0 ? void 0 : table.offsetTop) || 0)}px`;
73
- } else {
74
- element.style.top = `${meta.dom.offsetTop}px`;
75
- }
76
- element.setAttribute('data-testid', 'block-ctrl-decorator-widget');
77
111
  return element;
78
112
  }, {
79
- side: -1
80
- })]);
113
+ side: -1,
114
+ id: 'drag-handle'
115
+ });
81
116
  };
@@ -1,7 +1,8 @@
1
+ import rafSchedule from 'raf-schd';
1
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
3
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
4
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
4
- import { dragHandleDecoration, dropTargetDecorations } from './decorations';
5
+ import { dragHandleDecoration, dropTargetDecorations, mouseMoveWrapperDecorations, nodeDecorations } from './decorations';
5
6
  export const key = new PluginKey('blockControls');
6
7
  export const createPlugin = api => {
7
8
  return new SafePlugin({
@@ -15,27 +16,47 @@ export const createPlugin = api => {
15
16
  isDragging: false,
16
17
  isMenuOpen: false,
17
18
  start: null,
18
- end: null
19
+ end: null,
20
+ editorHeight: 0
19
21
  };
20
22
  },
21
23
  apply(tr, currentState, oldState, newState) {
22
- var _decorationState, _meta$pos, _meta$isDragging;
24
+ var _decorationState, _meta$activeNode, _meta$isDragging, _meta$editorHeight;
23
25
  // return currentState;
24
26
  let {
25
27
  activeNode,
26
28
  decorations,
27
29
  isMenuOpen,
28
- decorationState
30
+ decorationState,
31
+ editorHeight
29
32
  } = currentState;
30
33
  const meta = tr.getMeta(key);
31
34
 
32
- // Drag handle decoration
33
- if (meta && meta.pos !== (activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos) && api) {
34
- decorations = dragHandleDecoration(newState, meta, api);
35
- }
36
- // Drop target decorations
37
- if (meta !== null && meta !== void 0 && meta.isDragging && api) {
35
+ // Draw node and mouseWrapper decorations at top level node if decorations is empty, editor height changes or node is moved
36
+ const redrawDecorations = decorations === DecorationSet.empty || (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== undefined && (meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== editorHeight || tr.docChanged && tr.doc.childCount === newState.doc.childCount || (meta === null || meta === void 0 ? void 0 : meta.nodeMoved) && tr.docChanged;
37
+ if (redrawDecorations && api) {
38
38
  decorations = DecorationSet.create(newState.doc, []);
39
+ const nodeDecs = nodeDecorations(newState);
40
+ const mouseWrapperDecs = mouseMoveWrapperDecorations(newState, api);
41
+ decorations = decorations.add(newState.doc, [...nodeDecs, ...mouseWrapperDecs]);
42
+ if (activeNode) {
43
+ const draghandleDec = dragHandleDecoration(activeNode.pos, activeNode.anchorName, activeNode.nodeType, api);
44
+ decorations = decorations.add(newState.doc, [draghandleDec]);
45
+ }
46
+ }
47
+
48
+ // Remove previous drag handle widget and draw new drag handle widget when activeNode changes
49
+ if (meta !== null && meta !== void 0 && meta.activeNode && (meta === null || meta === void 0 ? void 0 : meta.activeNode.pos) !== (activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos) && (meta === null || meta === void 0 ? void 0 : meta.activeNode.anchorName) !== (activeNode === null || activeNode === void 0 ? void 0 : activeNode.anchorName) && api) {
50
+ const oldHandle = decorations.find().filter(({
51
+ spec
52
+ }) => spec.id === 'drag-handle');
53
+ decorations = decorations.remove(oldHandle);
54
+ const decs = dragHandleDecoration(meta.activeNode.pos, meta.activeNode.anchorName, meta.activeNode.nodeType, api);
55
+ decorations = decorations.add(newState.doc, [decs]);
56
+ }
57
+
58
+ // Add drop targets when node is being dragged
59
+ if (meta !== null && meta !== void 0 && meta.isDragging && !tr.docChanged && api) {
39
60
  const {
40
61
  decs,
41
62
  decorationState: updatedDecorationState
@@ -44,14 +65,12 @@ export const createPlugin = api => {
44
65
  decorations = decorations.add(newState.doc, decs);
45
66
  }
46
67
 
47
- // Remove drop target decorations when dragging is stopped
48
- if ((meta === null || meta === void 0 ? void 0 : meta.isDragging) === false) {
49
- decorations = DecorationSet.create(newState.doc, []);
50
- }
51
-
52
- // Map decorations when the document changes
53
- if (tr.docChanged && decorations !== DecorationSet.empty) {
54
- decorations = decorations.map(tr.mapping, tr.doc);
68
+ // Remove drop target decoration when dragging stops
69
+ if ((meta === null || meta === void 0 ? void 0 : meta.isDragging) === false && !tr.docChanged) {
70
+ const dropTargetDecs = decorations.find().filter(({
71
+ spec
72
+ }) => spec.type === 'drop-target-decoration');
73
+ decorations = decorations.remove(dropTargetDecs);
55
74
  }
56
75
 
57
76
  // Map drop target decoration positions when the document changes
@@ -67,16 +86,23 @@ export const createPlugin = api => {
67
86
  });
68
87
  }
69
88
 
89
+ // Map decorations if document changes and node decorations do not need to be redrawn
90
+ if (tr.docChanged && !redrawDecorations) {
91
+ decorations = decorations.map(tr.mapping, tr.doc);
92
+ }
93
+
70
94
  // Map active node position when the document changes
71
- const mappedActiveNodePos = tr.docChanged && activeNode ? tr.mapping.map(activeNode.pos) : activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos;
95
+ const mappedActiveNodePos = tr.docChanged && activeNode ? {
96
+ pos: tr.mapping.map(activeNode.pos),
97
+ anchorName: activeNode.anchorName
98
+ } : activeNode;
72
99
  return {
73
100
  decorations,
74
101
  decorationState: (_decorationState = decorationState) !== null && _decorationState !== void 0 ? _decorationState : currentState.decorationState,
75
- activeNode: {
76
- pos: (_meta$pos = meta === null || meta === void 0 ? void 0 : meta.pos) !== null && _meta$pos !== void 0 ? _meta$pos : mappedActiveNodePos
77
- },
102
+ activeNode: (_meta$activeNode = meta === null || meta === void 0 ? void 0 : meta.activeNode) !== null && _meta$activeNode !== void 0 ? _meta$activeNode : mappedActiveNodePos,
78
103
  isDragging: (_meta$isDragging = meta === null || meta === void 0 ? void 0 : meta.isDragging) !== null && _meta$isDragging !== void 0 ? _meta$isDragging : currentState.isDragging,
79
- isMenuOpen: meta !== null && meta !== void 0 && meta.toggleMenu ? !isMenuOpen : isMenuOpen
104
+ isMenuOpen: meta !== null && meta !== void 0 && meta.toggleMenu ? !isMenuOpen : isMenuOpen,
105
+ editorHeight: (_meta$editorHeight = meta === null || meta === void 0 ? void 0 : meta.editorHeight) !== null && _meta$editorHeight !== void 0 ? _meta$editorHeight : currentState.editorHeight
80
106
  };
81
107
  }
82
108
  },
@@ -84,39 +110,31 @@ export const createPlugin = api => {
84
110
  decorations: state => {
85
111
  var _key$getState;
86
112
  return (_key$getState = key.getState(state)) === null || _key$getState === void 0 ? void 0 : _key$getState.decorations;
87
- },
88
- handleDOMEvents: {
89
- mousemove(view, event) {
90
- const pos = view.posAtCoords({
91
- left: event.clientX,
92
- top: event.clientY
93
- });
94
- if ((pos === null || pos === void 0 ? void 0 : pos.inside) !== undefined && pos.inside >= 0) {
95
- var _api$core;
96
- const node = view.state.doc.nodeAt(pos.inside);
97
- if (!node) {
98
- return;
99
- }
100
- const resolvedPos = view.state.doc.resolve(pos.pos);
101
- const topLevelPos = resolvedPos.before(1); // 1 here restricts the depth to the root level
102
- const topLevelNode = view.state.doc.nodeAt(topLevelPos);
103
- if (!topLevelNode) {
104
- return;
105
- }
106
- const dom = view.nodeDOM(topLevelPos);
107
- if (!dom) {
108
- return;
109
- }
110
- api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
111
- tr
112
- }) => tr.setMeta(key, {
113
- pos: topLevelPos,
114
- dom,
115
- type: topLevelNode.type.name
116
- }));
117
- }
118
- }
119
113
  }
114
+ },
115
+ view: editorView => {
116
+ const dom = editorView.dom;
117
+
118
+ // Use ResizeObserver to observe height changes
119
+ const resizeObserver = new ResizeObserver(rafSchedule(() => {
120
+ const editorHeight = dom.offsetHeight;
121
+
122
+ // Update the plugin state when the height changes
123
+ const pluginState = key.getState(editorView.state);
124
+ if (!(pluginState !== null && pluginState !== void 0 && pluginState.isDragging)) {
125
+ editorView.dispatch(editorView.state.tr.setMeta(key, {
126
+ editorHeight
127
+ }));
128
+ }
129
+ }));
130
+
131
+ // Start observing the editor DOM element
132
+ resizeObserver.observe(dom);
133
+ return {
134
+ destroy() {
135
+ resizeObserver.unobserve(dom);
136
+ }
137
+ };
120
138
  }
121
139
  });
122
140
  };
@@ -1,14 +1,16 @@
1
1
  /** @jsx jsx */
2
- import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { css, jsx } from '@emotion/react';
4
4
  import DragHandlerIcon from '@atlaskit/icon/glyph/drag-handler';
5
5
  import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
6
  import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
7
7
  import { key } from '../pm-plugins/main';
8
+ import { getLeftPosition, getTopPosition } from '../utils/drag-handle-positions';
8
9
  import { getSelection } from '../utils/getSelection';
9
- import { DRAG_HANDLE_BORDER_RADIUS, DRAG_HANDLE_HEIGHT, DRAG_HANDLE_WIDTH } from './consts';
10
+ import { DRAG_HANDLE_BORDER_RADIUS, DRAG_HANDLE_HEIGHT, DRAG_HANDLE_WIDTH, dragHandleGap } from './consts';
10
11
  import { dragPreview } from './drag-preview';
11
12
  const dragHandleButtonStyles = css({
13
+ position: 'absolute',
12
14
  padding: `${"var(--ds-space-025, 2px)"} 0`,
13
15
  boxSizing: 'border-box',
14
16
  display: 'flex',
@@ -22,10 +24,11 @@ const dragHandleButtonStyles = css({
22
24
  borderRadius: DRAG_HANDLE_BORDER_RADIUS,
23
25
  color: "var(--ds-icon, #44546F)",
24
26
  cursor: 'grab',
25
- ':hover': {
27
+ zIndex: 2,
28
+ '&:hover': {
26
29
  backgroundColor: "var(--ds-background-neutral-subtle-hovered, #091E420F)"
27
30
  },
28
- ':active': {
31
+ '&:active': {
29
32
  backgroundColor: "var(--ds-background-neutral-subtle-pressed, #091E4224)"
30
33
  }
31
34
  });
@@ -34,26 +37,28 @@ const selectedStyles = css({
34
37
  color: "var(--ds-icon-selected, #0C66E4)"
35
38
  });
36
39
  export const DragHandle = ({
37
- dom,
40
+ view,
38
41
  api,
39
- start
42
+ getPos,
43
+ anchorName,
44
+ nodeType
40
45
  }) => {
46
+ const start = getPos();
41
47
  const buttonRef = useRef(null);
42
- const domRef = useRef(dom);
43
48
  const [dragHandleSelected, setDragHandleSelected] = useState(false);
44
49
  const handleClick = useCallback(() => {
45
50
  var _api$core, _api$core2;
46
51
  setDragHandleSelected(!dragHandleSelected);
47
- // TODO - add drag menu
48
- // api?.core?.actions.execute(({ tr }) =>
49
- // tr.setMeta(key, {
50
- // toggleMenu: true,
51
- // }),
52
- // );
53
52
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
54
53
  tr
55
54
  }) => {
55
+ if (start === undefined) {
56
+ return tr;
57
+ }
56
58
  tr.setSelection(getSelection(tr, start));
59
+ tr.setMeta(key, {
60
+ pos: start
61
+ });
57
62
  return tr;
58
63
  });
59
64
  api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.focus();
@@ -72,40 +77,58 @@ export const DragHandle = ({
72
77
  render: ({
73
78
  container
74
79
  }) => {
75
- return dragPreview(container, domRef);
80
+ const dom = view.dom.querySelector(`[data-drag-handler-anchor-name="${anchorName}"]`);
81
+ if (!dom) {
82
+ return;
83
+ }
84
+ return dragPreview(container, dom);
76
85
  },
77
86
  nativeSetDragImage
78
87
  });
79
88
  },
80
89
  onDragStart() {
81
- var _api$core3, _api$core4;
82
- api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(({
83
- tr
84
- }) => {
85
- const newTr = tr;
86
- newTr.setSelection(getSelection(newTr, start));
87
- newTr.setMeta(key, {
88
- isDragging: true,
89
- start
90
- });
91
- return newTr;
92
- });
90
+ var _api$core3, _api$blockControls, _api$core4;
91
+ if (start === undefined) {
92
+ return;
93
+ }
94
+ api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.commands.setNodeDragged(start, anchorName));
93
95
  api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.focus();
94
96
  },
95
97
  onDrop() {
96
98
  var _api$core5;
97
99
  api === null || api === void 0 ? void 0 : (_api$core5 = api.core) === null || _api$core5 === void 0 ? void 0 : _api$core5.actions.execute(({
98
100
  tr
99
- }) => tr.setMeta(key, {
100
- isDragging: false
101
- }));
101
+ }) => {
102
+ return tr.setMeta(key, {
103
+ isDragging: false
104
+ });
105
+ });
102
106
  }
103
107
  });
104
- }, [api, start]);
108
+ }, [api, start, view, anchorName]);
109
+ const positionStyles = useMemo(() => {
110
+ const supportsAnchor = CSS.supports('top', `anchor(${anchorName} start)`) && CSS.supports('left', `anchor(${anchorName} start)`);
111
+ const dom = view.dom.querySelector(`[data-drag-handler-anchor-name="${anchorName}"]`);
112
+ if (supportsAnchor) {
113
+ const hasResizer = (anchorName.includes('table') || anchorName.includes('mediaSingle')) && dom;
114
+ return {
115
+ left: hasResizer ? getLeftPosition(dom, nodeType) : `calc(anchor(${anchorName} start) - ${DRAG_HANDLE_WIDTH}px - ${dragHandleGap(nodeType)}px)`,
116
+ top: anchorName.includes('table') ? `calc(anchor(${anchorName} start) + ${DRAG_HANDLE_HEIGHT}px)` : `anchor(${anchorName} start)`
117
+ };
118
+ }
119
+ if (!dom) {
120
+ return;
121
+ }
122
+ return {
123
+ left: getLeftPosition(dom, nodeType),
124
+ top: getTopPosition(dom)
125
+ };
126
+ }, [anchorName, view, nodeType]);
105
127
  return jsx("button", {
106
128
  type: "button",
107
129
  css: [dragHandleButtonStyles, dragHandleSelected && selectedStyles],
108
130
  ref: buttonRef,
131
+ style: positionStyles,
109
132
  onClick: handleClick,
110
133
  "data-testid": "block-ctrl-drag-handle"
111
134
  }, jsx(DragHandlerIcon, {
@@ -1,5 +1,5 @@
1
- export const dragPreview = (container, domRef) => {
2
- const rect = domRef.current.getBoundingClientRect();
1
+ export const dragPreview = (container, dom) => {
2
+ const rect = dom.getBoundingClientRect();
3
3
  container.style.width = `${rect.width}px`;
4
4
  container.style.height = `${rect.height}px`;
5
5
  container.style.pointerEvents = 'none';
@@ -7,8 +7,8 @@ export const dragPreview = (container, domRef) => {
7
7
  // ProseMirror class is required to make sure the cloned dom is styled correctly
8
8
  parent.classList.add('ProseMirror');
9
9
  parent.style.opacity = '0.3';
10
- const resizer = domRef.current.querySelector('.resizer-item');
11
- const clonedDom = resizer ? resizer.cloneNode(true) : domRef.current.cloneNode(true);
10
+ const resizer = dom.querySelector('.resizer-item');
11
+ const clonedDom = resizer ? resizer.cloneNode(true) : dom.cloneNode(true);
12
12
 
13
13
  // Remove any margin from the cloned element to ensure is doesn't position incorrectly
14
14
  clonedDom.style.marginLeft = '0';
@@ -0,0 +1,77 @@
1
+ /** @jsx jsx */
2
+ import { useCallback, useLayoutEffect, useState } from 'react';
3
+ import { css, jsx } from '@emotion/react';
4
+ import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
5
+ import { getTopPosition } from '../utils/drag-handle-positions';
6
+ const basicStyles = css({
7
+ position: 'absolute',
8
+ width: '100%',
9
+ left: '0',
10
+ display: 'block',
11
+ zIndex: -1
12
+ });
13
+ const mouseMoveWrapperStyles = css({
14
+ zIndex: 1
15
+ });
16
+ export const MouseMoveWrapper = ({
17
+ view,
18
+ api,
19
+ anchorName,
20
+ nodeType,
21
+ getPos
22
+ }) => {
23
+ const {
24
+ blockControlsState
25
+ } = useSharedPluginState(api, ['blockControls']);
26
+ const activeNode = blockControlsState === null || blockControlsState === void 0 ? void 0 : blockControlsState.activeNode;
27
+ const isDragging = blockControlsState === null || blockControlsState === void 0 ? void 0 : blockControlsState.isDragging;
28
+ const [hideWrapper, setHideWrapper] = useState(false);
29
+ const [pos, setPos] = useState();
30
+ const onMouseEnter = useCallback(() => {
31
+ const pos = getPos();
32
+ if (pos === undefined) {
33
+ return;
34
+ }
35
+ if (api && api.blockControls && (activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos) !== pos && !isDragging) {
36
+ var _api$core;
37
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(api.blockControls.commands.showDragHandleAt(pos, anchorName, nodeType));
38
+ }
39
+ }, [getPos, api, anchorName, isDragging, activeNode === null || activeNode === void 0 ? void 0 : activeNode.pos, nodeType]);
40
+ useLayoutEffect(() => {
41
+ if (!activeNode) {
42
+ return;
43
+ }
44
+ const pos = getPos();
45
+ if (activeNode.pos !== pos && !isDragging) {
46
+ setHideWrapper(false);
47
+ return;
48
+ }
49
+ setHideWrapper(true);
50
+ }, [activeNode, isDragging, getPos]);
51
+ useLayoutEffect(() => {
52
+ const supportsAnchor = CSS.supports('height', `anchor-size(${anchorName} height)`) && CSS.supports('top', `anchor(${anchorName} start)`);
53
+ if (supportsAnchor) {
54
+ setPos({
55
+ height: `anchor-size(${anchorName} height)`,
56
+ top: `anchor(${anchorName} start)`
57
+ });
58
+ return;
59
+ }
60
+ const calcPos = requestAnimationFrame(() => {
61
+ const dom = view.dom.querySelector(`[data-drag-handler-anchor-name="${anchorName}"]`);
62
+ if (!dom) {
63
+ return;
64
+ }
65
+ setPos({
66
+ height: `${dom.offsetHeight}px`,
67
+ top: getTopPosition(dom)
68
+ });
69
+ });
70
+ return () => cancelAnimationFrame(calcPos);
71
+ }, [view, anchorName]);
72
+ return jsx("div", {
73
+ onMouseEnter: onMouseEnter,
74
+ css: [basicStyles, !hideWrapper && mouseMoveWrapperStyles],
75
+ style: pos
76
+ });
77
+ };
@@ -0,0 +1,17 @@
1
+ import { DRAG_HANDLE_WIDTH, dragHandleGap } from '../ui/consts';
2
+ export const getTopPosition = dom => {
3
+ const table = dom.querySelector('table');
4
+ if (table) {
5
+ return `${dom.offsetTop + ((table === null || table === void 0 ? void 0 : table.offsetTop) || 0)}px`;
6
+ } else {
7
+ return `${dom.offsetTop}px`;
8
+ }
9
+ };
10
+ export const getLeftPosition = (dom, type) => {
11
+ const resizer = ['table', 'mediaSingle'].includes(type) ? dom.querySelector('.resizer-item') : null;
12
+ let left = `${dom.offsetLeft - dragHandleGap(type) - DRAG_HANDLE_WIDTH}px`;
13
+ if (resizer) {
14
+ left = getComputedStyle(resizer).transform === 'none' ? `${resizer.offsetLeft - dragHandleGap(type) - DRAG_HANDLE_WIDTH}px` : `${resizer.offsetLeft - resizer.offsetWidth / 2 - dragHandleGap(type) - DRAG_HANDLE_WIDTH}px`;
15
+ }
16
+ return left;
17
+ };
@@ -28,9 +28,43 @@ export var blockControlsPlugin = function blockControlsPlugin(_ref) {
28
28
  var mappedTo = tr.mapping.map(to);
29
29
  tr.insert(mappedTo, nodeCopy); // insert the content at the new position
30
30
  tr.setSelection(getSelection(tr, mappedTo));
31
+ tr.setMeta(key, {
32
+ nodeMoved: true
33
+ });
31
34
  api === null || api === void 0 || api.core.actions.focus();
32
35
  return tr;
33
36
  };
37
+ },
38
+ showDragHandleAt: function showDragHandleAt(pos, anchorName, nodeType) {
39
+ return function (_ref3) {
40
+ var tr = _ref3.tr;
41
+ tr.setMeta(key, {
42
+ activeNode: {
43
+ pos: pos,
44
+ anchorName: anchorName,
45
+ nodeType: nodeType
46
+ }
47
+ });
48
+ return tr;
49
+ };
50
+ },
51
+ setNodeDragged: function setNodeDragged(pos, anchorName) {
52
+ return function (_ref4) {
53
+ var tr = _ref4.tr;
54
+ var newTr = tr;
55
+ if (pos === undefined) {
56
+ return tr;
57
+ }
58
+ newTr.setSelection(getSelection(newTr, pos));
59
+ newTr.setMeta(key, {
60
+ isDragging: true,
61
+ activeNode: {
62
+ pos: pos,
63
+ anchorName: anchorName
64
+ }
65
+ });
66
+ return newTr;
67
+ };
34
68
  }
35
69
  },
36
70
  getSharedState: function getSharedState(editorState) {