diff --git a/assets/js/hooks/cell_editor/live_editor.js b/assets/js/hooks/cell_editor/live_editor.js index 8b767723d..00f4b7826 100644 --- a/assets/js/hooks/cell_editor/live_editor.js +++ b/assets/js/hooks/cell_editor/live_editor.js @@ -45,7 +45,7 @@ import { settingsStore } from "../../lib/settings"; import Delta from "../../lib/delta"; import Markdown from "../../lib/markdown"; import { readOnlyHint } from "./live_editor/codemirror/read_only_hint"; -import { isMacOS, wait } from "../../lib/utils"; +import { isMacOS, wait, isSafari } from "../../lib/utils"; import Emitter from "../../lib/emitter"; import CollabClient from "./live_editor/collab_client"; import { languages } from "./live_editor/codemirror/languages"; @@ -397,6 +397,33 @@ export default class LiveEditor { selectionChangeListener, ], }); + + // In Safari, when a contenteditable element (here the editor) is + // blurred in favour of non-input element, typing something still + // targets the contenteditable element. This is a known bug [1], + // with a corresponding CodeMirror thread [2]. A workaround is to + // create and focus a temporary input, to properly remove focus + // from the contenteditable element. + // + // This is particularly annoying in our case, because the user may + // blur the editor and then use a letter shortcut, such as "n" for + // a new cell, which ends up typing in the editor. + // + // [1]: https://bugs.webkit.org/show_bug.cgi?id=112854 + // [2]: https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095 + if (isSafari()) { + this.view.contentDOM.addEventListener("blur", (event) => { + const input = document.createElement("input"); + input.style.cssText = + "width: 1px; height: 1px; border: none; margin: 0; padding: 0;"; + input.tabIndex = -1; + this.view.contentDOM.appendChild(input); + input.focus(); + input.setSelectionRange(0, 0); + input.blur(); + input.remove(); + }); + } } /** @private */ diff --git a/assets/package-lock.json b/assets/package-lock.json index 70b43abcf..8e38ec786 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -70,35 +70,55 @@ } }, "../deps/phoenix": { - "version": "1.7.20", - "license": "MIT" + "version": "1.8.0", + "license": "MIT", + "devDependencies": { + "@babel/cli": "7.28.0", + "@babel/core": "7.28.0", + "@babel/preset-env": "7.28.0", + "@eslint/js": "^9.28.0", + "@stylistic/eslint-plugin": "^5.0.0", + "documentation": "^14.0.3", + "eslint": "9.32.0", + "eslint-plugin-jest": "29.0.1", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", + "jsdom": "^26.1.0", + "mock-socket": "^9.3.1" + } }, "../deps/phoenix_html": { "version": "4.2.1" }, "../deps/phoenix_live_view": { - "version": "1.0.5", + "version": "1.1.11", "license": "MIT", "dependencies": { - "morphdom": "2.7.4" + "morphdom": "2.7.7" }, "devDependencies": { - "@babel/cli": "7.26.4", - "@babel/core": "7.26.0", - "@babel/preset-env": "7.26.0", - "@eslint/js": "^9.18.0", - "@playwright/test": "^1.49.1", - "@stylistic/eslint-plugin-js": "^2.12.1", + "@babel/cli": "7.27.2", + "@babel/core": "7.27.4", + "@babel/preset-env": "7.27.2", + "@babel/preset-typescript": "^7.27.1", + "@eslint/js": "^9.29.0", + "@playwright/test": "^1.53.0", + "@types/jest": "^30.0.0", + "@types/phoenix": "^1.6.6", "css.escape": "^1.5.1", - "eslint": "9.18.0", - "eslint-plugin-jest": "28.10.0", - "eslint-plugin-playwright": "^2.1.0", - "globals": "^15.14.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", + "eslint": "9.29.0", + "eslint-plugin-jest": "28.14.0", + "eslint-plugin-playwright": "^2.2.0", + "globals": "^16.2.0", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", "jest-monocart-coverage": "^1.1.1", - "monocart-reporter": "^2.9.13", - "phoenix": "1.7.18" + "monocart-reporter": "^2.9.21", + "phoenix": "1.7.21", + "prettier": "3.5.3", + "ts-jest": "^29.4.0", + "typescript": "^5.8.3", + "typescript-eslint": "^8.34.0" } }, "node_modules/@alloc/quick-lru": { @@ -4342,9 +4362,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001703", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz", - "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==", + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", "funding": [ { "type": "opencollective",