text-input-guard 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -741,6 +741,11 @@
741
741
  */
742
742
  this.beforeInputSnapshot = null;
743
743
 
744
+ /**
745
+ * onBeforeInput イベントが発生したか否か
746
+ */
747
+ this.existBeforeInputEvent = false;
748
+
744
749
  /**
745
750
  * ルールからのrevert要求
746
751
  * @type {RevertRequest|null}
@@ -997,12 +1002,12 @@
997
1002
  */
998
1003
  createCtx({ useSnapshot = true } = {}) {
999
1004
  const snap = useSnapshot ? this.beforeInputSnapshot : null;
1000
- const inputType = snap?.inputType ?? "";
1001
- const insertedText = snap?.insertedText ?? "";
1005
+ let inputType = snap?.inputType ?? "";
1006
+ let insertedText = snap?.insertedText ?? "";
1002
1007
 
1003
1008
  // 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
1004
1009
  // display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
1005
- const beforeText = this.lastAcceptedValue ?? "";
1010
+ let beforeText = this.lastAcceptedValue ?? "";
1006
1011
 
1007
1012
  // selection は2系統ある:
1008
1013
  // - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
@@ -1038,6 +1043,38 @@
1038
1043
  baseSel = lastSel;
1039
1044
  }
1040
1045
 
1046
+ // beforeinput がない環境では、差分再構成の基準が「前回の受理値」しかないため、そこから今回の編集内容を推測する必要がある。
1047
+ if (beforeText.length === 0 || !this.existBeforeInputEvent) {
1048
+ const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1049
+ const current = display.value;
1050
+ // 前回の値がとれないものの、何かしら入力情報がある状態
1051
+ if (current.length > 0) {
1052
+ // 文字列の先頭が前回の受理値と同じなら、末尾に何かしら入力されたと考えられる(オートコンプリート等)
1053
+ if (current.toLocaleLowerCase().startsWith(beforeText.toLocaleLowerCase())) {
1054
+ if (!current.startsWith(beforeText)) {
1055
+ // 文字は同じだが、大文字と小文字の情報が替わっているなどのパターン
1056
+ // 差し代わりが起きているため、前回値は基準にならないと判断して、差分全体を insertedText とする
1057
+ beforeText = "";
1058
+ insertedText = current;
1059
+ } else {
1060
+ // 末尾に追加されたと考えられる部分を insertedText とする
1061
+ // 例: beforeText="abc" → current="abcde" なら、"de" が insertedText
1062
+ insertedText = current.slice(beforeText.length);
1063
+ }
1064
+ // キャレットは前回値の末尾にあると推測する
1065
+ baseSel = /** @type {SelectionState} */ {
1066
+ start: beforeText.length,
1067
+ end: beforeText.length,
1068
+ direction: "none"
1069
+ };
1070
+ inputType = "insertText";
1071
+ }
1072
+ }
1073
+ }
1074
+ // existBeforeInputEvent は、少なくとも1回 beforeinput が発生したかどうかのフラグ
1075
+ // これが false の場合、上記のような「beforeinputがない環境での推測ロジック」を走らせる。
1076
+ this.existBeforeInputEvent = false;
1077
+
1041
1078
  let replaceStart = baseSel.start ?? 0;
1042
1079
  let replaceEnd = baseSel.end ?? 0;
1043
1080
 
@@ -1263,9 +1300,7 @@
1263
1300
  try {
1264
1301
  this.evaluateInput();
1265
1302
  } finally {
1266
- // beforeinput が来ない入力経路(autocomplete等)で
1267
- // 古い snapshot を使い回さないよう、1イベントごとに破棄する
1268
- this.beforeInputSnapshot = null;
1303
+ this.existBeforeInputEvent = false;
1269
1304
  }
1270
1305
  }
1271
1306
 
@@ -1286,6 +1321,7 @@
1286
1321
  const inputType = typeof e.inputType === "string" ? e.inputType : null;
1287
1322
  /** @type {string|null} */
1288
1323
  const insertedText = typeof e.data === "string" ? e.data : null;
1324
+ this.existBeforeInputEvent = true;
1289
1325
  this.beforeInputSnapshot = { selection, inputType, insertedText };
1290
1326
  }
1291
1327
 
@@ -1425,45 +1461,6 @@
1425
1461
  const ctx = this.createCtx();
1426
1462
  ctx.afterText = current;
1427
1463
 
1428
- /**
1429
- * 入力値情報のみを使用するフォールバック
1430
- * @returns {GuardContext}
1431
- */
1432
- const applyFullNormalizeFromCurrent = () => {
1433
- let newText = current;
1434
- ctx.beforeText = "";
1435
- newText = this.runNormalizeChar(newText, ctx);
1436
- newText = this.runNormalizeStructure(newText, ctx);
1437
- this.setDisplayValuePreserveCaret(display, newText, ctx);
1438
- ctx.afterText = newText;
1439
- return ctx;
1440
- };
1441
-
1442
- // beforeinput が取得できない経路(初回評価)では
1443
- // 差分再構成を行うと lastAcceptedValue 基準で値を落とす可能性があるため、
1444
- // 現在の全文を正規化して扱うフォールバックへ切り替える。
1445
- if (!this.beforeInputSnapshot) {
1446
- return applyFullNormalizeFromCurrent();
1447
- }
1448
-
1449
- // オートコンプリート等では beforeinput は来ても data が空のことがあり、
1450
- // 差分情報だけでは再構成不能になる。表示値がすでに変わっている場合は
1451
- // 再構成を諦めて current 全体の正規化に切り替える。
1452
- const isDeleteInput =
1453
- ctx.inputType === "deleteContentBackward" ||
1454
- ctx.inputType === "deleteContentForward";
1455
- const isInsertLikeInput =
1456
- ctx.inputType === "" ||
1457
- ctx.inputType?.startsWith("insert");
1458
- const lacksDelta =
1459
- ctx.insertedText === "" &&
1460
- ctx.beforeText !== current &&
1461
- isInsertLikeInput &&
1462
- !isDeleteInput;
1463
- if (lacksDelta) {
1464
- return applyFullNormalizeFromCurrent();
1465
- }
1466
-
1467
1464
  // 元のテキスト
1468
1465
  const beforeText = ctx.beforeText;
1469
1466
 
@@ -7495,11 +7492,11 @@
7495
7492
 
7496
7493
  /**
7497
7494
  * バージョン(ビルド時に置換したいならここを差し替える)
7498
- * 例: rollup replace で ""1.0.1"" を package.json の version に置換
7495
+ * 例: rollup replace で ""1.0.2"" を package.json の version に置換
7499
7496
  */
7500
7497
  // @ts-ignore
7501
7498
  // eslint-disable-next-line no-undef
7502
- const version = "1.0.1" ;
7499
+ const version = "1.0.2" ;
7503
7500
 
7504
7501
  exports.ascii = ascii;
7505
7502
  exports.attach = attach;