@atlaskit/editor-plugin-breakout 15.0.16 → 15.0.18

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,36 @@
1
1
  # @atlaskit/editor-plugin-breakout
2
2
 
3
+ ## 15.0.18
4
+
5
+ ### Patch Changes
6
+
7
+ - [`656801b9e097c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/656801b9e097c) -
8
+ VOLTC-331 - Migrate updated package usage in platform/editor: rewrite barrel imports of
9
+ voltCompliant provider packages to deep/subpath imports (consumer-side debarrel). No public API
10
+ changes.
11
+ - Updated dependencies
12
+
13
+ ## 15.0.17
14
+
15
+ ### Patch Changes
16
+
17
+ - [`288e340380dbb`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/288e340380dbb) -
18
+ Behind experiment platform_editor_reduce_event_listener_count: cut the number of native DOM event
19
+ listeners the editor registers per node. React 18 registers a bubble and a capture listener for
20
+ every supported event type on each portal container (~130 listeners) and never removes them, so
21
+ (1) node views whose render() returns null no longer mount a portal at all, and (2) the breakout
22
+ resize handle tooltip portal is deferred until the pointer enters the node instead of being
23
+ mounted for every resizable node up front.
24
+
25
+ Also reduces the work the layout column divider decorations do on every `decorations(state)` read:
26
+ the mousedown listener is unbound when the widget decoration is destroyed, the widget DOM is built
27
+ lazily rather than once per call, the document walk no longer descends into node types that cannot
28
+ contain a layout, and the decoration key is derived from the layout's ordinal rather than its
29
+ position so an edit above a layout no longer invalidates and rebuilds every divider below it. The
30
+ widget now resolves its own layout section and column index from its live position at mousedown.
31
+
32
+ - Updated dependencies
33
+
3
34
  ## 15.0.16
4
35
 
5
36
  ### Patch Changes
@@ -10,18 +10,15 @@ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers
10
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
11
  var _react = _interopRequireWildcard(require("react"));
12
12
  var _bindEventListener = require("bind-event-listener");
13
- var _v = _interopRequireDefault(require("uuid/v4"));
14
13
  var _messages = require("@atlaskit/editor-common/messages");
15
14
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
16
- var _adapter = require("@atlaskit/pragmatic-drag-and-drop/element/adapter");
17
- var _disableNativeDragPreview = require("@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview");
18
- var _preventUnhandled = require("@atlaskit/pragmatic-drag-and-drop/prevent-unhandled");
15
+ var _elementAdapter = require("@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter");
16
+ var _disableNativeDragPreview = require("@atlaskit/pragmatic-drag-and-drop/utils/disable-native-drag-preview");
17
+ var _preventUnhandled = require("@atlaskit/pragmatic-drag-and-drop/utils/prevent-unhandled");
19
18
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
20
19
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
21
- var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
20
+ var _Tooltip = _interopRequireDefault(require("@atlaskit/tooltip/Tooltip"));
22
21
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
23
- // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
24
-
25
22
  var getNodeName = function getNodeName(nodeName) {
26
23
  if (nodeName === 'layoutSection') {
27
24
  return 'layout';
@@ -60,7 +57,7 @@ var RailWithTooltip = function RailWithTooltip(_ref) {
60
57
  var name = getNodeName(node === null || node === void 0 ? void 0 : node.dataset.prosemirrorNodeName);
61
58
  setNodeName(name);
62
59
  }, [target]);
63
- return /*#__PURE__*/_react.default.createElement(_tooltip.default, {
60
+ return /*#__PURE__*/_react.default.createElement(_Tooltip.default, {
64
61
  content: intl.formatMessage(resizeHandleMessage[nodeName]),
65
62
  position: "mouse"
66
63
  }, /*#__PURE__*/_react.default.createElement("div", {
@@ -81,6 +78,9 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
81
78
  intl = _ref2.intl,
82
79
  nodeViewPortalProviderAPI = _ref2.nodeViewPortalProviderAPI;
83
80
  var state = 'default';
81
+ // Read once per resizer: `isExperimentEnabled` fires an exposure event, and `registerEvents` is
82
+ // bound to several elements so checking it inside the handler would emit one per element hovered.
83
+ var isDeferredTooltipEnabled = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_reduce_event_listener_count');
84
84
  var isLeftResizeHandleDisabled = (0, _expValEquals.expValEquals)('platform_editor_lovability_resize_dividers_panels', 'isEnabled', true) || (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_lovability_resize_extensions');
85
85
  var createHandle = function createHandle(side) {
86
86
  var handle = document.createElement('div');
@@ -106,22 +106,62 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
106
106
  tooltipContainer.classList.add('pm-breakout-resize-handle-rail-wrapper');
107
107
  handle.appendChild(tooltipContainer);
108
108
  handle.appendChild(handleHitBox);
109
+ var key = crypto.randomUUID();
110
+ var renderTooltip = function renderTooltip() {
111
+ return nodeViewPortalProviderAPI.render(function () {
112
+ return /*#__PURE__*/_react.default.createElement(RailWithTooltip, {
113
+ rail: rail,
114
+ target: target,
115
+ intl: intl
116
+ });
117
+ }, tooltipContainer, key);
118
+ };
109
119
 
110
- // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
111
- var key = (0, _v.default)();
112
- nodeViewPortalProviderAPI.render(function () {
113
- return /*#__PURE__*/_react.default.createElement(RailWithTooltip, {
114
- rail: rail,
115
- target: target,
116
- intl: intl
117
- });
118
- }, tooltipContainer, key);
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.
122
+ var isTooltipMounted = false;
123
+ var mountTooltip = function mountTooltip() {
124
+ if (isTooltipMounted) {
125
+ return;
126
+ }
127
+ isTooltipMounted = true;
128
+ // `RailWithTooltip`'s ref moves `rail` into the tooltip wrapper, so the DOM ends up
129
+ // identical to the eager path.
130
+ renderTooltip();
131
+ };
132
+
133
+ // React 18 registers ~130 native event listeners on every portal container (see
134
+ // `listenToAllSupportedEvents`), and never removes them. Mounting this tooltip up front
135
+ // costs that for every resizable node in the document — two containers per node when the
136
+ // left handle is enabled — even though only the hovered handle can ever show a tooltip.
137
+ //
138
+ // Instead, park `rail` directly in the wrapper and mount the tooltip on the first
139
+ // `mouseenter` of the node (see `registerEvents` below). The rail has `opacity: 0` until
140
+ // the pointer is inside the node, so the pre-mount DOM is never visible, and the
141
+ // `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.
147
+ rail.style.width = '100%';
148
+ tooltipContainer.appendChild(rail);
149
+ } else {
150
+ renderTooltip();
151
+ }
119
152
  return {
120
153
  handle: handle,
121
154
  rail: rail,
122
155
  handleHitBox: handleHitBox,
156
+ mountTooltip: mountTooltip,
123
157
  destroyTooltip: function destroyTooltip() {
124
- return nodeViewPortalProviderAPI.remove(key);
158
+ // The eager path always mounted, so it always removes — unchanged from before. Only
159
+ // the deferred path can reach `destroyTooltip` without having mounted anything.
160
+ if (isDeferredTooltipEnabled && !isTooltipMounted) {
161
+ return;
162
+ }
163
+ nodeViewPortalProviderAPI.remove(key);
164
+ isTooltipMounted = false;
125
165
  }
126
166
  };
127
167
  };
@@ -129,7 +169,7 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
129
169
  // Remove const leftHandle during 'platform_editor_lovability_resize_dividers_panels' cleanup
130
170
  var leftHandle = isLeftResizeHandleDisabled ? undefined : createHandle('left');
131
171
  var registerHandle = function registerHandle(handleElement, handleSide) {
132
- return (0, _adapter.draggable)({
172
+ return (0, _elementAdapter.draggable)({
133
173
  element: handleElement,
134
174
  onGenerateDragPreview: function onGenerateDragPreview(_ref3) {
135
175
  var nativeSetDragImage = _ref3.nativeSetDragImage;
@@ -161,6 +201,10 @@ var createPragmaticResizer = exports.createPragmaticResizer = function createPra
161
201
  return [(0, _bindEventListener.bind)(element, {
162
202
  type: 'mouseenter',
163
203
  listener: function listener() {
204
+ if (isDeferredTooltipEnabled) {
205
+ rightHandle.mountTooltip();
206
+ leftHandle === null || leftHandle === void 0 || leftHandle.mountTooltip();
207
+ }
164
208
  rightHandle.rail.style.setProperty('opacity', '1');
165
209
  leftHandle === null || leftHandle === void 0 || leftHandle.rail.style.setProperty('opacity', '1');
166
210
  }
@@ -10,7 +10,7 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
  var _styles = require("@atlaskit/editor-common/styles");
12
12
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
13
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
14
14
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
15
15
  var _pragmaticResizer = require("./pragmatic-resizer");
16
16
  var _resizerCallbacks = require("./resizer-callbacks");
@@ -98,13 +98,13 @@ var ResizingMarkView = exports.ResizingMarkView = /*#__PURE__*/function () {
98
98
  this.nodeViewPortalProviderAPI = nodeViewPortalProviderAPI;
99
99
  var isLiveViewMode = (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) === 'view';
100
100
  if (!isLiveViewMode) {
101
- (0, _platformFeatureFlags.fg)('platform_editor_lovability_resize_gracefully') || (0, _platformFeatureFlags.fg)('platform_editor_lovability_resize_exts_gracefully') ? this.setupResizerCallbacksIfSupported(dom, contentDOM, view, mark, isResizingDividersPanelsEnabled, isResizingExtensionsEnabled, api) : this.setupResizerCallbacks(dom, contentDOM, view, mark, api);
101
+ (0, _fg.fg)('platform_editor_lovability_resize_gracefully') || (0, _fg.fg)('platform_editor_lovability_resize_exts_gracefully') ? this.setupResizerCallbacksIfSupported(dom, contentDOM, view, mark, isResizingDividersPanelsEnabled, isResizingExtensionsEnabled, api) : this.setupResizerCallbacks(dom, contentDOM, view, mark, api);
102
102
  }
103
103
  this.unsubscribeToViewModeChange = api === null || api === void 0 || (_api$editorViewMode2 = api.editorViewMode) === null || _api$editorViewMode2 === void 0 ? void 0 : _api$editorViewMode2.sharedState.onChange(function (sharedState) {
104
104
  var _sharedState$nextShar, _sharedState$prevShar;
105
105
  if (((_sharedState$nextShar = sharedState.nextSharedState) === null || _sharedState$nextShar === void 0 ? void 0 : _sharedState$nextShar.mode) !== ((_sharedState$prevShar = sharedState.prevSharedState) === null || _sharedState$prevShar === void 0 ? void 0 : _sharedState$prevShar.mode)) {
106
106
  var _sharedState$nextShar4, _sharedState$nextShar5;
107
- if ((0, _platformFeatureFlags.fg)('platform_editor_lovability_resize_gracefully') || (0, _platformFeatureFlags.fg)('platform_editor_lovability_resize_exts_gracefully')) {
107
+ if ((0, _fg.fg)('platform_editor_lovability_resize_gracefully') || (0, _fg.fg)('platform_editor_lovability_resize_exts_gracefully')) {
108
108
  var _sharedState$nextShar2, _sharedState$nextShar3;
109
109
  if (((_sharedState$nextShar2 = sharedState.nextSharedState) === null || _sharedState$nextShar2 === void 0 ? void 0 : _sharedState$nextShar2.mode) === 'view') {
110
110
  var _this$cancelScheduled;
@@ -1,15 +1,13 @@
1
1
  import React, { useLayoutEffect, useState } from 'react';
2
2
  import { bind } from 'bind-event-listener';
3
- // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
4
- import uuid from 'uuid/v4';
5
3
  import { breakoutMessages as messages } from '@atlaskit/editor-common/messages';
6
4
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
7
- import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
8
- import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
9
- import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
5
+ import { draggable } from '@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter';
6
+ import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/utils/disable-native-drag-preview';
7
+ import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/utils/prevent-unhandled';
10
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
11
9
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
12
- import Tooltip from '@atlaskit/tooltip';
10
+ import Tooltip from '@atlaskit/tooltip/Tooltip';
13
11
  const getNodeName = nodeName => {
14
12
  if (nodeName === 'layoutSection') {
15
13
  return 'layout';
@@ -68,6 +66,9 @@ export const createPragmaticResizer = ({
68
66
  nodeViewPortalProviderAPI
69
67
  }) => {
70
68
  let state = 'default';
69
+ // Read once per resizer: `isExperimentEnabled` fires an exposure event, and `registerEvents` is
70
+ // bound to several elements so checking it inside the handler would emit one per element hovered.
71
+ const isDeferredTooltipEnabled = isExperimentEnabled('platform_editor_reduce_event_listener_count');
71
72
  const isLeftResizeHandleDisabled = expValEquals('platform_editor_lovability_resize_dividers_panels', 'isEnabled', true) || isExperimentEnabled('platform_editor_lovability_resize_extensions');
72
73
  const createHandle = side => {
73
74
  const handle = document.createElement('div');
@@ -93,19 +94,59 @@ export const createPragmaticResizer = ({
93
94
  tooltipContainer.classList.add('pm-breakout-resize-handle-rail-wrapper');
94
95
  handle.appendChild(tooltipContainer);
95
96
  handle.appendChild(handleHitBox);
96
-
97
- // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
98
- const key = uuid();
99
- nodeViewPortalProviderAPI.render(() => /*#__PURE__*/React.createElement(RailWithTooltip, {
97
+ const key = crypto.randomUUID();
98
+ const renderTooltip = () => nodeViewPortalProviderAPI.render(() => /*#__PURE__*/React.createElement(RailWithTooltip, {
100
99
  rail: rail,
101
100
  target: target,
102
101
  intl: intl
103
102
  }), tooltipContainer, key);
103
+
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.
106
+ let isTooltipMounted = false;
107
+ const mountTooltip = () => {
108
+ if (isTooltipMounted) {
109
+ return;
110
+ }
111
+ isTooltipMounted = true;
112
+ // `RailWithTooltip`'s ref moves `rail` into the tooltip wrapper, so the DOM ends up
113
+ // identical to the eager path.
114
+ renderTooltip();
115
+ };
116
+
117
+ // React 18 registers ~130 native event listeners on every portal container (see
118
+ // `listenToAllSupportedEvents`), and never removes them. Mounting this tooltip up front
119
+ // costs that for every resizable node in the document — two containers per node when the
120
+ // left handle is enabled — even though only the hovered handle can ever show a tooltip.
121
+ //
122
+ // Instead, park `rail` directly in the wrapper and mount the tooltip on the first
123
+ // `mouseenter` of the node (see `registerEvents` below). The rail has `opacity: 0` until
124
+ // the pointer is inside the node, so the pre-mount DOM is never visible, and the
125
+ // `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.
131
+ rail.style.width = '100%';
132
+ tooltipContainer.appendChild(rail);
133
+ } else {
134
+ renderTooltip();
135
+ }
104
136
  return {
105
137
  handle,
106
138
  rail,
107
139
  handleHitBox,
108
- destroyTooltip: () => nodeViewPortalProviderAPI.remove(key)
140
+ mountTooltip,
141
+ destroyTooltip: () => {
142
+ // The eager path always mounted, so it always removes — unchanged from before. Only
143
+ // the deferred path can reach `destroyTooltip` without having mounted anything.
144
+ if (isDeferredTooltipEnabled && !isTooltipMounted) {
145
+ return;
146
+ }
147
+ nodeViewPortalProviderAPI.remove(key);
148
+ isTooltipMounted = false;
149
+ }
109
150
  };
110
151
  };
111
152
  const rightHandle = createHandle('right');
@@ -143,6 +184,10 @@ export const createPragmaticResizer = ({
143
184
  return [bind(element, {
144
185
  type: 'mouseenter',
145
186
  listener: () => {
187
+ if (isDeferredTooltipEnabled) {
188
+ rightHandle.mountTooltip();
189
+ leftHandle === null || leftHandle === void 0 ? void 0 : leftHandle.mountTooltip();
190
+ }
146
191
  rightHandle.rail.style.setProperty('opacity', '1');
147
192
  leftHandle === null || leftHandle === void 0 ? void 0 : leftHandle.rail.style.setProperty('opacity', '1');
148
193
  }
@@ -1,7 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { BreakoutCssClassName } from '@atlaskit/editor-common/styles';
3
3
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
4
- import { fg } from '@atlaskit/platform-feature-flags';
4
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
5
5
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
6
6
  import { createPragmaticResizer } from './pragmatic-resizer';
7
7
  import { createResizerCallbacks } from './resizer-callbacks';
@@ -2,16 +2,14 @@ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import React, { useLayoutEffect, useState } from 'react';
4
4
  import { bind } from 'bind-event-listener';
5
- // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
6
- import uuid from 'uuid/v4';
7
5
  import { breakoutMessages as messages } from '@atlaskit/editor-common/messages';
8
6
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
9
- import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
10
- import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
11
- import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
7
+ import { draggable } from '@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter';
8
+ import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/utils/disable-native-drag-preview';
9
+ import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/utils/prevent-unhandled';
12
10
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
13
11
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
14
- import Tooltip from '@atlaskit/tooltip';
12
+ import Tooltip from '@atlaskit/tooltip/Tooltip';
15
13
  var getNodeName = function getNodeName(nodeName) {
16
14
  if (nodeName === 'layoutSection') {
17
15
  return 'layout';
@@ -71,6 +69,9 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
71
69
  intl = _ref2.intl,
72
70
  nodeViewPortalProviderAPI = _ref2.nodeViewPortalProviderAPI;
73
71
  var state = 'default';
72
+ // Read once per resizer: `isExperimentEnabled` fires an exposure event, and `registerEvents` is
73
+ // bound to several elements so checking it inside the handler would emit one per element hovered.
74
+ var isDeferredTooltipEnabled = isExperimentEnabled('platform_editor_reduce_event_listener_count');
74
75
  var isLeftResizeHandleDisabled = expValEquals('platform_editor_lovability_resize_dividers_panels', 'isEnabled', true) || isExperimentEnabled('platform_editor_lovability_resize_extensions');
75
76
  var createHandle = function createHandle(side) {
76
77
  var handle = document.createElement('div');
@@ -96,22 +97,62 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
96
97
  tooltipContainer.classList.add('pm-breakout-resize-handle-rail-wrapper');
97
98
  handle.appendChild(tooltipContainer);
98
99
  handle.appendChild(handleHitBox);
100
+ var key = crypto.randomUUID();
101
+ var renderTooltip = function renderTooltip() {
102
+ return nodeViewPortalProviderAPI.render(function () {
103
+ return /*#__PURE__*/React.createElement(RailWithTooltip, {
104
+ rail: rail,
105
+ target: target,
106
+ intl: intl
107
+ });
108
+ }, tooltipContainer, key);
109
+ };
99
110
 
100
- // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
101
- var key = uuid();
102
- nodeViewPortalProviderAPI.render(function () {
103
- return /*#__PURE__*/React.createElement(RailWithTooltip, {
104
- rail: rail,
105
- target: target,
106
- intl: intl
107
- });
108
- }, tooltipContainer, key);
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.
113
+ var isTooltipMounted = false;
114
+ var mountTooltip = function mountTooltip() {
115
+ if (isTooltipMounted) {
116
+ return;
117
+ }
118
+ isTooltipMounted = true;
119
+ // `RailWithTooltip`'s ref moves `rail` into the tooltip wrapper, so the DOM ends up
120
+ // identical to the eager path.
121
+ renderTooltip();
122
+ };
123
+
124
+ // React 18 registers ~130 native event listeners on every portal container (see
125
+ // `listenToAllSupportedEvents`), and never removes them. Mounting this tooltip up front
126
+ // costs that for every resizable node in the document — two containers per node when the
127
+ // left handle is enabled — even though only the hovered handle can ever show a tooltip.
128
+ //
129
+ // Instead, park `rail` directly in the wrapper and mount the tooltip on the first
130
+ // `mouseenter` of the node (see `registerEvents` below). The rail has `opacity: 0` until
131
+ // the pointer is inside the node, so the pre-mount DOM is never visible, and the
132
+ // `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.
138
+ rail.style.width = '100%';
139
+ tooltipContainer.appendChild(rail);
140
+ } else {
141
+ renderTooltip();
142
+ }
109
143
  return {
110
144
  handle: handle,
111
145
  rail: rail,
112
146
  handleHitBox: handleHitBox,
147
+ mountTooltip: mountTooltip,
113
148
  destroyTooltip: function destroyTooltip() {
114
- return nodeViewPortalProviderAPI.remove(key);
149
+ // The eager path always mounted, so it always removes — unchanged from before. Only
150
+ // the deferred path can reach `destroyTooltip` without having mounted anything.
151
+ if (isDeferredTooltipEnabled && !isTooltipMounted) {
152
+ return;
153
+ }
154
+ nodeViewPortalProviderAPI.remove(key);
155
+ isTooltipMounted = false;
115
156
  }
116
157
  };
117
158
  };
@@ -151,6 +192,10 @@ export var createPragmaticResizer = function createPragmaticResizer(_ref2) {
151
192
  return [bind(element, {
152
193
  type: 'mouseenter',
153
194
  listener: function listener() {
195
+ if (isDeferredTooltipEnabled) {
196
+ rightHandle.mountTooltip();
197
+ leftHandle === null || leftHandle === void 0 || leftHandle.mountTooltip();
198
+ }
154
199
  rightHandle.rail.style.setProperty('opacity', '1');
155
200
  leftHandle === null || leftHandle === void 0 || leftHandle.rail.style.setProperty('opacity', '1');
156
201
  }
@@ -5,7 +5,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
5
5
  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) { _defineProperty(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; }
6
6
  import { BreakoutCssClassName } from '@atlaskit/editor-common/styles';
7
7
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
8
- import { fg } from '@atlaskit/platform-feature-flags';
8
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
9
9
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
10
10
  import { createPragmaticResizer } from './pragmatic-resizer';
11
11
  import { createResizerCallbacks } from './resizer-callbacks';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-breakout",
3
- "version": "15.0.16",
3
+ "version": "15.0.18",
4
4
  "description": "Breakout plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",