Created
November 3, 2025 07:27
-
-
Save zengxs/eb9ac78878daae1bb375bd3c2427ff65 to your computer and use it in GitHub Desktop.
A polyfill to fix input method (IME) problems for Avalonia apps running on WebAssembly in macOS browsers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function () { | |
| // 仅在 Apple 浏览器上启用 | |
| if (!navigator.userAgent.match(/(macintosh|macintel|macppc|mac68k|macos)/i)) return; | |
| let stateMap = new WeakMap(); | |
| function getState(el) { | |
| let s = stateMap.get(el); | |
| if (!s) { | |
| s = {composing: false, lastData: null}; | |
| stateMap.set(el, s); | |
| } | |
| return s; | |
| } | |
| function isAvaloniaImeElement(t) { | |
| return t instanceof HTMLElement && t.classList && t.classList.contains('avalonia-input-element'); | |
| } | |
| function isPrintableKey(e) { | |
| return typeof e.key === 'string' && e.key.length === 1; | |
| } | |
| function isDeleteKey(e) { | |
| return e.key === 'Backspace' || e.key === 'Delete'; | |
| } | |
| function handleCompositionStart(e) { | |
| if (!isAvaloniaImeElement(e.target)) return; | |
| let st = getState(e.target); | |
| st.composing = true; | |
| st.lastData = null; | |
| } | |
| function handleBeforeInput(e) { | |
| if (!isAvaloniaImeElement(e.target)) return; | |
| let st = getState(e.target); | |
| let it = e.inputType || ''; | |
| let data = (typeof e.data === 'string') ? e.data : ''; | |
| // 统一用 beforeinput 的插入型 inputType 提交字符 | |
| if ((it === 'insertText' || it === 'insertFromComposition' || it === 'insertReplacementText') && data) { | |
| st.lastData = data; | |
| queueMicrotask(function () { | |
| let cur = getState(e.target); | |
| let commit = cur.lastData || ''; | |
| let ev = new CompositionEvent('compositionend', {data: commit, bubbles: true, cancelable: true}); | |
| e.target.dispatchEvent(ev); | |
| // 结束(或保持非组合)状态,清理缓存,避免重复提交 | |
| cur.composing = false; | |
| cur.lastData = null; | |
| }); | |
| } | |
| // 删除型 inputType(例如 deleteCompositionText)无需我们做额外动作, | |
| // 浏览器会通过 compositionupdate 更新预编辑文本;关键是不要让 keydown 传到 Avalonia。 | |
| } | |
| function handleCompositionEnd(e) { | |
| if (!isAvaloniaImeElement(e.target)) return; | |
| let st = getState(e.target); | |
| // 无论原生还是合成的 end,统一清理状态,交由 Avalonia 监听器处理提交 | |
| st.composing = false; | |
| st.lastData = null; | |
| } | |
| // 捕获阶段拦截 keydown:避免首字符泄漏;组合中也拦截删除键,避免删实际内容 | |
| function keydownCapture(e) { | |
| let active = document.activeElement; | |
| if (!isAvaloniaImeElement(active)) return; | |
| // 有 Ctrl/Alt/Meta 时不拦截,保留快捷键 | |
| if (e.ctrlKey || e.altKey || e.metaKey) return; | |
| let st = getState(active); | |
| // 1) 始终拦截可打印字符,避免 Avalonia 的 RawTextEvent 回退(首键泄漏) | |
| if (isPrintableKey(e)) { | |
| e.stopImmediatePropagation(); | |
| return; | |
| } | |
| // 2) 仅在组合进行中,拦截删除键,避免删实际内容;让 IME 自己处理预编辑删除 | |
| if (st.composing && isDeleteKey(e)) { | |
| e.stopImmediatePropagation(); | |
| return; | |
| } | |
| } | |
| function attach(root) { | |
| root.addEventListener('compositionstart', handleCompositionStart, true); | |
| root.addEventListener('beforeinput', handleBeforeInput, true); | |
| root.addEventListener('compositionend', handleCompositionEnd, true); | |
| root.addEventListener('keydown', keydownCapture, true); | |
| } | |
| function waitAndAttach() { | |
| if (document.querySelector('.avalonia-input-element')) { | |
| attach(document); | |
| return; | |
| } | |
| let obs = new MutationObserver(function () { | |
| if (document.querySelector('.avalonia-input-element')) { | |
| try { | |
| attach(document); | |
| } finally { | |
| obs.disconnect(); | |
| } | |
| } | |
| }); | |
| obs.observe(document.documentElement, {childList: true, subtree: true}); | |
| } | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', waitAndAttach, {once: true}); | |
| } else { | |
| waitAndAttach(); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment