Firefox + SKKでonCompositionStart/Endを使ったReactのinputが正常に動かない
onCompositionEnd直下で直接状態更新を行うとReactの秘孔を突いて壊れてしまうので,Promise.resolve().then(microtask)程度でいいので遅らせてやるといいsetTimeout(fn,0)でちゃんとタスクキューに積んでやらないとだめになってそう
悪化してる,なんで?
??? < 本当に解決しているのか?
TODO: Reactがどうやってキーボードイベントをハンドリングして,どういう死に方をしているのかしらべる
textareaだと死なないらしい?(Scrapboxのエディタ部分はtextarea)
そんなことはない
Scrapboxのような,自分で手が入れられない場合は,次のUserScriptを参考にしてやるといい
code:Scrapboxの検索欄に入力できるようにする.user.js
'use strict';
async function waitFor(selector) {
while(true) {
const el = document.querySelector(selector)
if(el != null) return el
await new Promise(r => requestAnimationFrame(r))
}
}
waitFor('.navbar input.form-control').then(el => {
const seen = new Set()
el.addEventListener('compositionend', ev => {
if(seen.has(ev)) return
ev.stopPropagation()
seen.add(ev)
Promise.resolve().then(() => {
el.dispatchEvent(ev)
setTimeout(() => {
seen.delete(ev)
}, 1000)
})
}, { capture: true })
})
https://gist.github.com/tosuke/b792424ddf645c040623b83338bd8c06
ほんの僅かだけ遅らせて再送出するようにしている
きっかけ
Firefox + Linux + ibusでScrapboxの検索が効かない
fcitx5でも起きた
macOS(+AquaSKK)でも同様の現象が起きて、「露払後確定」を有効にしたら正常動作するようになった
実は正常動作してない.確定時に▼が残る
Windowsでも起きてる
https://tweet-card.now.sh/1395677454736388097.png?lang=ja&theme=dark https://twitter.com/rokoucha/status/1395677454736388097
おそらくSKK特有のキーボードイベントの送出パターンがあって,それがうまく動いてない
なんでやって思って検索ボックスの実装を見ると,composition{start,end}が使われている.ScrapboxはReactを使っているので,Reactを使って同等のコードを書いてみる.
https://esnip.dev/play/34136b2c-fc71-4bb7-9e07-69c12e45357b
なんかガチャガチャやってたらonCompositionUpdate={onCompositionEnd}のときだけうまく動く
確かに動かないので,これはScrapbox特有の問題ではなくReactアプリならどこでも起きる問題みたいだ
話がデカくなってきた
とりあえずちゃんと動いてるChromeとFirefoxで投げられてるイベントを比較して色々試しています
https://esnip.dev/play/0d923751-a4b6-4341-8c41-48ee10e10251
rokoucha.icon < これほとんど同じじゃね?
あとはReact本体のコードを読んでいく
ChromeやFirefoxなどの特定のブラウザを特別扱いするコードは,IE8,9以外には特になかった
なんとなく思いついて,refを使ってuseEffect内で直接composition{start,end}を購読して,onComposition{Start,End}でやってる処理をやってみた→入力できないまま
もしかして更新が悪い?って思ってその中でやってる状態更新を止めると入力できるようになった
Firefoxの実装の関係で,Reactの秘孔を突くタイミングでcompositionendが来てしまってるみたい
とりあえずstopPropagationしてみる
→ちゃんと動く
onCompositionUpdate={onCompositionEnd}でちゃんと動いたのは,compositionendでの状態更新が再レンダーを引き起こさなかったから
rokoucha.iconがイベントの並びは同じだと調べてくれたのもよかった.問題なのはイベントの並びじゃなくてタイミングなのだと気付けた