mirror of
https://github.com/zotero/zotero.git
synced 2026-10-03 02:21:49 +00:00
Ctrl-Enter handler: Position correctly when selection is empty
In empty <input>s and the note editor. Also add comment explaining possible alternate approach. Fixes #4763
This commit is contained in:
parent
bb6085bbe0
commit
1f5b7f5414
1 changed files with 50 additions and 6 deletions
|
|
@ -22,6 +22,20 @@ export function getTargetElement(doc) {
|
|||
* @returns {{ clientX: number, clientY: number }}
|
||||
*/
|
||||
function getContextMenuPosition(targetElement) {
|
||||
// In theory, we could find the caret position using some privileged-code
|
||||
// magic:
|
||||
//
|
||||
// let x = {}, y = {}, w = {}, h = {};
|
||||
// Cc["@mozilla.org/accessibilityService;1"].getService(Ci.nsIAccessibilityService)
|
||||
// .getAccessibleFor(targetElement)
|
||||
// .QueryInterface(Ci.nsIAccessibleText)
|
||||
// .getCaretRect(x, y, w, h);
|
||||
//
|
||||
// But this returns hardware screen units, doesn't work for selections, and
|
||||
// doesn't *precisely* correspond to the position of the caret on screen
|
||||
// without adjustments.
|
||||
// Consider revisiting if the manual method gets too weighed down by edge cases.
|
||||
|
||||
let selection;
|
||||
if (targetElement.editor?.selection) {
|
||||
selection = targetElement.editor.selection;
|
||||
|
|
@ -41,23 +55,53 @@ function getContextMenuPosition(targetElement) {
|
|||
let anchorToEnd;
|
||||
if (selection) {
|
||||
let range = selection.getRangeAt(0);
|
||||
|
||||
// Easy case: We have a range with rects, so use its bounding rect
|
||||
if (range.getClientRects().length) {
|
||||
rect = range.getBoundingClientRect();
|
||||
|
||||
// ...Except an empty <input>'s text node will be anchored
|
||||
// to the bottom with height 0, so move the rect to the top
|
||||
if (rect.height === 0
|
||||
&& range.collapsed
|
||||
&& range.startContainer.isNativeAnonymous
|
||||
&& range.startContainer.parentElement?.getClientRects().length) {
|
||||
// We know we have a mutable DOMRect here, so we can
|
||||
// mutate without cloning
|
||||
rect.y = range.startContainer.parentElement.getBoundingClientRect().y;
|
||||
|
||||
anchorToBottom = false;
|
||||
anchorToEnd = true;
|
||||
}
|
||||
}
|
||||
// If the selection is between lines in an editor, it'll be
|
||||
// inside the editor's native anonymous text node and won't
|
||||
// have any rects for some reason.
|
||||
// If the caret is between lines in an editor, it'll be inside the
|
||||
// editor's native anonymous text node and won't have any rects for
|
||||
// some reason.
|
||||
// If that's the case, use the text node's bounds.
|
||||
else if (range.startContainer === range.endContainer && range.startContainer.isNativeAnonymous
|
||||
else if (range.collapsed && range.startContainer.isNativeAnonymous
|
||||
&& range.startContainer.firstChild?.nodeType === Node.TEXT_NODE) {
|
||||
let quads = range.startContainer.firstChild.getBoxQuads();
|
||||
rect = quads[quads.length - 1].getBounds();
|
||||
}
|
||||
// In a contenteditable (ProseMirror), when the selection is in an
|
||||
// empty block or at the end of a block, it won't be within a text node
|
||||
// and won't have any rects. Place at the end of the previous node.
|
||||
else if (range.startContainer === range.endContainer
|
||||
&& range.startOffset === range.endOffset
|
||||
&& range.startOffset < range.startContainer.childNodes.length) {
|
||||
range = range.cloneRange();
|
||||
range.selectNode(range.startContainer.childNodes[range.startOffset]);
|
||||
rect = range.getBoundingClientRect();
|
||||
|
||||
anchorToBottom = false;
|
||||
anchorToEnd = true;
|
||||
}
|
||||
else {
|
||||
rect = range.commonAncestorContainer.getBoundingClientRect();
|
||||
}
|
||||
anchorToBottom = !range.collapsed;
|
||||
anchorToEnd = range.collapsed;
|
||||
|
||||
anchorToBottom ??= !range.collapsed;
|
||||
anchorToEnd ??= range.collapsed;
|
||||
}
|
||||
else {
|
||||
rect = targetElement.getBoundingClientRect();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue