@atlaskit/editor-plugin-block-controls 24.0.0 → 24.0.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.
- package/CHANGELOG.md +10 -0
- package/dist/cjs/pm-plugins/{handle-sparse-mouse-over.js → handle-sparse-hover-target.js} +30 -17
- package/dist/cjs/pm-plugins/main.js +7 -7
- package/dist/cjs/pm-plugins/sparse-gutter-hover.js +317 -0
- package/dist/cjs/pm-plugins/{utils/sparse-surfaces-view.js → sparse-surfaces-view.js} +10 -4
- package/dist/cjs/pm-plugins/sparse-surfaces.js +1 -1
- package/dist/cjs/pm-plugins/utils/visible-box.js +27 -0
- package/dist/es2019/pm-plugins/{handle-sparse-mouse-over.js → handle-sparse-hover-target.js} +29 -16
- package/dist/es2019/pm-plugins/main.js +7 -7
- package/dist/es2019/pm-plugins/sparse-gutter-hover.js +305 -0
- package/dist/es2019/pm-plugins/{utils/sparse-surfaces-view.js → sparse-surfaces-view.js} +10 -4
- package/dist/es2019/pm-plugins/sparse-surfaces.js +1 -1
- package/dist/es2019/pm-plugins/utils/visible-box.js +21 -0
- package/dist/esm/pm-plugins/{handle-sparse-mouse-over.js → handle-sparse-hover-target.js} +29 -16
- package/dist/esm/pm-plugins/main.js +7 -7
- package/dist/esm/pm-plugins/sparse-gutter-hover.js +311 -0
- package/dist/esm/pm-plugins/{utils/sparse-surfaces-view.js → sparse-surfaces-view.js} +10 -4
- package/dist/esm/pm-plugins/sparse-surfaces.js +1 -1
- package/dist/esm/pm-plugins/utils/visible-box.js +21 -0
- package/dist/types/pm-plugins/handle-sparse-hover-target.d.ts +10 -0
- package/dist/types/pm-plugins/sparse-gutter-hover.d.ts +27 -0
- package/dist/types/pm-plugins/{utils/sparse-surfaces-view.d.ts → sparse-surfaces-view.d.ts} +2 -2
- package/dist/types/pm-plugins/utils/visible-box.d.ts +5 -0
- package/package.json +4 -4
- package/dist/types/pm-plugins/handle-sparse-mouse-over.d.ts +0 -5
|
@@ -29,7 +29,6 @@ import { getActiveDropTargetDecorations } from './decorations-drop-target-active
|
|
|
29
29
|
import { createActiveQuickInsertNodeDecoration, findActiveQuickInsertNodeDec, findQuickInsertInsertButtonDecoration, quickInsertButtonDecoration, TYPE_ACTIVE_QUICK_INSERT_NODE } from './decorations-quick-insert-button';
|
|
30
30
|
import { handleMouseDown } from './handle-mouse-down';
|
|
31
31
|
import { handleMouseOver } from './handle-mouse-over';
|
|
32
|
-
import { handleSparseMouseOver } from './handle-sparse-mouse-over';
|
|
33
32
|
import { boundKeydownHandler } from './keymap';
|
|
34
33
|
import { defaultActiveAnchorTracker } from './utils/active-anchor-tracker';
|
|
35
34
|
import { getMultiSelectAnalyticsAttributes } from './utils/analytics';
|
|
@@ -1073,6 +1072,11 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI, nodeDecora
|
|
|
1073
1072
|
},
|
|
1074
1073
|
mouseover: (view, event) => {
|
|
1075
1074
|
var _api$limitedMode7, _api$limitedMode7$sha, _getNodeIdProvider;
|
|
1075
|
+
// Sparse surfaces handle all hover in bindSparseGutterHover, which also covers the gutters
|
|
1076
|
+
// outside view.dom.
|
|
1077
|
+
if (isExperimentEnabled('platform_editor_block_control_migration')) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1076
1080
|
if (api !== null && api !== void 0 && (_api$limitedMode7 = api.limitedMode) !== null && _api$limitedMode7 !== void 0 && (_api$limitedMode7$sha = _api$limitedMode7.sharedState.currentState()) !== null && _api$limitedMode7$sha !== void 0 && _api$limitedMode7$sha.enabled) {
|
|
1077
1081
|
return;
|
|
1078
1082
|
}
|
|
@@ -1080,14 +1084,10 @@ export const createPlugin = (api, getIntl, nodeViewPortalProviderAPI, nodeDecora
|
|
|
1080
1084
|
// another layer of protection to prevent handling mouseover
|
|
1081
1085
|
// when native anchor with dnd is enabled in limited mode
|
|
1082
1086
|
// in case there are descripancies between getNodeIdProvider limited mode state
|
|
1083
|
-
if ((_getNodeIdProvider = getNodeIdProvider(view)) !== null && _getNodeIdProvider !== void 0 && _getNodeIdProvider.isLimitedMode() &&
|
|
1087
|
+
if ((_getNodeIdProvider = getNodeIdProvider(view)) !== null && _getNodeIdProvider !== void 0 && _getNodeIdProvider.isLimitedMode() && expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) {
|
|
1084
1088
|
return;
|
|
1085
1089
|
}
|
|
1086
|
-
|
|
1087
|
-
handleSparseMouseOver(view, event, api);
|
|
1088
|
-
} else {
|
|
1089
|
-
handleMouseOver(view, event, api);
|
|
1090
|
-
}
|
|
1090
|
+
handleMouseOver(view, event, api);
|
|
1091
1091
|
return false;
|
|
1092
1092
|
},
|
|
1093
1093
|
mousedown: (view, event) => {
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { bind } from 'bind-event-listener';
|
|
2
|
+
import { DRAG_HANDLE_WIDTH } from '@atlaskit/editor-common/styles';
|
|
3
|
+
import { akEditorFullPageNarrowBreakout, akEditorGutterPaddingDynamic, akEditorGutterPaddingReduced } from '@atlaskit/editor-shared-styles/constants';
|
|
4
|
+
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
5
|
+
import { BLOCK_CONTROLS_SURFACE_SELECTOR, DRAG_HANDLE_MAX_WIDTH_PLUS_GAP } from '../ui/consts';
|
|
6
|
+
import { handleSparseHoverTarget, isSparseHoverSuppressed } from './handle-sparse-hover-target';
|
|
7
|
+
import { mouseEnter, setHoverSide, stopEditing } from './interaction-tracking/commands';
|
|
8
|
+
import { handleMouseLeave } from './interaction-tracking/handle-mouse-move';
|
|
9
|
+
import { getInteractionTrackingState } from './interaction-tracking/pm-plugin';
|
|
10
|
+
import { getVisibleBox } from './utils/visible-box';
|
|
11
|
+
const BLOCK_SELECTOR = '[data-prosemirror-node-block="true"]';
|
|
12
|
+
const TABLE_SELECTOR = '[data-prosemirror-node-name="table"]';
|
|
13
|
+
const LAYOUT_SECTION_SELECTOR = '[data-prosemirror-node-name="layoutSection"]';
|
|
14
|
+
const LAYOUT_COLUMN_SELECTOR = '[data-prosemirror-node-name="layoutColumn"]';
|
|
15
|
+
|
|
16
|
+
// `layoutColumnExtendedHoverZone` is a DRAG_HANDLE_WIDTH-tall strip centred on the column's top edge.
|
|
17
|
+
const LAYOUT_COLUMN_STRIP_REACH = DRAG_HANDLE_WIDTH / 2;
|
|
18
|
+
const isBlockElement = element => element.getAttribute('data-prosemirror-node-block') === 'true';
|
|
19
|
+
const getVisibleLeft = block => {
|
|
20
|
+
var _block$getAttribute;
|
|
21
|
+
return getVisibleBox(block, (_block$getAttribute = block.getAttribute('data-prosemirror-node-name')) !== null && _block$getAttribute !== void 0 ? _block$getAttribute : '').getBoundingClientRect().left;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether the element under the pointer can be part of a block's left gutter: a container block's
|
|
26
|
+
* own padding (panel, layout, list…) or an editor wrapper around the content. Text and leaf blocks
|
|
27
|
+
* have no gutter inside them, and neither do tables: like the legacy hover zones, anywhere inside a
|
|
28
|
+
* table, including cell padding, shows the table's controls. Elements rendered beside the editor
|
|
29
|
+
* (title, popups, toolbars) cover any gutter beneath them.
|
|
30
|
+
*/
|
|
31
|
+
export const isPossibleGutterElement = (view, hoveredElement) => {
|
|
32
|
+
if (!(hoveredElement instanceof Element)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const owner = hoveredElement.closest(`${BLOCK_SELECTOR}, ${BLOCK_CONTROLS_SURFACE_SELECTOR}`);
|
|
36
|
+
if (owner !== null && owner !== void 0 && owner.matches(BLOCK_CONTROLS_SURFACE_SELECTOR)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (owner && view.dom.contains(owner)) {
|
|
40
|
+
var _owner$getAttribute;
|
|
41
|
+
if (owner.closest(TABLE_SELECTOR)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const nodeType = view.state.schema.nodes[(_owner$getAttribute = owner.getAttribute('data-prosemirror-node-name')) !== null && _owner$getAttribute !== void 0 ? _owner$getAttribute : ''];
|
|
45
|
+
return !(nodeType !== null && nodeType !== void 0 && nodeType.isTextblock) && !(nodeType !== null && nodeType !== void 0 && nodeType.isLeaf);
|
|
46
|
+
}
|
|
47
|
+
return view.dom.contains(hoveredElement) || hoveredElement.contains(view.dom);
|
|
48
|
+
};
|
|
49
|
+
const getGutterWidths = () => ({
|
|
50
|
+
topLevel: akEditorGutterPaddingDynamic(),
|
|
51
|
+
narrowTopLevel: expValEqualsNoExposure('platform_editor_preview_panel_responsiveness', 'isEnabled', true) ? akEditorGutterPaddingReduced : undefined
|
|
52
|
+
});
|
|
53
|
+
const getTopLevelGutterWidth = (api, widths) => {
|
|
54
|
+
var _api$width, _api$width$sharedStat;
|
|
55
|
+
const editorWidth = api === null || api === void 0 ? void 0 : (_api$width = api.width) === null || _api$width === void 0 ? void 0 : (_api$width$sharedStat = _api$width.sharedState.currentState()) === null || _api$width$sharedStat === void 0 ? void 0 : _api$width$sharedStat.width;
|
|
56
|
+
return widths.narrowTopLevel !== undefined && editorWidth !== undefined && editorWidth <= akEditorFullPageNarrowBreakout ? widths.narrowTopLevel : widths.topLevel;
|
|
57
|
+
};
|
|
58
|
+
const getEditorElementAt = (view, clientX, clientY) => {
|
|
59
|
+
const element = view.dom.ownerDocument.elementFromPoint(clientX, clientY);
|
|
60
|
+
return element && element !== view.dom && view.dom.contains(element) ? element : null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** The outermost block around `element` that does not contain the pointer owns that gutter. */
|
|
64
|
+
const findGutterOwner = (view, element, pointerTarget) => {
|
|
65
|
+
let owner = null;
|
|
66
|
+
for (let current = element; current && current !== view.dom;) {
|
|
67
|
+
if (isBlockElement(current) && !current.contains(pointerTarget) &&
|
|
68
|
+
// Layout columns have a top hover strip instead of a left gutter.
|
|
69
|
+
current.getAttribute('data-prosemirror-node-name') !== 'layoutColumn') {
|
|
70
|
+
owner = current;
|
|
71
|
+
}
|
|
72
|
+
current = current.parentElement;
|
|
73
|
+
}
|
|
74
|
+
return owner;
|
|
75
|
+
};
|
|
76
|
+
const findBlockBesidePointer = (view, api, event, pointerTarget, widths) => {
|
|
77
|
+
const pointerBlock = pointerTarget.closest(BLOCK_SELECTOR);
|
|
78
|
+
const gutterWidth = pointerBlock && view.dom.contains(pointerBlock) ? DRAG_HANDLE_MAX_WIDTH_PLUS_GAP : getTopLevelGutterWidth(api, widths);
|
|
79
|
+
const sample = getEditorElementAt(view, event.clientX + gutterWidth, event.clientY);
|
|
80
|
+
const owner = sample && findGutterOwner(view, sample, pointerTarget);
|
|
81
|
+
if (!owner) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const left = getVisibleLeft(owner);
|
|
85
|
+
return event.clientX < left && left - event.clientX <= gutterWidth ? owner : null;
|
|
86
|
+
};
|
|
87
|
+
const findColumnBelowPointer = (view, event, pointerTarget) => {
|
|
88
|
+
if (pointerTarget.closest(LAYOUT_COLUMN_SELECTOR) || pointerTarget !== view.dom && !pointerTarget.closest(LAYOUT_SECTION_SELECTOR)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const sample = getEditorElementAt(view, event.clientX, event.clientY + LAYOUT_COLUMN_STRIP_REACH);
|
|
92
|
+
const column = sample === null || sample === void 0 ? void 0 : sample.closest(LAYOUT_COLUMN_SELECTOR);
|
|
93
|
+
return column && view.dom.contains(column) && event.clientY < column.getBoundingClientRect().top ? column : null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Returns a function that finds the block whose legacy hover zone contains the pointer. */
|
|
97
|
+
export const createGutterBlockFinder = (view, api) => {
|
|
98
|
+
const widths = getGutterWidths();
|
|
99
|
+
return event => {
|
|
100
|
+
var _findBlockBesidePoint;
|
|
101
|
+
if (!(event.target instanceof Element)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return (_findBlockBesidePoint = findBlockBesidePointer(view, api, event, event.target, widths)) !== null && _findBlockBesidePoint !== void 0 ? _findBlockBesidePoint : view.dom.contains(event.target) ? findColumnBelowPointer(view, event, event.target) : null;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** The latest gutter event waiting to be handled after the frame renders. */
|
|
109
|
+
|
|
110
|
+
/** What gutter hover does with a pointer position. */
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Leaving block controls does not count as reaching a new element: a nested block's drag handle
|
|
114
|
+
* overhangs the block, so leaving the handle can put the pointer on the container with no block
|
|
115
|
+
* beside it, and that exit must keep the controls on the block.
|
|
116
|
+
*/
|
|
117
|
+
const isLeavingBlockControls = event => event.relatedTarget instanceof Element && Boolean(event.relatedTarget.closest(BLOCK_CONTROLS_SURFACE_SELECTOR));
|
|
118
|
+
const classifyGutterHover = (view, findGutterBlock, {
|
|
119
|
+
event,
|
|
120
|
+
reachedNewElement
|
|
121
|
+
}) => {
|
|
122
|
+
const block = findGutterBlock(event);
|
|
123
|
+
if (block) {
|
|
124
|
+
return {
|
|
125
|
+
kind: 'block',
|
|
126
|
+
element: block
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const hoveredElement = event.target;
|
|
130
|
+
if (reachedNewElement && hoveredElement instanceof Element && view.dom.contains(hoveredElement)) {
|
|
131
|
+
return {
|
|
132
|
+
kind: 'container',
|
|
133
|
+
element: hoveredElement
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
kind: 'none'
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Runs `callback` once after the next frame renders (`requestAnimationFrame`, then `setTimeout`).
|
|
143
|
+
* Layout is clean then, so reading positions never forces a synchronous layout. Calls made while a
|
|
144
|
+
* run is already scheduled are ignored.
|
|
145
|
+
*/
|
|
146
|
+
const createAfterRenderScheduler = (win, callback) => {
|
|
147
|
+
let frame;
|
|
148
|
+
let timeout;
|
|
149
|
+
return {
|
|
150
|
+
schedule: () => {
|
|
151
|
+
if (frame !== undefined || timeout !== undefined) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
frame = win.requestAnimationFrame(() => {
|
|
155
|
+
frame = undefined;
|
|
156
|
+
timeout = win.setTimeout(() => {
|
|
157
|
+
timeout = undefined;
|
|
158
|
+
callback();
|
|
159
|
+
}, 0);
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
cancel: () => {
|
|
163
|
+
if (frame !== undefined) {
|
|
164
|
+
win.cancelAnimationFrame(frame);
|
|
165
|
+
frame = undefined;
|
|
166
|
+
}
|
|
167
|
+
if (timeout !== undefined) {
|
|
168
|
+
win.clearTimeout(timeout);
|
|
169
|
+
timeout = undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Keeps interaction tracking in step with gutter hover, as the legacy hover zones did. They sat
|
|
177
|
+
* inside the block tree, so hovering one cleared the typing state, counted as inside the editor even
|
|
178
|
+
* where the zone reached past the content area, and put the pointer on the left side of the
|
|
179
|
+
* right-side controls split.
|
|
180
|
+
*/
|
|
181
|
+
const createInteractionTrackingSync = (view, api, contentArea) => {
|
|
182
|
+
// Set after gutter hover calls mouseEnter for a gutter outside the content area, so that leaving
|
|
183
|
+
// that gutter calls mouseLeave.
|
|
184
|
+
let isHoldingMouseEnter = false;
|
|
185
|
+
return (event, isOverGutter) => {
|
|
186
|
+
var _api$blockControls$sh;
|
|
187
|
+
const state = getInteractionTrackingState(view.state);
|
|
188
|
+
const rightSideControlsEnabled = api === null || api === void 0 ? void 0 : (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.rightSideControlsEnabled;
|
|
189
|
+
const side = rightSideControlsEnabled ? 'left' : undefined;
|
|
190
|
+
if (isOverGutter && state !== null && state !== void 0 && state.isEditing) {
|
|
191
|
+
stopEditing(view);
|
|
192
|
+
}
|
|
193
|
+
if (event.target instanceof Node && contentArea !== null && contentArea !== void 0 && contentArea.contains(event.target)) {
|
|
194
|
+
isHoldingMouseEnter = false;
|
|
195
|
+
} else if (isOverGutter && state !== null && state !== void 0 && state.isMouseOut) {
|
|
196
|
+
isHoldingMouseEnter = true;
|
|
197
|
+
mouseEnter(view, side);
|
|
198
|
+
return;
|
|
199
|
+
} else if (!isOverGutter && isHoldingMouseEnter) {
|
|
200
|
+
isHoldingMouseEnter = false;
|
|
201
|
+
handleMouseLeave(view, rightSideControlsEnabled);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (isOverGutter && side && (state === null || state === void 0 ? void 0 : state.hoverSide) !== side) {
|
|
205
|
+
setHoverSide(view, side);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Shows block controls for the block the pointer hovers under sparse surfaces, including the empty
|
|
212
|
+
* space to a block's left (the gutter). This is the only hover handler for sparse surfaces.
|
|
213
|
+
*
|
|
214
|
+
* Reaching text or a leaf block shows its controls straight away. Over an element that can be part
|
|
215
|
+
* of a gutter, only the latest pointer event is kept, and it is resolved once after the frame
|
|
216
|
+
* renders so that reading positions never forces a layout.
|
|
217
|
+
*
|
|
218
|
+
* Returns an unbind function, or `undefined` when the editor's document has no window.
|
|
219
|
+
*/
|
|
220
|
+
export const bindSparseGutterHover = ({
|
|
221
|
+
api,
|
|
222
|
+
view
|
|
223
|
+
}) => {
|
|
224
|
+
var _ref, _ref2, _view$dom$closest;
|
|
225
|
+
const win = view.dom.ownerDocument.defaultView;
|
|
226
|
+
if (!win) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
const contentArea = view.dom.closest('.ak-editor-content-area');
|
|
230
|
+
// A wide block's gutter can sit outside the padded content area. Editors without a scroll
|
|
231
|
+
// container or content area still get hover, over as much of the gutter as their wrapper covers.
|
|
232
|
+
const eventArea = (_ref = (_ref2 = (_view$dom$closest = view.dom.closest('[data-editor-scroll-container="true"]')) !== null && _view$dom$closest !== void 0 ? _view$dom$closest : contentArea) !== null && _ref2 !== void 0 ? _ref2 : view.dom.parentElement) !== null && _ref !== void 0 ? _ref : view.dom;
|
|
233
|
+
const findGutterBlock = createGutterBlockFinder(view, api);
|
|
234
|
+
const syncInteractionTracking = createInteractionTrackingSync(view, api, contentArea);
|
|
235
|
+
let pending;
|
|
236
|
+
const handlePendingPointer = () => {
|
|
237
|
+
const pointer = pending;
|
|
238
|
+
pending = undefined;
|
|
239
|
+
if (!pointer || isSparseHoverSuppressed(view, api)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const hover = classifyGutterHover(view, findGutterBlock, pointer);
|
|
243
|
+
if (hover.kind !== 'none') {
|
|
244
|
+
handleSparseHoverTarget(view, hover.element, api);
|
|
245
|
+
}
|
|
246
|
+
syncInteractionTracking(pointer.event, hover.kind === 'block');
|
|
247
|
+
};
|
|
248
|
+
const scheduler = createAfterRenderScheduler(win, handlePendingPointer);
|
|
249
|
+
const queueGutterEvent = (event, reachedNewElement) => {
|
|
250
|
+
var _pending;
|
|
251
|
+
pending = {
|
|
252
|
+
event,
|
|
253
|
+
reachedNewElement: Boolean((_pending = pending) === null || _pending === void 0 ? void 0 : _pending.reachedNewElement) || reachedNewElement
|
|
254
|
+
};
|
|
255
|
+
scheduler.schedule();
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Classified on every mouseover and reused while the pointer moves within that element, so
|
|
259
|
+
// movement over text costs one comparison.
|
|
260
|
+
let hovered;
|
|
261
|
+
const classifyHoveredElement = element => {
|
|
262
|
+
hovered = {
|
|
263
|
+
element,
|
|
264
|
+
isPossibleGutter: isPossibleGutterElement(view, element)
|
|
265
|
+
};
|
|
266
|
+
return hovered.isPossibleGutter;
|
|
267
|
+
};
|
|
268
|
+
const onMouseOver = event => {
|
|
269
|
+
if (classifyHoveredElement(event.target)) {
|
|
270
|
+
queueGutterEvent(event, !isLeavingBlockControls(event));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
pending = undefined;
|
|
274
|
+
const hoveredElement = event.target;
|
|
275
|
+
if (hoveredElement instanceof Element && view.dom.contains(hoveredElement) && !isSparseHoverSuppressed(view, api)) {
|
|
276
|
+
handleSparseHoverTarget(view, hoveredElement, api);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const onMouseMove = event => {
|
|
280
|
+
var _hovered;
|
|
281
|
+
const isPossibleGutter = ((_hovered = hovered) === null || _hovered === void 0 ? void 0 : _hovered.element) === event.target ? hovered.isPossibleGutter : classifyHoveredElement(event.target);
|
|
282
|
+
if (isPossibleGutter) {
|
|
283
|
+
queueGutterEvent(event, false);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const bindMouseListener = (type, listener) => bind(eventArea, {
|
|
287
|
+
type,
|
|
288
|
+
listener: event => {
|
|
289
|
+
if (event instanceof win.MouseEvent) {
|
|
290
|
+
listener(event);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
options: {
|
|
294
|
+
passive: true
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
const unbindMouseOver = bindMouseListener('mouseover', onMouseOver);
|
|
298
|
+
const unbindMouseMove = bindMouseListener('mousemove', onMouseMove);
|
|
299
|
+
return () => {
|
|
300
|
+
unbindMouseOver();
|
|
301
|
+
unbindMouseMove();
|
|
302
|
+
scheduler.cancel();
|
|
303
|
+
pending = undefined;
|
|
304
|
+
};
|
|
305
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { BLOCK_CONTROLS_LEFT_SURFACE, BLOCK_CONTROLS_RIGHT_SURFACE } from '@atlaskit/editor-common/block-controls/surface-keys';
|
|
2
2
|
import { getNodeIdProvider } from '@atlaskit/editor-common/node-anchor';
|
|
3
3
|
import { resolveSurface } from '@atlaskit/editor-ui-control-model/surface-renderer';
|
|
4
|
-
import { createSurfaceAnchorDecorations } from '
|
|
5
|
-
import { key as blockControlsKey } from '
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { createSurfaceAnchorDecorations } from './decorations-surface-anchor';
|
|
5
|
+
import { key as blockControlsKey } from './main';
|
|
6
|
+
import { bindSparseGutterHover } from './sparse-gutter-hover';
|
|
7
|
+
import { reconcileSparseSurfaceCandidates } from './utils/sparse-surface-candidates';
|
|
8
|
+
import { observeSurfaceViewport } from './utils/surface-viewport';
|
|
8
9
|
const haveAnchorPositionsChanged = (positions, anchors) => positions.length !== anchors.size || positions.some(position => !anchors.has(position));
|
|
9
10
|
export const createSparseSurfacesView = ({
|
|
10
11
|
api,
|
|
@@ -95,6 +96,10 @@ export const createSparseSurfacesView = ({
|
|
|
95
96
|
schedule();
|
|
96
97
|
};
|
|
97
98
|
const unsubscribe = api === null || api === void 0 ? void 0 : (_api$uiControlRegistr3 = api.uiControlRegistry) === null || _api$uiControlRegistr3 === void 0 ? void 0 : _api$uiControlRegistr3.actions.subscribe(invalidate);
|
|
99
|
+
const unbindGutterHover = bindSparseGutterHover({
|
|
100
|
+
api,
|
|
101
|
+
view
|
|
102
|
+
});
|
|
98
103
|
const unsubscribers = [api === null || api === void 0 ? void 0 : (_api$editorDisabled = api.editorDisabled) === null || _api$editorDisabled === void 0 ? void 0 : _api$editorDisabled.sharedState.onChange(invalidate), api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.sharedState.onChange(invalidate), api === null || api === void 0 ? void 0 : (_api$limitedMode2 = api.limitedMode) === null || _api$limitedMode2 === void 0 ? void 0 : _api$limitedMode2.sharedState.onChange(invalidate), api === null || api === void 0 ? void 0 : (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 ? void 0 : _api$showDiff.sharedState.onChange(invalidate), api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.sharedState.onChange(invalidate)];
|
|
99
104
|
return {
|
|
100
105
|
update(currentView, previousState) {
|
|
@@ -114,6 +119,7 @@ export const createSparseSurfacesView = ({
|
|
|
114
119
|
win === null || win === void 0 ? void 0 : win.cancelAnimationFrame(frame);
|
|
115
120
|
}
|
|
116
121
|
observer.destroy();
|
|
122
|
+
unbindGutterHover === null || unbindGutterHover === void 0 ? void 0 : unbindGutterHover();
|
|
117
123
|
unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe();
|
|
118
124
|
unsubscribers.forEach(unsubscribe => unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe());
|
|
119
125
|
}
|
|
@@ -3,9 +3,9 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
|
3
3
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
4
4
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
5
5
|
import { key as blockControlsKey } from './main';
|
|
6
|
+
import { createSparseSurfacesView } from './sparse-surfaces-view';
|
|
6
7
|
import { emptySparseSurfaceCandidateState } from './utils/sparse-surface-candidates';
|
|
7
8
|
import { applySparseSurfacesTransaction, createInitialSparseSurfacesState } from './utils/sparse-surfaces-state';
|
|
8
|
-
import { createSparseSurfacesView } from './utils/sparse-surfaces-view';
|
|
9
9
|
export const sparseSurfacesKey = new PluginKey('blockControlsSparseSurfaces');
|
|
10
10
|
|
|
11
11
|
/** Owns sparse anchors independently of drag/drop decorations and their lifecycle. */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Matches the anchor selectors in `ui/global-styles.tsx`. A table's `.pm-table-resizer-container`
|
|
2
|
+
// wraps its cells too, so only a direct child `.resizer-item` is the table's own box, not the
|
|
3
|
+
// resizer of media inside a cell.
|
|
4
|
+
const VISIBLE_BOX_SELECTORS = {
|
|
5
|
+
table: '.pm-table-resizer-container > .resizer-item',
|
|
6
|
+
mediaSingle: '.resizer-item',
|
|
7
|
+
embedCard: '.rich-media-item',
|
|
8
|
+
extension: '.extension-container[data-layout]',
|
|
9
|
+
bodiedExtension: '.extension-container[data-layout]',
|
|
10
|
+
multiBodiedExtension: '.extension-container[data-layout]',
|
|
11
|
+
blockCard: '.datasourceView-content-inner-wrap'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The box a block's controls anchor to under sparse surfaces. For some blocks this is an inner
|
|
16
|
+
* element, which can be wider than the block itself.
|
|
17
|
+
*/
|
|
18
|
+
export const getVisibleBox = (dom, nodeType) => {
|
|
19
|
+
const selector = VISIBLE_BOX_SELECTORS[nodeType];
|
|
20
|
+
return selector && dom.querySelector(selector) || dom;
|
|
21
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getNodeIdProvider } from '@atlaskit/editor-common/node-anchor';
|
|
1
2
|
import { fg } from '@atlaskit/platform-feature-flags/fg';
|
|
2
3
|
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
|
|
3
4
|
import { getNodeTypeWithLevel } from './decorations-common';
|
|
@@ -34,26 +35,38 @@ var redirectParagraphToWrappedMedia = function redirectParagraphToWrappedMedia(v
|
|
|
34
35
|
};
|
|
35
36
|
};
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
-
export var
|
|
39
|
-
var _api$editorDisabled$s, _api$editorDisabled, _api$editorViewMode, _api$showDiff$sharedS, _api$showDiff, _api$userIntent
|
|
38
|
+
/** Whether hovering must not show block controls under sparse surfaces. */
|
|
39
|
+
export var isSparseHoverSuppressed = function isSparseHoverSuppressed(view, api) {
|
|
40
|
+
var _api$limitedMode, _getNodeIdProvider, _api$typeAhead, _api$editorDisabled$s, _api$editorDisabled, _api$editorViewMode, _api$showDiff$sharedS, _api$showDiff, _api$userIntent;
|
|
41
|
+
if (api !== null && api !== void 0 && (_api$limitedMode = api.limitedMode) !== null && _api$limitedMode !== void 0 && (_api$limitedMode = _api$limitedMode.sharedState.currentState()) !== null && _api$limitedMode !== void 0 && _api$limitedMode.enabled || (_getNodeIdProvider = getNodeIdProvider(view)) !== null && _getNodeIdProvider !== void 0 && _getNodeIdProvider.isLimitedMode()) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
40
44
|
var controls = api === null || api === void 0 ? void 0 : api.blockControls.sharedState.currentState();
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
45
|
+
if (controls !== null && controls !== void 0 && controls.isDragging || api !== null && api !== void 0 && (_api$typeAhead = api.typeAhead) !== null && _api$typeAhead !== void 0 && (_api$typeAhead = _api$typeAhead.sharedState.currentState()) !== null && _api$typeAhead !== void 0 && _api$typeAhead.isOpen) {
|
|
46
|
+
return true;
|
|
43
47
|
}
|
|
44
|
-
var editorDisabled = (_api$editorDisabled$s = (_api$editorDisabled = api.editorDisabled) === null || _api$editorDisabled === void 0 || (_api$editorDisabled = _api$editorDisabled.sharedState.currentState()) === null || _api$editorDisabled === void 0 ? void 0 : _api$editorDisabled.editorDisabled) !== null && _api$editorDisabled$s !== void 0 ? _api$editorDisabled$s : false;
|
|
45
|
-
var editorViewMode = (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode;
|
|
48
|
+
var editorDisabled = (_api$editorDisabled$s = api === null || api === void 0 || (_api$editorDisabled = api.editorDisabled) === null || _api$editorDisabled === void 0 || (_api$editorDisabled = _api$editorDisabled.sharedState.currentState()) === null || _api$editorDisabled === void 0 ? void 0 : _api$editorDisabled.editorDisabled) !== null && _api$editorDisabled$s !== void 0 ? _api$editorDisabled$s : false;
|
|
49
|
+
var editorViewMode = api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode;
|
|
46
50
|
if (editorDisabled && (editorViewMode !== 'view' || !(controls !== null && controls !== void 0 && controls.rightSideControlsEnabled))) {
|
|
47
|
-
return
|
|
51
|
+
return true;
|
|
48
52
|
}
|
|
49
|
-
var isDisplayingDiff = (_api$showDiff$sharedS = (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 || (_api$showDiff = _api$showDiff.sharedState.currentState()) === null || _api$showDiff === void 0 ? void 0 : _api$showDiff.isDisplayingChanges) !== null && _api$showDiff$sharedS !== void 0 ? _api$showDiff$sharedS : false;
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
var isDisplayingDiff = (_api$showDiff$sharedS = api === null || api === void 0 || (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 || (_api$showDiff = _api$showDiff.sharedState.currentState()) === null || _api$showDiff === void 0 ? void 0 : _api$showDiff.isDisplayingChanges) !== null && _api$showDiff$sharedS !== void 0 ? _api$showDiff$sharedS : false;
|
|
54
|
+
return isDisplayingDiff || (api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) === 'reviewing';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Shows controls for the block that owns `target`, resolved from PM structure so the first hover
|
|
59
|
+
* can create its sparse candidate. Callers check `isSparseHoverSuppressed` first.
|
|
60
|
+
*/
|
|
61
|
+
export var handleSparseHoverTarget = function handleSparseHoverTarget(view, target, api) {
|
|
62
|
+
var _$pos$nodeAfter, _controls$activeNode, _anchors$get, _anchors$get2;
|
|
63
|
+
if (!api || target === view.dom) {
|
|
52
64
|
return false;
|
|
53
65
|
}
|
|
66
|
+
var controls = api.blockControls.sharedState.currentState();
|
|
54
67
|
var position;
|
|
55
68
|
try {
|
|
56
|
-
position = view.posAtDOM(
|
|
69
|
+
position = view.posAtDOM(target, 0);
|
|
57
70
|
} catch (_unused) {
|
|
58
71
|
return false;
|
|
59
72
|
}
|
|
@@ -75,7 +88,7 @@ export var handleSparseMouseOver = function handleSparseMouseOver(view, event, a
|
|
|
75
88
|
// Browser positions over non-editable node chrome can resolve to the first editable child.
|
|
76
89
|
// Keep the ancestor unless that child actually owns the hovered DOM target.
|
|
77
90
|
var nodeAfterDOM = view.nodeDOM(position);
|
|
78
|
-
if (nodeAfterDOM instanceof Element && nodeAfterDOM.contains(
|
|
91
|
+
if (nodeAfterDOM instanceof Element && nodeAfterDOM.contains(target)) {
|
|
79
92
|
targetPosition = position;
|
|
80
93
|
}
|
|
81
94
|
}
|
|
@@ -86,9 +99,9 @@ export var handleSparseMouseOver = function handleSparseMouseOver(view, event, a
|
|
|
86
99
|
if (!initialNode) {
|
|
87
100
|
return false;
|
|
88
101
|
}
|
|
89
|
-
var
|
|
90
|
-
targetPosition =
|
|
91
|
-
var node =
|
|
102
|
+
var redirected = redirectParagraphToWrappedMedia(view, view.state.doc.resolve(targetPosition), targetPosition);
|
|
103
|
+
targetPosition = redirected.position;
|
|
104
|
+
var node = redirected.node;
|
|
92
105
|
if (!node || (controls === null || controls === void 0 || (_controls$activeNode = controls.activeNode) === null || _controls$activeNode === void 0 ? void 0 : _controls$activeNode.pos) === targetPosition) {
|
|
93
106
|
return false;
|
|
94
107
|
}
|
|
@@ -35,7 +35,6 @@ import { getActiveDropTargetDecorations } from './decorations-drop-target-active
|
|
|
35
35
|
import { createActiveQuickInsertNodeDecoration, findActiveQuickInsertNodeDec, findQuickInsertInsertButtonDecoration, quickInsertButtonDecoration, TYPE_ACTIVE_QUICK_INSERT_NODE } from './decorations-quick-insert-button';
|
|
36
36
|
import { handleMouseDown } from './handle-mouse-down';
|
|
37
37
|
import { handleMouseOver } from './handle-mouse-over';
|
|
38
|
-
import { handleSparseMouseOver } from './handle-sparse-mouse-over';
|
|
39
38
|
import { boundKeydownHandler } from './keymap';
|
|
40
39
|
import { defaultActiveAnchorTracker } from './utils/active-anchor-tracker';
|
|
41
40
|
import { getMultiSelectAnalyticsAttributes } from './utils/analytics';
|
|
@@ -1159,6 +1158,11 @@ export var createPlugin = function createPlugin(api, getIntl, nodeViewPortalProv
|
|
|
1159
1158
|
},
|
|
1160
1159
|
mouseover: function mouseover(view, event) {
|
|
1161
1160
|
var _api$limitedMode7, _getNodeIdProvider;
|
|
1161
|
+
// Sparse surfaces handle all hover in bindSparseGutterHover, which also covers the gutters
|
|
1162
|
+
// outside view.dom.
|
|
1163
|
+
if (isExperimentEnabled('platform_editor_block_control_migration')) {
|
|
1164
|
+
return false;
|
|
1165
|
+
}
|
|
1162
1166
|
if (api !== null && api !== void 0 && (_api$limitedMode7 = api.limitedMode) !== null && _api$limitedMode7 !== void 0 && (_api$limitedMode7 = _api$limitedMode7.sharedState.currentState()) !== null && _api$limitedMode7 !== void 0 && _api$limitedMode7.enabled) {
|
|
1163
1167
|
return;
|
|
1164
1168
|
}
|
|
@@ -1166,14 +1170,10 @@ export var createPlugin = function createPlugin(api, getIntl, nodeViewPortalProv
|
|
|
1166
1170
|
// another layer of protection to prevent handling mouseover
|
|
1167
1171
|
// when native anchor with dnd is enabled in limited mode
|
|
1168
1172
|
// in case there are descripancies between getNodeIdProvider limited mode state
|
|
1169
|
-
if ((_getNodeIdProvider = getNodeIdProvider(view)) !== null && _getNodeIdProvider !== void 0 && _getNodeIdProvider.isLimitedMode() &&
|
|
1173
|
+
if ((_getNodeIdProvider = getNodeIdProvider(view)) !== null && _getNodeIdProvider !== void 0 && _getNodeIdProvider.isLimitedMode() && expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) {
|
|
1170
1174
|
return;
|
|
1171
1175
|
}
|
|
1172
|
-
|
|
1173
|
-
handleSparseMouseOver(view, event, api);
|
|
1174
|
-
} else {
|
|
1175
|
-
handleMouseOver(view, event, api);
|
|
1176
|
-
}
|
|
1176
|
+
handleMouseOver(view, event, api);
|
|
1177
1177
|
return false;
|
|
1178
1178
|
},
|
|
1179
1179
|
mousedown: function mousedown(view, event) {
|