diff --git a/demo/demo.ts b/demo/demo.ts index 3cee56ab..9e0fb067 100644 --- a/demo/demo.ts +++ b/demo/demo.ts @@ -11,7 +11,7 @@ import { schema as baseSchema } from 'prosemirror-schema-basic'; import { keymap } from 'prosemirror-keymap'; import { exampleSetup, buildMenuItems } from 'prosemirror-example-setup'; import { MenuItem, Dropdown } from 'prosemirror-menu'; - +import { imeSpan } from '../src/ime'; import { addColumnAfter, addColumnBefore, @@ -89,6 +89,7 @@ let state = EditorState.create({ Tab: goToNextCell(1), 'Shift-Tab': goToNextCell(-1), }), + imeSpan, ].concat( exampleSetup({ schema, diff --git a/package.json b/package.json index 1ffec0ec..a06abf1d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "dependencies": { "prosemirror-keymap": "^1.1.2", "prosemirror-model": "^1.8.1", + "prosemirror-safari-ime-span": "^1.0.1", "prosemirror-state": "^1.3.1", "prosemirror-transform": "^1.2.1", "prosemirror-view": "^1.13.3" diff --git a/src/ime/browser.ts b/src/ime/browser.ts new file mode 100644 index 00000000..df26c5d1 --- /dev/null +++ b/src/ime/browser.ts @@ -0,0 +1,12 @@ +// Copied from https://github.com/prosemirror/prosemirror-view/blob/1.33.8/src/browser.ts + +const nav = typeof navigator != 'undefined' ? navigator : null; +const agent = (nav && nav.userAgent) || ''; + +const ie_edge = /Edge\/(\d+)/.exec(agent); +const ie_upto10 = /MSIE \d/.exec(agent); +const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent); + +const ie = !!(ie_upto10 || ie_11up || ie_edge); + +export const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor); diff --git a/src/ime/index.ts b/src/ime/index.ts new file mode 100644 index 00000000..765531ae --- /dev/null +++ b/src/ime/index.ts @@ -0,0 +1,55 @@ +import { + Plugin, + PluginKey, + type PluginSpec, + type EditorState, +} from 'prosemirror-state'; +import { Decoration, DecorationSet, type EditorView } from 'prosemirror-view'; + +import { safari } from './browser'; + +const key = new PluginKey('safari-ime-span'); + +let isComposing = false; + +const spec: PluginSpec = { + key, + props: { + decorations: createDecorations, + handleDOMEvents: { + compositionstart: () => { + isComposing = true; + }, + compositionend: () => { + isComposing = false; + }, + }, + }, +}; + +function createDecorations(state: EditorState): DecorationSet | undefined { + const { $from, $to, to } = state.selection; + if (isComposing && $from.sameParent($to)) { + const deco = Decoration.widget(to, createSpan, { + ignoreSelection: true, + key: 'safari-ime-span', + }); + return DecorationSet.create(state.doc, [deco]); + } +} + +function createSpan(view: EditorView): HTMLSpanElement { + const span = view.dom.ownerDocument.createElement('span'); + span.className = 'ProseMirror-safari-ime-span'; + return span; +} + +/** + * A plugin as a workaround for a bug in Safari that causes the composition + * based IME to remove the empty HTML element with CSS `position: relative`. + * + * See also https://github.com/ProseMirror/prosemirror/issues/934 + * + * @public @group Plugins + */ +export const imeSpan = new Plugin(safari ? spec : { key }); diff --git a/yarn.lock b/yarn.lock index 487061e6..d40eb23c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1504,13 +1504,21 @@ prosemirror-menu@^1.0.0, prosemirror-menu@^1.2.2: prosemirror-history "^1.0.0" prosemirror-state "^1.0.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1: - version "1.19.2" - resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.2.tgz#297c9ecfb103154e605f0dbaf3cc72ee32ca0ad5" - integrity sha512-RXl0Waiss4YtJAUY3NzKH0xkJmsZupCIccqcIFoLTIKFlKNbIvFDRl27/kQy1FP8iUAxrjRRfIVvOebnnXJgqQ== +prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.20.0, prosemirror-model@^1.8.1: + version "1.21.3" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.21.3.tgz#97fa434d670331c1ab25f75964b1bcd7a948ce61" + integrity sha512-nt2Xs/RNGepD9hrrkzXvtCm1mpGJoQfFSPktGa0BF/aav6XsnmVGZ9sTXNWRLupAz5SCLa3EyKlFeK7zJWROKg== dependencies: orderedmap "^2.0.0" +prosemirror-safari-ime-span@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prosemirror-safari-ime-span/-/prosemirror-safari-ime-span-1.0.1.tgz#cd4b09fda12ce07b8b5877a11808c8f141bc88ff" + integrity sha512-4WjIR6d5HfKJ5JP4UK7SOzV7aOMqy8Q3b09IzGoFDPWA2GdeNWls6zxO37g4Ny3DRhoYg0hFaXu7JGpzzUlprw== + dependencies: + prosemirror-state "^1.4.3" + prosemirror-view "^1.33.8" + prosemirror-schema-basic@^1.0.0, prosemirror-schema-basic@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7" @@ -1527,7 +1535,7 @@ prosemirror-schema-list@^1.0.0: prosemirror-state "^1.0.0" prosemirror-transform "^1.7.3" -prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1: +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.3.tgz#94aecf3ffd54ec37e87aa7179d13508da181a080" integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q== @@ -1552,12 +1560,12 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor dependencies: prosemirror-model "^1.0.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0: - version "1.31.4" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.31.4.tgz#d9a40363bf517605f77a5cfe6c0106958a7dbe5e" - integrity sha512-nJzH2LpYbonSTYFqQ1BUdEhbd1WPN/rp/K9T9qxBEYpgg3jK3BvEUCR45Ymc9IHpO0m3nBJwPm19RBxZdoBVuw== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.33.8: + version "1.33.8" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.33.8.tgz#cfd76dff421730cbca0b6ea40ce36994daaeda41" + integrity sha512-4PhMr/ufz2cdvFgpUAnZfs+0xij3RsFysreeG9V/utpwX7AJtYCDVyuRxzWoMJIEf4C7wVihuBNMPpFLPCiLQw== dependencies: - prosemirror-model "^1.16.0" + prosemirror-model "^1.20.0" prosemirror-state "^1.0.0" prosemirror-transform "^1.1.0"