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.
@@ -112,11 +112,16 @@ class SwapState {
112
112
 
113
113
  const UI_ATTRS = [
114
114
  "placeholder",
115
+ "list",
115
116
  "inputmode",
116
117
  "autocomplete",
118
+ "autocapitalize",
119
+ "autocorrect",
117
120
  "minlength",
118
121
  "maxlength",
122
+ "size",
119
123
  "pattern",
124
+ "dir",
120
125
  "title",
121
126
  "tabindex",
122
127
  "style",
@@ -148,6 +153,7 @@ class SwapState {
148
153
 
149
154
  for (const [k, v] of Object.entries(input.dataset)) {
150
155
  if (k.startsWith("tig")) { continue; }
156
+ if (v == null) { continue; }
151
157
  this.originalDataset[k] = v;
152
158
  }
153
159
  }
@@ -568,7 +574,7 @@ class InputGuard {
568
574
 
569
575
  /**
570
576
  * attach時に登録されたバリデーション結果コールバック
571
- * @type {(result: ValidateResult) => void | undefined}
577
+ * @type {((result: ValidateResult) => void) | undefined}
572
578
  */
573
579
  this.onValidate = options.onValidate;
574
580
 
@@ -729,6 +735,11 @@ class InputGuard {
729
735
  */
730
736
  this.beforeInputSnapshot = null;
731
737
 
738
+ /**
739
+ * onBeforeInput イベントが発生したか否か
740
+ */
741
+ this.existBeforeInputEvent = false;
742
+
732
743
  /**
733
744
  * ルールからのrevert要求
734
745
  * @type {RevertRequest|null}
@@ -746,7 +757,7 @@ class InputGuard {
746
757
  this.applySeparateValue();
747
758
  this.bindEvents();
748
759
  // 初期値を評価
749
- this.evaluateInput();
760
+ this.evaluateCommit();
750
761
  }
751
762
 
752
763
  /**
@@ -985,12 +996,12 @@ class InputGuard {
985
996
  */
986
997
  createCtx({ useSnapshot = true } = {}) {
987
998
  const snap = useSnapshot ? this.beforeInputSnapshot : null;
988
- const inputType = snap?.inputType ?? "";
989
- const insertedText = snap?.insertedText ?? "";
999
+ let inputType = snap?.inputType ?? "";
1000
+ let insertedText = snap?.insertedText ?? "";
990
1001
 
991
1002
  // 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
992
1003
  // display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
993
- const beforeText = this.lastAcceptedValue ?? "";
1004
+ let beforeText = this.lastAcceptedValue ?? "";
994
1005
 
995
1006
  // selection は2系統ある:
996
1007
  // - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
@@ -1026,6 +1037,38 @@ class InputGuard {
1026
1037
  baseSel = lastSel;
1027
1038
  }
1028
1039
 
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
+
1029
1072
  let replaceStart = baseSel.start ?? 0;
1030
1073
  let replaceEnd = baseSel.end ?? 0;
1031
1074
 
@@ -1038,10 +1081,10 @@ class InputGuard {
1038
1081
  if (inputType === "deleteContentBackward") {
1039
1082
  // Backspace: キャレットの左側1文字を削除
1040
1083
  replaceStart = Math.max(0, replaceStart - 1);
1041
- replaceEnd = snapSel.start ?? replaceEnd;
1084
+ replaceEnd = snapSel?.start ?? replaceEnd;
1042
1085
  } else if (inputType === "deleteContentForward") {
1043
1086
  // Delete: キャレットの右側1文字を削除
1044
- replaceStart = snapSel.start ?? replaceStart;
1087
+ replaceStart = snapSel?.start ?? replaceStart;
1045
1088
  replaceEnd = Math.min(beforeText.length, replaceEnd + 1);
1046
1089
  }
1047
1090
  // 追加で拾うならここ:
@@ -1248,16 +1291,23 @@ class InputGuard {
1248
1291
  // console.log("[text-input-guard] input");
1249
1292
  // compositionend後に input が来た場合、フォールバックを無効化
1250
1293
  this.pendingCompositionCommit = false;
1251
- this.evaluateInput();
1294
+ try {
1295
+ this.evaluateInput();
1296
+ } finally {
1297
+ this.existBeforeInputEvent = false;
1298
+ }
1252
1299
  }
1253
1300
 
1254
1301
  /**
1255
1302
  * beforeinput:入力が反映される直前に呼ばれる
1256
1303
  * - ここでの value/selection が「今回の編集の基準点」になる
1257
- * @param {InputEvent} e
1304
+ * @param {Event} e
1258
1305
  * @returns {void}
1259
1306
  */
1260
1307
  onBeforeInput(e) {
1308
+ if (!(e instanceof InputEvent)) {
1309
+ return;
1310
+ }
1261
1311
  const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1262
1312
  // 現時点(反映前)の選択範囲
1263
1313
  const selection = this.readSelection(el);
@@ -1265,6 +1315,7 @@ class InputGuard {
1265
1315
  const inputType = typeof e.inputType === "string" ? e.inputType : null;
1266
1316
  /** @type {string|null} */
1267
1317
  const insertedText = typeof e.data === "string" ? e.data : null;
1318
+ this.existBeforeInputEvent = true;
1268
1319
  this.beforeInputSnapshot = { selection, inputType, insertedText };
1269
1320
  }
1270
1321
 
@@ -7435,10 +7486,10 @@ const rules = {
7435
7486
 
7436
7487
  /**
7437
7488
  * バージョン(ビルド時に置換したいならここを差し替える)
7438
- * 例: rollup replace で ""1.0.0"" を package.json の version に置換
7489
+ * 例: rollup replace で ""1.0.2"" を package.json の version に置換
7439
7490
  */
7440
7491
  // @ts-ignore
7441
7492
  // eslint-disable-next-line no-undef
7442
- const version = "1.0.0" ;
7493
+ const version = "1.0.2" ;
7443
7494
 
7444
7495
  export { ascii, attach, attachAll, autoAttach, bytes, comma, digits, filter, imeOff, kana, length, numeric, prefix, rules, suffix, trim, version, width };