@@ -735,6 +735,11 @@ class InputGuard {
735735 */
736736 this . beforeInputSnapshot = null ;
737737
738+ /**
739+ * onBeforeInput イベントが発生したか否か
740+ */
741+ this . existBeforeInputEvent = false ;
742+
738743 /**
739744 * ルールからのrevert要求
740745 * @type {RevertRequest|null }
@@ -991,12 +996,12 @@ class InputGuard {
991996 */
992997 createCtx ( { useSnapshot = true } = { } ) {
993998 const snap = useSnapshot ? this . beforeInputSnapshot : null ;
994- const inputType = snap ?. inputType ?? "" ;
995- const insertedText = snap ?. insertedText ?? "" ;
999+ let inputType = snap ?. inputType ?? "" ;
1000+ let insertedText = snap ?. insertedText ?? "" ;
9961001
9971002 // 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
9981003 // display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
999- const beforeText = this . lastAcceptedValue ?? "" ;
1004+ let beforeText = this . lastAcceptedValue ?? "" ;
10001005
10011006 // selection は2系統ある:
10021007 // - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
@@ -1032,6 +1037,38 @@ class InputGuard {
10321037 baseSel = lastSel ;
10331038 }
10341039
1040+ // beforeinput がない環境では、差分再構成の基準が「前回の受理値」しかないため、そこから今回の編集内容を推測する必要がある。
1041+ if ( beforeText . length === 0 || ! this . existBeforeInputEvent ) {
1042+ const display = /** @type {HTMLInputElement|HTMLTextAreaElement } */ ( this . displayElement ) ;
1043+ const current = display . value ;
1044+ // 前回の値がとれないものの、何かしら入力情報がある状態
1045+ if ( current . length > 0 ) {
1046+ // 文字列の先頭が前回の受理値と同じなら、末尾に何かしら入力されたと考えられる(オートコンプリート等)
1047+ if ( current . toLocaleLowerCase ( ) . startsWith ( beforeText . toLocaleLowerCase ( ) ) ) {
1048+ if ( ! current . startsWith ( beforeText ) ) {
1049+ // 文字は同じだが、大文字と小文字の情報が替わっているなどのパターン
1050+ // 差し代わりが起きているため、前回値は基準にならないと判断して、差分全体を insertedText とする
1051+ beforeText = "" ;
1052+ insertedText = current ;
1053+ } else {
1054+ // 末尾に追加されたと考えられる部分を insertedText とする
1055+ // 例: beforeText="abc" → current="abcde" なら、"de" が insertedText
1056+ insertedText = current . slice ( beforeText . length ) ;
1057+ }
1058+ // キャレットは前回値の末尾にあると推測する
1059+ baseSel = /** @type {SelectionState } */ {
1060+ start : beforeText . length ,
1061+ end : beforeText . length ,
1062+ direction : "none"
1063+ } ;
1064+ inputType = "insertText" ;
1065+ }
1066+ }
1067+ }
1068+ // existBeforeInputEvent は、少なくとも1回 beforeinput が発生したかどうかのフラグ
1069+ // これが false の場合、上記のような「beforeinputがない環境での推測ロジック」を走らせる。
1070+ this . existBeforeInputEvent = false ;
1071+
10351072 let replaceStart = baseSel . start ?? 0 ;
10361073 let replaceEnd = baseSel . end ?? 0 ;
10371074
@@ -1257,9 +1294,7 @@ class InputGuard {
12571294 try {
12581295 this . evaluateInput ( ) ;
12591296 } finally {
1260- // beforeinput が来ない入力経路(autocomplete等)で
1261- // 古い snapshot を使い回さないよう、1イベントごとに破棄する
1262- this . beforeInputSnapshot = null ;
1297+ this . existBeforeInputEvent = false ;
12631298 }
12641299 }
12651300
@@ -1280,6 +1315,7 @@ class InputGuard {
12801315 const inputType = typeof e . inputType === "string" ? e . inputType : null ;
12811316 /** @type {string|null } */
12821317 const insertedText = typeof e . data === "string" ? e . data : null ;
1318+ this . existBeforeInputEvent = true ;
12831319 this . beforeInputSnapshot = { selection, inputType, insertedText } ;
12841320 }
12851321
@@ -1419,45 +1455,6 @@ class InputGuard {
14191455 const ctx = this . createCtx ( ) ;
14201456 ctx . afterText = current ;
14211457
1422- /**
1423- * 入力値情報のみを使用するフォールバック
1424- * @returns {GuardContext }
1425- */
1426- const applyFullNormalizeFromCurrent = ( ) => {
1427- let newText = current ;
1428- ctx . beforeText = "" ;
1429- newText = this . runNormalizeChar ( newText , ctx ) ;
1430- newText = this . runNormalizeStructure ( newText , ctx ) ;
1431- this . setDisplayValuePreserveCaret ( display , newText , ctx ) ;
1432- ctx . afterText = newText ;
1433- return ctx ;
1434- } ;
1435-
1436- // beforeinput が取得できない経路(初回評価)では
1437- // 差分再構成を行うと lastAcceptedValue 基準で値を落とす可能性があるため、
1438- // 現在の全文を正規化して扱うフォールバックへ切り替える。
1439- if ( ! this . beforeInputSnapshot ) {
1440- return applyFullNormalizeFromCurrent ( ) ;
1441- }
1442-
1443- // オートコンプリート等では beforeinput は来ても data が空のことがあり、
1444- // 差分情報だけでは再構成不能になる。表示値がすでに変わっている場合は
1445- // 再構成を諦めて current 全体の正規化に切り替える。
1446- const isDeleteInput =
1447- ctx . inputType === "deleteContentBackward" ||
1448- ctx . inputType === "deleteContentForward" ;
1449- const isInsertLikeInput =
1450- ctx . inputType === "" ||
1451- ctx . inputType ?. startsWith ( "insert" ) ;
1452- const lacksDelta =
1453- ctx . insertedText === "" &&
1454- ctx . beforeText !== current &&
1455- isInsertLikeInput &&
1456- ! isDeleteInput ;
1457- if ( lacksDelta ) {
1458- return applyFullNormalizeFromCurrent ( ) ;
1459- }
1460-
14611458 // 元のテキスト
14621459 const beforeText = ctx . beforeText ;
14631460
@@ -7489,10 +7486,10 @@ const rules = {
74897486
74907487/**
74917488 * バージョン(ビルド時に置換したいならここを差し替える)
7492- * 例: rollup replace で ""1.0.1 "" を package.json の version に置換
7489+ * 例: rollup replace で ""1.0.2 "" を package.json の version に置換
74937490 */
74947491// @ts -ignore
74957492// eslint-disable-next-line no-undef
7496- const version = "1.0.1 " ;
7493+ const version = "1.0.2 " ;
74977494
74987495export { ascii , attach , attachAll , autoAttach , bytes , comma , digits , filter , imeOff , kana , length , numeric , prefix , rules , suffix , trim , version , width } ;
0 commit comments