Skip to content

Commit 7efd1a8

Browse files
committed
v1.0.2
1 parent db5da03 commit 7efd1a8

12 files changed

Lines changed: 194 additions & 193 deletions

HISTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
### History
22

3+
#### 1.0.2 2026/4/24
4+
5+
- オートコンプリート対応が十分ではなかった問題を修正
6+
37
#### 1.0.1 2026/4/23
48

59
- 型チェックエラーが発生していた箇所を修正

dist/cjs/text-input-guard.cjs

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,11 @@ class InputGuard {
737737
*/
738738
this.beforeInputSnapshot = null;
739739

740+
/**
741+
* onBeforeInput イベントが発生したか否か
742+
*/
743+
this.existBeforeInputEvent = false;
744+
740745
/**
741746
* ルールからのrevert要求
742747
* @type {RevertRequest|null}
@@ -993,12 +998,12 @@ class InputGuard {
993998
*/
994999
createCtx({ useSnapshot = true } = {}) {
9951000
const snap = useSnapshot ? this.beforeInputSnapshot : null;
996-
const inputType = snap?.inputType ?? "";
997-
const insertedText = snap?.insertedText ?? "";
1001+
let inputType = snap?.inputType ?? "";
1002+
let insertedText = snap?.insertedText ?? "";
9981003

9991004
// 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
10001005
// display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
1001-
const beforeText = this.lastAcceptedValue ?? "";
1006+
let beforeText = this.lastAcceptedValue ?? "";
10021007

10031008
// selection は2系統ある:
10041009
// - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
@@ -1034,6 +1039,38 @@ class InputGuard {
10341039
baseSel = lastSel;
10351040
}
10361041

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+
10371074
let replaceStart = baseSel.start ?? 0;
10381075
let replaceEnd = baseSel.end ?? 0;
10391076

@@ -1259,9 +1296,7 @@ class InputGuard {
12591296
try {
12601297
this.evaluateInput();
12611298
} finally {
1262-
// beforeinput が来ない入力経路(autocomplete等)で
1263-
// 古い snapshot を使い回さないよう、1イベントごとに破棄する
1264-
this.beforeInputSnapshot = null;
1299+
this.existBeforeInputEvent = false;
12651300
}
12661301
}
12671302

@@ -1282,6 +1317,7 @@ class InputGuard {
12821317
const inputType = typeof e.inputType === "string" ? e.inputType : null;
12831318
/** @type {string|null} */
12841319
const insertedText = typeof e.data === "string" ? e.data : null;
1320+
this.existBeforeInputEvent = true;
12851321
this.beforeInputSnapshot = { selection, inputType, insertedText };
12861322
}
12871323

@@ -1421,45 +1457,6 @@ class InputGuard {
14211457
const ctx = this.createCtx();
14221458
ctx.afterText = current;
14231459

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-
14631460
// 元のテキスト
14641461
const beforeText = ctx.beforeText;
14651462

@@ -7491,11 +7488,11 @@ const rules = {
74917488

74927489
/**
74937490
* バージョン(ビルド時に置換したいならここを差し替える)
7494-
* 例: rollup replace で ""1.0.1"" を package.json の version に置換
7491+
* 例: rollup replace で ""1.0.2"" を package.json の version に置換
74957492
*/
74967493
// @ts-ignore
74977494
// eslint-disable-next-line no-undef
7498-
const version = "1.0.1" ;
7495+
const version = "1.0.2" ;
74997496

75007497
exports.ascii = ascii;
75017498
exports.attach = attach;

dist/cjs/text-input-guard.min.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/esm/text-input-guard.js

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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

74987495
export { ascii, attach, attachAll, autoAttach, bytes, comma, digits, filter, imeOff, kana, length, numeric, prefix, rules, suffix, trim, version, width };

dist/esm/text-input-guard.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)