@atlaskit/editor-plugin-block-controls 8.3.0 → 8.3.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 +8 -0
- package/dist/cjs/pm-plugins/selection-preservation/pm-plugin.js +45 -8
- package/dist/cjs/pm-plugins/selection-preservation/utils.js +55 -15
- package/dist/es2019/pm-plugins/selection-preservation/pm-plugin.js +45 -8
- package/dist/es2019/pm-plugins/selection-preservation/utils.js +55 -15
- package/dist/esm/pm-plugins/selection-preservation/pm-plugin.js +45 -8
- package/dist/esm/pm-plugins/selection-preservation/utils.js +55 -15
- package/dist/types/pm-plugins/selection-preservation/utils.d.ts +8 -7
- package/dist/types-ts4.5/pm-plugins/selection-preservation/utils.d.ts +8 -7
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-block-controls
|
|
2
2
|
|
|
3
|
+
## 8.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`9d67968b1ac03`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9d67968b1ac03) -
|
|
8
|
+
[ux] Fixed issue with browser selection sync logic for preserved selection plugin
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
|
|
3
11
|
## 8.3.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -7,8 +7,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
exports.createSelectionPreservationPlugin = void 0;
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
9
|
var _bindEventListener = require("bind-event-listener");
|
|
10
|
+
var _browserApis = require("@atlaskit/browser-apis");
|
|
10
11
|
var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
|
|
11
12
|
var _styles = require("@atlaskit/editor-common/styles");
|
|
13
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
12
14
|
var _main = require("../main");
|
|
13
15
|
var _selection = require("../utils/selection");
|
|
14
16
|
var _editorCommands = require("./editor-commands");
|
|
@@ -109,7 +111,14 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
|
|
|
109
111
|
},
|
|
110
112
|
view: function view(initialView) {
|
|
111
113
|
var view = initialView;
|
|
112
|
-
var
|
|
114
|
+
var doc = (0, _browserApis.getDocument)();
|
|
115
|
+
if (!doc) {
|
|
116
|
+
return {
|
|
117
|
+
update: function update() {},
|
|
118
|
+
destroy: function destroy() {}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
var unbindDocumentMouseDown = (0, _bindEventListener.bind)(doc, {
|
|
113
122
|
type: 'mousedown',
|
|
114
123
|
listener: function listener(e) {
|
|
115
124
|
if (!(e.target instanceof HTMLElement)) {
|
|
@@ -150,14 +159,42 @@ var createSelectionPreservationPlugin = exports.createSelectionPreservationPlugi
|
|
|
150
159
|
});
|
|
151
160
|
return {
|
|
152
161
|
update: function update(updateView, prevState) {
|
|
153
|
-
var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
|
|
154
162
|
view = updateView;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
|
|
164
|
+
// [FEATURE FLAG: platform_editor_selection_sync_fix]
|
|
165
|
+
// When enabled, syncs DOM selection even when editor doesn't have focus.
|
|
166
|
+
// This prevents ghost highlighting after moving nodes when block menu is open.
|
|
167
|
+
// To clean up: remove the if-else block and keep only the flag-on behavior.
|
|
168
|
+
if ((0, _platformFeatureFlags.fg)('platform_editor_selection_sync_fix')) {
|
|
169
|
+
var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
|
|
170
|
+
var prevPreservedSelection = (_selectionPreservatio = _pluginKey.selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
|
|
171
|
+
var currPreservedSelection = (_selectionPreservatio2 = _pluginKey.selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
|
|
172
|
+
var prevActiveNode = (_key$getState = _main.key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
|
|
173
|
+
var currActiveNode = (_key$getState2 = _main.key.getState(view.state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.activeNode;
|
|
174
|
+
|
|
175
|
+
// Sync DOM selection when the preserved selection or active node changes
|
|
176
|
+
// AND the document has changed (e.g., nodes moved)
|
|
177
|
+
// This prevents stealing focus during menu navigation while still fixing ghost highlighting
|
|
178
|
+
var hasPreservedSelection = !!currPreservedSelection;
|
|
179
|
+
var preservedSelectionChanged = !(0, _utils.compareSelections)(prevPreservedSelection, currPreservedSelection);
|
|
180
|
+
var activeNodeChanged = prevActiveNode !== currActiveNode;
|
|
181
|
+
var docChanged = prevState.doc !== view.state.doc;
|
|
182
|
+
var shouldSyncDOMSelection = hasPreservedSelection && (preservedSelectionChanged || activeNodeChanged) && docChanged;
|
|
183
|
+
if (shouldSyncDOMSelection) {
|
|
184
|
+
(0, _utils.syncDOMSelection)(view.state.selection, view);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
var _selectionPreservatio3, _selectionPreservatio4, _key$getState3, _key$getState4;
|
|
188
|
+
// OLD BEHAVIOR (to be removed when flag is cleaned up)
|
|
189
|
+
// Only synced when editor had focus, causing ghost highlighting issues
|
|
190
|
+
var _prevPreservedSelection = (_selectionPreservatio3 = _pluginKey.selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio3 === void 0 ? void 0 : _selectionPreservatio3.preservedSelection;
|
|
191
|
+
var _currPreservedSelection = (_selectionPreservatio4 = _pluginKey.selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio4 === void 0 ? void 0 : _selectionPreservatio4.preservedSelection;
|
|
192
|
+
var _prevActiveNode = (_key$getState3 = _main.key.getState(prevState)) === null || _key$getState3 === void 0 ? void 0 : _key$getState3.activeNode;
|
|
193
|
+
var _currActiveNode = (_key$getState4 = _main.key.getState(view.state)) === null || _key$getState4 === void 0 ? void 0 : _key$getState4.activeNode;
|
|
194
|
+
if (_currPreservedSelection && view.hasFocus() && (!(0, _utils.compareSelections)(_prevPreservedSelection, _currPreservedSelection) || _prevActiveNode !== _currActiveNode)) {
|
|
195
|
+
// Old syncDOMSelection signature (to be removed)
|
|
196
|
+
(0, _utils.syncDOMSelection)(view.state.selection);
|
|
197
|
+
}
|
|
161
198
|
}
|
|
162
199
|
},
|
|
163
200
|
destroy: function destroy() {
|
|
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.syncDOMSelection = exports.hasUserSelectionChange = exports.getSelectionPreservationMeta = exports.compareSelections = void 0;
|
|
7
|
+
var _browserApis = require("@atlaskit/browser-apis");
|
|
8
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
7
9
|
var _pluginKey = require("./plugin-key");
|
|
8
10
|
/**
|
|
9
11
|
* Detects if any of the transactions include user-driven selection changes.
|
|
@@ -32,23 +34,61 @@ var compareSelections = exports.compareSelections = function compareSelections(a
|
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
36
|
-
* only if it is out of sync with the provided ProseMirror selection state.
|
|
37
|
+
* Forces the browser's native selection to match ProseMirror's selection state.
|
|
37
38
|
*
|
|
38
|
-
* This is
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* This is necessary when the editor doesn't have focus (e.g., when block menu is open)
|
|
40
|
+
* but we still need to update the visual selection after moving nodes. Without this,
|
|
41
|
+
* the browser's native selection remains at the old position, causing ghost highlighting.
|
|
41
42
|
*
|
|
42
|
-
* @param selection The current ProseMirror selection state to
|
|
43
|
+
* @param selection The current ProseMirror selection state to sync to DOM.
|
|
44
|
+
* @param view The EditorView instance used to convert ProseMirror positions to DOM positions (when feature flag is enabled).
|
|
43
45
|
*/
|
|
44
|
-
var syncDOMSelection = exports.syncDOMSelection = function syncDOMSelection(selection) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
var syncDOMSelection = exports.syncDOMSelection = function syncDOMSelection(selection, view) {
|
|
47
|
+
// [FEATURE FLAG: platform_editor_selection_sync_fix]
|
|
48
|
+
// When enabled, uses improved DOM selection syncing with EditorView.
|
|
49
|
+
// To clean up: remove the if-else block, remove the optional view parameter,
|
|
50
|
+
// make view required, and keep only the flag-on behavior.
|
|
51
|
+
if (view && (0, _platformFeatureFlags.fg)('platform_editor_selection_sync_fix')) {
|
|
52
|
+
try {
|
|
53
|
+
var domSelection = window.getSelection();
|
|
54
|
+
if (!domSelection) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
var doc = (0, _browserApis.getDocument)();
|
|
58
|
+
if (!doc) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Convert ProseMirror selection to DOM selection using view.domAtPos
|
|
63
|
+
var anchor = view.domAtPos(selection.anchor);
|
|
64
|
+
var head = view.domAtPos(selection.head);
|
|
65
|
+
if (!anchor || !head) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Create a new DOM range from the ProseMirror selection
|
|
70
|
+
var range = doc.createRange();
|
|
71
|
+
range.setStart(anchor.node, anchor.offset);
|
|
72
|
+
range.setEnd(head.node, head.offset);
|
|
73
|
+
|
|
74
|
+
// Update the DOM selection to match ProseMirror's selection
|
|
75
|
+
domSelection.removeAllRanges();
|
|
76
|
+
domSelection.addRange(range);
|
|
77
|
+
} catch (_unused) {
|
|
78
|
+
// Silently fail if DOM selection sync fails
|
|
79
|
+
// This can happen if positions are invalid or DOM hasn't updated yet
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// OLD BEHAVIOR (to be removed when flag is cleaned up)
|
|
83
|
+
// Only checked if selection was out of sync using incorrect offset comparison
|
|
84
|
+
var _domSelection = window.getSelection();
|
|
85
|
+
var domRange = _domSelection && _domSelection.rangeCount === 1 && _domSelection.getRangeAt(0).cloneRange();
|
|
86
|
+
var isOutOfSync = domRange && (selection.from !== domRange.startOffset || selection.to !== domRange.endOffset);
|
|
87
|
+
if (isOutOfSync && _domSelection && domRange) {
|
|
88
|
+
// Force the DOM selection to refresh, setting it to the same range
|
|
89
|
+
// This will trigger ProseMirror to re-apply its selection logic based on the current state
|
|
90
|
+
_domSelection.removeAllRanges();
|
|
91
|
+
_domSelection.addRange(domRange);
|
|
92
|
+
}
|
|
53
93
|
}
|
|
54
94
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { bind } from 'bind-event-listener';
|
|
2
|
+
import { getDocument } from '@atlaskit/browser-apis';
|
|
2
3
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
4
|
import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
|
|
5
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
6
|
import { key } from '../main';
|
|
5
7
|
import { createPreservedSelection, mapPreservedSelection } from '../utils/selection';
|
|
6
8
|
import { stopPreservingSelection } from './editor-commands';
|
|
@@ -101,7 +103,14 @@ export const createSelectionPreservationPlugin = api => () => {
|
|
|
101
103
|
},
|
|
102
104
|
view(initialView) {
|
|
103
105
|
let view = initialView;
|
|
104
|
-
const
|
|
106
|
+
const doc = getDocument();
|
|
107
|
+
if (!doc) {
|
|
108
|
+
return {
|
|
109
|
+
update() {},
|
|
110
|
+
destroy() {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const unbindDocumentMouseDown = bind(doc, {
|
|
105
114
|
type: 'mousedown',
|
|
106
115
|
listener: e => {
|
|
107
116
|
if (!(e.target instanceof HTMLElement)) {
|
|
@@ -143,14 +152,42 @@ export const createSelectionPreservationPlugin = api => () => {
|
|
|
143
152
|
});
|
|
144
153
|
return {
|
|
145
154
|
update(updateView, prevState) {
|
|
146
|
-
var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
|
|
147
155
|
view = updateView;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
|
|
157
|
+
// [FEATURE FLAG: platform_editor_selection_sync_fix]
|
|
158
|
+
// When enabled, syncs DOM selection even when editor doesn't have focus.
|
|
159
|
+
// This prevents ghost highlighting after moving nodes when block menu is open.
|
|
160
|
+
// To clean up: remove the if-else block and keep only the flag-on behavior.
|
|
161
|
+
if (fg('platform_editor_selection_sync_fix')) {
|
|
162
|
+
var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
|
|
163
|
+
const prevPreservedSelection = (_selectionPreservatio = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
|
|
164
|
+
const currPreservedSelection = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
|
|
165
|
+
const prevActiveNode = (_key$getState = key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
|
|
166
|
+
const currActiveNode = (_key$getState2 = key.getState(view.state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.activeNode;
|
|
167
|
+
|
|
168
|
+
// Sync DOM selection when the preserved selection or active node changes
|
|
169
|
+
// AND the document has changed (e.g., nodes moved)
|
|
170
|
+
// This prevents stealing focus during menu navigation while still fixing ghost highlighting
|
|
171
|
+
const hasPreservedSelection = !!currPreservedSelection;
|
|
172
|
+
const preservedSelectionChanged = !compareSelections(prevPreservedSelection, currPreservedSelection);
|
|
173
|
+
const activeNodeChanged = prevActiveNode !== currActiveNode;
|
|
174
|
+
const docChanged = prevState.doc !== view.state.doc;
|
|
175
|
+
const shouldSyncDOMSelection = hasPreservedSelection && (preservedSelectionChanged || activeNodeChanged) && docChanged;
|
|
176
|
+
if (shouldSyncDOMSelection) {
|
|
177
|
+
syncDOMSelection(view.state.selection, view);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
var _selectionPreservatio3, _selectionPreservatio4, _key$getState3, _key$getState4;
|
|
181
|
+
// OLD BEHAVIOR (to be removed when flag is cleaned up)
|
|
182
|
+
// Only synced when editor had focus, causing ghost highlighting issues
|
|
183
|
+
const prevPreservedSelection = (_selectionPreservatio3 = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio3 === void 0 ? void 0 : _selectionPreservatio3.preservedSelection;
|
|
184
|
+
const currPreservedSelection = (_selectionPreservatio4 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio4 === void 0 ? void 0 : _selectionPreservatio4.preservedSelection;
|
|
185
|
+
const prevActiveNode = (_key$getState3 = key.getState(prevState)) === null || _key$getState3 === void 0 ? void 0 : _key$getState3.activeNode;
|
|
186
|
+
const currActiveNode = (_key$getState4 = key.getState(view.state)) === null || _key$getState4 === void 0 ? void 0 : _key$getState4.activeNode;
|
|
187
|
+
if (currPreservedSelection && view.hasFocus() && (!compareSelections(prevPreservedSelection, currPreservedSelection) || prevActiveNode !== currActiveNode)) {
|
|
188
|
+
// Old syncDOMSelection signature (to be removed)
|
|
189
|
+
syncDOMSelection(view.state.selection);
|
|
190
|
+
}
|
|
154
191
|
}
|
|
155
192
|
},
|
|
156
193
|
destroy() {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getDocument } from '@atlaskit/browser-apis';
|
|
2
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
1
3
|
import { selectionPreservationPluginKey } from './plugin-key';
|
|
2
4
|
/**
|
|
3
5
|
* Detects if any of the transactions include user-driven selection changes.
|
|
@@ -24,23 +26,61 @@ export const compareSelections = (a, b) => {
|
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
|
-
*
|
|
28
|
-
* only if it is out of sync with the provided ProseMirror selection state.
|
|
29
|
+
* Forces the browser's native selection to match ProseMirror's selection state.
|
|
29
30
|
*
|
|
30
|
-
* This is
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* This is necessary when the editor doesn't have focus (e.g., when block menu is open)
|
|
32
|
+
* but we still need to update the visual selection after moving nodes. Without this,
|
|
33
|
+
* the browser's native selection remains at the old position, causing ghost highlighting.
|
|
33
34
|
*
|
|
34
|
-
* @param selection The current ProseMirror selection state to
|
|
35
|
+
* @param selection The current ProseMirror selection state to sync to DOM.
|
|
36
|
+
* @param view The EditorView instance used to convert ProseMirror positions to DOM positions (when feature flag is enabled).
|
|
35
37
|
*/
|
|
36
|
-
export const syncDOMSelection = selection => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
export const syncDOMSelection = (selection, view) => {
|
|
39
|
+
// [FEATURE FLAG: platform_editor_selection_sync_fix]
|
|
40
|
+
// When enabled, uses improved DOM selection syncing with EditorView.
|
|
41
|
+
// To clean up: remove the if-else block, remove the optional view parameter,
|
|
42
|
+
// make view required, and keep only the flag-on behavior.
|
|
43
|
+
if (view && fg('platform_editor_selection_sync_fix')) {
|
|
44
|
+
try {
|
|
45
|
+
const domSelection = window.getSelection();
|
|
46
|
+
if (!domSelection) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const doc = getDocument();
|
|
50
|
+
if (!doc) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Convert ProseMirror selection to DOM selection using view.domAtPos
|
|
55
|
+
const anchor = view.domAtPos(selection.anchor);
|
|
56
|
+
const head = view.domAtPos(selection.head);
|
|
57
|
+
if (!anchor || !head) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create a new DOM range from the ProseMirror selection
|
|
62
|
+
const range = doc.createRange();
|
|
63
|
+
range.setStart(anchor.node, anchor.offset);
|
|
64
|
+
range.setEnd(head.node, head.offset);
|
|
65
|
+
|
|
66
|
+
// Update the DOM selection to match ProseMirror's selection
|
|
67
|
+
domSelection.removeAllRanges();
|
|
68
|
+
domSelection.addRange(range);
|
|
69
|
+
} catch {
|
|
70
|
+
// Silently fail if DOM selection sync fails
|
|
71
|
+
// This can happen if positions are invalid or DOM hasn't updated yet
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
// OLD BEHAVIOR (to be removed when flag is cleaned up)
|
|
75
|
+
// Only checked if selection was out of sync using incorrect offset comparison
|
|
76
|
+
const domSelection = window.getSelection();
|
|
77
|
+
const domRange = domSelection && domSelection.rangeCount === 1 && domSelection.getRangeAt(0).cloneRange();
|
|
78
|
+
const isOutOfSync = domRange && (selection.from !== domRange.startOffset || selection.to !== domRange.endOffset);
|
|
79
|
+
if (isOutOfSync && domSelection && domRange) {
|
|
80
|
+
// Force the DOM selection to refresh, setting it to the same range
|
|
81
|
+
// This will trigger ProseMirror to re-apply its selection logic based on the current state
|
|
82
|
+
domSelection.removeAllRanges();
|
|
83
|
+
domSelection.addRange(domRange);
|
|
84
|
+
}
|
|
45
85
|
}
|
|
46
86
|
};
|
|
@@ -2,8 +2,10 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
2
2
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
3
|
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; }
|
|
4
4
|
import { bind } from 'bind-event-listener';
|
|
5
|
+
import { getDocument } from '@atlaskit/browser-apis';
|
|
5
6
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
6
7
|
import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
|
|
8
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
7
9
|
import { key } from '../main';
|
|
8
10
|
import { createPreservedSelection, mapPreservedSelection } from '../utils/selection';
|
|
9
11
|
import { stopPreservingSelection } from './editor-commands';
|
|
@@ -103,7 +105,14 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
|
|
|
103
105
|
},
|
|
104
106
|
view: function view(initialView) {
|
|
105
107
|
var view = initialView;
|
|
106
|
-
var
|
|
108
|
+
var doc = getDocument();
|
|
109
|
+
if (!doc) {
|
|
110
|
+
return {
|
|
111
|
+
update: function update() {},
|
|
112
|
+
destroy: function destroy() {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
var unbindDocumentMouseDown = bind(doc, {
|
|
107
116
|
type: 'mousedown',
|
|
108
117
|
listener: function listener(e) {
|
|
109
118
|
if (!(e.target instanceof HTMLElement)) {
|
|
@@ -144,14 +153,42 @@ export var createSelectionPreservationPlugin = function createSelectionPreservat
|
|
|
144
153
|
});
|
|
145
154
|
return {
|
|
146
155
|
update: function update(updateView, prevState) {
|
|
147
|
-
var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
|
|
148
156
|
view = updateView;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
|
|
158
|
+
// [FEATURE FLAG: platform_editor_selection_sync_fix]
|
|
159
|
+
// When enabled, syncs DOM selection even when editor doesn't have focus.
|
|
160
|
+
// This prevents ghost highlighting after moving nodes when block menu is open.
|
|
161
|
+
// To clean up: remove the if-else block and keep only the flag-on behavior.
|
|
162
|
+
if (fg('platform_editor_selection_sync_fix')) {
|
|
163
|
+
var _selectionPreservatio, _selectionPreservatio2, _key$getState, _key$getState2;
|
|
164
|
+
var prevPreservedSelection = (_selectionPreservatio = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection;
|
|
165
|
+
var currPreservedSelection = (_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection;
|
|
166
|
+
var prevActiveNode = (_key$getState = key.getState(prevState)) === null || _key$getState === void 0 ? void 0 : _key$getState.activeNode;
|
|
167
|
+
var currActiveNode = (_key$getState2 = key.getState(view.state)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.activeNode;
|
|
168
|
+
|
|
169
|
+
// Sync DOM selection when the preserved selection or active node changes
|
|
170
|
+
// AND the document has changed (e.g., nodes moved)
|
|
171
|
+
// This prevents stealing focus during menu navigation while still fixing ghost highlighting
|
|
172
|
+
var hasPreservedSelection = !!currPreservedSelection;
|
|
173
|
+
var preservedSelectionChanged = !compareSelections(prevPreservedSelection, currPreservedSelection);
|
|
174
|
+
var activeNodeChanged = prevActiveNode !== currActiveNode;
|
|
175
|
+
var docChanged = prevState.doc !== view.state.doc;
|
|
176
|
+
var shouldSyncDOMSelection = hasPreservedSelection && (preservedSelectionChanged || activeNodeChanged) && docChanged;
|
|
177
|
+
if (shouldSyncDOMSelection) {
|
|
178
|
+
syncDOMSelection(view.state.selection, view);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
var _selectionPreservatio3, _selectionPreservatio4, _key$getState3, _key$getState4;
|
|
182
|
+
// OLD BEHAVIOR (to be removed when flag is cleaned up)
|
|
183
|
+
// Only synced when editor had focus, causing ghost highlighting issues
|
|
184
|
+
var _prevPreservedSelection = (_selectionPreservatio3 = selectionPreservationPluginKey.getState(prevState)) === null || _selectionPreservatio3 === void 0 ? void 0 : _selectionPreservatio3.preservedSelection;
|
|
185
|
+
var _currPreservedSelection = (_selectionPreservatio4 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio4 === void 0 ? void 0 : _selectionPreservatio4.preservedSelection;
|
|
186
|
+
var _prevActiveNode = (_key$getState3 = key.getState(prevState)) === null || _key$getState3 === void 0 ? void 0 : _key$getState3.activeNode;
|
|
187
|
+
var _currActiveNode = (_key$getState4 = key.getState(view.state)) === null || _key$getState4 === void 0 ? void 0 : _key$getState4.activeNode;
|
|
188
|
+
if (_currPreservedSelection && view.hasFocus() && (!compareSelections(_prevPreservedSelection, _currPreservedSelection) || _prevActiveNode !== _currActiveNode)) {
|
|
189
|
+
// Old syncDOMSelection signature (to be removed)
|
|
190
|
+
syncDOMSelection(view.state.selection);
|
|
191
|
+
}
|
|
155
192
|
}
|
|
156
193
|
},
|
|
157
194
|
destroy: function destroy() {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getDocument } from '@atlaskit/browser-apis';
|
|
2
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
1
3
|
import { selectionPreservationPluginKey } from './plugin-key';
|
|
2
4
|
/**
|
|
3
5
|
* Detects if any of the transactions include user-driven selection changes.
|
|
@@ -26,23 +28,61 @@ export var compareSelections = function compareSelections(a, b) {
|
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
|
-
*
|
|
30
|
-
* only if it is out of sync with the provided ProseMirror selection state.
|
|
31
|
+
* Forces the browser's native selection to match ProseMirror's selection state.
|
|
31
32
|
*
|
|
32
|
-
* This is
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* This is necessary when the editor doesn't have focus (e.g., when block menu is open)
|
|
34
|
+
* but we still need to update the visual selection after moving nodes. Without this,
|
|
35
|
+
* the browser's native selection remains at the old position, causing ghost highlighting.
|
|
35
36
|
*
|
|
36
|
-
* @param selection The current ProseMirror selection state to
|
|
37
|
+
* @param selection The current ProseMirror selection state to sync to DOM.
|
|
38
|
+
* @param view The EditorView instance used to convert ProseMirror positions to DOM positions (when feature flag is enabled).
|
|
37
39
|
*/
|
|
38
|
-
export var syncDOMSelection = function syncDOMSelection(selection) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
export var syncDOMSelection = function syncDOMSelection(selection, view) {
|
|
41
|
+
// [FEATURE FLAG: platform_editor_selection_sync_fix]
|
|
42
|
+
// When enabled, uses improved DOM selection syncing with EditorView.
|
|
43
|
+
// To clean up: remove the if-else block, remove the optional view parameter,
|
|
44
|
+
// make view required, and keep only the flag-on behavior.
|
|
45
|
+
if (view && fg('platform_editor_selection_sync_fix')) {
|
|
46
|
+
try {
|
|
47
|
+
var domSelection = window.getSelection();
|
|
48
|
+
if (!domSelection) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
var doc = getDocument();
|
|
52
|
+
if (!doc) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Convert ProseMirror selection to DOM selection using view.domAtPos
|
|
57
|
+
var anchor = view.domAtPos(selection.anchor);
|
|
58
|
+
var head = view.domAtPos(selection.head);
|
|
59
|
+
if (!anchor || !head) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create a new DOM range from the ProseMirror selection
|
|
64
|
+
var range = doc.createRange();
|
|
65
|
+
range.setStart(anchor.node, anchor.offset);
|
|
66
|
+
range.setEnd(head.node, head.offset);
|
|
67
|
+
|
|
68
|
+
// Update the DOM selection to match ProseMirror's selection
|
|
69
|
+
domSelection.removeAllRanges();
|
|
70
|
+
domSelection.addRange(range);
|
|
71
|
+
} catch (_unused) {
|
|
72
|
+
// Silently fail if DOM selection sync fails
|
|
73
|
+
// This can happen if positions are invalid or DOM hasn't updated yet
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// OLD BEHAVIOR (to be removed when flag is cleaned up)
|
|
77
|
+
// Only checked if selection was out of sync using incorrect offset comparison
|
|
78
|
+
var _domSelection = window.getSelection();
|
|
79
|
+
var domRange = _domSelection && _domSelection.rangeCount === 1 && _domSelection.getRangeAt(0).cloneRange();
|
|
80
|
+
var isOutOfSync = domRange && (selection.from !== domRange.startOffset || selection.to !== domRange.endOffset);
|
|
81
|
+
if (isOutOfSync && _domSelection && domRange) {
|
|
82
|
+
// Force the DOM selection to refresh, setting it to the same range
|
|
83
|
+
// This will trigger ProseMirror to re-apply its selection logic based on the current state
|
|
84
|
+
_domSelection.removeAllRanges();
|
|
85
|
+
_domSelection.addRange(domRange);
|
|
86
|
+
}
|
|
47
87
|
}
|
|
48
88
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReadonlyTransaction, Selection, Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
2
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
2
3
|
import type { SelectionPreservationMeta } from './types';
|
|
3
4
|
/**
|
|
4
5
|
* Detects if any of the transactions include user-driven selection changes.
|
|
@@ -17,13 +18,13 @@ export declare const getSelectionPreservationMeta: (tr: Transaction | ReadonlyTr
|
|
|
17
18
|
*/
|
|
18
19
|
export declare const compareSelections: (a?: Selection, b?: Selection) => boolean;
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
* only if it is out of sync with the provided ProseMirror selection state.
|
|
21
|
+
* Forces the browser's native selection to match ProseMirror's selection state.
|
|
22
22
|
*
|
|
23
|
-
* This is
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* This is necessary when the editor doesn't have focus (e.g., when block menu is open)
|
|
24
|
+
* but we still need to update the visual selection after moving nodes. Without this,
|
|
25
|
+
* the browser's native selection remains at the old position, causing ghost highlighting.
|
|
26
26
|
*
|
|
27
|
-
* @param selection The current ProseMirror selection state to
|
|
27
|
+
* @param selection The current ProseMirror selection state to sync to DOM.
|
|
28
|
+
* @param view The EditorView instance used to convert ProseMirror positions to DOM positions (when feature flag is enabled).
|
|
28
29
|
*/
|
|
29
|
-
export declare const syncDOMSelection: (selection: Selection) => void;
|
|
30
|
+
export declare const syncDOMSelection: (selection: Selection, view?: EditorView) => void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReadonlyTransaction, Selection, Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
2
|
+
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
2
3
|
import type { SelectionPreservationMeta } from './types';
|
|
3
4
|
/**
|
|
4
5
|
* Detects if any of the transactions include user-driven selection changes.
|
|
@@ -17,13 +18,13 @@ export declare const getSelectionPreservationMeta: (tr: Transaction | ReadonlyTr
|
|
|
17
18
|
*/
|
|
18
19
|
export declare const compareSelections: (a?: Selection, b?: Selection) => boolean;
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
* only if it is out of sync with the provided ProseMirror selection state.
|
|
21
|
+
* Forces the browser's native selection to match ProseMirror's selection state.
|
|
22
22
|
*
|
|
23
|
-
* This is
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* This is necessary when the editor doesn't have focus (e.g., when block menu is open)
|
|
24
|
+
* but we still need to update the visual selection after moving nodes. Without this,
|
|
25
|
+
* the browser's native selection remains at the old position, causing ghost highlighting.
|
|
26
26
|
*
|
|
27
|
-
* @param selection The current ProseMirror selection state to
|
|
27
|
+
* @param selection The current ProseMirror selection state to sync to DOM.
|
|
28
|
+
* @param view The EditorView instance used to convert ProseMirror positions to DOM positions (when feature flag is enabled).
|
|
28
29
|
*/
|
|
29
|
-
export declare const syncDOMSelection: (selection: Selection) => void;
|
|
30
|
+
export declare const syncDOMSelection: (selection: Selection, view?: EditorView) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-block-controls",
|
|
3
|
-
"version": "8.3.
|
|
3
|
+
"version": "8.3.1",
|
|
4
4
|
"description": "Block controls plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.0",
|
|
55
55
|
"@atlaskit/primitives": "^18.0.0",
|
|
56
56
|
"@atlaskit/theme": "^21.0.0",
|
|
57
|
-
"@atlaskit/tmp-editor-statsig": "^23.
|
|
57
|
+
"@atlaskit/tmp-editor-statsig": "^23.2.0",
|
|
58
58
|
"@atlaskit/tokens": "^11.0.0",
|
|
59
59
|
"@atlaskit/tooltip": "^20.14.0",
|
|
60
60
|
"@babel/runtime": "^7.0.0",
|
|
@@ -148,6 +148,9 @@
|
|
|
148
148
|
},
|
|
149
149
|
"platform_synced_block_patch_2": {
|
|
150
150
|
"type": "boolean"
|
|
151
|
+
},
|
|
152
|
+
"platform_editor_selection_sync_fix": {
|
|
153
|
+
"type": "boolean"
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
}
|