text-input-guard 1.0.0 → 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.
@@ -114,11 +114,16 @@ class SwapState {
114
114
 
115
115
  const UI_ATTRS = [
116
116
  "placeholder",
117
+ "list",
117
118
  "inputmode",
118
119
  "autocomplete",
120
+ "autocapitalize",
121
+ "autocorrect",
119
122
  "minlength",
120
123
  "maxlength",
124
+ "size",
121
125
  "pattern",
126
+ "dir",
122
127
  "title",
123
128
  "tabindex",
124
129
  "style",
@@ -150,6 +155,7 @@ class SwapState {
150
155
 
151
156
  for (const [k, v] of Object.entries(input.dataset)) {
152
157
  if (k.startsWith("tig")) { continue; }
158
+ if (v == null) { continue; }
153
159
  this.originalDataset[k] = v;
154
160
  }
155
161
  }
@@ -570,7 +576,7 @@ class InputGuard {
570
576
 
571
577
  /**
572
578
  * attach時に登録されたバリデーション結果コールバック
573
- * @type {(result: ValidateResult) => void | undefined}
579
+ * @type {((result: ValidateResult) => void) | undefined}
574
580
  */
575
581
  this.onValidate = options.onValidate;
576
582
 
@@ -731,6 +737,11 @@ class InputGuard {
731
737
  */
732
738
  this.beforeInputSnapshot = null;
733
739
 
740
+ /**
741
+ * onBeforeInput イベントが発生したか否か
742
+ */
743
+ this.existBeforeInputEvent = false;
744
+
734
745
  /**
735
746
  * ルールからのrevert要求
736
747
  * @type {RevertRequest|null}
@@ -748,7 +759,7 @@ class InputGuard {
748
759
  this.applySeparateValue();
749
760
  this.bindEvents();
750
761
  // 初期値を評価
751
- this.evaluateInput();
762
+ this.evaluateCommit();
752
763
  }
753
764
 
754
765
  /**
@@ -987,12 +998,12 @@ class InputGuard {
987
998
  */
988
999
  createCtx({ useSnapshot = true } = {}) {
989
1000
  const snap = useSnapshot ? this.beforeInputSnapshot : null;
990
- const inputType = snap?.inputType ?? "";
991
- const insertedText = snap?.insertedText ?? "";
1001
+ let inputType = snap?.inputType ?? "";
1002
+ let insertedText = snap?.insertedText ?? "";
992
1003
 
993
1004
  // 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
994
1005
  // display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
995
- const beforeText = this.lastAcceptedValue ?? "";
1006
+ let beforeText = this.lastAcceptedValue ?? "";
996
1007
 
997
1008
  // selection は2系統ある:
998
1009
  // - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
@@ -1028,6 +1039,38 @@ class InputGuard {
1028
1039
  baseSel = lastSel;
1029
1040
  }
1030
1041
 
1042
+ // beforeinput がない環境では、差分再構成の基準が「前回の受理値」しかないため、そこから今回の編集内容を推測する必要がある。
1043
+ if (beforeText.length === 0 || !this.existBeforeInputEvent) {
1044
+ const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1045
+ const current = display.value;
1046
+ // 前回の値がとれないものの、何かしら入力情報がある状態
1047
+ if (current.length > 0) {
1048
+ // 文字列の先頭が前回の受理値と同じなら、末尾に何かしら入力されたと考えられる(オートコンプリート等)
1049
+ if (current.toLocaleLowerCase().startsWith(beforeText.toLocaleLowerCase())) {
1050
+ if (!current.startsWith(beforeText)) {
1051
+ // 文字は同じだが、大文字と小文字の情報が替わっているなどのパターン
1052
+ // 差し代わりが起きているため、前回値は基準にならないと判断して、差分全体を insertedText とする
1053
+ beforeText = "";
1054
+ insertedText = current;
1055
+ } else {
1056
+ // 末尾に追加されたと考えられる部分を insertedText とする
1057
+ // 例: beforeText="abc" → current="abcde" なら、"de" が insertedText
1058
+ insertedText = current.slice(beforeText.length);
1059
+ }
1060
+ // キャレットは前回値の末尾にあると推測する
1061
+ baseSel = /** @type {SelectionState} */ {
1062
+ start: beforeText.length,
1063
+ end: beforeText.length,
1064
+ direction: "none"
1065
+ };
1066
+ inputType = "insertText";
1067
+ }
1068
+ }
1069
+ }
1070
+ // existBeforeInputEvent は、少なくとも1回 beforeinput が発生したかどうかのフラグ
1071
+ // これが false の場合、上記のような「beforeinputがない環境での推測ロジック」を走らせる。
1072
+ this.existBeforeInputEvent = false;
1073
+
1031
1074
  let replaceStart = baseSel.start ?? 0;
1032
1075
  let replaceEnd = baseSel.end ?? 0;
1033
1076
 
@@ -1040,10 +1083,10 @@ class InputGuard {
1040
1083
  if (inputType === "deleteContentBackward") {
1041
1084
  // Backspace: キャレットの左側1文字を削除
1042
1085
  replaceStart = Math.max(0, replaceStart - 1);
1043
- replaceEnd = snapSel.start ?? replaceEnd;
1086
+ replaceEnd = snapSel?.start ?? replaceEnd;
1044
1087
  } else if (inputType === "deleteContentForward") {
1045
1088
  // Delete: キャレットの右側1文字を削除
1046
- replaceStart = snapSel.start ?? replaceStart;
1089
+ replaceStart = snapSel?.start ?? replaceStart;
1047
1090
  replaceEnd = Math.min(beforeText.length, replaceEnd + 1);
1048
1091
  }
1049
1092
  // 追加で拾うならここ:
@@ -1250,16 +1293,23 @@ class InputGuard {
1250
1293
  // console.log("[text-input-guard] input");
1251
1294
  // compositionend後に input が来た場合、フォールバックを無効化
1252
1295
  this.pendingCompositionCommit = false;
1253
- this.evaluateInput();
1296
+ try {
1297
+ this.evaluateInput();
1298
+ } finally {
1299
+ this.existBeforeInputEvent = false;
1300
+ }
1254
1301
  }
1255
1302
 
1256
1303
  /**
1257
1304
  * beforeinput:入力が反映される直前に呼ばれる
1258
1305
  * - ここでの value/selection が「今回の編集の基準点」になる
1259
- * @param {InputEvent} e
1306
+ * @param {Event} e
1260
1307
  * @returns {void}
1261
1308
  */
1262
1309
  onBeforeInput(e) {
1310
+ if (!(e instanceof InputEvent)) {
1311
+ return;
1312
+ }
1263
1313
  const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1264
1314
  // 現時点(反映前)の選択範囲
1265
1315
  const selection = this.readSelection(el);
@@ -1267,6 +1317,7 @@ class InputGuard {
1267
1317
  const inputType = typeof e.inputType === "string" ? e.inputType : null;
1268
1318
  /** @type {string|null} */
1269
1319
  const insertedText = typeof e.data === "string" ? e.data : null;
1320
+ this.existBeforeInputEvent = true;
1270
1321
  this.beforeInputSnapshot = { selection, inputType, insertedText };
1271
1322
  }
1272
1323
 
@@ -7437,11 +7488,11 @@ const rules = {
7437
7488
 
7438
7489
  /**
7439
7490
  * バージョン(ビルド時に置換したいならここを差し替える)
7440
- * 例: rollup replace で ""1.0.0"" を package.json の version に置換
7491
+ * 例: rollup replace で ""1.0.2"" を package.json の version に置換
7441
7492
  */
7442
7493
  // @ts-ignore
7443
7494
  // eslint-disable-next-line no-undef
7444
- const version = "1.0.0" ;
7495
+ const version = "1.0.2" ;
7445
7496
 
7446
7497
  exports.ascii = ascii;
7447
7498
  exports.attach = attach;