@atlaskit/editor-plugin-paste-options-toolbar 9.1.5 → 9.1.6
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/util/format-handlers.js +1 -1
- package/dist/cjs/ui/on-paste-actions-menu/PasteActionsMenu.js +75 -6
- package/dist/es2019/pm-plugins/util/format-handlers.js +1 -1
- package/dist/es2019/ui/on-paste-actions-menu/PasteActionsMenu.js +74 -6
- package/dist/esm/pm-plugins/util/format-handlers.js +1 -1
- package/dist/esm/ui/on-paste-actions-menu/PasteActionsMenu.js +74 -6
- package/dist/types/ui/on-paste-actions-menu/PasteActionsMenu.d.ts +22 -4
- package/dist/types-ts4.5/ui/on-paste-actions-menu/PasteActionsMenu.d.ts +22 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-paste-options-toolbar
|
|
2
2
|
|
|
3
|
+
## 9.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`fc5915138b437`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/fc5915138b437) -
|
|
8
|
+
EDITOR-6110 Ensure that paste actions menu appears in the correct position when the first item of
|
|
9
|
+
the paste is an inline node or mark
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
|
|
3
12
|
## 9.1.5
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -157,7 +157,7 @@ function getMarkdownSlice(text, schema, selection) {
|
|
|
157
157
|
for (var i = 0; i < textSplitByCodeBlock.length; i++) {
|
|
158
158
|
if (i % 2 === 0) {
|
|
159
159
|
// Ignored via go/ees005
|
|
160
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
160
|
+
// eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
|
|
161
161
|
textSplitByCodeBlock[i] = textSplitByCodeBlock[i].replace(/\\/g, '\\\\');
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
value: true
|
|
7
7
|
});
|
|
8
8
|
exports.PasteActionsMenu = void 0;
|
|
9
|
+
exports.findBlockAncestorDOM = findBlockAncestorDOM;
|
|
9
10
|
exports.getTargetElement = getTargetElement;
|
|
10
11
|
exports.getVisualEndBottom = getVisualEndBottom;
|
|
11
12
|
exports.onPositionCalculated = onPositionCalculated;
|
|
@@ -82,10 +83,47 @@ function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
86
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
87
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
88
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
89
|
+
* document model.
|
|
90
|
+
*
|
|
91
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
92
|
+
*/
|
|
93
|
+
function findBlockAncestorDOM(editorView, pos) {
|
|
94
|
+
try {
|
|
95
|
+
var $pos = editorView.state.doc.resolve(pos);
|
|
96
|
+
// Walk up the document tree from the resolved position's innermost
|
|
97
|
+
// node towards the root. $pos.node(depth) gives the ancestor at each
|
|
98
|
+
// depth; $pos.start(depth) gives the position just inside that ancestor,
|
|
99
|
+
// so `$pos.start(depth) - 1` is the position of the ancestor node itself
|
|
100
|
+
// (which is what nodeDOM expects).
|
|
101
|
+
for (var depth = $pos.depth; depth >= 0; depth--) {
|
|
102
|
+
var node = $pos.node(depth);
|
|
103
|
+
if (node.isBlock) {
|
|
104
|
+
var domNode = editorView.nodeDOM($pos.start(depth) - 1);
|
|
105
|
+
if (domNode instanceof HTMLElement) {
|
|
106
|
+
return domNode;
|
|
107
|
+
}
|
|
108
|
+
// depth 0 is the doc node — nodeDOM(–1) won't work, so try
|
|
109
|
+
// the editor's own DOM element as a fallback.
|
|
110
|
+
if (depth === 0 && editorView.dom instanceof HTMLElement) {
|
|
111
|
+
return editorView.dom;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (_unused2) {
|
|
116
|
+
// Position may be out of range after a concurrent edit — fall through.
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Adjusts the position of the paste menu so that:
|
|
123
|
+
*
|
|
124
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
125
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
126
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
89
127
|
*
|
|
90
128
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
91
129
|
* element's bottom edge. This override:
|
|
@@ -96,16 +134,26 @@ function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
96
134
|
* to the scroll container's top edge (sticky-top).
|
|
97
135
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
98
136
|
* above the visible area.
|
|
137
|
+
*
|
|
138
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
139
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
140
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
141
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
142
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
143
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
144
|
+
* appears at the right side of the content area.
|
|
99
145
|
*/
|
|
100
146
|
function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, targetElement, scrollableElement) {
|
|
101
147
|
// Pre-compute once per render to avoid doc.resolve() on every scroll frame.
|
|
102
148
|
var tableAfterPos = resolveTableAfterPos(editorView, pasteEndPos);
|
|
149
|
+
var blockAncestorDOM = findBlockAncestorDOM(editorView, pasteStartPos);
|
|
103
150
|
return function (position) {
|
|
104
151
|
var _position$top;
|
|
105
152
|
var startCoords = editorView.coordsAtPos(pasteStartPos);
|
|
106
153
|
var endBottom = getVisualEndBottom(editorView, pasteEndPos, tableAfterPos);
|
|
107
154
|
var targetRect = targetElement.getBoundingClientRect();
|
|
108
155
|
|
|
156
|
+
// ── Vertical adjustment ──────────────────────────────────────────
|
|
109
157
|
// The Popup places the menu at the target's bottom edge by default.
|
|
110
158
|
// We shift it up so it aligns with the paste start position.
|
|
111
159
|
// Both coordinates are in viewport space, so the delta is offset-parent agnostic.
|
|
@@ -121,8 +169,28 @@ function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, targetElem
|
|
|
121
169
|
adjustedTop += scrollContainerTop - startCoords.top + _constants.PASTE_MENU_GAP_TOP;
|
|
122
170
|
}
|
|
123
171
|
}
|
|
172
|
+
|
|
173
|
+
// ── Horizontal adjustment ────────────────────────────────────────
|
|
174
|
+
// When pasted content starts with a mark (bold, italic, link …) or
|
|
175
|
+
// an inline node (emoji, smart link, inline image …),
|
|
176
|
+
// findDomRefAtPos returns the narrow inline wrapper element. The
|
|
177
|
+
// Popup's alignX="end" then places the menu at that element's right
|
|
178
|
+
// edge instead of the content area's right edge. We correct this by
|
|
179
|
+
// resolving the nearest block-level ProseMirror ancestor and
|
|
180
|
+
// re-anchoring to its right edge.
|
|
181
|
+
var adjustedLeft = position.left;
|
|
182
|
+
if (blockAncestorDOM && blockAncestorDOM !== targetElement) {
|
|
183
|
+
var _position$left;
|
|
184
|
+
var blockRect = blockAncestorDOM.getBoundingClientRect();
|
|
185
|
+
// Shift left by the difference between the block's right edge and
|
|
186
|
+
// the inline target's right edge. This mirrors what alignX="end"
|
|
187
|
+
// would have computed if the target were the block element.
|
|
188
|
+
var leftDelta = blockRect.right - targetRect.right;
|
|
189
|
+
adjustedLeft = ((_position$left = position.left) !== null && _position$left !== void 0 ? _position$left : 0) + leftDelta;
|
|
190
|
+
}
|
|
124
191
|
return _objectSpread(_objectSpread({}, position), {}, {
|
|
125
|
-
top: adjustedTop
|
|
192
|
+
top: adjustedTop,
|
|
193
|
+
left: adjustedLeft
|
|
126
194
|
});
|
|
127
195
|
};
|
|
128
196
|
}
|
|
@@ -248,7 +316,8 @@ var PasteActionsMenu = exports.PasteActionsMenu = function PasteActionsMenu(_ref
|
|
|
248
316
|
minPopupMargin: _constants.PASTE_MENU_GAP_HORIZONTAL,
|
|
249
317
|
zIndex: _editorSharedStyles.akEditorFloatingPanelZIndex,
|
|
250
318
|
alignX: "end",
|
|
251
|
-
alignY: "bottom"
|
|
319
|
+
alignY: "bottom"
|
|
320
|
+
/* eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) */,
|
|
252
321
|
offset: [_constants.PASTE_MENU_GAP_HORIZONTAL, 0],
|
|
253
322
|
onPositionCalculated: onPositionCalculated(editorView, pasteStartPos, pasteEndPos, target, effectiveScrollableElement),
|
|
254
323
|
handleClickOutside: handleClickOutside,
|
|
@@ -152,7 +152,7 @@ export function getMarkdownSlice(text, schema, selection) {
|
|
|
152
152
|
for (let i = 0; i < textSplitByCodeBlock.length; i++) {
|
|
153
153
|
if (i % 2 === 0) {
|
|
154
154
|
// Ignored via go/ees005
|
|
155
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
155
|
+
// eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
|
|
156
156
|
textSplitByCodeBlock[i] = textSplitByCodeBlock[i].replace(/\\/g, '\\\\');
|
|
157
157
|
}
|
|
158
158
|
}
|
|
@@ -66,10 +66,47 @@ export function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
70
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
71
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
72
|
+
* document model.
|
|
73
|
+
*
|
|
74
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
75
|
+
*/
|
|
76
|
+
export function findBlockAncestorDOM(editorView, pos) {
|
|
77
|
+
try {
|
|
78
|
+
const $pos = editorView.state.doc.resolve(pos);
|
|
79
|
+
// Walk up the document tree from the resolved position's innermost
|
|
80
|
+
// node towards the root. $pos.node(depth) gives the ancestor at each
|
|
81
|
+
// depth; $pos.start(depth) gives the position just inside that ancestor,
|
|
82
|
+
// so `$pos.start(depth) - 1` is the position of the ancestor node itself
|
|
83
|
+
// (which is what nodeDOM expects).
|
|
84
|
+
for (let depth = $pos.depth; depth >= 0; depth--) {
|
|
85
|
+
const node = $pos.node(depth);
|
|
86
|
+
if (node.isBlock) {
|
|
87
|
+
const domNode = editorView.nodeDOM($pos.start(depth) - 1);
|
|
88
|
+
if (domNode instanceof HTMLElement) {
|
|
89
|
+
return domNode;
|
|
90
|
+
}
|
|
91
|
+
// depth 0 is the doc node — nodeDOM(–1) won't work, so try
|
|
92
|
+
// the editor's own DOM element as a fallback.
|
|
93
|
+
if (depth === 0 && editorView.dom instanceof HTMLElement) {
|
|
94
|
+
return editorView.dom;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Position may be out of range after a concurrent edit — fall through.
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Adjusts the position of the paste menu so that:
|
|
106
|
+
*
|
|
107
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
108
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
109
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
73
110
|
*
|
|
74
111
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
75
112
|
* element's bottom edge. This override:
|
|
@@ -80,16 +117,26 @@ export function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
80
117
|
* to the scroll container's top edge (sticky-top).
|
|
81
118
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
82
119
|
* above the visible area.
|
|
120
|
+
*
|
|
121
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
122
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
123
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
124
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
125
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
126
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
127
|
+
* appears at the right side of the content area.
|
|
83
128
|
*/
|
|
84
129
|
export function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, targetElement, scrollableElement) {
|
|
85
130
|
// Pre-compute once per render to avoid doc.resolve() on every scroll frame.
|
|
86
131
|
const tableAfterPos = resolveTableAfterPos(editorView, pasteEndPos);
|
|
132
|
+
const blockAncestorDOM = findBlockAncestorDOM(editorView, pasteStartPos);
|
|
87
133
|
return position => {
|
|
88
134
|
var _position$top;
|
|
89
135
|
const startCoords = editorView.coordsAtPos(pasteStartPos);
|
|
90
136
|
const endBottom = getVisualEndBottom(editorView, pasteEndPos, tableAfterPos);
|
|
91
137
|
const targetRect = targetElement.getBoundingClientRect();
|
|
92
138
|
|
|
139
|
+
// ── Vertical adjustment ──────────────────────────────────────────
|
|
93
140
|
// The Popup places the menu at the target's bottom edge by default.
|
|
94
141
|
// We shift it up so it aligns with the paste start position.
|
|
95
142
|
// Both coordinates are in viewport space, so the delta is offset-parent agnostic.
|
|
@@ -105,9 +152,29 @@ export function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, tar
|
|
|
105
152
|
adjustedTop += scrollContainerTop - startCoords.top + PASTE_MENU_GAP_TOP;
|
|
106
153
|
}
|
|
107
154
|
}
|
|
155
|
+
|
|
156
|
+
// ── Horizontal adjustment ────────────────────────────────────────
|
|
157
|
+
// When pasted content starts with a mark (bold, italic, link …) or
|
|
158
|
+
// an inline node (emoji, smart link, inline image …),
|
|
159
|
+
// findDomRefAtPos returns the narrow inline wrapper element. The
|
|
160
|
+
// Popup's alignX="end" then places the menu at that element's right
|
|
161
|
+
// edge instead of the content area's right edge. We correct this by
|
|
162
|
+
// resolving the nearest block-level ProseMirror ancestor and
|
|
163
|
+
// re-anchoring to its right edge.
|
|
164
|
+
let adjustedLeft = position.left;
|
|
165
|
+
if (blockAncestorDOM && blockAncestorDOM !== targetElement) {
|
|
166
|
+
var _position$left;
|
|
167
|
+
const blockRect = blockAncestorDOM.getBoundingClientRect();
|
|
168
|
+
// Shift left by the difference between the block's right edge and
|
|
169
|
+
// the inline target's right edge. This mirrors what alignX="end"
|
|
170
|
+
// would have computed if the target were the block element.
|
|
171
|
+
const leftDelta = blockRect.right - targetRect.right;
|
|
172
|
+
adjustedLeft = ((_position$left = position.left) !== null && _position$left !== void 0 ? _position$left : 0) + leftDelta;
|
|
173
|
+
}
|
|
108
174
|
return {
|
|
109
175
|
...position,
|
|
110
|
-
top: adjustedTop
|
|
176
|
+
top: adjustedTop,
|
|
177
|
+
left: adjustedLeft
|
|
111
178
|
};
|
|
112
179
|
};
|
|
113
180
|
}
|
|
@@ -236,7 +303,8 @@ export const PasteActionsMenu = ({
|
|
|
236
303
|
minPopupMargin: PASTE_MENU_GAP_HORIZONTAL,
|
|
237
304
|
zIndex: akEditorFloatingPanelZIndex,
|
|
238
305
|
alignX: "end",
|
|
239
|
-
alignY: "bottom"
|
|
306
|
+
alignY: "bottom"
|
|
307
|
+
/* eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) */,
|
|
240
308
|
offset: [PASTE_MENU_GAP_HORIZONTAL, 0],
|
|
241
309
|
onPositionCalculated: onPositionCalculated(editorView, pasteStartPos, pasteEndPos, target, effectiveScrollableElement),
|
|
242
310
|
handleClickOutside: handleClickOutside,
|
|
@@ -150,7 +150,7 @@ export function getMarkdownSlice(text, schema, selection) {
|
|
|
150
150
|
for (var i = 0; i < textSplitByCodeBlock.length; i++) {
|
|
151
151
|
if (i % 2 === 0) {
|
|
152
152
|
// Ignored via go/ees005
|
|
153
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
153
|
+
// eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace -- Ignored via go/ees017 (to be fixed)
|
|
154
154
|
textSplitByCodeBlock[i] = textSplitByCodeBlock[i].replace(/\\/g, '\\\\');
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -69,10 +69,47 @@ export function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
73
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
74
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
75
|
+
* document model.
|
|
76
|
+
*
|
|
77
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
78
|
+
*/
|
|
79
|
+
export function findBlockAncestorDOM(editorView, pos) {
|
|
80
|
+
try {
|
|
81
|
+
var $pos = editorView.state.doc.resolve(pos);
|
|
82
|
+
// Walk up the document tree from the resolved position's innermost
|
|
83
|
+
// node towards the root. $pos.node(depth) gives the ancestor at each
|
|
84
|
+
// depth; $pos.start(depth) gives the position just inside that ancestor,
|
|
85
|
+
// so `$pos.start(depth) - 1` is the position of the ancestor node itself
|
|
86
|
+
// (which is what nodeDOM expects).
|
|
87
|
+
for (var depth = $pos.depth; depth >= 0; depth--) {
|
|
88
|
+
var node = $pos.node(depth);
|
|
89
|
+
if (node.isBlock) {
|
|
90
|
+
var domNode = editorView.nodeDOM($pos.start(depth) - 1);
|
|
91
|
+
if (domNode instanceof HTMLElement) {
|
|
92
|
+
return domNode;
|
|
93
|
+
}
|
|
94
|
+
// depth 0 is the doc node — nodeDOM(–1) won't work, so try
|
|
95
|
+
// the editor's own DOM element as a fallback.
|
|
96
|
+
if (depth === 0 && editorView.dom instanceof HTMLElement) {
|
|
97
|
+
return editorView.dom;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (_unused2) {
|
|
102
|
+
// Position may be out of range after a concurrent edit — fall through.
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Adjusts the position of the paste menu so that:
|
|
109
|
+
*
|
|
110
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
111
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
112
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
76
113
|
*
|
|
77
114
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
78
115
|
* element's bottom edge. This override:
|
|
@@ -83,16 +120,26 @@ export function getVisualEndBottom(editorView, pasteEndPos, tableAfterPos) {
|
|
|
83
120
|
* to the scroll container's top edge (sticky-top).
|
|
84
121
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
85
122
|
* above the visible area.
|
|
123
|
+
*
|
|
124
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
125
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
126
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
127
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
128
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
129
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
130
|
+
* appears at the right side of the content area.
|
|
86
131
|
*/
|
|
87
132
|
export function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, targetElement, scrollableElement) {
|
|
88
133
|
// Pre-compute once per render to avoid doc.resolve() on every scroll frame.
|
|
89
134
|
var tableAfterPos = resolveTableAfterPos(editorView, pasteEndPos);
|
|
135
|
+
var blockAncestorDOM = findBlockAncestorDOM(editorView, pasteStartPos);
|
|
90
136
|
return function (position) {
|
|
91
137
|
var _position$top;
|
|
92
138
|
var startCoords = editorView.coordsAtPos(pasteStartPos);
|
|
93
139
|
var endBottom = getVisualEndBottom(editorView, pasteEndPos, tableAfterPos);
|
|
94
140
|
var targetRect = targetElement.getBoundingClientRect();
|
|
95
141
|
|
|
142
|
+
// ── Vertical adjustment ──────────────────────────────────────────
|
|
96
143
|
// The Popup places the menu at the target's bottom edge by default.
|
|
97
144
|
// We shift it up so it aligns with the paste start position.
|
|
98
145
|
// Both coordinates are in viewport space, so the delta is offset-parent agnostic.
|
|
@@ -108,8 +155,28 @@ export function onPositionCalculated(editorView, pasteStartPos, pasteEndPos, tar
|
|
|
108
155
|
adjustedTop += scrollContainerTop - startCoords.top + PASTE_MENU_GAP_TOP;
|
|
109
156
|
}
|
|
110
157
|
}
|
|
158
|
+
|
|
159
|
+
// ── Horizontal adjustment ────────────────────────────────────────
|
|
160
|
+
// When pasted content starts with a mark (bold, italic, link …) or
|
|
161
|
+
// an inline node (emoji, smart link, inline image …),
|
|
162
|
+
// findDomRefAtPos returns the narrow inline wrapper element. The
|
|
163
|
+
// Popup's alignX="end" then places the menu at that element's right
|
|
164
|
+
// edge instead of the content area's right edge. We correct this by
|
|
165
|
+
// resolving the nearest block-level ProseMirror ancestor and
|
|
166
|
+
// re-anchoring to its right edge.
|
|
167
|
+
var adjustedLeft = position.left;
|
|
168
|
+
if (blockAncestorDOM && blockAncestorDOM !== targetElement) {
|
|
169
|
+
var _position$left;
|
|
170
|
+
var blockRect = blockAncestorDOM.getBoundingClientRect();
|
|
171
|
+
// Shift left by the difference between the block's right edge and
|
|
172
|
+
// the inline target's right edge. This mirrors what alignX="end"
|
|
173
|
+
// would have computed if the target were the block element.
|
|
174
|
+
var leftDelta = blockRect.right - targetRect.right;
|
|
175
|
+
adjustedLeft = ((_position$left = position.left) !== null && _position$left !== void 0 ? _position$left : 0) + leftDelta;
|
|
176
|
+
}
|
|
111
177
|
return _objectSpread(_objectSpread({}, position), {}, {
|
|
112
|
-
top: adjustedTop
|
|
178
|
+
top: adjustedTop,
|
|
179
|
+
left: adjustedLeft
|
|
113
180
|
});
|
|
114
181
|
};
|
|
115
182
|
}
|
|
@@ -235,7 +302,8 @@ export var PasteActionsMenu = function PasteActionsMenu(_ref) {
|
|
|
235
302
|
minPopupMargin: PASTE_MENU_GAP_HORIZONTAL,
|
|
236
303
|
zIndex: akEditorFloatingPanelZIndex,
|
|
237
304
|
alignX: "end",
|
|
238
|
-
alignY: "bottom"
|
|
305
|
+
alignY: "bottom"
|
|
306
|
+
/* eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) */,
|
|
239
307
|
offset: [PASTE_MENU_GAP_HORIZONTAL, 0],
|
|
240
308
|
onPositionCalculated: onPositionCalculated(editorView, pasteStartPos, pasteEndPos, target, effectiveScrollableElement),
|
|
241
309
|
handleClickOutside: handleClickOutside,
|
|
@@ -25,10 +25,20 @@ export declare function resolveTableAfterPos(editorView: EditorView, pos: number
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos: number, tableAfterPos?: number): number;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
29
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
30
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
31
|
+
* document model.
|
|
32
|
+
*
|
|
33
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
34
|
+
*/
|
|
35
|
+
export declare function findBlockAncestorDOM(editorView: EditorView, pos: number): HTMLElement | null;
|
|
36
|
+
/**
|
|
37
|
+
* Adjusts the position of the paste menu so that:
|
|
38
|
+
*
|
|
39
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
40
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
41
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
32
42
|
*
|
|
33
43
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
34
44
|
* element's bottom edge. This override:
|
|
@@ -39,6 +49,14 @@ export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos:
|
|
|
39
49
|
* to the scroll container's top edge (sticky-top).
|
|
40
50
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
41
51
|
* above the visible area.
|
|
52
|
+
*
|
|
53
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
54
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
55
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
56
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
57
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
58
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
59
|
+
* appears at the right side of the content area.
|
|
42
60
|
*/
|
|
43
61
|
export declare function onPositionCalculated(editorView: EditorView, pasteStartPos: number, pasteEndPos: number, targetElement: HTMLElement, scrollableElement?: HTMLElement | false): (position: {
|
|
44
62
|
bottom?: number;
|
|
@@ -25,10 +25,20 @@ export declare function resolveTableAfterPos(editorView: EditorView, pos: number
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos: number, tableAfterPos?: number): number;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Finds the DOM element for the nearest block-level ProseMirror ancestor of
|
|
29
|
+
* the given document position. Uses ProseMirror's schema (`node.isBlock`)
|
|
30
|
+
* rather than CSS display properties, so the check is always in sync with the
|
|
31
|
+
* document model.
|
|
32
|
+
*
|
|
33
|
+
* Returns `null` if no block ancestor can be resolved to a DOM element.
|
|
34
|
+
*/
|
|
35
|
+
export declare function findBlockAncestorDOM(editorView: EditorView, pos: number): HTMLElement | null;
|
|
36
|
+
/**
|
|
37
|
+
* Adjusts the position of the paste menu so that:
|
|
38
|
+
*
|
|
39
|
+
* **Vertical:** The menu aligns with the top of the pasted content using the
|
|
40
|
+
* exact coordinates at the paste start position, and sticks to the top of the
|
|
41
|
+
* scroll container when the pasted content scrolls above the visible area.
|
|
32
42
|
*
|
|
33
43
|
* The Popup uses alignY="bottom", which positions the popup below the target
|
|
34
44
|
* element's bottom edge. This override:
|
|
@@ -39,6 +49,14 @@ export declare function getVisualEndBottom(editorView: EditorView, pasteEndPos:
|
|
|
39
49
|
* to the scroll container's top edge (sticky-top).
|
|
40
50
|
* 3. Stops sticking once the entire pasted range (pasteEndPos) has scrolled
|
|
41
51
|
* above the visible area.
|
|
52
|
+
*
|
|
53
|
+
* **Horizontal:** When the target element is an inline element (e.g. a mark
|
|
54
|
+
* wrapper like `<strong>`, or an inline node like an emoji), the Popup's
|
|
55
|
+
* `alignX="end"` would place the menu at the right edge of that narrow
|
|
56
|
+
* element. This override resolves the nearest block-level ProseMirror
|
|
57
|
+
* ancestor (using `node.isBlock` from the document schema) and re-anchors
|
|
58
|
+
* the horizontal position to its right edge, so the menu consistently
|
|
59
|
+
* appears at the right side of the content area.
|
|
42
60
|
*/
|
|
43
61
|
export declare function onPositionCalculated(editorView: EditorView, pasteStartPos: number, pasteEndPos: number, targetElement: HTMLElement, scrollableElement?: HTMLElement | false): (position: {
|
|
44
62
|
bottom?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-paste-options-toolbar",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.6",
|
|
4
4
|
"description": "Paste options toolbar for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"react-intl-next": "npm:react-intl@^5.18.1"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@atlaskit/editor-common": "^112.
|
|
51
|
+
"@atlaskit/editor-common": "^112.8.0",
|
|
52
52
|
"react": "^18.2.0",
|
|
53
53
|
"react-dom": "^18.2.0"
|
|
54
54
|
},
|