@@ -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