text-input-guard 0.2.2 → 1.0.1

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.
package/README.md CHANGED
@@ -6,14 +6,12 @@
6
6
  </picture>
7
7
  </p>
8
8
 
9
- TextInputGuard は、**開発中**の日本語入力環境を前提に設計された入力補助ライブラリです。
9
+ TextInputGuard は、日本語入力環境を前提に設計された入力補助ライブラリです。
10
10
 
11
- `<input>` / `<textarea>` に対して、全角混在・桁数制限・小数処理・表示整形など、日本語環境特有の入力制御を扱いやすい形で提供します。業務系フォームや金額入力など、IME の影響を受けやすい入力欄でも、表示用の値と送信用の値を分離しながら、安定した入力制御を実現できます。
12
-
13
- ---
11
+ `<input>` / `<textarea>` に対して、全角混在・桁数制限・小数処理・表示整形など、日本語環境特有の入力制御を扱いやすい形で提供します。業務系フォームや金額入力など、IME の影響を受けやすい入力欄でも、ライブラリ内で表示用の値と送信用の値を分離して持つことで、安定した入力制御を実現できます。
14
12
 
15
13
  ## Documentation
16
14
 
17
15
  詳しいドキュメントはこちら
18
16
 
19
- 👉 https://natade-jp.github.io/text-input-guard/
17
+ https://natade-jp.github.io/text-input-guard/
@@ -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
 
@@ -748,7 +754,7 @@ class InputGuard {
748
754
  this.applySeparateValue();
749
755
  this.bindEvents();
750
756
  // 初期値を評価
751
- this.evaluateInput();
757
+ this.evaluateCommit();
752
758
  }
753
759
 
754
760
  /**
@@ -1040,10 +1046,10 @@ class InputGuard {
1040
1046
  if (inputType === "deleteContentBackward") {
1041
1047
  // Backspace: キャレットの左側1文字を削除
1042
1048
  replaceStart = Math.max(0, replaceStart - 1);
1043
- replaceEnd = snapSel.start ?? replaceEnd;
1049
+ replaceEnd = snapSel?.start ?? replaceEnd;
1044
1050
  } else if (inputType === "deleteContentForward") {
1045
1051
  // Delete: キャレットの右側1文字を削除
1046
- replaceStart = snapSel.start ?? replaceStart;
1052
+ replaceStart = snapSel?.start ?? replaceStart;
1047
1053
  replaceEnd = Math.min(beforeText.length, replaceEnd + 1);
1048
1054
  }
1049
1055
  // 追加で拾うならここ:
@@ -1250,16 +1256,25 @@ class InputGuard {
1250
1256
  // console.log("[text-input-guard] input");
1251
1257
  // compositionend後に input が来た場合、フォールバックを無効化
1252
1258
  this.pendingCompositionCommit = false;
1253
- this.evaluateInput();
1259
+ try {
1260
+ this.evaluateInput();
1261
+ } finally {
1262
+ // beforeinput が来ない入力経路(autocomplete等)で
1263
+ // 古い snapshot を使い回さないよう、1イベントごとに破棄する
1264
+ this.beforeInputSnapshot = null;
1265
+ }
1254
1266
  }
1255
1267
 
1256
1268
  /**
1257
1269
  * beforeinput:入力が反映される直前に呼ばれる
1258
1270
  * - ここでの value/selection が「今回の編集の基準点」になる
1259
- * @param {InputEvent} e
1271
+ * @param {Event} e
1260
1272
  * @returns {void}
1261
1273
  */
1262
1274
  onBeforeInput(e) {
1275
+ if (!(e instanceof InputEvent)) {
1276
+ return;
1277
+ }
1263
1278
  const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1264
1279
  // 現時点(反映前)の選択範囲
1265
1280
  const selection = this.readSelection(el);
@@ -1406,6 +1421,45 @@ class InputGuard {
1406
1421
  const ctx = this.createCtx();
1407
1422
  ctx.afterText = current;
1408
1423
 
1424
+ /**
1425
+ * 入力値情報のみを使用するフォールバック
1426
+ * @returns {GuardContext}
1427
+ */
1428
+ const applyFullNormalizeFromCurrent = () => {
1429
+ let newText = current;
1430
+ ctx.beforeText = "";
1431
+ newText = this.runNormalizeChar(newText, ctx);
1432
+ newText = this.runNormalizeStructure(newText, ctx);
1433
+ this.setDisplayValuePreserveCaret(display, newText, ctx);
1434
+ ctx.afterText = newText;
1435
+ return ctx;
1436
+ };
1437
+
1438
+ // beforeinput が取得できない経路(初回評価)では
1439
+ // 差分再構成を行うと lastAcceptedValue 基準で値を落とす可能性があるため、
1440
+ // 現在の全文を正規化して扱うフォールバックへ切り替える。
1441
+ if (!this.beforeInputSnapshot) {
1442
+ return applyFullNormalizeFromCurrent();
1443
+ }
1444
+
1445
+ // オートコンプリート等では beforeinput は来ても data が空のことがあり、
1446
+ // 差分情報だけでは再構成不能になる。表示値がすでに変わっている場合は
1447
+ // 再構成を諦めて current 全体の正規化に切り替える。
1448
+ const isDeleteInput =
1449
+ ctx.inputType === "deleteContentBackward" ||
1450
+ ctx.inputType === "deleteContentForward";
1451
+ const isInsertLikeInput =
1452
+ ctx.inputType === "" ||
1453
+ ctx.inputType?.startsWith("insert");
1454
+ const lacksDelta =
1455
+ ctx.insertedText === "" &&
1456
+ ctx.beforeText !== current &&
1457
+ isInsertLikeInput &&
1458
+ !isDeleteInput;
1459
+ if (lacksDelta) {
1460
+ return applyFullNormalizeFromCurrent();
1461
+ }
1462
+
1409
1463
  // 元のテキスト
1410
1464
  const beforeText = ctx.beforeText;
1411
1465
 
@@ -7437,11 +7491,11 @@ const rules = {
7437
7491
 
7438
7492
  /**
7439
7493
  * バージョン(ビルド時に置換したいならここを差し替える)
7440
- * 例: rollup replace で ""0.2.2"" を package.json の version に置換
7494
+ * 例: rollup replace で ""1.0.1"" を package.json の version に置換
7441
7495
  */
7442
7496
  // @ts-ignore
7443
7497
  // eslint-disable-next-line no-undef
7444
- const version = "0.2.2" ;
7498
+ const version = "1.0.1" ;
7445
7499
 
7446
7500
  exports.ascii = ascii;
7447
7501
  exports.attach = attach;