Skip to content

Commit 814ae7f

Browse files
committed
attach の引数に onChange を追加
1 parent 7efd1a8 commit 814ae7f

4 files changed

Lines changed: 49 additions & 15 deletions

File tree

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.1.x 2026/x/xx
4+
5+
- `attach` の引数に `onChange` を追加
6+
37
#### 1.0.2 2026/4/24
48

59
- オートコンプリート対応が十分ではなかった問題を修正

docs/api.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ const guard = guards.getGuards()[0];
107107
| `warn` | `boolean` | `true` | 非対応ルールや不正な設定があった場合に `console.warn` を出力するかどうか。 |
108108
| `invalidClass` | `string` | `"is-invalid"` | エラーが存在する場合に `displayElement` に付与される CSS クラス名。 |
109109
| `onValidate` | `(result: ValidateResult) => void` | - | バリデーション評価が完了した際に呼び出されるコールバック。入力中 (`input`) と確定時 (`commit`) の両方で呼ばれます。 |
110+
| `onChange` | `(result: Guard) => void` | - | フォーカスが外れた値が変更されていた際に呼び出されるコールバック。 |
110111

111112
### Guard
112113

@@ -296,6 +297,14 @@ guard.setValue("", "none");
296297
| `errors` | `TigError[]` | 発生しているエラー一覧 |
297298
| `isValid` | `boolean` | エラーが存在しない場合 `true` |
298299

300+
### onChange
301+
302+
`onChange` はフォーカスが外れた値が変更されていた際に呼び出されます。
303+
304+
コールバックには `Guard` が渡されます。
305+
変更後に何か処理をしたい場合は、
306+
このコールバック内で `guard.getRawValue()`, `guard.getDisplayValue()` を利用して変更後の値を取得し、処理をすることを想定しています。
307+
299308
### TigError
300309

301310
バリデーションエラーを表すオブジェクトです。

examples/dev/text.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ const guard = attach(input, {
2525
rules.prefix({ text: "[" }),
2626
rules.suffix({ text: "]" })
2727
*/
28-
]
28+
],
29+
onChange: (guard) => {
30+
console.log("value changed", guard.getRawValue());
31+
}
2932
});
3033

3134
// guard.setValue("123");

src/text-input-guard.js

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ import { SwapState } from "./swap-state.js";
143143
* @property {string} [invalidClass="is-invalid"] - エラー時に付けるclass名
144144
* @property {SeparateValueOptions} [separateValue] - 表示値と内部値の分離設定
145145
* @property {(result: ValidateResult) => void} [onValidate] - 評価完了時の通知(input/commitごと)
146+
* @property {(result: Guard) => void} [onChange] - フォーカスが外れた値が変更されていた場合の通知
146147
*/
147148

148149
/**
@@ -308,6 +309,18 @@ class InputGuard {
308309
*/
309310
this.onValidate = options.onValidate;
310311

312+
/**
313+
* blur時に値が変更されていた場合の通知
314+
* @type {((result: Guard) => void) | undefined}
315+
*/
316+
this.onChange = options.onChange;
317+
318+
/**
319+
* onChange 判定のための直前の値(blur時にこれと比較して変化を検知する)
320+
* @type {string}
321+
*/
322+
this.previousValue = "";
323+
311324
/**
312325
* 実際に送信を担う要素(swap時は hidden(raw) 側)
313326
* swapしない場合は originalElement と同一
@@ -488,6 +501,8 @@ class InputGuard {
488501
this.bindEvents();
489502
// 初期値を評価
490503
this.evaluateCommit();
504+
// 初期値を記録
505+
this.previousValue = this.getDisplayValue();
491506
}
492507

493508
/**
@@ -727,6 +742,8 @@ class InputGuard {
727742
* @returns {GuardContext}
728743
*/
729744
createCtx({ useSnapshot = true } = {}) {
745+
// 入力後のテキストを取得
746+
const afterText = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement).value;
730747
const snap = useSnapshot ? this.beforeInputSnapshot : null;
731748
let inputType = snap?.inputType ?? "";
732749
let insertedText = snap?.insertedText ?? "";
@@ -771,21 +788,19 @@ class InputGuard {
771788

772789
// beforeinput がない環境では、差分再構成の基準が「前回の受理値」しかないため、そこから今回の編集内容を推測する必要がある。
773790
if (beforeText.length === 0 || !this.existBeforeInputEvent) {
774-
const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
775-
const current = display.value;
776791
// 前回の値がとれないものの、何かしら入力情報がある状態
777-
if (current.length > 0) {
792+
if (afterText.length > 0) {
778793
// 文字列の先頭が前回の受理値と同じなら、末尾に何かしら入力されたと考えられる(オートコンプリート等)
779-
if (current.toLocaleLowerCase().startsWith(beforeText.toLocaleLowerCase())) {
780-
if (!current.startsWith(beforeText)) {
794+
if (afterText.toLocaleLowerCase().startsWith(beforeText.toLocaleLowerCase())) {
795+
if (!afterText.startsWith(beforeText)) {
781796
// 文字は同じだが、大文字と小文字の情報が替わっているなどのパターン
782797
// 差し代わりが起きているため、前回値は基準にならないと判断して、差分全体を insertedText とする
783798
beforeText = "";
784-
insertedText = current;
799+
insertedText = afterText;
785800
} else {
786801
// 末尾に追加されたと考えられる部分を insertedText とする
787-
// 例: beforeText="abc" → current="abcde" なら、"de" が insertedText
788-
insertedText = current.slice(beforeText.length);
802+
// 例: beforeText="abc" → afterText="abcde" なら、"de" が insertedText
803+
insertedText = afterText.slice(beforeText.length);
789804
}
790805
// キャレットは前回値の末尾にあると推測する
791806
baseSel = /** @type {SelectionState} */ {
@@ -836,7 +851,7 @@ class InputGuard {
836851
replaceStart,
837852
replaceEnd,
838853
insertedText,
839-
afterText: null, // 後で代入する
854+
afterText,
840855
pushError: (e) => this.errors.push(e),
841856
requestRevert: (req) => {
842857
// 1回でもrevert要求が出たら採用(最初の理由を保持)
@@ -1058,6 +1073,12 @@ class InputGuard {
10581073
onBlur() {
10591074
// console.log("[text-input-guard] blur");
10601075
this.evaluateCommit();
1076+
if (this.previousValue !== this.getDisplayValue()) {
1077+
this.previousValue = this.getDisplayValue();
1078+
if (this.onChange) {
1079+
this.onChange(this.getGuard());
1080+
}
1081+
}
10611082
}
10621083

10631084
/**
@@ -1182,10 +1203,7 @@ class InputGuard {
11821203
* @returns {GuardContext}
11831204
*/
11841205
createCtxAndNormalize() {
1185-
const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1186-
const current = display.value;
11871206
const ctx = this.createCtx();
1188-
ctx.afterText = current;
11891207

11901208
// 元のテキスト
11911209
const beforeText = ctx.beforeText;
@@ -1197,7 +1215,7 @@ class InputGuard {
11971215
const replaceStart = ctx.replaceStart;
11981216

11991217
// 現状のテキスト
1200-
const tempText = current;
1218+
const tempText = ctx.afterText;
12011219

12021220
// 作成する全体のテキスト
12031221
let newText = beforeText;
@@ -1242,7 +1260,7 @@ class InputGuard {
12421260

12431261
// 画面を更新
12441262
this.syncDisplay(newText);
1245-
this.writeSelection(display, newSelection);
1263+
this.writeSelection(this.displayElement, newSelection);
12461264

12471265
// CTX の情報を最新の情報へ更新する
12481266
ctx.afterText = newText;

0 commit comments

Comments
 (0)