fix: finally fix the cursor key bug in monaco
This commit is contained in:
@@ -5,6 +5,52 @@ import {
|
|||||||
unregisterMonacoEditor
|
unregisterMonacoEditor
|
||||||
} from "../monaco/services.js";
|
} from "../monaco/services.js";
|
||||||
|
|
||||||
|
const WEBKIT_TEXTAREA_ARROW_COMMANDS = {
|
||||||
|
ArrowLeft: "cursorLeft",
|
||||||
|
ArrowRight: "cursorRight",
|
||||||
|
ArrowUp: "cursorUp",
|
||||||
|
ArrowDown: "cursorDown"
|
||||||
|
};
|
||||||
|
|
||||||
|
const isWebKitEngine = () => {
|
||||||
|
const userAgent = globalThis.navigator?.userAgent || "";
|
||||||
|
return userAgent.includes("AppleWebKit") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium");
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMonacoInputArea = (target) =>
|
||||||
|
target instanceof HTMLTextAreaElement && target.classList.contains("inputarea");
|
||||||
|
|
||||||
|
export const webKitTextAreaArrowCommand = (event) => {
|
||||||
|
if (event?.altKey || event?.ctrlKey || event?.metaKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = WEBKIT_TEXTAREA_ARROW_COMMANDS[event?.key];
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.shiftKey ? `${command}Select` : command;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bridgeWebKitTextAreaArrowKey = (editor, event) => {
|
||||||
|
if (!editor || !isMonacoInputArea(event.target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = webKitTextAreaArrowCommand(event);
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
editor.trigger("keyboard", command, { source: "keyboard" });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
export const MonacoEditor = {
|
export const MonacoEditor = {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
||||||
@@ -17,6 +63,10 @@ export const MonacoEditor = {
|
|||||||
this.isApplyingRemoteUpdate = false;
|
this.isApplyingRemoteUpdate = false;
|
||||||
this.lastKnownValue = this.textarea?.value || "";
|
this.lastKnownValue = this.textarea?.value || "";
|
||||||
|
|
||||||
|
this.handleWebKitTextAreaArrowKey = (event) => {
|
||||||
|
bridgeWebKitTextAreaArrowKey(this.editor, event);
|
||||||
|
};
|
||||||
|
|
||||||
this.syncEditorFromTextarea = () => {
|
this.syncEditorFromTextarea = () => {
|
||||||
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
||||||
|
|
||||||
@@ -246,6 +296,10 @@ export const MonacoEditor = {
|
|||||||
this.el.addEventListener("dragover", this.handleDragOver);
|
this.el.addEventListener("dragover", this.handleDragOver);
|
||||||
this.el.addEventListener("drop", this.handleDrop);
|
this.el.addEventListener("drop", this.handleDrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isWebKitEngine()) {
|
||||||
|
this.host.addEventListener("keydown", this.handleWebKitTextAreaArrowKey, true);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Failed to load Monaco editor", error);
|
console.error("Failed to load Monaco editor", error);
|
||||||
@@ -287,6 +341,8 @@ export const MonacoEditor = {
|
|||||||
this.el.removeEventListener("drop", this.handleDrop);
|
this.el.removeEventListener("drop", this.handleDrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.host?.removeEventListener("keydown", this.handleWebKitTextAreaArrowKey, true);
|
||||||
|
|
||||||
unregisterMonacoEditor(this.editorId || this.el.id);
|
unregisterMonacoEditor(this.editorId || this.el.id);
|
||||||
this.editor?.dispose();
|
this.editor?.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
41
priv/static/assets/app.js
vendored
41
priv/static/assets/app.js
vendored
@@ -9470,6 +9470,40 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
|
|||||||
};
|
};
|
||||||
|
|
||||||
// js/hooks/monaco_editor.js
|
// js/hooks/monaco_editor.js
|
||||||
|
var WEBKIT_TEXTAREA_ARROW_COMMANDS = {
|
||||||
|
ArrowLeft: "cursorLeft",
|
||||||
|
ArrowRight: "cursorRight",
|
||||||
|
ArrowUp: "cursorUp",
|
||||||
|
ArrowDown: "cursorDown"
|
||||||
|
};
|
||||||
|
var isWebKitEngine = () => {
|
||||||
|
const userAgent = globalThis.navigator?.userAgent || "";
|
||||||
|
return userAgent.includes("AppleWebKit") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium");
|
||||||
|
};
|
||||||
|
var isMonacoInputArea = (target) => target instanceof HTMLTextAreaElement && target.classList.contains("inputarea");
|
||||||
|
var webKitTextAreaArrowCommand = (event) => {
|
||||||
|
if (event?.altKey || event?.ctrlKey || event?.metaKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const command = WEBKIT_TEXTAREA_ARROW_COMMANDS[event?.key];
|
||||||
|
if (!command) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return event.shiftKey ? `${command}Select` : command;
|
||||||
|
};
|
||||||
|
var bridgeWebKitTextAreaArrowKey = (editor, event) => {
|
||||||
|
if (!editor || !isMonacoInputArea(event.target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const command = webKitTextAreaArrowCommand(event);
|
||||||
|
if (!command) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
editor.trigger("keyboard", command, { source: "keyboard" });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
var MonacoEditor = {
|
var MonacoEditor = {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
||||||
@@ -9481,6 +9515,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
|
|||||||
this.syncTimer = null;
|
this.syncTimer = null;
|
||||||
this.isApplyingRemoteUpdate = false;
|
this.isApplyingRemoteUpdate = false;
|
||||||
this.lastKnownValue = this.textarea?.value || "";
|
this.lastKnownValue = this.textarea?.value || "";
|
||||||
|
this.handleWebKitTextAreaArrowKey = (event) => {
|
||||||
|
bridgeWebKitTextAreaArrowKey(this.editor, event);
|
||||||
|
};
|
||||||
this.syncEditorFromTextarea = () => {
|
this.syncEditorFromTextarea = () => {
|
||||||
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
this.textarea = document.getElementById(this.el.dataset.monacoInputId) || this.el.querySelector("textarea");
|
||||||
if (!this.textarea || !this.editor) {
|
if (!this.textarea || !this.editor) {
|
||||||
@@ -9660,6 +9697,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
|
|||||||
this.el.addEventListener("dragover", this.handleDragOver);
|
this.el.addEventListener("dragover", this.handleDragOver);
|
||||||
this.el.addEventListener("drop", this.handleDrop);
|
this.el.addEventListener("drop", this.handleDrop);
|
||||||
}
|
}
|
||||||
|
if (isWebKitEngine()) {
|
||||||
|
this.host.addEventListener("keydown", this.handleWebKitTextAreaArrowKey, true);
|
||||||
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Failed to load Monaco editor", error);
|
console.error("Failed to load Monaco editor", error);
|
||||||
});
|
});
|
||||||
@@ -9691,6 +9731,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
|
|||||||
this.el.removeEventListener("dragover", this.handleDragOver);
|
this.el.removeEventListener("dragover", this.handleDragOver);
|
||||||
this.el.removeEventListener("drop", this.handleDrop);
|
this.el.removeEventListener("drop", this.handleDrop);
|
||||||
}
|
}
|
||||||
|
this.host?.removeEventListener("keydown", this.handleWebKitTextAreaArrowKey, true);
|
||||||
unregisterMonacoEditor(this.editorId || this.el.id);
|
unregisterMonacoEditor(this.editorId || this.el.id);
|
||||||
this.editor?.dispose();
|
this.editor?.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,6 +358,48 @@ defmodule BDS.UI.ShellTest do
|
|||||||
assert File.exists?("/Users/gb/Projects/bDS2/assets/js/monaco/languages.js")
|
assert File.exists?("/Users/gb/Projects/bDS2/assets/js/monaco/languages.js")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "monaco hook maps WebKit textarea arrow keys to cursor commands" do
|
||||||
|
script = """
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
class FakeTextArea {}
|
||||||
|
globalThis.HTMLTextAreaElement = FakeTextArea;
|
||||||
|
|
||||||
|
const { bridgeWebKitTextAreaArrowKey, webKitTextAreaArrowCommand } = await import("./assets/js/hooks/monaco_editor.js");
|
||||||
|
|
||||||
|
assert.equal(webKitTextAreaArrowCommand({ key: "ArrowLeft", shiftKey: false }), "cursorLeft");
|
||||||
|
assert.equal(webKitTextAreaArrowCommand({ key: "ArrowRight", shiftKey: false }), "cursorRight");
|
||||||
|
assert.equal(webKitTextAreaArrowCommand({ key: "ArrowUp", shiftKey: true }), "cursorUpSelect");
|
||||||
|
assert.equal(webKitTextAreaArrowCommand({ key: "ArrowDown", shiftKey: true }), "cursorDownSelect");
|
||||||
|
assert.equal(webKitTextAreaArrowCommand({ key: "ArrowLeft", altKey: true }), null);
|
||||||
|
assert.equal(webKitTextAreaArrowCommand({ key: "A", shiftKey: false }), null);
|
||||||
|
|
||||||
|
const target = new FakeTextArea();
|
||||||
|
target.classList = { contains: (className) => className === "inputarea" };
|
||||||
|
|
||||||
|
const calls = [];
|
||||||
|
const event = {
|
||||||
|
key: "ArrowLeft",
|
||||||
|
shiftKey: false,
|
||||||
|
target,
|
||||||
|
preventDefault: () => calls.push("preventDefault"),
|
||||||
|
stopPropagation: () => calls.push("stopPropagation")
|
||||||
|
};
|
||||||
|
const editor = { trigger: (...args) => calls.push(args) };
|
||||||
|
|
||||||
|
assert.equal(bridgeWebKitTextAreaArrowKey(editor, event), true);
|
||||||
|
assert.deepEqual(calls, ["preventDefault", "stopPropagation", ["keyboard", "cursorLeft", { source: "keyboard" }]]);
|
||||||
|
assert.equal(bridgeWebKitTextAreaArrowKey(editor, { ...event, target: {} }), false);
|
||||||
|
"""
|
||||||
|
|
||||||
|
{output, status} =
|
||||||
|
System.cmd("node", ["--input-type=module", "--eval", script],
|
||||||
|
cd: "/Users/gb/Projects/bDS2",
|
||||||
|
stderr_to_stdout: true
|
||||||
|
)
|
||||||
|
|
||||||
|
assert status == 0, output
|
||||||
|
end
|
||||||
|
|
||||||
test "monaco ESM build config emits served worker bundles" do
|
test "monaco ESM build config emits served worker bundles" do
|
||||||
mix_exs = File.read!("/Users/gb/Projects/bDS2/mix.exs")
|
mix_exs = File.read!("/Users/gb/Projects/bDS2/mix.exs")
|
||||||
config = File.read!("/Users/gb/Projects/bDS2/config/config.exs")
|
config = File.read!("/Users/gb/Projects/bDS2/config/config.exs")
|
||||||
|
|||||||
Reference in New Issue
Block a user