@atlaskit/editor-plugin-breakout 16.0.13 → 16.1.0

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 CHANGED
@@ -1,5 +1,42 @@
1
1
  # @atlaskit/editor-plugin-breakout
2
2
 
3
+ ## 16.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`4b796b0158182`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4b796b0158182) -
8
+ EDITOR-8561 Render the breakout resize handle tooltip with `VanillaTooltip` behind the
9
+ `platform_editor_use_vanilla_components` experiment, replacing a React tree per resize handle.
10
+ When the experiment is off the tooltip is still rendered with React through the node view portal
11
+ provider.
12
+
13
+ The tooltip is anchored to a zero-height element parked where the pointer entered the rail, rather
14
+ than to the rail itself, which spans the node's full height. This matches the ADS tooltip's
15
+ `position="mouse"`, which latches the cursor position as the tooltip opens and does not move it
16
+ afterwards. The anchor is positioned by `.pm-breakout-resize-tooltip-anchor` in the editor's
17
+ resizer styles, which reads the pointer's offset from a custom property; the rule is inert while
18
+ the experiment is off, because nothing carries the class.
19
+
20
+ On the experiment the tooltip always mounts on the first hover, whatever
21
+ `platform_editor_reduce_event_listener_count` is set to: there is no portal to defer, and the
22
+ handles are built before ProseMirror populates `contentDOM`, so mounting any earlier would label
23
+ the tooltip with a generic "node" rather than the node being resized.
24
+
25
+ `VanillaTooltip` accepts an optional `placement`, forwarded to Popper when the tooltip is
26
+ positioned. Defaults to `top`, so existing callers are unaffected.
27
+
28
+ ### Patch Changes
29
+
30
+ - [`0f76719a50b8f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0f76719a50b8f) -
31
+ Clean up feature gate `platform_editor_breakout_auto_normalization_history_patch`
32
+ - Updated dependencies
33
+
34
+ ## 16.0.14
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies
39
+
3
40
  ## 16.0.13
4
41
 
5
42
  ### Patch Changes
@@ -11,6 +11,7 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
11
11
  var _react = _interopRequireWildcard(require("react"));
12
12
  var _bindEventListener = require("bind-event-listener");
13
13
  var _messages = require("@atlaskit/editor-common/messages");
14
+ var _vanillaTooltip = require("@atlaskit/editor-common/vanilla-tooltip");
14
15
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
15
16
  var _elementAdapter = require("@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter");
16
17
  var _disableNativeDragPreview = require("@atlaskit/pragmatic-drag-and-drop/utils/disable-native-drag-preview");
@@ -32,6 +33,12 @@ var getNodeName = function getNodeName(nodeName) {
32
33
  return 'node';
33
34
  }
34
35
  };
36
+
37
+ /**
38
+ * Read by `.pm-breakout-resize-tooltip-anchor`, declared in `editor-core`'s `resizerStyles.ts` and
39
+ * its Compiled mirror. Rename in all three or the tooltip pins to the top of the rail.
40
+ */
41
+ var tooltipAnchorTopVar = '--pm-breakout-resize-tooltip-anchor-top';
35
42
  var resizeHandleMessage = exports.resizeHandleMessage = {
36
43
  expand: _messages.breakoutMessages.resizeExpand,
37
44
  codeBlock: _messages.breakoutMessages.resizeCodeBlock,
@@ -44,6 +51,10 @@ var resizeHandleMessage = exports.resizeHandleMessage = {
44
51
  bodiedExtension: _messages.breakoutMessages.resizeExtension,
45
52
  multiBodiedExtension: _messages.breakoutMessages.resizeExtension
46
53
  };
54
+ /**
55
+ * Remove during `platform_editor_use_vanilla_components` cleanup — replaced by
56
+ * `createHandleTooltip`, which does the same job without a React tree per resize handle.
57
+ */
47
58
  var RailWithTooltip = function RailWithTooltip(_ref) {
48
59
  var rail = _ref.rail,
49
60
  target = _ref.target,
@@ -70,13 +81,54 @@ var RailWithTooltip = function RailWithTooltip(_ref) {
70
81
  }
71
82
  }));
72
83
  };
73
- var createPragmaticResizer = exports.createPragmaticResizer = function createPragmaticResizer(_ref2) {
74
- var target = _ref2.target,
75
- _onDragStart = _ref2.onDragStart,
76
- onDrag = _ref2.onDrag,
77
- _onDrop = _ref2.onDrop,
84
+
85
+ /** Identifies the vanilla tooltip for tests and debugging. */
86
+ var TOOLTIP_CLASS_NAME = 'pm-breakout-resize-tooltip';
87
+
88
+ /**
89
+ * `ak-editor-vanilla-tooltip-default` opts into `vanillaTooltipDefaultStyles` in the
90
+ * `EditorContentContainer` stylesheets, which still reaches a `[popover]` because it is painted in
91
+ * the top layer but stays in the DOM under `.ProseMirror`.
92
+ */
93
+ var TOOLTIP_CLASS_NAMES = "ak-editor-vanilla-tooltip-default ".concat(TOOLTIP_CLASS_NAME);
94
+
95
+ /**
96
+ * Vanilla DOM equivalent of `RailWithTooltip`, behind `platform_editor_use_vanilla_components`.
97
+ *
98
+ * Anchored to a point rather than to the rail, which spans the node's full height and would strand
99
+ * the tooltip at the top of a tall node. This reproduces the control arm's `position="mouse"`,
100
+ * which latches the cursor as the tooltip opens and does not move it afterwards — so no cursor
101
+ * tracking is needed, just one style write per hover.
102
+ */
103
+ var createHandleTooltip = function createHandleTooltip(_ref2) {
104
+ var anchor = _ref2.anchor,
78
105
  intl = _ref2.intl,
79
- nodeViewPortalProviderAPI = _ref2.nodeViewPortalProviderAPI;
106
+ target = _ref2.target;
107
+ // Only correct once ProseMirror has populated `contentDOM`, so callers must build on first
108
+ // hover — earlier reads fall back to the generic 'node' label.
109
+ var node = target.querySelector('[data-prosemirror-node-name]');
110
+ var nodeName = getNodeName(node === null || node === void 0 ? void 0 : node.dataset.prosemirrorNodeName);
111
+ return new _vanillaTooltip.VanillaTooltip(anchor, intl.formatMessage(resizeHandleMessage[nodeName]),
112
+ // Let the tooltip generate its own id.
113
+ undefined, TOOLTIP_CLASS_NAMES,
114
+ // Default delay, matching the ADS tooltip.
115
+ 300, {
116
+ // The only thing the shared look does not cover. Already inherited from the anchor,
117
+ // but set explicitly so a change there can never let this swallow a resize drag.
118
+ pointerEvents: 'none'
119
+ },
120
+ // No `onShow`.
121
+ undefined,
122
+ // Below the cursor, matching the control arm's `position="mouse"`.
123
+ 'bottom');
124
+ };
125
+ var createPragmaticResizer = exports.createPragmaticResizer = function createPragmaticResizer(_ref3) {
126
+ var target = _ref3.target,
127
+ _onDragStart = _ref3.onDragStart,
128
+ onDrag = _ref3.onDrag,
129
+ _onDrop = _ref3.onDrop,
130
+ intl = _ref3.intl,
131
+ nodeViewPortalProviderAPI = _ref3.nodeViewPortalProviderAPI;
80
132
  var state = 'default';
81
133
  // Read once per resizer: `isExperimentEnabled` fires an exposure event, and `registerEvents` is
82
134
  // bound to several elements so checking it inside the handler would emit one per element hovered.
@@ -102,6 +154,21 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
102
154
  thumb.classList.add('pm-breakout-resize-handle-thumb');
103
155
  thumb.style.pointerEvents = 'none';
104
156
  rail.appendChild(thumb);
157
+
158
+ // Marks the point the pointer entered the rail, and anchors the tooltip there: zero height
159
+ // so it is a point not a box, full rail width so the tooltip centres like the control arm.
160
+ //
161
+ // Must stay a child of `rail` — `openTooltipAt` sets `top` from the rail's `offsetY`, which
162
+ // only lines up while the rail is the containing block. jsdom cannot catch a wrong parent.
163
+ //
164
+ // Not built on the control arm, which must keep byte-identical DOM.
165
+ var tooltipAnchor;
166
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_use_vanilla_components')) {
167
+ // Positioned by `.pm-breakout-resize-tooltip-anchor`; only the pointer's Y is passed in.
168
+ tooltipAnchor = document.createElement('div');
169
+ tooltipAnchor.classList.add('pm-breakout-resize-tooltip-anchor');
170
+ rail.appendChild(tooltipAnchor);
171
+ }
105
172
  var tooltipContainer = document.createElement('div');
106
173
  tooltipContainer.classList.add('pm-breakout-resize-handle-rail-wrapper');
107
174
  handle.appendChild(tooltipContainer);
@@ -116,17 +183,29 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
116
183
  });
117
184
  }, tooltipContainer, key);
118
185
  };
186
+ var vanillaTooltip;
119
187
 
120
- // Only meaningful on the deferred path, where the tooltip mounts on first hover and so may
121
- // never mount at all. The eager path below mounts unconditionally and does not consult it.
188
+ // Tracks whether there is anything to tear down. On the deferred path the tooltip mounts
189
+ // on first hover and so may never mount at all.
122
190
  var isTooltipMounted = false;
123
191
  var mountTooltip = function mountTooltip() {
124
192
  if (isTooltipMounted) {
125
193
  return;
126
194
  }
127
195
  isTooltipMounted = true;
196
+
197
+ // `tooltipAnchor` is always set when the experiment is on; the check also narrows it.
198
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_use_vanilla_components') && tooltipAnchor) {
199
+ vanillaTooltip = createHandleTooltip({
200
+ anchor: tooltipAnchor,
201
+ target: target,
202
+ intl: intl
203
+ });
204
+ return;
205
+ }
206
+
128
207
  // `RailWithTooltip`'s ref moves `rail` into the tooltip wrapper, so the DOM ends up
129
- // identical to the eager path.
208
+ // identical to the pre-mount state below.
130
209
  renderTooltip();
131
210
  };
132
211
 
@@ -139,22 +218,45 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
139
218
  // `mouseenter` of the node (see `registerEvents` below). The rail has `opacity: 0` until
140
219
  // the pointer is inside the node, so the pre-mount DOM is never visible, and the
141
220
  // `mouseenter` fires on the whole node — long before the pointer reaches the 7px handle.
142
- if (isDeferredTooltipEnabled) {
143
- // Without the tooltip's `[role="presentation"]` / `.pm-breakout-resize-handle-rail-inside-tooltip`
144
- // wrappers (both stretched to `width: 100%` by `pragmaticResizerStylesForTooltip`), the rail
145
- // would size to its content as a flex item. Stretch it explicitly so the pre-mount and
146
- // post-mount geometry match exactly — `100%` of the 7px handle container either way.
221
+ //
222
+ // Parking the rail also needs `width: 100%`: without the ADS tooltip's wrappers (stretched
223
+ // by `pragmaticResizerStylesForTooltip`) it would size to its content as a flex item.
224
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_use_vanilla_components')) {
225
+ // Always parks, and always defers whatever `platform_editor_reduce_event_listener_count`
226
+ // says: no portal means no listener cost, and first hover is when the node name reads
227
+ // correctly.
147
228
  rail.style.width = '100%';
148
229
  tooltipContainer.appendChild(rail);
149
230
  } else {
150
- renderTooltip();
231
+ // Remove this while at `platform_editor_use_vanilla_components` cleanup.
232
+ if (isDeferredTooltipEnabled) {
233
+ rail.style.width = '100%';
234
+ tooltipContainer.appendChild(rail);
235
+ } else {
236
+ // Not `mountTooltip`: that would set `isTooltipMounted` where the control arm never did.
237
+ renderTooltip();
238
+ }
151
239
  }
152
240
  return {
153
241
  handle: handle,
154
242
  rail: rail,
243
+ // Exposed so the rail's hover listeners can position and open the tooltip.
244
+ tooltipAnchor: tooltipAnchor,
155
245
  handleHitBox: handleHitBox,
156
246
  mountTooltip: mountTooltip,
157
247
  destroyTooltip: function destroyTooltip() {
248
+ // Re-read is safe: the gate is fixed for the page, so it matches what `mountTooltip`
249
+ // saw. At cleanup, keep this body and delete the control arm below.
250
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_use_vanilla_components')) {
251
+ // May never have mounted, and there is no portal to remove either way.
252
+ if (vanillaTooltip) {
253
+ vanillaTooltip.destroy();
254
+ vanillaTooltip = undefined;
255
+ }
256
+ isTooltipMounted = false;
257
+ return;
258
+ }
259
+
158
260
  // The eager path always mounted, so it always removes — unchanged from before. Only
159
261
  // the deferred path can reach `destroyTooltip` without having mounted anything.
160
262
  if (isDeferredTooltipEnabled && !isTooltipMounted) {
@@ -171,8 +273,8 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
171
273
  var registerHandle = function registerHandle(handleElement, handleSide) {
172
274
  return (0, _elementAdapter.draggable)({
173
275
  element: handleElement,
174
- onGenerateDragPreview: function onGenerateDragPreview(_ref3) {
175
- var nativeSetDragImage = _ref3.nativeSetDragImage;
276
+ onGenerateDragPreview: function onGenerateDragPreview(_ref4) {
277
+ var nativeSetDragImage = _ref4.nativeSetDragImage;
176
278
  (0, _disableNativeDragPreview.disableNativeDragPreview)({
177
279
  nativeSetDragImage: nativeSetDragImage
178
280
  });
@@ -186,6 +288,14 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
186
288
  onDragStart: function onDragStart(args) {
187
289
  state = 'resizing';
188
290
  handleElement.classList.add('pm-breakout-resize-handle-container--active');
291
+
292
+ // The ADS tooltip hides itself on press; the vanilla one has no pointer-press
293
+ // listener, and its anchor moves with the rail, so it would follow the drag.
294
+ // Tearing it down hides it immediately; the next hover rebuilds it.
295
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_use_vanilla_components')) {
296
+ rightHandle.destroyTooltip();
297
+ leftHandle === null || leftHandle === void 0 || leftHandle.destroyTooltip();
298
+ }
189
299
  _onDragStart(args);
190
300
  },
191
301
  onDrag: onDrag,
@@ -197,20 +307,57 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
197
307
  }
198
308
  });
199
309
  };
200
- var registerEvents = function registerEvents(element) {
310
+
311
+ /**
312
+ * Parks the anchor where the pointer entered the rail, then opens the tooltip. The anchor is
313
+ * `pointer-events: none`, so the rail's hover listeners drive it. `offsetY` is already in the
314
+ * anchor's coordinate space, so no layout read is needed.
315
+ *
316
+ * Positions on enter only, matching the ADS tooltip as observed — though `Tooltip` does bind an
317
+ * `onMouseMove`, so start here if the two arms disagree on placement.
318
+ */
319
+ var openTooltipAt = function openTooltipAt(anchor, event) {
320
+ // Only the rail bindings pass an anchor; `target` and the hit boxes do not.
321
+ if (!anchor) {
322
+ return;
323
+ }
324
+ anchor.style.setProperty(tooltipAnchorTopVar, "".concat(event.offsetY, "px"));
325
+ anchor.dispatchEvent(new MouseEvent('mouseenter'));
326
+ };
327
+ var closeTooltip = function closeTooltip(anchor) {
328
+ if (!anchor) {
329
+ return;
330
+ }
331
+ anchor.dispatchEvent(new MouseEvent('mouseleave'));
332
+ };
333
+ var registerEvents = function registerEvents(element, hoverAnchor) {
201
334
  return [(0, _bindEventListener.bind)(element, {
202
335
  type: 'mouseenter',
203
- listener: function listener() {
336
+ listener: function listener(event) {
204
337
  if (isDeferredTooltipEnabled) {
205
338
  rightHandle.mountTooltip();
206
339
  leftHandle === null || leftHandle === void 0 || leftHandle.mountTooltip();
207
340
  }
341
+ // Always mounts on first hover, so it also mounts when the deferred experiment is
342
+ // off. `mountTooltip` is a no-op once mounted, so the overlap is safe. At
343
+ // cleanup, drop the gate and delete the block above — this covers both.
344
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_use_vanilla_components')) {
345
+ rightHandle.mountTooltip();
346
+ leftHandle === null || leftHandle === void 0 || leftHandle.mountTooltip();
347
+ // After the mount above, so the tooltip always exists to be opened.
348
+ openTooltipAt(hoverAnchor, event);
349
+ }
208
350
  rightHandle.rail.style.setProperty('opacity', '1');
209
351
  leftHandle === null || leftHandle === void 0 || leftHandle.rail.style.setProperty('opacity', '1');
210
352
  }
211
353
  }), (0, _bindEventListener.bind)(element, {
212
354
  type: 'mouseleave',
213
355
  listener: function listener() {
356
+ // Ahead of the resize guard below: the ADS tooltip hides whenever the pointer
357
+ // leaves the rail, including part-way through a resize.
358
+ if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_use_vanilla_components')) {
359
+ closeTooltip(hoverAnchor);
360
+ }
214
361
  if (state === 'resizing') {
215
362
  return;
216
363
  }
@@ -220,8 +367,8 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
220
367
  })];
221
368
  };
222
369
  var unbindFns = leftHandle ? // old code - left + right
223
- [].concat((0, _toConsumableArray2.default)(registerEvents(target)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.handleHitBox)), (0, _toConsumableArray2.default)(registerEvents(leftHandle.handleHitBox)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.rail)), (0, _toConsumableArray2.default)(registerEvents(leftHandle.rail))) : // new code - right only
224
- [].concat((0, _toConsumableArray2.default)(registerEvents(target)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.handleHitBox)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.rail)));
370
+ [].concat((0, _toConsumableArray2.default)(registerEvents(target)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.handleHitBox)), (0, _toConsumableArray2.default)(registerEvents(leftHandle.handleHitBox)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.rail, rightHandle.tooltipAnchor)), (0, _toConsumableArray2.default)(registerEvents(leftHandle.rail, leftHandle.tooltipAnchor))) : // new code - right only
371
+ [].concat((0, _toConsumableArray2.default)(registerEvents(target)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.handleHitBox)), (0, _toConsumableArray2.default)(registerEvents(rightHandle.rail, rightHandle.tooltipAnchor)));
225
372
  var handleElement = 'rail';
226
373
  var destroyFns = leftHandle ?
227
374
  // old code - left + right
@@ -12,7 +12,6 @@ var _document = require("@atlaskit/editor-common/utils/document");
12
12
  var _state = require("@atlaskit/editor-prosemirror/state");
13
13
  var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
14
14
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
15
- var _fg = require("@atlaskit/platform-feature-flags/fg");
16
15
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
17
16
  var _handleKeyDown = require("./handle-key-down");
18
17
  var _resizingMarkView = require("./resizing-mark-view");
@@ -265,7 +264,7 @@ var createResizingPlugin = exports.createResizingPlugin = function createResizin
265
264
  // This transaction normalizes a newly inserted node to the editor's default breakout
266
265
  // width. It is not a user resize and must not become an undo step — in particular, a
267
266
  // source synced block can be deleted permanently before Undo reaches this normalization.
268
- return (0, _fg.fg)('platform_editor_breakout_auto_normalization_history_patch') ? newTr.setMeta('addToHistory', false) : newTr;
267
+ return newTr.setMeta('addToHistory', false);
269
268
  }
270
269
  }
271
270
  });
@@ -1,6 +1,7 @@
1
1
  import React, { useLayoutEffect, useState } from 'react';
2
2
  import { bind } from 'bind-event-listener';
3
3
  import { breakoutMessages as messages } from '@atlaskit/editor-common/messages';
4
+ import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
4
5
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
5
6
  import { draggable } from '@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter';
6
7
  import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/utils/disable-native-drag-preview';
@@ -21,6 +22,12 @@ const getNodeName = nodeName => {
21
22
  return 'node';
22
23
  }
23
24
  };
25
+
26
+ /**
27
+ * Read by `.pm-breakout-resize-tooltip-anchor`, declared in `editor-core`'s `resizerStyles.ts` and
28
+ * its Compiled mirror. Rename in all three or the tooltip pins to the top of the rail.
29
+ */
30
+ const tooltipAnchorTopVar = '--pm-breakout-resize-tooltip-anchor-top';
24
31
  export const resizeHandleMessage = {
25
32
  expand: messages.resizeExpand,
26
33
  codeBlock: messages.resizeCodeBlock,
@@ -33,6 +40,10 @@ export const resizeHandleMessage = {
33
40
  bodiedExtension: messages.resizeExtension,
34
41
  multiBodiedExtension: messages.resizeExtension
35
42
  };
43
+ /**
44
+ * Remove during `platform_editor_use_vanilla_components` cleanup — replaced by
45
+ * `createHandleTooltip`, which does the same job without a React tree per resize handle.
46
+ */
36
47
  const RailWithTooltip = ({
37
48
  rail,
38
49
  target,
@@ -57,6 +68,48 @@ const RailWithTooltip = ({
57
68
  }
58
69
  }));
59
70
  };
71
+
72
+ /** Identifies the vanilla tooltip for tests and debugging. */
73
+ const TOOLTIP_CLASS_NAME = 'pm-breakout-resize-tooltip';
74
+
75
+ /**
76
+ * `ak-editor-vanilla-tooltip-default` opts into `vanillaTooltipDefaultStyles` in the
77
+ * `EditorContentContainer` stylesheets, which still reaches a `[popover]` because it is painted in
78
+ * the top layer but stays in the DOM under `.ProseMirror`.
79
+ */
80
+ const TOOLTIP_CLASS_NAMES = `ak-editor-vanilla-tooltip-default ${TOOLTIP_CLASS_NAME}`;
81
+
82
+ /**
83
+ * Vanilla DOM equivalent of `RailWithTooltip`, behind `platform_editor_use_vanilla_components`.
84
+ *
85
+ * Anchored to a point rather than to the rail, which spans the node's full height and would strand
86
+ * the tooltip at the top of a tall node. This reproduces the control arm's `position="mouse"`,
87
+ * which latches the cursor as the tooltip opens and does not move it afterwards — so no cursor
88
+ * tracking is needed, just one style write per hover.
89
+ */
90
+ const createHandleTooltip = ({
91
+ anchor,
92
+ intl,
93
+ target
94
+ }) => {
95
+ // Only correct once ProseMirror has populated `contentDOM`, so callers must build on first
96
+ // hover — earlier reads fall back to the generic 'node' label.
97
+ const node = target.querySelector('[data-prosemirror-node-name]');
98
+ const nodeName = getNodeName(node === null || node === void 0 ? void 0 : node.dataset.prosemirrorNodeName);
99
+ return new VanillaTooltip(anchor, intl.formatMessage(resizeHandleMessage[nodeName]),
100
+ // Let the tooltip generate its own id.
101
+ undefined, TOOLTIP_CLASS_NAMES,
102
+ // Default delay, matching the ADS tooltip.
103
+ 300, {
104
+ // The only thing the shared look does not cover. Already inherited from the anchor,
105
+ // but set explicitly so a change there can never let this swallow a resize drag.
106
+ pointerEvents: 'none'
107
+ },
108
+ // No `onShow`.
109
+ undefined,
110
+ // Below the cursor, matching the control arm's `position="mouse"`.
111
+ 'bottom');
112
+ };
60
113
  export const createPragmaticResizer = ({
61
114
  target,
62
115
  onDragStart,
@@ -90,6 +143,21 @@ export const createPragmaticResizer = ({
90
143
  thumb.classList.add('pm-breakout-resize-handle-thumb');
91
144
  thumb.style.pointerEvents = 'none';
92
145
  rail.appendChild(thumb);
146
+
147
+ // Marks the point the pointer entered the rail, and anchors the tooltip there: zero height
148
+ // so it is a point not a box, full rail width so the tooltip centres like the control arm.
149
+ //
150
+ // Must stay a child of `rail` — `openTooltipAt` sets `top` from the rail's `offsetY`, which
151
+ // only lines up while the rail is the containing block. jsdom cannot catch a wrong parent.
152
+ //
153
+ // Not built on the control arm, which must keep byte-identical DOM.
154
+ let tooltipAnchor;
155
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
156
+ // Positioned by `.pm-breakout-resize-tooltip-anchor`; only the pointer's Y is passed in.
157
+ tooltipAnchor = document.createElement('div');
158
+ tooltipAnchor.classList.add('pm-breakout-resize-tooltip-anchor');
159
+ rail.appendChild(tooltipAnchor);
160
+ }
93
161
  const tooltipContainer = document.createElement('div');
94
162
  tooltipContainer.classList.add('pm-breakout-resize-handle-rail-wrapper');
95
163
  handle.appendChild(tooltipContainer);
@@ -100,17 +168,29 @@ export const createPragmaticResizer = ({
100
168
  target: target,
101
169
  intl: intl
102
170
  }), tooltipContainer, key);
171
+ let vanillaTooltip;
103
172
 
104
- // Only meaningful on the deferred path, where the tooltip mounts on first hover and so may
105
- // never mount at all. The eager path below mounts unconditionally and does not consult it.
173
+ // Tracks whether there is anything to tear down. On the deferred path the tooltip mounts
174
+ // on first hover and so may never mount at all.
106
175
  let isTooltipMounted = false;
107
176
  const mountTooltip = () => {
108
177
  if (isTooltipMounted) {
109
178
  return;
110
179
  }
111
180
  isTooltipMounted = true;
181
+
182
+ // `tooltipAnchor` is always set when the experiment is on; the check also narrows it.
183
+ if (isExperimentEnabled('platform_editor_use_vanilla_components') && tooltipAnchor) {
184
+ vanillaTooltip = createHandleTooltip({
185
+ anchor: tooltipAnchor,
186
+ target,
187
+ intl
188
+ });
189
+ return;
190
+ }
191
+
112
192
  // `RailWithTooltip`'s ref moves `rail` into the tooltip wrapper, so the DOM ends up
113
- // identical to the eager path.
193
+ // identical to the pre-mount state below.
114
194
  renderTooltip();
115
195
  };
116
196
 
@@ -123,22 +203,45 @@ export const createPragmaticResizer = ({
123
203
  // `mouseenter` of the node (see `registerEvents` below). The rail has `opacity: 0` until
124
204
  // the pointer is inside the node, so the pre-mount DOM is never visible, and the
125
205
  // `mouseenter` fires on the whole node — long before the pointer reaches the 7px handle.
126
- if (isDeferredTooltipEnabled) {
127
- // Without the tooltip's `[role="presentation"]` / `.pm-breakout-resize-handle-rail-inside-tooltip`
128
- // wrappers (both stretched to `width: 100%` by `pragmaticResizerStylesForTooltip`), the rail
129
- // would size to its content as a flex item. Stretch it explicitly so the pre-mount and
130
- // post-mount geometry match exactly — `100%` of the 7px handle container either way.
206
+ //
207
+ // Parking the rail also needs `width: 100%`: without the ADS tooltip's wrappers (stretched
208
+ // by `pragmaticResizerStylesForTooltip`) it would size to its content as a flex item.
209
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
210
+ // Always parks, and always defers whatever `platform_editor_reduce_event_listener_count`
211
+ // says: no portal means no listener cost, and first hover is when the node name reads
212
+ // correctly.
131
213
  rail.style.width = '100%';
132
214
  tooltipContainer.appendChild(rail);
133
215
  } else {
134
- renderTooltip();
216
+ // Remove this while at `platform_editor_use_vanilla_components` cleanup.
217
+ if (isDeferredTooltipEnabled) {
218
+ rail.style.width = '100%';
219
+ tooltipContainer.appendChild(rail);
220
+ } else {
221
+ // Not `mountTooltip`: that would set `isTooltipMounted` where the control arm never did.
222
+ renderTooltip();
223
+ }
135
224
  }
136
225
  return {
137
226
  handle,
138
227
  rail,
228
+ // Exposed so the rail's hover listeners can position and open the tooltip.
229
+ tooltipAnchor,
139
230
  handleHitBox,
140
231
  mountTooltip,
141
232
  destroyTooltip: () => {
233
+ // Re-read is safe: the gate is fixed for the page, so it matches what `mountTooltip`
234
+ // saw. At cleanup, keep this body and delete the control arm below.
235
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
236
+ // May never have mounted, and there is no portal to remove either way.
237
+ if (vanillaTooltip) {
238
+ vanillaTooltip.destroy();
239
+ vanillaTooltip = undefined;
240
+ }
241
+ isTooltipMounted = false;
242
+ return;
243
+ }
244
+
142
245
  // The eager path always mounted, so it always removes — unchanged from before. Only
143
246
  // the deferred path can reach `destroyTooltip` without having mounted anything.
144
247
  if (isDeferredTooltipEnabled && !isTooltipMounted) {
@@ -169,6 +272,14 @@ export const createPragmaticResizer = ({
169
272
  onDragStart(args) {
170
273
  state = 'resizing';
171
274
  handleElement.classList.add('pm-breakout-resize-handle-container--active');
275
+
276
+ // The ADS tooltip hides itself on press; the vanilla one has no pointer-press
277
+ // listener, and its anchor moves with the rail, so it would follow the drag.
278
+ // Tearing it down hides it immediately; the next hover rebuilds it.
279
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
280
+ rightHandle.destroyTooltip();
281
+ leftHandle === null || leftHandle === void 0 ? void 0 : leftHandle.destroyTooltip();
282
+ }
172
283
  onDragStart(args);
173
284
  },
174
285
  onDrag,
@@ -180,20 +291,57 @@ export const createPragmaticResizer = ({
180
291
  }
181
292
  });
182
293
  };
183
- const registerEvents = element => {
294
+
295
+ /**
296
+ * Parks the anchor where the pointer entered the rail, then opens the tooltip. The anchor is
297
+ * `pointer-events: none`, so the rail's hover listeners drive it. `offsetY` is already in the
298
+ * anchor's coordinate space, so no layout read is needed.
299
+ *
300
+ * Positions on enter only, matching the ADS tooltip as observed — though `Tooltip` does bind an
301
+ * `onMouseMove`, so start here if the two arms disagree on placement.
302
+ */
303
+ const openTooltipAt = (anchor, event) => {
304
+ // Only the rail bindings pass an anchor; `target` and the hit boxes do not.
305
+ if (!anchor) {
306
+ return;
307
+ }
308
+ anchor.style.setProperty(tooltipAnchorTopVar, `${event.offsetY}px`);
309
+ anchor.dispatchEvent(new MouseEvent('mouseenter'));
310
+ };
311
+ const closeTooltip = anchor => {
312
+ if (!anchor) {
313
+ return;
314
+ }
315
+ anchor.dispatchEvent(new MouseEvent('mouseleave'));
316
+ };
317
+ const registerEvents = (element, hoverAnchor) => {
184
318
  return [bind(element, {
185
319
  type: 'mouseenter',
186
- listener: () => {
320
+ listener: event => {
187
321
  if (isDeferredTooltipEnabled) {
188
322
  rightHandle.mountTooltip();
189
323
  leftHandle === null || leftHandle === void 0 ? void 0 : leftHandle.mountTooltip();
190
324
  }
325
+ // Always mounts on first hover, so it also mounts when the deferred experiment is
326
+ // off. `mountTooltip` is a no-op once mounted, so the overlap is safe. At
327
+ // cleanup, drop the gate and delete the block above — this covers both.
328
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
329
+ rightHandle.mountTooltip();
330
+ leftHandle === null || leftHandle === void 0 ? void 0 : leftHandle.mountTooltip();
331
+ // After the mount above, so the tooltip always exists to be opened.
332
+ openTooltipAt(hoverAnchor, event);
333
+ }
191
334
  rightHandle.rail.style.setProperty('opacity', '1');
192
335
  leftHandle === null || leftHandle === void 0 ? void 0 : leftHandle.rail.style.setProperty('opacity', '1');
193
336
  }
194
337
  }), bind(element, {
195
338
  type: 'mouseleave',
196
339
  listener: () => {
340
+ // Ahead of the resize guard below: the ADS tooltip hides whenever the pointer
341
+ // leaves the rail, including part-way through a resize.
342
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
343
+ closeTooltip(hoverAnchor);
344
+ }
197
345
  if (state === 'resizing') {
198
346
  return;
199
347
  }
@@ -204,9 +352,9 @@ export const createPragmaticResizer = ({
204
352
  };
205
353
  const unbindFns = leftHandle ?
206
354
  // old code - left + right
207
- [...registerEvents(target), ...registerEvents(rightHandle.handleHitBox), ...registerEvents(leftHandle.handleHitBox), ...registerEvents(rightHandle.rail), ...registerEvents(leftHandle.rail)] :
355
+ [...registerEvents(target), ...registerEvents(rightHandle.handleHitBox), ...registerEvents(leftHandle.handleHitBox), ...registerEvents(rightHandle.rail, rightHandle.tooltipAnchor), ...registerEvents(leftHandle.rail, leftHandle.tooltipAnchor)] :
208
356
  // new code - right only
209
- [...registerEvents(target), ...registerEvents(rightHandle.handleHitBox), ...registerEvents(rightHandle.rail)];
357
+ [...registerEvents(target), ...registerEvents(rightHandle.handleHitBox), ...registerEvents(rightHandle.rail, rightHandle.tooltipAnchor)];
210
358
  const handleElement = 'rail';
211
359
  const destroyFns = leftHandle ?
212
360
  // old code - left + right
@@ -4,7 +4,6 @@ import { getChangedNodes, isReplaceDocOperation } from '@atlaskit/editor-common/
4
4
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
5
  import { akEditorDefaultLayoutWidth, akEditorFullWidthLayoutWidth, akEditorMaxWidthLayoutWidth, akEditorCalculatedWideLayoutWidth } from '@atlaskit/editor-shared-styles';
6
6
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
7
- import { fg } from '@atlaskit/platform-feature-flags/fg';
8
7
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
8
  import { handleKeyDown } from './handle-key-down';
10
9
  import { ResizingMarkView } from './resizing-mark-view';
@@ -266,7 +265,7 @@ export const createResizingPlugin = (api, getIntl, nodeViewPortalProviderAPI, op
266
265
  // This transaction normalizes a newly inserted node to the editor's default breakout
267
266
  // width. It is not a user resize and must not become an undo step — in particular, a
268
267
  // source synced block can be deleted permanently before Undo reaches this normalization.
269
- return fg('platform_editor_breakout_auto_normalization_history_patch') ? newTr.setMeta('addToHistory', false) : newTr;
268
+ return newTr.setMeta('addToHistory', false);
270
269
  }
271
270
  }
272
271
  });
@@ -3,6 +3,7 @@ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import React, { useLayoutEffect, useState } from 'react';
4
4
  import { bind } from 'bind-event-listener';
5
5
  import { breakoutMessages as messages } from '@atlaskit/editor-common/messages';
6
+ import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
6
7
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
7
8
  import { draggable } from '@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter';
8
9
  import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/utils/disable-native-drag-preview';
@@ -23,6 +24,12 @@ var getNodeName = function getNodeName(nodeName) {
23
24
  return 'node';
24
25
  }
25
26
  };
27
+
28
+ /**
29
+ * Read by `.pm-breakout-resize-tooltip-anchor`, declared in `editor-core`'s `resizerStyles.ts` and
30
+ * its Compiled mirror. Rename in all three or the tooltip pins to the top of the rail.
31
+ */
32
+ var tooltipAnchorTopVar = '--pm-breakout-resize-tooltip-anchor-top';
26
33
  export var resizeHandleMessage = {
27
34
  expand: messages.resizeExpand,
28
35
  codeBlock: messages.resizeCodeBlock,
@@ -35,6 +42,10 @@ export var resizeHandleMessage = {
35
42
  bodiedExtension: messages.resizeExtension,
36
43
  multiBodiedExtension: messages.resizeExtension
37
44
  };
45
+ /**
46
+ * Remove during `platform_editor_use_vanilla_components` cleanup — replaced by
47
+ * `createHandleTooltip`, which does the same job without a React tree per resize handle.
48
+ */
38
49
  var RailWithTooltip = function RailWithTooltip(_ref) {
39
50
  var rail = _ref.rail,
40
51
  target = _ref.target,
@@ -61,13 +72,54 @@ var RailWithTooltip = function RailWithTooltip(_ref) {
61
72
  }
62
73
  }));
63
74
  };
64
- export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
65
- var target = _ref2.target,
66
- _onDragStart = _ref2.onDragStart,
67
- onDrag = _ref2.onDrag,
68
- _onDrop = _ref2.onDrop,
75
+
76
+ /** Identifies the vanilla tooltip for tests and debugging. */
77
+ var TOOLTIP_CLASS_NAME = 'pm-breakout-resize-tooltip';
78
+
79
+ /**
80
+ * `ak-editor-vanilla-tooltip-default` opts into `vanillaTooltipDefaultStyles` in the
81
+ * `EditorContentContainer` stylesheets, which still reaches a `[popover]` because it is painted in
82
+ * the top layer but stays in the DOM under `.ProseMirror`.
83
+ */
84
+ var TOOLTIP_CLASS_NAMES = "ak-editor-vanilla-tooltip-default ".concat(TOOLTIP_CLASS_NAME);
85
+
86
+ /**
87
+ * Vanilla DOM equivalent of `RailWithTooltip`, behind `platform_editor_use_vanilla_components`.
88
+ *
89
+ * Anchored to a point rather than to the rail, which spans the node's full height and would strand
90
+ * the tooltip at the top of a tall node. This reproduces the control arm's `position="mouse"`,
91
+ * which latches the cursor as the tooltip opens and does not move it afterwards — so no cursor
92
+ * tracking is needed, just one style write per hover.
93
+ */
94
+ var createHandleTooltip = function createHandleTooltip(_ref2) {
95
+ var anchor = _ref2.anchor,
69
96
  intl = _ref2.intl,
70
- nodeViewPortalProviderAPI = _ref2.nodeViewPortalProviderAPI;
97
+ target = _ref2.target;
98
+ // Only correct once ProseMirror has populated `contentDOM`, so callers must build on first
99
+ // hover — earlier reads fall back to the generic 'node' label.
100
+ var node = target.querySelector('[data-prosemirror-node-name]');
101
+ var nodeName = getNodeName(node === null || node === void 0 ? void 0 : node.dataset.prosemirrorNodeName);
102
+ return new VanillaTooltip(anchor, intl.formatMessage(resizeHandleMessage[nodeName]),
103
+ // Let the tooltip generate its own id.
104
+ undefined, TOOLTIP_CLASS_NAMES,
105
+ // Default delay, matching the ADS tooltip.
106
+ 300, {
107
+ // The only thing the shared look does not cover. Already inherited from the anchor,
108
+ // but set explicitly so a change there can never let this swallow a resize drag.
109
+ pointerEvents: 'none'
110
+ },
111
+ // No `onShow`.
112
+ undefined,
113
+ // Below the cursor, matching the control arm's `position="mouse"`.
114
+ 'bottom');
115
+ };
116
+ export var createPragmaticResizer = function createPragmaticResizer(_ref3) {
117
+ var target = _ref3.target,
118
+ _onDragStart = _ref3.onDragStart,
119
+ onDrag = _ref3.onDrag,
120
+ _onDrop = _ref3.onDrop,
121
+ intl = _ref3.intl,
122
+ nodeViewPortalProviderAPI = _ref3.nodeViewPortalProviderAPI;
71
123
  var state = 'default';
72
124
  // Read once per resizer: `isExperimentEnabled` fires an exposure event, and `registerEvents` is
73
125
  // bound to several elements so checking it inside the handler would emit one per element hovered.
@@ -93,6 +145,21 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
93
145
  thumb.classList.add('pm-breakout-resize-handle-thumb');
94
146
  thumb.style.pointerEvents = 'none';
95
147
  rail.appendChild(thumb);
148
+
149
+ // Marks the point the pointer entered the rail, and anchors the tooltip there: zero height
150
+ // so it is a point not a box, full rail width so the tooltip centres like the control arm.
151
+ //
152
+ // Must stay a child of `rail` — `openTooltipAt` sets `top` from the rail's `offsetY`, which
153
+ // only lines up while the rail is the containing block. jsdom cannot catch a wrong parent.
154
+ //
155
+ // Not built on the control arm, which must keep byte-identical DOM.
156
+ var tooltipAnchor;
157
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
158
+ // Positioned by `.pm-breakout-resize-tooltip-anchor`; only the pointer's Y is passed in.
159
+ tooltipAnchor = document.createElement('div');
160
+ tooltipAnchor.classList.add('pm-breakout-resize-tooltip-anchor');
161
+ rail.appendChild(tooltipAnchor);
162
+ }
96
163
  var tooltipContainer = document.createElement('div');
97
164
  tooltipContainer.classList.add('pm-breakout-resize-handle-rail-wrapper');
98
165
  handle.appendChild(tooltipContainer);
@@ -107,17 +174,29 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
107
174
  });
108
175
  }, tooltipContainer, key);
109
176
  };
177
+ var vanillaTooltip;
110
178
 
111
- // Only meaningful on the deferred path, where the tooltip mounts on first hover and so may
112
- // never mount at all. The eager path below mounts unconditionally and does not consult it.
179
+ // Tracks whether there is anything to tear down. On the deferred path the tooltip mounts
180
+ // on first hover and so may never mount at all.
113
181
  var isTooltipMounted = false;
114
182
  var mountTooltip = function mountTooltip() {
115
183
  if (isTooltipMounted) {
116
184
  return;
117
185
  }
118
186
  isTooltipMounted = true;
187
+
188
+ // `tooltipAnchor` is always set when the experiment is on; the check also narrows it.
189
+ if (isExperimentEnabled('platform_editor_use_vanilla_components') && tooltipAnchor) {
190
+ vanillaTooltip = createHandleTooltip({
191
+ anchor: tooltipAnchor,
192
+ target: target,
193
+ intl: intl
194
+ });
195
+ return;
196
+ }
197
+
119
198
  // `RailWithTooltip`'s ref moves `rail` into the tooltip wrapper, so the DOM ends up
120
- // identical to the eager path.
199
+ // identical to the pre-mount state below.
121
200
  renderTooltip();
122
201
  };
123
202
 
@@ -130,22 +209,45 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
130
209
  // `mouseenter` of the node (see `registerEvents` below). The rail has `opacity: 0` until
131
210
  // the pointer is inside the node, so the pre-mount DOM is never visible, and the
132
211
  // `mouseenter` fires on the whole node — long before the pointer reaches the 7px handle.
133
- if (isDeferredTooltipEnabled) {
134
- // Without the tooltip's `[role="presentation"]` / `.pm-breakout-resize-handle-rail-inside-tooltip`
135
- // wrappers (both stretched to `width: 100%` by `pragmaticResizerStylesForTooltip`), the rail
136
- // would size to its content as a flex item. Stretch it explicitly so the pre-mount and
137
- // post-mount geometry match exactly — `100%` of the 7px handle container either way.
212
+ //
213
+ // Parking the rail also needs `width: 100%`: without the ADS tooltip's wrappers (stretched
214
+ // by `pragmaticResizerStylesForTooltip`) it would size to its content as a flex item.
215
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
216
+ // Always parks, and always defers whatever `platform_editor_reduce_event_listener_count`
217
+ // says: no portal means no listener cost, and first hover is when the node name reads
218
+ // correctly.
138
219
  rail.style.width = '100%';
139
220
  tooltipContainer.appendChild(rail);
140
221
  } else {
141
- renderTooltip();
222
+ // Remove this while at `platform_editor_use_vanilla_components` cleanup.
223
+ if (isDeferredTooltipEnabled) {
224
+ rail.style.width = '100%';
225
+ tooltipContainer.appendChild(rail);
226
+ } else {
227
+ // Not `mountTooltip`: that would set `isTooltipMounted` where the control arm never did.
228
+ renderTooltip();
229
+ }
142
230
  }
143
231
  return {
144
232
  handle: handle,
145
233
  rail: rail,
234
+ // Exposed so the rail's hover listeners can position and open the tooltip.
235
+ tooltipAnchor: tooltipAnchor,
146
236
  handleHitBox: handleHitBox,
147
237
  mountTooltip: mountTooltip,
148
238
  destroyTooltip: function destroyTooltip() {
239
+ // Re-read is safe: the gate is fixed for the page, so it matches what `mountTooltip`
240
+ // saw. At cleanup, keep this body and delete the control arm below.
241
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
242
+ // May never have mounted, and there is no portal to remove either way.
243
+ if (vanillaTooltip) {
244
+ vanillaTooltip.destroy();
245
+ vanillaTooltip = undefined;
246
+ }
247
+ isTooltipMounted = false;
248
+ return;
249
+ }
250
+
149
251
  // The eager path always mounted, so it always removes — unchanged from before. Only
150
252
  // the deferred path can reach `destroyTooltip` without having mounted anything.
151
253
  if (isDeferredTooltipEnabled && !isTooltipMounted) {
@@ -162,8 +264,8 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
162
264
  var registerHandle = function registerHandle(handleElement, handleSide) {
163
265
  return draggable({
164
266
  element: handleElement,
165
- onGenerateDragPreview: function onGenerateDragPreview(_ref3) {
166
- var nativeSetDragImage = _ref3.nativeSetDragImage;
267
+ onGenerateDragPreview: function onGenerateDragPreview(_ref4) {
268
+ var nativeSetDragImage = _ref4.nativeSetDragImage;
167
269
  disableNativeDragPreview({
168
270
  nativeSetDragImage: nativeSetDragImage
169
271
  });
@@ -177,6 +279,14 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
177
279
  onDragStart: function onDragStart(args) {
178
280
  state = 'resizing';
179
281
  handleElement.classList.add('pm-breakout-resize-handle-container--active');
282
+
283
+ // The ADS tooltip hides itself on press; the vanilla one has no pointer-press
284
+ // listener, and its anchor moves with the rail, so it would follow the drag.
285
+ // Tearing it down hides it immediately; the next hover rebuilds it.
286
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
287
+ rightHandle.destroyTooltip();
288
+ leftHandle === null || leftHandle === void 0 || leftHandle.destroyTooltip();
289
+ }
180
290
  _onDragStart(args);
181
291
  },
182
292
  onDrag: onDrag,
@@ -188,20 +298,57 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
188
298
  }
189
299
  });
190
300
  };
191
- var registerEvents = function registerEvents(element) {
301
+
302
+ /**
303
+ * Parks the anchor where the pointer entered the rail, then opens the tooltip. The anchor is
304
+ * `pointer-events: none`, so the rail's hover listeners drive it. `offsetY` is already in the
305
+ * anchor's coordinate space, so no layout read is needed.
306
+ *
307
+ * Positions on enter only, matching the ADS tooltip as observed — though `Tooltip` does bind an
308
+ * `onMouseMove`, so start here if the two arms disagree on placement.
309
+ */
310
+ var openTooltipAt = function openTooltipAt(anchor, event) {
311
+ // Only the rail bindings pass an anchor; `target` and the hit boxes do not.
312
+ if (!anchor) {
313
+ return;
314
+ }
315
+ anchor.style.setProperty(tooltipAnchorTopVar, "".concat(event.offsetY, "px"));
316
+ anchor.dispatchEvent(new MouseEvent('mouseenter'));
317
+ };
318
+ var closeTooltip = function closeTooltip(anchor) {
319
+ if (!anchor) {
320
+ return;
321
+ }
322
+ anchor.dispatchEvent(new MouseEvent('mouseleave'));
323
+ };
324
+ var registerEvents = function registerEvents(element, hoverAnchor) {
192
325
  return [bind(element, {
193
326
  type: 'mouseenter',
194
- listener: function listener() {
327
+ listener: function listener(event) {
195
328
  if (isDeferredTooltipEnabled) {
196
329
  rightHandle.mountTooltip();
197
330
  leftHandle === null || leftHandle === void 0 || leftHandle.mountTooltip();
198
331
  }
332
+ // Always mounts on first hover, so it also mounts when the deferred experiment is
333
+ // off. `mountTooltip` is a no-op once mounted, so the overlap is safe. At
334
+ // cleanup, drop the gate and delete the block above — this covers both.
335
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
336
+ rightHandle.mountTooltip();
337
+ leftHandle === null || leftHandle === void 0 || leftHandle.mountTooltip();
338
+ // After the mount above, so the tooltip always exists to be opened.
339
+ openTooltipAt(hoverAnchor, event);
340
+ }
199
341
  rightHandle.rail.style.setProperty('opacity', '1');
200
342
  leftHandle === null || leftHandle === void 0 || leftHandle.rail.style.setProperty('opacity', '1');
201
343
  }
202
344
  }), bind(element, {
203
345
  type: 'mouseleave',
204
346
  listener: function listener() {
347
+ // Ahead of the resize guard below: the ADS tooltip hides whenever the pointer
348
+ // leaves the rail, including part-way through a resize.
349
+ if (isExperimentEnabled('platform_editor_use_vanilla_components')) {
350
+ closeTooltip(hoverAnchor);
351
+ }
205
352
  if (state === 'resizing') {
206
353
  return;
207
354
  }
@@ -211,8 +358,8 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
211
358
  })];
212
359
  };
213
360
  var unbindFns = leftHandle ? // old code - left + right
214
- [].concat(_toConsumableArray(registerEvents(target)), _toConsumableArray(registerEvents(rightHandle.handleHitBox)), _toConsumableArray(registerEvents(leftHandle.handleHitBox)), _toConsumableArray(registerEvents(rightHandle.rail)), _toConsumableArray(registerEvents(leftHandle.rail))) : // new code - right only
215
- [].concat(_toConsumableArray(registerEvents(target)), _toConsumableArray(registerEvents(rightHandle.handleHitBox)), _toConsumableArray(registerEvents(rightHandle.rail)));
361
+ [].concat(_toConsumableArray(registerEvents(target)), _toConsumableArray(registerEvents(rightHandle.handleHitBox)), _toConsumableArray(registerEvents(leftHandle.handleHitBox)), _toConsumableArray(registerEvents(rightHandle.rail, rightHandle.tooltipAnchor)), _toConsumableArray(registerEvents(leftHandle.rail, leftHandle.tooltipAnchor))) : // new code - right only
362
+ [].concat(_toConsumableArray(registerEvents(target)), _toConsumableArray(registerEvents(rightHandle.handleHitBox)), _toConsumableArray(registerEvents(rightHandle.rail, rightHandle.tooltipAnchor)));
216
363
  var handleElement = 'rail';
217
364
  var destroyFns = leftHandle ?
218
365
  // old code - left + right
@@ -7,7 +7,6 @@ import { getChangedNodes, isReplaceDocOperation } from '@atlaskit/editor-common/
7
7
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
8
8
  import { akEditorDefaultLayoutWidth, akEditorFullWidthLayoutWidth, akEditorMaxWidthLayoutWidth, akEditorCalculatedWideLayoutWidth } from '@atlaskit/editor-shared-styles';
9
9
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
10
- import { fg } from '@atlaskit/platform-feature-flags/fg';
11
10
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
12
11
  import { handleKeyDown } from './handle-key-down';
13
12
  import { ResizingMarkView } from './resizing-mark-view';
@@ -258,7 +257,7 @@ export var createResizingPlugin = function createResizingPlugin(api, getIntl, no
258
257
  // This transaction normalizes a newly inserted node to the editor's default breakout
259
258
  // width. It is not a user resize and must not become an undo step — in particular, a
260
259
  // source synced block can be deleted permanently before Undo reaches this normalization.
261
- return fg('platform_editor_breakout_auto_normalization_history_patch') ? newTr.setMeta('addToHistory', false) : newTr;
260
+ return newTr.setMeta('addToHistory', false);
262
261
  }
263
262
  }
264
263
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-breakout",
3
- "version": "16.0.13",
3
+ "version": "16.1.0",
4
4
  "description": "Breakout plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "atlaskit:src": "src/index.ts",
23
23
  "dependencies": {
24
- "@atlaskit/adf-schema": "^57.2.0",
24
+ "@atlaskit/adf-schema": "^57.3.0",
25
25
  "@atlaskit/editor-plugin-block-controls": "^17.3.0",
26
26
  "@atlaskit/editor-plugin-editor-disabled": "^16.1.0",
27
27
  "@atlaskit/editor-plugin-editor-viewmode": "^18.0.0",
@@ -36,7 +36,7 @@
36
36
  "@atlaskit/platform-feature-flags": "^2.2.0",
37
37
  "@atlaskit/pragmatic-drag-and-drop": "^3.1.0",
38
38
  "@atlaskit/theme": "^28.2.0",
39
- "@atlaskit/tmp-editor-statsig": "^173.0.0",
39
+ "@atlaskit/tmp-editor-statsig": "^174.0.0",
40
40
  "@atlaskit/tokens": "^16.11.0",
41
41
  "@atlaskit/tooltip": "^24.3.0",
42
42
  "@babel/runtime": "^7.0.0",
@@ -46,7 +46,7 @@
46
46
  "uuid": "^3.1.0"
47
47
  },
48
48
  "peerDependencies": {
49
- "@atlaskit/editor-common": "^120.11.0",
49
+ "@atlaskit/editor-common": "^120.13.0",
50
50
  "react": "^18.2.0 || ^19.2.0",
51
51
  "react-dom": "^18.2.0 || ^19.2.0",
52
52
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
@@ -64,9 +64,6 @@
64
64
  },
65
65
  "platform_editor_lovability_resize_gracefully": {
66
66
  "type": "boolean"
67
- },
68
- "platform_editor_breakout_auto_normalization_history_patch": {
69
- "type": "boolean"
70
67
  }
71
68
  }
72
69
  }