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