@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.
- package/CHANGELOG.md +9 -0
- package/dist/cjs/plugin.js +34 -0
- package/dist/cjs/pm-plugins/decorations.js +60 -25
- package/dist/cjs/pm-plugins/main.js +81 -60
- package/dist/cjs/ui/drag-handle.js +48 -26
- package/dist/cjs/ui/drag-preview.js +4 -4
- package/dist/cjs/ui/mouse-move-wrapper.js +92 -0
- package/dist/cjs/utils/drag-handle-positions.js +23 -0
- package/dist/es2019/plugin.js +32 -0
- package/dist/es2019/pm-plugins/decorations.js +60 -25
- package/dist/es2019/pm-plugins/main.js +73 -55
- package/dist/es2019/ui/drag-handle.js +53 -30
- package/dist/es2019/ui/drag-preview.js +4 -4
- package/dist/es2019/ui/mouse-move-wrapper.js +77 -0
- package/dist/es2019/utils/drag-handle-positions.js +17 -0
- package/dist/esm/plugin.js +34 -0
- package/dist/esm/pm-plugins/decorations.js +60 -25
- package/dist/esm/pm-plugins/main.js +81 -61
- package/dist/esm/ui/drag-handle.js +50 -28
- package/dist/esm/ui/drag-preview.js +4 -4
- package/dist/esm/ui/mouse-move-wrapper.js +84 -0
- package/dist/esm/utils/drag-handle-positions.js +17 -0
- package/dist/types/pm-plugins/decorations.d.ts +10 -3
- package/dist/types/types.d.ts +13 -1
- package/dist/types/ui/drag-handle.d.ts +6 -3
- package/dist/types/ui/drag-preview.d.ts +1 -1
- package/dist/types/ui/mouse-move-wrapper.d.ts +11 -0
- package/dist/types/utils/drag-handle-positions.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/decorations.d.ts +10 -3
- package/dist/types-ts4.5/types.d.ts +13 -1
- package/dist/types-ts4.5/ui/drag-handle.d.ts +6 -3
- package/dist/types-ts4.5/ui/drag-preview.d.ts +1 -1
- package/dist/types-ts4.5/ui/mouse-move-wrapper.d.ts +11 -0
- package/dist/types-ts4.5/utils/drag-handle-positions.d.ts +2 -0
- package/package.json +3 -2
package/dist/es2019/plugin.js
CHANGED
|
@@ -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
|
|
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
|
|
53
|
-
|
|
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
|
-
|
|
105
|
+
view,
|
|
57
106
|
api,
|
|
58
|
-
|
|
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$
|
|
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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
48
|
-
if ((meta === null || meta === void 0 ? void 0 : meta.isDragging) === false) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 ?
|
|
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
|
-
|
|
27
|
+
zIndex: 2,
|
|
28
|
+
'&:hover': {
|
|
26
29
|
backgroundColor: "var(--ds-background-neutral-subtle-hovered, #091E420F)"
|
|
27
30
|
},
|
|
28
|
-
'
|
|
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
|
-
|
|
40
|
+
view,
|
|
38
41
|
api,
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
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
|
-
}) =>
|
|
100
|
-
|
|
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,
|
|
2
|
-
const rect =
|
|
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 =
|
|
11
|
-
const clonedDom = resizer ? resizer.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
|
+
};
|
package/dist/esm/plugin.js
CHANGED
|
@@ -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) {
|