@atlaskit/editor-plugin-block-controls 13.4.0 → 13.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/cjs/pm-plugins/decorations-common.js +2 -2
- package/dist/cjs/pm-plugins/decorations-drag-handle.js +3 -3
- package/dist/cjs/pm-plugins/react-root-registry.js +69 -0
- package/dist/cjs/ui/drag-handle.js +41 -0
- package/dist/es2019/pm-plugins/decorations-common.js +2 -2
- package/dist/es2019/pm-plugins/decorations-drag-handle.js +3 -3
- package/dist/es2019/pm-plugins/react-root-registry.js +60 -0
- package/dist/es2019/ui/drag-handle.js +41 -0
- package/dist/esm/pm-plugins/decorations-common.js +2 -2
- package/dist/esm/pm-plugins/decorations-drag-handle.js +3 -3
- package/dist/esm/pm-plugins/react-root-registry.js +62 -0
- package/dist/esm/ui/drag-handle.js +41 -0
- package/dist/types/pm-plugins/react-root-registry.d.ts +22 -0
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-block-controls
|
|
2
2
|
|
|
3
|
+
## 13.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`82270dfb1e67e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/82270dfb1e67e) -
|
|
8
|
+
Migrate block-controls drag-handle render/unmount to the React 18/19 createRoot API behind
|
|
9
|
+
nike_r19_render_unmount
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
|
|
3
12
|
## 13.4.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
|
@@ -8,10 +8,10 @@ exports.unmountDecorations = exports.getNodeTypeWithLevel = exports.getNodeAncho
|
|
|
8
8
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
9
9
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
-
var _reactDom = _interopRequireDefault(require("react-dom"));
|
|
12
11
|
var _uuid = _interopRequireDefault(require("uuid"));
|
|
13
12
|
var _nodeTypeUtils = require("@atlaskit/editor-common/utils/node-type-utils");
|
|
14
13
|
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
|
|
14
|
+
var _reactRootRegistry = require("./react-root-registry");
|
|
15
15
|
var _validation = require("./utils/validation");
|
|
16
16
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
17
17
|
|
|
@@ -60,6 +60,6 @@ var unmountDecorations = exports.unmountDecorations = function unmountDecoration
|
|
|
60
60
|
// as it was more responsive and causes less re-rendering
|
|
61
61
|
var decorationsToRemove = document.querySelectorAll("[".concat(selector, "=\"true\"]"));
|
|
62
62
|
decorationsToRemove.forEach(function (el) {
|
|
63
|
-
|
|
63
|
+
(0, _reactRootRegistry.unmountFromMountPoint)(el);
|
|
64
64
|
});
|
|
65
65
|
};
|
|
@@ -8,7 +8,6 @@ exports.findHandleDec = exports.findActiveDragHandleNodeDec = exports.emptyParag
|
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
9
|
var _react = require("react");
|
|
10
10
|
var _bindEventListener = require("bind-event-listener");
|
|
11
|
-
var _reactDom = _interopRequireDefault(require("react-dom"));
|
|
12
11
|
var _uuid = _interopRequireDefault(require("uuid"));
|
|
13
12
|
var _view = require("@atlaskit/editor-prosemirror/view");
|
|
14
13
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
@@ -17,6 +16,7 @@ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
|
|
|
17
16
|
var _consts = require("../ui/consts");
|
|
18
17
|
var _dragHandle = require("../ui/drag-handle");
|
|
19
18
|
var _decorationsCommon = require("./decorations-common");
|
|
19
|
+
var _reactRootRegistry = require("./react-root-registry");
|
|
20
20
|
var _marks = require("./utils/marks");
|
|
21
21
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
22
22
|
|
|
@@ -157,7 +157,7 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
|
|
|
157
157
|
// );
|
|
158
158
|
|
|
159
159
|
if ((0, _experiments.editorExperiment)('platform_editor_controls', 'variant1')) {
|
|
160
|
-
|
|
160
|
+
(0, _reactRootRegistry.renderToMountPoint)( /*#__PURE__*/(0, _react.createElement)(_dragHandle.DragHandleWithVisibility, {
|
|
161
161
|
view: view,
|
|
162
162
|
api: api,
|
|
163
163
|
formatMessage: formatMessage,
|
|
@@ -169,7 +169,7 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
|
|
|
169
169
|
anchorRectCache: anchorRectCache
|
|
170
170
|
}), element);
|
|
171
171
|
} else {
|
|
172
|
-
|
|
172
|
+
(0, _reactRootRegistry.renderToMountPoint)( /*#__PURE__*/(0, _react.createElement)(_dragHandle.DragHandle, {
|
|
173
173
|
view: view,
|
|
174
174
|
api: api,
|
|
175
175
|
formatMessage: formatMessage,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.unmountFromMountPoint = exports.renderToMountPoint = void 0;
|
|
8
|
+
var _reactDom = _interopRequireWildcard(require("react-dom"));
|
|
9
|
+
var _client = require("react-dom/client");
|
|
10
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
11
|
+
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); }
|
|
12
|
+
/**
|
|
13
|
+
* Shared registry of React roots created via `createRoot`.
|
|
14
|
+
*
|
|
15
|
+
* Both `decorations-drag-handle.ts` (render) and `decorations-common.ts`
|
|
16
|
+
* (unmount) must operate on the **same** root for a given DOM element, so
|
|
17
|
+
* the WeakMap lives here and is imported by both modules.
|
|
18
|
+
*/
|
|
19
|
+
var reactRoots = new WeakMap();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Mounts `element` into `mountPoint`.
|
|
23
|
+
*
|
|
24
|
+
* When `nike_r19_render_unmount` is **on**, uses the React 18/19 `createRoot`
|
|
25
|
+
* API wrapped in `flushSync` so the render is synchronous – matching the
|
|
26
|
+
* legacy `ReactDOM.render` timing that ProseMirror decoration widgets depend
|
|
27
|
+
* on (focus management, anchor positioning, block-menu visibility all read
|
|
28
|
+
* the rendered DOM immediately after the callback returns).
|
|
29
|
+
*
|
|
30
|
+
* When the gate is **off**, falls back to the legacy `ReactDOM.render` path.
|
|
31
|
+
*/
|
|
32
|
+
var renderToMountPoint = exports.renderToMountPoint = function renderToMountPoint(element, mountPoint) {
|
|
33
|
+
if ((0, _platformFeatureFlags.fg)('nike_r19_render_unmount')) {
|
|
34
|
+
var root = reactRoots.get(mountPoint);
|
|
35
|
+
if (!root) {
|
|
36
|
+
root = (0, _client.createRoot)(mountPoint);
|
|
37
|
+
reactRoots.set(mountPoint, root);
|
|
38
|
+
}
|
|
39
|
+
var activeRoot = root;
|
|
40
|
+
|
|
41
|
+
// flushSync makes createRoot render synchronously, matching legacy
|
|
42
|
+
// ReactDOM.render timing that the widget/focus/positioning code depends on.
|
|
43
|
+
(0, _reactDom.flushSync)(function () {
|
|
44
|
+
return activeRoot.render(element);
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
_reactDom.default.render(element, mountPoint);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Unmounts the React tree at `mountPoint`.
|
|
53
|
+
*
|
|
54
|
+
* When `nike_r19_render_unmount` is **on**, looks up the root in the shared
|
|
55
|
+
* registry, calls `root.unmount()`, and removes the entry.
|
|
56
|
+
*
|
|
57
|
+
* When the gate is **off**, falls back to `ReactDOM.unmountComponentAtNode`.
|
|
58
|
+
*/
|
|
59
|
+
var unmountFromMountPoint = exports.unmountFromMountPoint = function unmountFromMountPoint(mountPoint) {
|
|
60
|
+
if ((0, _platformFeatureFlags.fg)('nike_r19_render_unmount')) {
|
|
61
|
+
var root = reactRoots.get(mountPoint);
|
|
62
|
+
if (root) {
|
|
63
|
+
root.unmount();
|
|
64
|
+
reactRoots.delete(mountPoint);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
_reactDom.default.unmountComponentAtNode(mountPoint);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -1011,7 +1011,11 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref2) {
|
|
|
1011
1011
|
(_cleanUpTransitionLis2 = cleanUpTransitionListener) === null || _cleanUpTransitionLis2 === void 0 || _cleanUpTransitionLis2();
|
|
1012
1012
|
};
|
|
1013
1013
|
}, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType, docDepForReliableAnchor]);
|
|
1014
|
+
var isHandleShown = positionStylesOld.display !== 'none';
|
|
1014
1015
|
(0, _react.useEffect)(function () {
|
|
1016
|
+
if ((0, _platformFeatureFlags.fg)('nike_r19_render_unmount')) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1015
1019
|
if (handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && buttonRef.current) {
|
|
1016
1020
|
var id = requestAnimationFrame(function () {
|
|
1017
1021
|
var _buttonRef$current;
|
|
@@ -1023,6 +1027,43 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref2) {
|
|
|
1023
1027
|
};
|
|
1024
1028
|
}
|
|
1025
1029
|
}, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view]);
|
|
1030
|
+
(0, _react.useEffect)(function () {
|
|
1031
|
+
if (!(0, _platformFeatureFlags.fg)('nike_r19_render_unmount')) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && isHandleShown && buttonRef.current)) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// isHandleShown means the handle has been positioned, but an ancestor can still be
|
|
1039
|
+
// visibility:hidden or display:none for a frame, in which case focus() is a silent no-op.
|
|
1040
|
+
// Focus now, and if activeElement shows it did not land, retry on the next few frames.
|
|
1041
|
+
var rafId;
|
|
1042
|
+
var attempts = 0;
|
|
1043
|
+
var MAX_ATTEMPTS = 5;
|
|
1044
|
+
var _focusHandle = function focusHandle() {
|
|
1045
|
+
var _getDocument2;
|
|
1046
|
+
var button = buttonRef.current;
|
|
1047
|
+
if (!button) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
button.focus();
|
|
1051
|
+
// getDocument() is the same document handleKeyDownNew reads activeElement from, so
|
|
1052
|
+
// this confirms Space/Enter will act on the handle rather than silently no-op.
|
|
1053
|
+
if (((_getDocument2 = (0, _browserApis.getDocument)()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement) === button || attempts >= MAX_ATTEMPTS) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
attempts++;
|
|
1057
|
+
rafId = requestAnimationFrame(_focusHandle);
|
|
1058
|
+
};
|
|
1059
|
+
_focusHandle();
|
|
1060
|
+
return function () {
|
|
1061
|
+
if (rafId !== undefined) {
|
|
1062
|
+
cancelAnimationFrame(rafId);
|
|
1063
|
+
}
|
|
1064
|
+
view.focus();
|
|
1065
|
+
};
|
|
1066
|
+
}, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view, isHandleShown]);
|
|
1026
1067
|
(0, _react.useEffect)(function () {
|
|
1027
1068
|
if (typeof start !== 'number' || !selection) {
|
|
1028
1069
|
return;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
-
import ReactDOM from 'react-dom';
|
|
3
2
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
4
3
|
import uuid from 'uuid';
|
|
5
4
|
import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
|
|
6
5
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
6
|
+
import { unmountFromMountPoint } from './react-root-registry';
|
|
7
7
|
import { isFontSizeMarkActive } from './utils/validation';
|
|
8
8
|
export const TYPE_DROP_TARGET_DEC = 'drop-target-decoration';
|
|
9
9
|
export const TYPE_HANDLE_DEC = 'drag-handle';
|
|
@@ -44,6 +44,6 @@ export const unmountDecorations = (nodeViewPortalProviderAPI, selector, key) =>
|
|
|
44
44
|
// as it was more responsive and causes less re-rendering
|
|
45
45
|
const decorationsToRemove = document.querySelectorAll(`[${selector}="true"]`);
|
|
46
46
|
decorationsToRemove.forEach(el => {
|
|
47
|
-
|
|
47
|
+
unmountFromMountPoint(el);
|
|
48
48
|
});
|
|
49
49
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createElement } from 'react';
|
|
2
2
|
import { bind } from 'bind-event-listener';
|
|
3
|
-
import ReactDOM from 'react-dom';
|
|
4
3
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
5
4
|
import uuid from 'uuid';
|
|
6
5
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
@@ -10,6 +9,7 @@ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
|
10
9
|
import { ACTIVE_DRAG_HANDLE_ATTR } from '../ui/consts';
|
|
11
10
|
import { DragHandle, DragHandleWithVisibility } from '../ui/drag-handle';
|
|
12
11
|
import { TYPE_HANDLE_DEC, TYPE_NODE_DEC, unmountDecorations } from './decorations-common';
|
|
12
|
+
import { renderToMountPoint } from './react-root-registry';
|
|
13
13
|
import { getMatchingBlockMarks } from './utils/marks';
|
|
14
14
|
const TYPE_ACTIVE_HANDLE_DEC = 'active-drag-handle-node';
|
|
15
15
|
|
|
@@ -144,7 +144,7 @@ export const dragHandleDecoration = ({
|
|
|
144
144
|
// );
|
|
145
145
|
|
|
146
146
|
if (editorExperiment('platform_editor_controls', 'variant1')) {
|
|
147
|
-
|
|
147
|
+
renderToMountPoint( /*#__PURE__*/createElement(DragHandleWithVisibility, {
|
|
148
148
|
view,
|
|
149
149
|
api,
|
|
150
150
|
formatMessage,
|
|
@@ -156,7 +156,7 @@ export const dragHandleDecoration = ({
|
|
|
156
156
|
anchorRectCache
|
|
157
157
|
}), element);
|
|
158
158
|
} else {
|
|
159
|
-
|
|
159
|
+
renderToMountPoint( /*#__PURE__*/createElement(DragHandle, {
|
|
160
160
|
view,
|
|
161
161
|
api,
|
|
162
162
|
formatMessage,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import ReactDOM, { flushSync } from 'react-dom';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared registry of React roots created via `createRoot`.
|
|
7
|
+
*
|
|
8
|
+
* Both `decorations-drag-handle.ts` (render) and `decorations-common.ts`
|
|
9
|
+
* (unmount) must operate on the **same** root for a given DOM element, so
|
|
10
|
+
* the WeakMap lives here and is imported by both modules.
|
|
11
|
+
*/
|
|
12
|
+
const reactRoots = new WeakMap();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Mounts `element` into `mountPoint`.
|
|
16
|
+
*
|
|
17
|
+
* When `nike_r19_render_unmount` is **on**, uses the React 18/19 `createRoot`
|
|
18
|
+
* API wrapped in `flushSync` so the render is synchronous – matching the
|
|
19
|
+
* legacy `ReactDOM.render` timing that ProseMirror decoration widgets depend
|
|
20
|
+
* on (focus management, anchor positioning, block-menu visibility all read
|
|
21
|
+
* the rendered DOM immediately after the callback returns).
|
|
22
|
+
*
|
|
23
|
+
* When the gate is **off**, falls back to the legacy `ReactDOM.render` path.
|
|
24
|
+
*/
|
|
25
|
+
export const renderToMountPoint = (element, mountPoint) => {
|
|
26
|
+
if (fg('nike_r19_render_unmount')) {
|
|
27
|
+
let root = reactRoots.get(mountPoint);
|
|
28
|
+
if (!root) {
|
|
29
|
+
root = createRoot(mountPoint);
|
|
30
|
+
reactRoots.set(mountPoint, root);
|
|
31
|
+
}
|
|
32
|
+
const activeRoot = root;
|
|
33
|
+
|
|
34
|
+
// flushSync makes createRoot render synchronously, matching legacy
|
|
35
|
+
// ReactDOM.render timing that the widget/focus/positioning code depends on.
|
|
36
|
+
flushSync(() => activeRoot.render(element));
|
|
37
|
+
} else {
|
|
38
|
+
ReactDOM.render(element, mountPoint);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Unmounts the React tree at `mountPoint`.
|
|
44
|
+
*
|
|
45
|
+
* When `nike_r19_render_unmount` is **on**, looks up the root in the shared
|
|
46
|
+
* registry, calls `root.unmount()`, and removes the entry.
|
|
47
|
+
*
|
|
48
|
+
* When the gate is **off**, falls back to `ReactDOM.unmountComponentAtNode`.
|
|
49
|
+
*/
|
|
50
|
+
export const unmountFromMountPoint = mountPoint => {
|
|
51
|
+
if (fg('nike_r19_render_unmount')) {
|
|
52
|
+
const root = reactRoots.get(mountPoint);
|
|
53
|
+
if (root) {
|
|
54
|
+
root.unmount();
|
|
55
|
+
reactRoots.delete(mountPoint);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
ReactDOM.unmountComponentAtNode(mountPoint);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
@@ -992,7 +992,11 @@ export const DragHandle = ({
|
|
|
992
992
|
(_cleanUpTransitionLis2 = cleanUpTransitionListener) === null || _cleanUpTransitionLis2 === void 0 ? void 0 : _cleanUpTransitionLis2();
|
|
993
993
|
};
|
|
994
994
|
}, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType, docDepForReliableAnchor]);
|
|
995
|
+
const isHandleShown = positionStylesOld.display !== 'none';
|
|
995
996
|
useEffect(() => {
|
|
997
|
+
if (fg('nike_r19_render_unmount')) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
996
1000
|
if (handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && buttonRef.current) {
|
|
997
1001
|
const id = requestAnimationFrame(() => {
|
|
998
1002
|
var _buttonRef$current;
|
|
@@ -1004,6 +1008,43 @@ export const DragHandle = ({
|
|
|
1004
1008
|
};
|
|
1005
1009
|
}
|
|
1006
1010
|
}, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view]);
|
|
1011
|
+
useEffect(() => {
|
|
1012
|
+
if (!fg('nike_r19_render_unmount')) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && isHandleShown && buttonRef.current)) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// isHandleShown means the handle has been positioned, but an ancestor can still be
|
|
1020
|
+
// visibility:hidden or display:none for a frame, in which case focus() is a silent no-op.
|
|
1021
|
+
// Focus now, and if activeElement shows it did not land, retry on the next few frames.
|
|
1022
|
+
let rafId;
|
|
1023
|
+
let attempts = 0;
|
|
1024
|
+
const MAX_ATTEMPTS = 5;
|
|
1025
|
+
const focusHandle = () => {
|
|
1026
|
+
var _getDocument2;
|
|
1027
|
+
const button = buttonRef.current;
|
|
1028
|
+
if (!button) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
button.focus();
|
|
1032
|
+
// getDocument() is the same document handleKeyDownNew reads activeElement from, so
|
|
1033
|
+
// this confirms Space/Enter will act on the handle rather than silently no-op.
|
|
1034
|
+
if (((_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement) === button || attempts >= MAX_ATTEMPTS) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
attempts++;
|
|
1038
|
+
rafId = requestAnimationFrame(focusHandle);
|
|
1039
|
+
};
|
|
1040
|
+
focusHandle();
|
|
1041
|
+
return () => {
|
|
1042
|
+
if (rafId !== undefined) {
|
|
1043
|
+
cancelAnimationFrame(rafId);
|
|
1044
|
+
}
|
|
1045
|
+
view.focus();
|
|
1046
|
+
};
|
|
1047
|
+
}, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view, isHandleShown]);
|
|
1007
1048
|
useEffect(() => {
|
|
1008
1049
|
if (typeof start !== 'number' || !selection) {
|
|
1009
1050
|
return;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
2
2
|
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
3
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
-
import ReactDOM from 'react-dom';
|
|
5
4
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
6
5
|
import uuid from 'uuid';
|
|
7
6
|
import { getBaseNodeTypeName } from '@atlaskit/editor-common/utils/node-type-utils';
|
|
8
7
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
8
|
+
import { unmountFromMountPoint } from './react-root-registry';
|
|
9
9
|
import { isFontSizeMarkActive } from './utils/validation';
|
|
10
10
|
export var TYPE_DROP_TARGET_DEC = 'drop-target-decoration';
|
|
11
11
|
export var TYPE_HANDLE_DEC = 'drag-handle';
|
|
@@ -52,6 +52,6 @@ export var unmountDecorations = function unmountDecorations(nodeViewPortalProvid
|
|
|
52
52
|
// as it was more responsive and causes less re-rendering
|
|
53
53
|
var decorationsToRemove = document.querySelectorAll("[".concat(selector, "=\"true\"]"));
|
|
54
54
|
decorationsToRemove.forEach(function (el) {
|
|
55
|
-
|
|
55
|
+
unmountFromMountPoint(el);
|
|
56
56
|
});
|
|
57
57
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import { createElement } from 'react';
|
|
3
3
|
import { bind } from 'bind-event-listener';
|
|
4
|
-
import ReactDOM from 'react-dom';
|
|
5
4
|
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
|
|
6
5
|
import uuid from 'uuid';
|
|
7
6
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
@@ -11,6 +10,7 @@ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
|
11
10
|
import { ACTIVE_DRAG_HANDLE_ATTR } from '../ui/consts';
|
|
12
11
|
import { DragHandle, DragHandleWithVisibility } from '../ui/drag-handle';
|
|
13
12
|
import { TYPE_HANDLE_DEC, TYPE_NODE_DEC, unmountDecorations } from './decorations-common';
|
|
13
|
+
import { renderToMountPoint } from './react-root-registry';
|
|
14
14
|
import { getMatchingBlockMarks } from './utils/marks';
|
|
15
15
|
var TYPE_ACTIVE_HANDLE_DEC = 'active-drag-handle-node';
|
|
16
16
|
|
|
@@ -149,7 +149,7 @@ export var dragHandleDecoration = function dragHandleDecoration(_ref) {
|
|
|
149
149
|
// );
|
|
150
150
|
|
|
151
151
|
if (editorExperiment('platform_editor_controls', 'variant1')) {
|
|
152
|
-
|
|
152
|
+
renderToMountPoint( /*#__PURE__*/createElement(DragHandleWithVisibility, {
|
|
153
153
|
view: view,
|
|
154
154
|
api: api,
|
|
155
155
|
formatMessage: formatMessage,
|
|
@@ -161,7 +161,7 @@ export var dragHandleDecoration = function dragHandleDecoration(_ref) {
|
|
|
161
161
|
anchorRectCache: anchorRectCache
|
|
162
162
|
}), element);
|
|
163
163
|
} else {
|
|
164
|
-
|
|
164
|
+
renderToMountPoint( /*#__PURE__*/createElement(DragHandle, {
|
|
165
165
|
view: view,
|
|
166
166
|
api: api,
|
|
167
167
|
formatMessage: formatMessage,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import ReactDOM, { flushSync } from 'react-dom';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared registry of React roots created via `createRoot`.
|
|
7
|
+
*
|
|
8
|
+
* Both `decorations-drag-handle.ts` (render) and `decorations-common.ts`
|
|
9
|
+
* (unmount) must operate on the **same** root for a given DOM element, so
|
|
10
|
+
* the WeakMap lives here and is imported by both modules.
|
|
11
|
+
*/
|
|
12
|
+
var reactRoots = new WeakMap();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Mounts `element` into `mountPoint`.
|
|
16
|
+
*
|
|
17
|
+
* When `nike_r19_render_unmount` is **on**, uses the React 18/19 `createRoot`
|
|
18
|
+
* API wrapped in `flushSync` so the render is synchronous – matching the
|
|
19
|
+
* legacy `ReactDOM.render` timing that ProseMirror decoration widgets depend
|
|
20
|
+
* on (focus management, anchor positioning, block-menu visibility all read
|
|
21
|
+
* the rendered DOM immediately after the callback returns).
|
|
22
|
+
*
|
|
23
|
+
* When the gate is **off**, falls back to the legacy `ReactDOM.render` path.
|
|
24
|
+
*/
|
|
25
|
+
export var renderToMountPoint = function renderToMountPoint(element, mountPoint) {
|
|
26
|
+
if (fg('nike_r19_render_unmount')) {
|
|
27
|
+
var root = reactRoots.get(mountPoint);
|
|
28
|
+
if (!root) {
|
|
29
|
+
root = createRoot(mountPoint);
|
|
30
|
+
reactRoots.set(mountPoint, root);
|
|
31
|
+
}
|
|
32
|
+
var activeRoot = root;
|
|
33
|
+
|
|
34
|
+
// flushSync makes createRoot render synchronously, matching legacy
|
|
35
|
+
// ReactDOM.render timing that the widget/focus/positioning code depends on.
|
|
36
|
+
flushSync(function () {
|
|
37
|
+
return activeRoot.render(element);
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
ReactDOM.render(element, mountPoint);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Unmounts the React tree at `mountPoint`.
|
|
46
|
+
*
|
|
47
|
+
* When `nike_r19_render_unmount` is **on**, looks up the root in the shared
|
|
48
|
+
* registry, calls `root.unmount()`, and removes the entry.
|
|
49
|
+
*
|
|
50
|
+
* When the gate is **off**, falls back to `ReactDOM.unmountComponentAtNode`.
|
|
51
|
+
*/
|
|
52
|
+
export var unmountFromMountPoint = function unmountFromMountPoint(mountPoint) {
|
|
53
|
+
if (fg('nike_r19_render_unmount')) {
|
|
54
|
+
var root = reactRoots.get(mountPoint);
|
|
55
|
+
if (root) {
|
|
56
|
+
root.unmount();
|
|
57
|
+
reactRoots.delete(mountPoint);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
ReactDOM.unmountComponentAtNode(mountPoint);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -1007,7 +1007,11 @@ export var DragHandle = function DragHandle(_ref2) {
|
|
|
1007
1007
|
(_cleanUpTransitionLis2 = cleanUpTransitionListener) === null || _cleanUpTransitionLis2 === void 0 || _cleanUpTransitionLis2();
|
|
1008
1008
|
};
|
|
1009
1009
|
}, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType, docDepForReliableAnchor]);
|
|
1010
|
+
var isHandleShown = positionStylesOld.display !== 'none';
|
|
1010
1011
|
useEffect(function () {
|
|
1012
|
+
if (fg('nike_r19_render_unmount')) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1011
1015
|
if (handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && buttonRef.current) {
|
|
1012
1016
|
var id = requestAnimationFrame(function () {
|
|
1013
1017
|
var _buttonRef$current;
|
|
@@ -1019,6 +1023,43 @@ export var DragHandle = function DragHandle(_ref2) {
|
|
|
1019
1023
|
};
|
|
1020
1024
|
}
|
|
1021
1025
|
}, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view]);
|
|
1026
|
+
useEffect(function () {
|
|
1027
|
+
if (!fg('nike_r19_render_unmount')) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && isHandleShown && buttonRef.current)) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// isHandleShown means the handle has been positioned, but an ancestor can still be
|
|
1035
|
+
// visibility:hidden or display:none for a frame, in which case focus() is a silent no-op.
|
|
1036
|
+
// Focus now, and if activeElement shows it did not land, retry on the next few frames.
|
|
1037
|
+
var rafId;
|
|
1038
|
+
var attempts = 0;
|
|
1039
|
+
var MAX_ATTEMPTS = 5;
|
|
1040
|
+
var _focusHandle = function focusHandle() {
|
|
1041
|
+
var _getDocument2;
|
|
1042
|
+
var button = buttonRef.current;
|
|
1043
|
+
if (!button) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
button.focus();
|
|
1047
|
+
// getDocument() is the same document handleKeyDownNew reads activeElement from, so
|
|
1048
|
+
// this confirms Space/Enter will act on the handle rather than silently no-op.
|
|
1049
|
+
if (((_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement) === button || attempts >= MAX_ATTEMPTS) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
attempts++;
|
|
1053
|
+
rafId = requestAnimationFrame(_focusHandle);
|
|
1054
|
+
};
|
|
1055
|
+
_focusHandle();
|
|
1056
|
+
return function () {
|
|
1057
|
+
if (rafId !== undefined) {
|
|
1058
|
+
cancelAnimationFrame(rafId);
|
|
1059
|
+
}
|
|
1060
|
+
view.focus();
|
|
1061
|
+
};
|
|
1062
|
+
}, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view, isHandleShown]);
|
|
1022
1063
|
useEffect(function () {
|
|
1023
1064
|
if (typeof start !== 'number' || !selection) {
|
|
1024
1065
|
return;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Mounts `element` into `mountPoint`.
|
|
4
|
+
*
|
|
5
|
+
* When `nike_r19_render_unmount` is **on**, uses the React 18/19 `createRoot`
|
|
6
|
+
* API wrapped in `flushSync` so the render is synchronous – matching the
|
|
7
|
+
* legacy `ReactDOM.render` timing that ProseMirror decoration widgets depend
|
|
8
|
+
* on (focus management, anchor positioning, block-menu visibility all read
|
|
9
|
+
* the rendered DOM immediately after the callback returns).
|
|
10
|
+
*
|
|
11
|
+
* When the gate is **off**, falls back to the legacy `ReactDOM.render` path.
|
|
12
|
+
*/
|
|
13
|
+
export declare const renderToMountPoint: (element: ReactElement, mountPoint: Element) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Unmounts the React tree at `mountPoint`.
|
|
16
|
+
*
|
|
17
|
+
* When `nike_r19_render_unmount` is **on**, looks up the root in the shared
|
|
18
|
+
* registry, calls `root.unmount()`, and removes the entry.
|
|
19
|
+
*
|
|
20
|
+
* When the gate is **off**, falls back to `ReactDOM.unmountComponentAtNode`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const unmountFromMountPoint: (mountPoint: Element) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-block-controls",
|
|
3
|
-
"version": "13.4.
|
|
3
|
+
"version": "13.4.1",
|
|
4
4
|
"description": "Block controls plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^4.1.0",
|
|
49
49
|
"@atlaskit/primitives": "^22.2.0",
|
|
50
50
|
"@atlaskit/theme": "^26.1.0",
|
|
51
|
-
"@atlaskit/tmp-editor-statsig": "^135.
|
|
51
|
+
"@atlaskit/tmp-editor-statsig": "^135.10.0",
|
|
52
52
|
"@atlaskit/tokens": "^16.3.0",
|
|
53
53
|
"@atlaskit/tooltip": "^24.0.0",
|
|
54
54
|
"@babel/runtime": "^7.0.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"uuid": "^3.1.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@atlaskit/editor-common": "^116.
|
|
62
|
+
"@atlaskit/editor-common": "^116.47.0",
|
|
63
63
|
"react": "^18.2.0 || ^19.2.0",
|
|
64
64
|
"react-dom": "^18.2.0 || ^19.2.0",
|
|
65
65
|
"react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
|
|
@@ -137,6 +137,9 @@
|
|
|
137
137
|
"platform-dst-top-layer-tooltip": {
|
|
138
138
|
"type": "boolean",
|
|
139
139
|
"referenceOnly": true
|
|
140
|
+
},
|
|
141
|
+
"nike_r19_render_unmount": {
|
|
142
|
+
"type": "boolean"
|
|
140
143
|
}
|
|
141
144
|
},
|
|
142
145
|
"devDependencies": {
|