text-input-guard 0.1.0 → 0.1.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.
@@ -1,5 +1,248 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ * The script is part of TextInputGuard.
5
+ *
6
+ * AUTHOR:
7
+ * natade-jp (https://github.com/natade-jp)
8
+ *
9
+ * LICENSE:
10
+ * The MIT license https://opensource.org/licenses/MIT
11
+ */
12
+
13
+ /**
14
+ * SwapState
15
+ *
16
+ * separateValue.mode="swap" のときに使用する
17
+ * 元 input 要素の状態スナップショットおよび復元ロジックを管理するクラス
18
+ *
19
+ * 役割
20
+ * - swap前の input の属性状態を保持する
21
+ * - raw化および display生成時に必要な属性を適用する
22
+ * - detach時に元の状態へ復元する
23
+ *
24
+ * 設計方針
25
+ * - 送信用属性は raw に残す
26
+ * - UIおよびアクセシビリティ属性は display に適用する
27
+ * - tig内部用の data-* は display にコピーしない
28
+ */
29
+ class SwapState {
30
+ /**
31
+ * 元 input の type 属性
32
+ * detach時に復元するため保持する
33
+ * @type {string}
34
+ */
35
+ originalType;
36
+
37
+ /**
38
+ * 元 input の id 属性
39
+ * swap時に display へ移し detach時に rawへ戻す
40
+ * @type {string|null}
41
+ */
42
+ originalId;
43
+
44
+ /**
45
+ * 元 input の name 属性
46
+ * 送信用属性のため raw側に残すが
47
+ * detach時の整合性のため保持する
48
+ * @type {string|null}
49
+ */
50
+ originalName;
51
+
52
+ /**
53
+ * 元 input の class 属性
54
+ * swap時に display へ移す
55
+ * @type {string}
56
+ */
57
+ originalClass;
58
+
59
+ /**
60
+ * UI系属性のスナップショット
61
+ * placeholder inputmode required などを保持する
62
+ *
63
+ * key 属性名
64
+ * value 属性値 未指定の場合は null
65
+ *
66
+ * @type {Object.<string, string|null>}
67
+ */
68
+ originalUiAttrs;
69
+
70
+ /**
71
+ * aria-* 属性のスナップショット
72
+ * アクセシビリティ維持のため display に適用する
73
+ *
74
+ * key aria属性名 例 aria-label
75
+ * value 属性値
76
+ *
77
+ * @type {Object.<string, string>}
78
+ */
79
+ originalAriaAttrs;
80
+
81
+ /**
82
+ * tig 以外の data-* 属性のスナップショット
83
+ * swap後も display へ引き継ぐ
84
+ *
85
+ * key datasetキー camelCase
86
+ * value 属性値
87
+ *
88
+ * @type {Object.<string, string>}
89
+ */
90
+ originalDataset;
91
+
92
+ /**
93
+ * swap時に生成された display 用 input 要素
94
+ * detach時に削除するため保持する
95
+ *
96
+ * @type {HTMLInputElement|null}
97
+ */
98
+ createdDisplay;
99
+
100
+ /**
101
+ * @param {HTMLInputElement} input
102
+ * swap前の元 input 要素
103
+ */
104
+ constructor(input) {
105
+ this.originalType = input.type;
106
+ this.originalId = input.getAttribute("id");
107
+ this.originalName = input.getAttribute("name");
108
+ this.originalClass = input.className;
109
+
110
+ this.originalUiAttrs = {};
111
+ this.originalAriaAttrs = {};
112
+ this.originalDataset = {};
113
+ this.createdDisplay = null;
114
+
115
+ const UI_ATTRS = [
116
+ "placeholder",
117
+ "inputmode",
118
+ "autocomplete",
119
+ "required",
120
+ "minlength",
121
+ "maxlength",
122
+ "pattern",
123
+ "title",
124
+ "tabindex"
125
+ ];
126
+
127
+ for (const name of UI_ATTRS) {
128
+ this.originalUiAttrs[name] =
129
+ input.hasAttribute(name) ? input.getAttribute(name) : null;
130
+ }
131
+
132
+ for (const attr of input.attributes) {
133
+ if (attr.name.startsWith("aria-")) {
134
+ this.originalAriaAttrs[attr.name] = attr.value ?? "";
135
+ }
136
+ }
137
+
138
+ for (const [k, v] of Object.entries(input.dataset)) {
139
+ if (k.startsWith("tig")) { continue; }
140
+ this.originalDataset[k] = v;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * raw 元input を hidden 化する
146
+ * 送信担当要素として扱う
147
+ *
148
+ * @param {HTMLInputElement} input
149
+ * @returns {void}
150
+ */
151
+ applyToRaw(input) {
152
+ // raw化(送信担当)
153
+ input.type = "hidden";
154
+ input.removeAttribute("id");
155
+ input.className = "";
156
+ input.dataset.tigRole = "raw";
157
+
158
+ // 元idのメタを残す(デバッグ/参照用)
159
+ if (this.originalId) {
160
+ input.dataset.tigOriginalId = this.originalId;
161
+ }
162
+ if (this.originalName) {
163
+ input.dataset.tigOriginalName = this.originalName;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * display用 input を生成し UI属性 aria属性 data属性を適用
169
+ *
170
+ * @param {HTMLInputElement} raw hidden化された元input
171
+ * @returns {HTMLInputElement}
172
+ */
173
+ createDisplay(raw) {
174
+ const display = document.createElement("input");
175
+ display.type = "text";
176
+ display.dataset.tigRole = "display";
177
+
178
+ if (this.originalId) {
179
+ display.id = this.originalId;
180
+ }
181
+
182
+ display.className = this.originalClass ?? "";
183
+ display.value = raw.value;
184
+
185
+ for (const [name, v] of Object.entries(this.originalUiAttrs)) {
186
+ if (v == null) {
187
+ display.removeAttribute(name);
188
+ } else {
189
+ display.setAttribute(name, v);
190
+ }
191
+ }
192
+
193
+ for (const [name, v] of Object.entries(this.originalAriaAttrs)) {
194
+ display.setAttribute(name, v);
195
+ }
196
+
197
+ for (const [k, v] of Object.entries(this.originalDataset)) {
198
+ display.dataset[k] = v;
199
+ }
200
+
201
+ this.createdDisplay = display;
202
+ return display;
203
+ }
204
+
205
+ /**
206
+ * detach時に display 要素を削除する
207
+ *
208
+ * @returns {void}
209
+ */
210
+ removeDisplay() {
211
+ if (this.createdDisplay?.parentNode) {
212
+ this.createdDisplay.parentNode.removeChild(this.createdDisplay);
213
+ }
214
+ this.createdDisplay = null;
215
+ }
216
+
217
+ /**
218
+ * raw hidden化された元input を元の状態へ復元する
219
+ *
220
+ * @param {HTMLInputElement} raw
221
+ * @returns {void}
222
+ */
223
+ restoreRaw(raw) {
224
+ raw.type = this.originalType;
225
+
226
+ if (this.originalId) {
227
+ raw.setAttribute("id", this.originalId);
228
+ } else {
229
+ raw.removeAttribute("id");
230
+ }
231
+
232
+ if (this.originalName) {
233
+ raw.setAttribute("name", this.originalName);
234
+ } else {
235
+ raw.removeAttribute("name");
236
+ }
237
+
238
+ raw.className = this.originalClass ?? "";
239
+
240
+ delete raw.dataset.tigRole;
241
+ delete raw.dataset.tigOriginalId;
242
+ delete raw.dataset.tigOriginalName;
243
+ }
244
+ }
245
+
3
246
  /**
4
247
  * The script is part of JPInputGuard.
5
248
  *
@@ -10,6 +253,7 @@
10
253
  * The MIT license https://opensource.org/licenses/MIT
11
254
  */
12
255
 
256
+
13
257
  /**
14
258
  * 対象要素の種別(現在は input と textarea のみ対応)
15
259
  * @typedef {"input"|"textarea"} ElementKind
@@ -30,6 +274,24 @@
30
274
  * @property {any} [detail] - 追加情報(制限値など)
31
275
  */
32
276
 
277
+ /**
278
+ * setValue で設定できる値型
279
+ * - number は String に変換して設定する
280
+ * - null/undefined は空文字として扱う
281
+ * @typedef {string | number | null | undefined} SetValueInput
282
+ */
283
+
284
+ /**
285
+ * setValue 実行モード
286
+ * - "commit" 確定評価まで実行 normalize→validate→fix→format
287
+ * - "input" 入力中評価のみ実行 normalize→validate
288
+ * - "none" 評価は実行しない 値だけを反映
289
+ *
290
+ * 既定値は "commit"
291
+ *
292
+ * @typedef {"none"|"input"|"commit"} SetValueMode
293
+ */
294
+
33
295
  /**
34
296
  * attach() が返す公開API(利用者が触れる最小インターフェース)
35
297
  * @typedef {Object} Guard
@@ -37,7 +299,12 @@
37
299
  * @property {() => boolean} isValid - 現在エラーが無いかどうか
38
300
  * @property {() => TigError[]} getErrors - エラー一覧を取得
39
301
  * @property {() => string} getRawValue - 送信用の正規化済み値を取得
40
- * @property {() => HTMLInputElement|HTMLTextAreaElement} getDisplayElement - ユーザーが実際に操作している要素(swap時はdisplay側)
302
+ * @property {() => string} getDisplayValue - ユーザーが実際に操作している要素の値を取得
303
+ * @property {() => HTMLInputElement|HTMLTextAreaElement} getRawElement - 送信用の正規化済み値の要素
304
+ * @property {() => HTMLInputElement|HTMLTextAreaElement} getDisplayElement - ユーザーが実際に操作している要素(swap時はdisplay専用)
305
+ * @property {() => void} evaluate 入力中評価を手動実行 normalize→validate
306
+ * @property {() => void} commit 確定評価を手動実行 normalize→validate→fix→format
307
+ * @property {(value: SetValueInput, mode?: SetValueMode) => void} setValue
41
308
  */
42
309
 
43
310
  /**
@@ -86,17 +353,6 @@
86
353
  * @property {SeparateValueOptions} [separateValue] - 表示値と内部値の分離設定
87
354
  */
88
355
 
89
- /**
90
- * swap時に退避する元inputの情報
91
- * detach時に元の状態へ復元するために使用する
92
- * @typedef {Object} SwapState
93
- * @property {string} originalType - 元のinput.type
94
- * @property {string|null} originalId - 元のid属性
95
- * @property {string|null} originalName - 元のname属性
96
- * @property {string} originalClass - 元のclass文字列
97
- * @property {HTMLInputElement} createdDisplay - 生成した表示用input
98
- */
99
-
100
356
  /**
101
357
  * selection(カーソル/選択範囲)の退避情報
102
358
  * @typedef {Object} SelectionState
@@ -440,61 +696,19 @@ class InputGuard {
440
696
 
441
697
  const input = /** @type {HTMLInputElement} */ (this.originalElement);
442
698
 
443
- // 退避(detachで戻すため)
444
- /** @type {SwapState} */
445
- this.swapState = {
446
- originalType: input.type,
447
- originalId: input.getAttribute("id"),
448
- originalName: input.getAttribute("name"),
449
- originalClass: input.className,
450
- // 必要になったらここに placeholder/aria/data を追加していく
451
- createdDisplay: null
452
- };
453
-
454
- // raw化(送信担当)
455
- input.type = "hidden";
456
- input.removeAttribute("id"); // displayに引き継ぐため
457
- input.dataset.tigRole = "raw";
458
-
459
- // 元idのメタを残す(デバッグ/参照用)
460
- if (this.swapState.originalId) {
461
- input.dataset.tigOriginalId = this.swapState.originalId;
462
- }
463
-
464
- if (this.swapState.originalName) {
465
- input.dataset.tigOriginalName = this.swapState.originalName;
466
- }
467
-
468
- // display生成(ユーザー入力担当)
469
- const display = document.createElement("input");
470
- display.type = "text";
471
- display.dataset.tigRole = "display";
472
-
473
- // id は display に移す
474
- if (this.swapState.originalId) {
475
- display.id = this.swapState.originalId;
476
- }
477
-
478
- // name は付けない(送信しない)
479
- display.removeAttribute("name");
480
-
481
- // class は display に
482
- display.className = this.swapState.originalClass;
483
- input.className = "";
484
-
485
- // value 初期同期
486
- display.value = input.value;
699
+ const state = new SwapState(input);
700
+ state.applyToRaw(input);
487
701
 
488
- // DOMに挿入(rawの直後)
702
+ const display = state.createDisplay(input);
489
703
  input.after(display);
490
704
 
705
+ this.swapState = state;
706
+
491
707
  // elements更新
492
- this.hostElement = input;
493
- this.displayElement = display;
708
+ this.hostElement = input; // raw
709
+ this.displayElement = display; // display
494
710
  this.rawElement = input;
495
711
 
496
- this.swapState.createdDisplay = display;
497
-
498
712
  // revert 機構
499
713
  this.lastAcceptedValue = display.value;
500
714
  this.lastAcceptedSelection = this.readSelection(display);
@@ -514,10 +728,10 @@ class InputGuard {
514
728
 
515
729
  // rawは元の input(hidden化されている)
516
730
  const raw = /** @type {HTMLInputElement} */ (this.hostElement);
517
- const display = state.createdDisplay;
518
731
 
519
732
  // displayが存在するなら、最新表示値をrawに同期してから消す(安全策)
520
733
  // ※ rawは常に正規化済みを持つ設計だけど、念のため
734
+ const display = state.createdDisplay;
521
735
  if (display) {
522
736
  try {
523
737
  raw.value = raw.value || display.value;
@@ -527,39 +741,18 @@ class InputGuard {
527
741
  }
528
742
 
529
743
  // display削除
530
- if (display && display.parentNode) {
531
- display.parentNode.removeChild(display);
532
- }
744
+ state.removeDisplay();
533
745
 
534
746
  // rawを元に戻す(type)
535
- raw.type = state.originalType;
536
-
537
- // id を戻す
538
- if (state.originalId) {
539
- raw.setAttribute("id", state.originalId);
540
- } else {
541
- raw.removeAttribute("id");
542
- }
543
-
544
- // name を戻す(swap中は残している想定だが、念のため)
545
- if (state.originalName) {
546
- raw.setAttribute("name", state.originalName);
547
- } else {
548
- raw.removeAttribute("name");
549
- }
550
-
551
- // class を戻す
552
- raw.className = state.originalClass ?? "";
553
-
554
- // data属性(tig用)は消しておく
555
- delete raw.dataset.tigRole;
556
- delete raw.dataset.tigOriginalId;
557
- delete raw.dataset.tigOriginalName;
747
+ state.restoreRaw(raw);
558
748
 
559
749
  // elements参照を original に戻す
560
750
  this.hostElement = this.originalElement;
561
751
  this.displayElement = this.originalElement;
562
752
  this.rawElement = null;
753
+
754
+ // swapState破棄
755
+ this.swapState = null;
563
756
  }
564
757
 
565
758
  /**
@@ -571,8 +764,6 @@ class InputGuard {
571
764
  this.unbindEvents();
572
765
  // swap復元
573
766
  this.restoreSeparateValue();
574
- // swapState破棄
575
- this.swapState = null;
576
767
  // 以後このインスタンスは利用不能にしてもいいが、今回は明示しない
577
768
  }
578
769
 
@@ -1082,12 +1273,53 @@ class InputGuard {
1082
1273
  * @returns {string}
1083
1274
  */
1084
1275
  getRawValue() {
1085
- if (this.rawElement) {
1086
- return this.rawElement.value;
1087
- }
1276
+ return /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.hostElement).value;
1277
+ }
1278
+
1279
+ /**
1280
+ * 表示用の値を返す(displayの値)
1281
+ * @returns {string}
1282
+ */
1283
+ getDisplayValue() {
1088
1284
  return /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement).value;
1089
1285
  }
1090
1286
 
1287
+ /**
1288
+ * 表示要素の値をプログラムから設定する
1289
+ *
1290
+ * @param {SetValueInput} value
1291
+ * @param {SetValueMode} [mode="commit"]
1292
+ * @returns {void}
1293
+ */
1294
+ setValue(value, mode = "commit") {
1295
+ /** @type {string} */
1296
+ let s;
1297
+
1298
+ if (value == null) {
1299
+ s = "";
1300
+ } else if (typeof value === "number") {
1301
+ // NaN/Infinity は事故りやすいので空に寄せる(方針)
1302
+ s = Number.isFinite(value) ? String(value) : "";
1303
+ } else {
1304
+ s = String(value);
1305
+ }
1306
+
1307
+ const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1308
+ display.value = s;
1309
+
1310
+ if (mode === "none") {
1311
+ this.syncRaw(s);
1312
+ this.lastAcceptedValue = s;
1313
+ this.lastAcceptedSelection = this.readSelection(display);
1314
+ return;
1315
+ }
1316
+ if (mode === "input") {
1317
+ this.evaluateInput();
1318
+ return;
1319
+ }
1320
+ this.evaluateCommit();
1321
+ }
1322
+
1091
1323
  /**
1092
1324
  * 外部に公開する Guard API を生成して返す
1093
1325
  * - InputGuard 自体を公開せず、最小の操作だけを渡す
@@ -1099,7 +1331,12 @@ class InputGuard {
1099
1331
  isValid: () => this.isValid(),
1100
1332
  getErrors: () => this.getErrors(),
1101
1333
  getRawValue: () => this.getRawValue(),
1102
- getDisplayElement: () => /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement)
1334
+ getDisplayValue: () => this.getDisplayValue(),
1335
+ getRawElement: () => /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.hostElement),
1336
+ getDisplayElement: () => /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement),
1337
+ evaluate: () => this.evaluateInput(),
1338
+ commit: () => this.evaluateCommit(),
1339
+ setValue: (value, mode) => this.setValue(value, mode)
1103
1340
  };
1104
1341
  }
1105
1342
  }
@@ -2164,11 +2401,11 @@ const rules = {
2164
2401
 
2165
2402
  /**
2166
2403
  * バージョン(ビルド時に置換したいならここを差し替える)
2167
- * 例: rollup replace で ""0.0.1"" を package.json の version に置換
2404
+ * 例: rollup replace で ""0.1.2"" を package.json の version に置換
2168
2405
  */
2169
2406
  // @ts-ignore
2170
2407
  // eslint-disable-next-line no-undef
2171
- const version = "0.0.1" ;
2408
+ const version = "0.1.2" ;
2172
2409
 
2173
2410
  exports.attach = attach;
2174
2411
  exports.attachAll = attachAll;
@@ -3,4 +3,4 @@
3
3
  * AUTHOR: natade (https://github.com/natade-jp/)
4
4
  * LICENSE: MIT https://opensource.org/licenses/MIT
5
5
  */
6
- "use strict";function t(t,e){e&&console.warn(t)}function e(t,e={}){const n=new i(t,e);return n.init(),n.toGuard()}class i{constructor(t,e){this.originalElement=t,this.options=e;const i=(n=t)instanceof HTMLInputElement?"input":n instanceof HTMLTextAreaElement?"textarea":null;var n;if(!i)throw new TypeError("[text-input-guard] attach() expects an <input> or <textarea> element.");this.kind=i,this.warn=e.warn??!0,this.invalidClass=e.invalidClass??"is-invalid",this.rules=Array.isArray(e.rules)?e.rules:[],this.hostElement=t,this.displayElement=t,this.rawElement=null,this.composing=!1,this.errors=[],this.normalizeCharRules=[],this.normalizeStructureRules=[],this.validateRules=[],this.fixRules=[],this.formatRules=[],this.onCompositionStart=this.onCompositionStart.bind(this),this.onCompositionEnd=this.onCompositionEnd.bind(this),this.onInput=this.onInput.bind(this),this.onBlur=this.onBlur.bind(this),this.onFocus=this.onFocus.bind(this),this.onSelectionChange=this.onSelectionChange.bind(this),this.swapState=null,this.pendingCompositionCommit=!1,this.lastAcceptedValue="",this.lastAcceptedSelection={start:null,end:null,direction:null},this.revertRequest=null}init(){this.buildPipeline(),this.applySeparateValue(),this.bindEvents(),this.evaluateInput()}readSelection(t){return{start:t.selectionStart,end:t.selectionEnd,direction:t.selectionDirection}}writeSelection(t,e){if(null!=e.start&&null!=e.end)try{e.direction?t.setSelectionRange(e.start,e.end,e.direction):t.setSelectionRange(e.start,e.end)}catch(t){}}applySeparateValue(){const e=this.options.separateValue?.mode??"auto";if("swap"!==("auto"===e?this.formatRules.length>0?"swap":"off":e))return;if("input"!==this.kind)return void t('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.',this.warn);const i=this.originalElement;this.swapState={originalType:i.type,originalId:i.getAttribute("id"),originalName:i.getAttribute("name"),originalClass:i.className,createdDisplay:null},i.type="hidden",i.removeAttribute("id"),i.dataset.tigRole="raw",this.swapState.originalId&&(i.dataset.tigOriginalId=this.swapState.originalId),this.swapState.originalName&&(i.dataset.tigOriginalName=this.swapState.originalName);const n=document.createElement("input");n.type="text",n.dataset.tigRole="display",this.swapState.originalId&&(n.id=this.swapState.originalId),n.removeAttribute("name"),n.className=this.swapState.originalClass,i.className="",n.value=i.value,i.after(n),this.hostElement=i,this.displayElement=n,this.rawElement=i,this.swapState.createdDisplay=n,this.lastAcceptedValue=n.value,this.lastAcceptedSelection=this.readSelection(n)}restoreSeparateValue(){if(!this.swapState)return;const t=this.swapState,e=this.hostElement,i=t.createdDisplay;if(i)try{e.value=e.value||i.value}catch(t){}i&&i.parentNode&&i.parentNode.removeChild(i),e.type=t.originalType,t.originalId?e.setAttribute("id",t.originalId):e.removeAttribute("id"),t.originalName?e.setAttribute("name",t.originalName):e.removeAttribute("name"),e.className=t.originalClass??"",delete e.dataset.tigRole,delete e.dataset.tigOriginalId,delete e.dataset.tigOriginalName,this.hostElement=this.originalElement,this.displayElement=this.originalElement,this.rawElement=null}detach(){this.unbindEvents(),this.restoreSeparateValue(),this.swapState=null}buildPipeline(){this.normalizeCharRules=[],this.normalizeStructureRules=[],this.validateRules=[],this.fixRules=[],this.formatRules=[];for(const e of this.rules){"input"===this.kind&&e.targets.includes("input")||"textarea"===this.kind&&e.targets.includes("textarea")?(e.normalizeChar&&this.normalizeCharRules.push(e),e.normalizeStructure&&this.normalizeStructureRules.push(e),e.validate&&this.validateRules.push(e),e.fix&&this.fixRules.push(e),e.format&&this.formatRules.push(e)):t(`[text-input-guard] Rule "${e.name}" is not supported for <${this.kind}>. skipped.`,this.warn)}}bindEvents(){this.displayElement.addEventListener("compositionstart",this.onCompositionStart),this.displayElement.addEventListener("compositionend",this.onCompositionEnd),this.displayElement.addEventListener("input",this.onInput),this.displayElement.addEventListener("blur",this.onBlur),this.displayElement.addEventListener("focus",this.onFocus),this.displayElement.addEventListener("keyup",this.onSelectionChange),this.displayElement.addEventListener("mouseup",this.onSelectionChange),this.displayElement.addEventListener("select",this.onSelectionChange),this.displayElement.addEventListener("focus",this.onSelectionChange)}unbindEvents(){this.displayElement.removeEventListener("compositionstart",this.onCompositionStart),this.displayElement.removeEventListener("compositionend",this.onCompositionEnd),this.displayElement.removeEventListener("input",this.onInput),this.displayElement.removeEventListener("blur",this.onBlur),this.displayElement.removeEventListener("focus",this.onFocus),this.displayElement.removeEventListener("keyup",this.onSelectionChange),this.displayElement.removeEventListener("mouseup",this.onSelectionChange),this.displayElement.removeEventListener("select",this.onSelectionChange),this.displayElement.removeEventListener("focus",this.onSelectionChange)}revertDisplay(t){const e=this.displayElement;e.value=this.lastAcceptedValue,this.writeSelection(e,this.lastAcceptedSelection),this.syncRaw(this.lastAcceptedValue),this.clearErrors(),this.applyInvalidClass(),this.revertRequest=null,this.warn}createCtx(){return{hostElement:this.hostElement,displayElement:this.displayElement,rawElement:this.rawElement,kind:this.kind,warn:this.warn,invalidClass:this.invalidClass,composing:this.composing,pushError:t=>this.errors.push(t),requestRevert:t=>{this.revertRequest||(this.revertRequest=t)}}}clearErrors(){this.errors=[]}runNormalizeChar(t,e){let i=t;for(const t of this.normalizeCharRules)i=t.normalizeChar?t.normalizeChar(i,e):i;return i}runNormalizeStructure(t,e){let i=t;for(const t of this.normalizeStructureRules)i=t.normalizeStructure?t.normalizeStructure(i,e):i;return i}runValidate(t,e){for(const i of this.validateRules)i.validate&&i.validate(t,e)}runFix(t,e){let i=t;for(const t of this.fixRules)i=t.fix?t.fix(i,e):i;return i}runFormat(t,e){let i=t;for(const t of this.formatRules)i=t.format?t.format(i,e):i;return i}applyInvalidClass(){const t=this.displayElement;this.errors.length>0?t.classList.add(this.invalidClass):t.classList.remove(this.invalidClass)}syncRaw(t){this.rawElement&&(this.rawElement.value=t)}syncDisplay(t){(this.displayElement instanceof HTMLInputElement||this.displayElement instanceof HTMLTextAreaElement)&&(this.displayElement.value=t)}onCompositionStart(){this.composing=!0}onCompositionEnd(){this.composing=!1,this.pendingCompositionCommit=!0,queueMicrotask(()=>{this.pendingCompositionCommit&&(this.pendingCompositionCommit=!1,this.evaluateInput())})}onInput(){this.pendingCompositionCommit=!1,this.evaluateInput()}onBlur(){this.evaluateCommit()}onFocus(){if(this.composing)return;const t=this.displayElement,e=t.value,i=this.createCtx();let n=e;n=this.runNormalizeChar(n,i),n=this.runNormalizeStructure(n,i),n!==e&&(this.setDisplayValuePreserveCaret(t,n,i),this.syncRaw(n)),this.lastAcceptedValue=n,this.lastAcceptedSelection=this.readSelection(t),this.onSelectionChange()}onSelectionChange(){if(this.composing)return;const t=this.displayElement;this.lastAcceptedSelection=this.readSelection(t)}setDisplayValuePreserveCaret(t,e,i){const n=t.value;if(n===e)return;const r=t.selectionStart,s=t.selectionEnd;if(null==r||null==s)return void(t.value=e);let a=n.slice(0,r);a=this.runNormalizeChar(a,i),a=this.runNormalizeStructure(a,i),t.value=e;const l=Math.min(a.length,e.length);try{t.setSelectionRange(l,l)}catch(t){}}evaluateInput(){if(this.composing)return;this.clearErrors(),this.revertRequest=null;const t=this.displayElement,e=t.value,i=this.createCtx();let n=e;n=this.runNormalizeChar(n,i),n=this.runNormalizeStructure(n,i),n!==e&&this.setDisplayValuePreserveCaret(t,n,i),this.runValidate(n,i),this.revertRequest?this.revertDisplay(this.revertRequest):(this.syncRaw(n),this.applyInvalidClass(),this.lastAcceptedValue=n,this.lastAcceptedSelection=this.readSelection(t))}evaluateCommit(){if(this.composing)return;this.clearErrors(),this.revertRequest=null;const t=this.displayElement,e=this.createCtx();let i=t.value;if(i=this.runNormalizeChar(i,e),i=this.runNormalizeStructure(i,e),this.runValidate(i,e),this.revertRequest)return void this.revertDisplay(this.revertRequest);if(i=this.runFix(i,e),this.clearErrors(),this.revertRequest=null,this.runValidate(i,e),this.revertRequest)return void this.revertDisplay(this.revertRequest);this.syncRaw(i);let n=i;n=this.runFormat(n,e),this.syncDisplay(n),this.applyInvalidClass(),this.lastAcceptedValue=i,this.lastAcceptedSelection=this.readSelection(t)}isValid(){return 0===this.errors.length}getErrors(){return this.errors.slice()}getRawValue(){return this.rawElement?this.rawElement.value:this.displayElement.value}toGuard(){return{detach:()=>this.detach(),isValid:()=>this.isValid(),getErrors:()=>this.getErrors(),getRawValue:()=>this.getRawValue(),getDisplayElement:()=>this.displayElement}}}function n(t){if(null==t)return;const e=String(t).trim().toLowerCase();return""===e||"true"===e||"1"===e||"yes"===e||"on"===e||"false"!==e&&"0"!==e&&"no"!==e&&"off"!==e&&void 0}function r(t){if(null==t)return;const e=String(t).trim();if(""===e)return;const i=Number(e);return Number.isFinite(i)?i:void 0}function s(t,e){if(null==t)return;const i=String(t).trim();return""!==i&&e.includes(i)?i:void 0}function a(t){if(null==t||""===String(t).trim())return"auto";const e=String(t).trim().toLowerCase();return"auto"===e||"swap"===e||"off"===e?e:"auto"}function l(t){if(null!=t.tigSeparate)return!0;if(null!=t.tigWarn)return!0;if(null!=t.tigInvalidClass)return!0;for(const e in t)if(e.startsWith("tigRules"))return!0;return!1}function o(t={}){const e=t.allowFullWidth??!0,i=t.allowMinus??!1,n=t.allowDecimal??!1,r=t.allowEmpty??!0,s=new Set(["ー","-","−","‐","-","‒","–","—","―"]),a=new Set([".","。","。"]);function l(t){if(t>="0"&&t<="9")return t;if(e){const e=function(t){const e=t.charCodeAt(0);return 65296<=e&&e<=65305?String.fromCharCode(e-65296+48):null}(t);if(e)return e}return"."===t||e&&a.has(t)?n?".":"":"-"===t?i?"-":"":e&&s.has(t)&&i?"-":""}return{name:"numeric",targets:["input"],normalizeChar(t){let e=String(t);e=e.replace(/,/g,"");let i="";for(const t of e)i+=l(t);return i},normalizeStructure(t){let e="",r=!1,s=!1;for(const a of String(t))a>="0"&&a<="9"?e+=a:"-"===a&&i?r||0!==e.length||(e+="-",r=!0):"."===a&&n&&(s||(e+=".",s=!0));return e},fix(t){let e=String(t);if(""===e)return r?"":"0";if("-"===e||"."===e||"-."===e)return r?"":"0";e.startsWith("-.")&&(e="-0"+e.slice(1)),e.startsWith(".")&&(e="0"+e),e.endsWith(".")&&(e=e.slice(0,-1));let i="";e.startsWith("-")&&(i="-",e=e.slice(1));const n=e.indexOf(".");let s=n>=0?e.slice(0,n):e;const a=n>=0?e.slice(n+1):"";return s=s.replace(/^0+/,""),""===s&&(s="0"),"-"!==i||"0"!==s||a&&!/^0*$/.test(a)||(i=""),n>=0?`${i}${s}.${a}`:`${i}${s}`},validate(t,e){}}}function u(t){let e="",i=String(t);i.startsWith("-")&&(e="-",i=i.slice(1));const n=i.indexOf(".");if(!(n>=0))return{sign:e,intPart:i,fracPart:"",hasDot:!1};return{sign:e,intPart:i.slice(0,n),fracPart:i.slice(n+1),hasDot:!0}}function c(t){let e=1;const i=t.split("");for(let t=i.length-1;t>=0;t--){const n=i[t].charCodeAt(0)-48+e;if(!(n>=10)){i[t]=String.fromCharCode(48+n),e=0;break}i[t]="0",e=1}return 1===e&&i.unshift("1"),i.join("")}function h(t={}){const e={int:"number"==typeof t.int?t.int:void 0,frac:"number"==typeof t.frac?t.frac:void 0,countLeadingZeros:t.countLeadingZeros??!0,fixIntOnBlur:t.fixIntOnBlur??"none",fixFracOnBlur:t.fixFracOnBlur??"none",overflowInputInt:t.overflowInputInt??"none",overflowInputFrac:t.overflowInputFrac??"none",forceFracOnBlur:t.forceFracOnBlur??!1};return{name:"digits",targets:["input"],validate(t,i){const n=String(t);if(""===n||"-"===n||"."===n||"-."===n)return;const{intPart:r,fracPart:s}=u(n);if("number"==typeof e.int){const t=function(t,e){const i=t??"";if(0===i.length)return 0;if(e)return i.length;const n=i.replace(/^0+/,"");return 0===n.length?1:n.length}(r,e.countLeadingZeros);if(t>e.int){if("block"===e.overflowInputInt)return void i.requestRevert({reason:"digits.int_overflow",detail:{limit:e.int,actual:t}});i.pushError({code:"digits.int_overflow",rule:"digits",phase:"validate",detail:{limit:e.int,actual:t}})}}if("number"==typeof e.frac){const t=(s??"").length;if(t>e.frac){if("block"===e.overflowInputFrac)return void i.requestRevert({reason:"digits.frac_overflow",detail:{limit:e.frac,actual:t}});i.pushError({code:"digits.frac_overflow",rule:"digits",phase:"validate",detail:{limit:e.frac,actual:t}})}}},fix(t,i){const n=String(t);if(""===n||"-"===n||"."===n||"-."===n)return n;const r=u(n);let{intPart:s,fracPart:a}=r;const{sign:l,hasDot:o}=r;if("number"==typeof e.int&&"none"!==e.fixIntOnBlur){(s??"").length>e.int&&("truncateLeft"===e.fixIntOnBlur?s=s.slice(s.length-e.int):"truncateRight"===e.fixIntOnBlur?s=s.slice(0,e.int):"clamp"===e.fixIntOnBlur&&(s="9".repeat(e.int)))}if("number"==typeof e.frac&&"none"!==e.fixFracOnBlur&&o){const t=e.frac,i=a??"";if(i.length>t)if("truncate"===e.fixFracOnBlur)a=i.slice(0,t);else if("round"===e.fixFracOnBlur){const e=function(t,e,i){const n=e??"";if(n.length<=i)return{intPart:t,fracPart:n};const r=n.slice(0,i);if(n.charCodeAt(i)-48<5)return{intPart:t,fracPart:r};if(0===i)return{intPart:c(t.length?t:"0"),fracPart:""};let s=1;const a=r.split("");for(let t=a.length-1;t>=0;t--){const e=a[t].charCodeAt(0)-48+s;if(!(e>=10)){a[t]=String.fromCharCode(48+e),s=0;break}a[t]="0",s=1}const l=a.join("");let o=t;return 1===s&&(o=c(t.length?t:"0")),{intPart:o,fracPart:l}}(s,i,t);s=e.intPart,a=e.fracPart}}if(e.forceFracOnBlur&&"number"==typeof e.frac&&e.frac>0){const t=e.frac;o||(a="");const i=a??"";i.length<t&&(a=i+"0".repeat(t-i.length))}if("number"!=typeof e.frac)return`${l}${s}`;if(0===e.frac)return`${l}${s}`;if(o||e.forceFracOnBlur&&e.frac>0){return`${l}${s}.${a??""}`}return`${l}${s}`}}}function d(){return{name:"comma",targets:["input"],format(t){const e=String(t);if(""===e||"-"===e||"."===e||"-."===e)return e;let i="",n=e;n.startsWith("-")&&(i="-",n=n.slice(1));const r=n.indexOf("."),s=r>=0?n.slice(0,r):n,a=r>=0?n.slice(r+1):null,l=s.replace(/\B(?=(\d{3})+(?!\d))/g,",");return null!=a?`${i}${l}.${a}`:`${i}${l}`}}}o.fromDataset=function(t,e){if(null==t.tigRulesNumeric)return null;const i={},r=n(t.tigRulesNumericAllowFullWidth);null!=r&&(i.allowFullWidth=r);const s=n(t.tigRulesNumericAllowMinus);null!=s&&(i.allowMinus=s);const a=n(t.tigRulesNumericAllowDecimal);null!=a&&(i.allowDecimal=a);const l=n(t.tigRulesNumericAllowEmpty);return null!=l&&(i.allowEmpty=l),o(i)},h.fromDataset=function(t,e){if(null==t.tigRulesDigits)return null;const i={},a=r(t.tigRulesDigitsInt);null!=a&&(i.int=a);const l=r(t.tigRulesDigitsFrac);null!=l&&(i.frac=l);const o=n(t.tigRulesDigitsCountLeadingZeros);null!=o&&(i.countLeadingZeros=o);const u=s(t.tigRulesDigitsFixIntOnBlur,["none","truncateLeft","truncateRight","clamp"]);null!=u&&(i.fixIntOnBlur=u);const c=s(t.tigRulesDigitsFixFracOnBlur,["none","truncate","round"]);null!=c&&(i.fixFracOnBlur=c);const d=s(t.tigRulesDigitsOverflowInputInt,["none","block"]);null!=d&&(i.overflowInputInt=d);const m=s(t.tigRulesDigitsOverflowInputFrac,["none","block"]);null!=m&&(i.overflowInputFrac=m);const p=n(t.tigRulesDigitsForceFracOnBlur);return null!=p&&(i.forceFracOnBlur=p),h(i)},d.fromDataset=function(t,e){return null==t.tigRulesComma?null:d()};const m=new class{constructor(t,e){this.attachFn=t,this.ruleFactories=Array.isArray(e)?e:[]}register(t){this.ruleFactories.push(t)}autoAttach(t=document){const e=[],i=[];if(t.querySelectorAll){const e=t.querySelectorAll("input, textarea");for(const t of e)(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&i.push(t)}(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&(i.includes(t)||i.push(t));for(const t of i){const i=t.dataset;if("true"===i.tigAttached)continue;if(!l(i))continue;const r={},s=n(i.tigWarn);null!=s&&(r.warn=s),null!=i.tigInvalidClass&&""!==String(i.tigInvalidClass).trim()&&(r.invalidClass=String(i.tigInvalidClass)),r.separateValue={mode:a(i.tigSeparate)};const o=[];for(const e of this.ruleFactories)try{const n=e.fromDataset(i,t);n&&o.push(n)}catch(t){(r.warn??!0)&&console.warn(`[text-input-guard] autoAttach: rule "${e.name}" fromDataset() threw an error.`,t)}if(o.length>0&&(r.rules=o),!r.rules||0===r.rules.length)continue;const u=this.attachFn(t,r);e.push(u),t.dataset.tigAttached="true"}return{detach:()=>{for(const t of e)t.detach()},isValid:()=>e.every(t=>t.isValid()),getErrors:()=>e.flatMap(t=>t.getErrors()),getGuards:()=>e}}}(e,[{name:"numeric",fromDataset:o.fromDataset},{name:"digits",fromDataset:h.fromDataset},{name:"comma",fromDataset:d.fromDataset}]),p={numeric:o,digits:h,comma:d};exports.attach=e,exports.attachAll=function(t,i={}){const n=[];for(const r of t)n.push(e(r,i));return{detach:()=>{for(const t of n)t.detach()},isValid:()=>n.every(t=>t.isValid()),getErrors:()=>n.flatMap(t=>t.getErrors()),getGuards:()=>n}},exports.autoAttach=t=>m.autoAttach(t),exports.comma=d,exports.digits=h,exports.numeric=o,exports.rules=p,exports.version="0.0.1";
6
+ "use strict";class t{originalType;originalId;originalName;originalClass;originalUiAttrs;originalAriaAttrs;originalDataset;createdDisplay;constructor(t){this.originalType=t.type,this.originalId=t.getAttribute("id"),this.originalName=t.getAttribute("name"),this.originalClass=t.className,this.originalUiAttrs={},this.originalAriaAttrs={},this.originalDataset={},this.createdDisplay=null;const e=["placeholder","inputmode","autocomplete","required","minlength","maxlength","pattern","title","tabindex"];for(const i of e)this.originalUiAttrs[i]=t.hasAttribute(i)?t.getAttribute(i):null;for(const e of t.attributes)e.name.startsWith("aria-")&&(this.originalAriaAttrs[e.name]=e.value??"");for(const[e,i]of Object.entries(t.dataset))e.startsWith("tig")||(this.originalDataset[e]=i)}applyToRaw(t){t.type="hidden",t.removeAttribute("id"),t.className="",t.dataset.tigRole="raw",this.originalId&&(t.dataset.tigOriginalId=this.originalId),this.originalName&&(t.dataset.tigOriginalName=this.originalName)}createDisplay(t){const e=document.createElement("input");e.type="text",e.dataset.tigRole="display",this.originalId&&(e.id=this.originalId),e.className=this.originalClass??"",e.value=t.value;for(const[t,i]of Object.entries(this.originalUiAttrs))null==i?e.removeAttribute(t):e.setAttribute(t,i);for(const[t,i]of Object.entries(this.originalAriaAttrs))e.setAttribute(t,i);for(const[t,i]of Object.entries(this.originalDataset))e.dataset[t]=i;return this.createdDisplay=e,e}removeDisplay(){this.createdDisplay?.parentNode&&this.createdDisplay.parentNode.removeChild(this.createdDisplay),this.createdDisplay=null}restoreRaw(t){t.type=this.originalType,this.originalId?t.setAttribute("id",this.originalId):t.removeAttribute("id"),this.originalName?t.setAttribute("name",this.originalName):t.removeAttribute("name"),t.className=this.originalClass??"",delete t.dataset.tigRole,delete t.dataset.tigOriginalId,delete t.dataset.tigOriginalName}}function e(t,e){e&&console.warn(t)}function i(t,e={}){const i=new n(t,e);return i.init(),i.toGuard()}class n{constructor(t,e){this.originalElement=t,this.options=e;const i=(n=t)instanceof HTMLInputElement?"input":n instanceof HTMLTextAreaElement?"textarea":null;var n;if(!i)throw new TypeError("[text-input-guard] attach() expects an <input> or <textarea> element.");this.kind=i,this.warn=e.warn??!0,this.invalidClass=e.invalidClass??"is-invalid",this.rules=Array.isArray(e.rules)?e.rules:[],this.hostElement=t,this.displayElement=t,this.rawElement=null,this.composing=!1,this.errors=[],this.normalizeCharRules=[],this.normalizeStructureRules=[],this.validateRules=[],this.fixRules=[],this.formatRules=[],this.onCompositionStart=this.onCompositionStart.bind(this),this.onCompositionEnd=this.onCompositionEnd.bind(this),this.onInput=this.onInput.bind(this),this.onBlur=this.onBlur.bind(this),this.onFocus=this.onFocus.bind(this),this.onSelectionChange=this.onSelectionChange.bind(this),this.swapState=null,this.pendingCompositionCommit=!1,this.lastAcceptedValue="",this.lastAcceptedSelection={start:null,end:null,direction:null},this.revertRequest=null}init(){this.buildPipeline(),this.applySeparateValue(),this.bindEvents(),this.evaluateInput()}readSelection(t){return{start:t.selectionStart,end:t.selectionEnd,direction:t.selectionDirection}}writeSelection(t,e){if(null!=e.start&&null!=e.end)try{e.direction?t.setSelectionRange(e.start,e.end,e.direction):t.setSelectionRange(e.start,e.end)}catch(t){}}applySeparateValue(){const i=this.options.separateValue?.mode??"auto";if("swap"!==("auto"===i?this.formatRules.length>0?"swap":"off":i))return;if("input"!==this.kind)return void e('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.',this.warn);const n=this.originalElement,s=new t(n);s.applyToRaw(n);const r=s.createDisplay(n);n.after(r),this.swapState=s,this.hostElement=n,this.displayElement=r,this.rawElement=n,this.lastAcceptedValue=r.value,this.lastAcceptedSelection=this.readSelection(r)}restoreSeparateValue(){if(!this.swapState)return;const t=this.swapState,e=this.hostElement,i=t.createdDisplay;if(i)try{e.value=e.value||i.value}catch(t){}t.removeDisplay(),t.restoreRaw(e),this.hostElement=this.originalElement,this.displayElement=this.originalElement,this.rawElement=null,this.swapState=null}detach(){this.unbindEvents(),this.restoreSeparateValue()}buildPipeline(){this.normalizeCharRules=[],this.normalizeStructureRules=[],this.validateRules=[],this.fixRules=[],this.formatRules=[];for(const t of this.rules){"input"===this.kind&&t.targets.includes("input")||"textarea"===this.kind&&t.targets.includes("textarea")?(t.normalizeChar&&this.normalizeCharRules.push(t),t.normalizeStructure&&this.normalizeStructureRules.push(t),t.validate&&this.validateRules.push(t),t.fix&&this.fixRules.push(t),t.format&&this.formatRules.push(t)):e(`[text-input-guard] Rule "${t.name}" is not supported for <${this.kind}>. skipped.`,this.warn)}}bindEvents(){this.displayElement.addEventListener("compositionstart",this.onCompositionStart),this.displayElement.addEventListener("compositionend",this.onCompositionEnd),this.displayElement.addEventListener("input",this.onInput),this.displayElement.addEventListener("blur",this.onBlur),this.displayElement.addEventListener("focus",this.onFocus),this.displayElement.addEventListener("keyup",this.onSelectionChange),this.displayElement.addEventListener("mouseup",this.onSelectionChange),this.displayElement.addEventListener("select",this.onSelectionChange),this.displayElement.addEventListener("focus",this.onSelectionChange)}unbindEvents(){this.displayElement.removeEventListener("compositionstart",this.onCompositionStart),this.displayElement.removeEventListener("compositionend",this.onCompositionEnd),this.displayElement.removeEventListener("input",this.onInput),this.displayElement.removeEventListener("blur",this.onBlur),this.displayElement.removeEventListener("focus",this.onFocus),this.displayElement.removeEventListener("keyup",this.onSelectionChange),this.displayElement.removeEventListener("mouseup",this.onSelectionChange),this.displayElement.removeEventListener("select",this.onSelectionChange),this.displayElement.removeEventListener("focus",this.onSelectionChange)}revertDisplay(t){const e=this.displayElement;e.value=this.lastAcceptedValue,this.writeSelection(e,this.lastAcceptedSelection),this.syncRaw(this.lastAcceptedValue),this.clearErrors(),this.applyInvalidClass(),this.revertRequest=null,this.warn}createCtx(){return{hostElement:this.hostElement,displayElement:this.displayElement,rawElement:this.rawElement,kind:this.kind,warn:this.warn,invalidClass:this.invalidClass,composing:this.composing,pushError:t=>this.errors.push(t),requestRevert:t=>{this.revertRequest||(this.revertRequest=t)}}}clearErrors(){this.errors=[]}runNormalizeChar(t,e){let i=t;for(const t of this.normalizeCharRules)i=t.normalizeChar?t.normalizeChar(i,e):i;return i}runNormalizeStructure(t,e){let i=t;for(const t of this.normalizeStructureRules)i=t.normalizeStructure?t.normalizeStructure(i,e):i;return i}runValidate(t,e){for(const i of this.validateRules)i.validate&&i.validate(t,e)}runFix(t,e){let i=t;for(const t of this.fixRules)i=t.fix?t.fix(i,e):i;return i}runFormat(t,e){let i=t;for(const t of this.formatRules)i=t.format?t.format(i,e):i;return i}applyInvalidClass(){const t=this.displayElement;this.errors.length>0?t.classList.add(this.invalidClass):t.classList.remove(this.invalidClass)}syncRaw(t){this.rawElement&&(this.rawElement.value=t)}syncDisplay(t){(this.displayElement instanceof HTMLInputElement||this.displayElement instanceof HTMLTextAreaElement)&&(this.displayElement.value=t)}onCompositionStart(){this.composing=!0}onCompositionEnd(){this.composing=!1,this.pendingCompositionCommit=!0,queueMicrotask(()=>{this.pendingCompositionCommit&&(this.pendingCompositionCommit=!1,this.evaluateInput())})}onInput(){this.pendingCompositionCommit=!1,this.evaluateInput()}onBlur(){this.evaluateCommit()}onFocus(){if(this.composing)return;const t=this.displayElement,e=t.value,i=this.createCtx();let n=e;n=this.runNormalizeChar(n,i),n=this.runNormalizeStructure(n,i),n!==e&&(this.setDisplayValuePreserveCaret(t,n,i),this.syncRaw(n)),this.lastAcceptedValue=n,this.lastAcceptedSelection=this.readSelection(t),this.onSelectionChange()}onSelectionChange(){if(this.composing)return;const t=this.displayElement;this.lastAcceptedSelection=this.readSelection(t)}setDisplayValuePreserveCaret(t,e,i){const n=t.value;if(n===e)return;const s=t.selectionStart,r=t.selectionEnd;if(null==s||null==r)return void(t.value=e);let a=n.slice(0,s);a=this.runNormalizeChar(a,i),a=this.runNormalizeStructure(a,i),t.value=e;const l=Math.min(a.length,e.length);try{t.setSelectionRange(l,l)}catch(t){}}evaluateInput(){if(this.composing)return;this.clearErrors(),this.revertRequest=null;const t=this.displayElement,e=t.value,i=this.createCtx();let n=e;n=this.runNormalizeChar(n,i),n=this.runNormalizeStructure(n,i),n!==e&&this.setDisplayValuePreserveCaret(t,n,i),this.runValidate(n,i),this.revertRequest?this.revertDisplay(this.revertRequest):(this.syncRaw(n),this.applyInvalidClass(),this.lastAcceptedValue=n,this.lastAcceptedSelection=this.readSelection(t))}evaluateCommit(){if(this.composing)return;this.clearErrors(),this.revertRequest=null;const t=this.displayElement,e=this.createCtx();let i=t.value;if(i=this.runNormalizeChar(i,e),i=this.runNormalizeStructure(i,e),this.runValidate(i,e),this.revertRequest)return void this.revertDisplay(this.revertRequest);if(i=this.runFix(i,e),this.clearErrors(),this.revertRequest=null,this.runValidate(i,e),this.revertRequest)return void this.revertDisplay(this.revertRequest);this.syncRaw(i);let n=i;n=this.runFormat(n,e),this.syncDisplay(n),this.applyInvalidClass(),this.lastAcceptedValue=i,this.lastAcceptedSelection=this.readSelection(t)}isValid(){return 0===this.errors.length}getErrors(){return this.errors.slice()}getRawValue(){return this.hostElement.value}getDisplayValue(){return this.displayElement.value}setValue(t,e="commit"){let i;i=null==t?"":"number"==typeof t?Number.isFinite(t)?String(t):"":String(t);const n=this.displayElement;if(n.value=i,"none"===e)return this.syncRaw(i),this.lastAcceptedValue=i,void(this.lastAcceptedSelection=this.readSelection(n));"input"!==e?this.evaluateCommit():this.evaluateInput()}toGuard(){return{detach:()=>this.detach(),isValid:()=>this.isValid(),getErrors:()=>this.getErrors(),getRawValue:()=>this.getRawValue(),getDisplayValue:()=>this.getDisplayValue(),getRawElement:()=>this.hostElement,getDisplayElement:()=>this.displayElement,evaluate:()=>this.evaluateInput(),commit:()=>this.evaluateCommit(),setValue:(t,e)=>this.setValue(t,e)}}}function s(t){if(null==t)return;const e=String(t).trim().toLowerCase();return""===e||"true"===e||"1"===e||"yes"===e||"on"===e||"false"!==e&&"0"!==e&&"no"!==e&&"off"!==e&&void 0}function r(t){if(null==t)return;const e=String(t).trim();if(""===e)return;const i=Number(e);return Number.isFinite(i)?i:void 0}function a(t,e){if(null==t)return;const i=String(t).trim();return""!==i&&e.includes(i)?i:void 0}function l(t){if(null==t||""===String(t).trim())return"auto";const e=String(t).trim().toLowerCase();return"auto"===e||"swap"===e||"off"===e?e:"auto"}function o(t){if(null!=t.tigSeparate)return!0;if(null!=t.tigWarn)return!0;if(null!=t.tigInvalidClass)return!0;for(const e in t)if(e.startsWith("tigRules"))return!0;return!1}function u(t={}){const e=t.allowFullWidth??!0,i=t.allowMinus??!1,n=t.allowDecimal??!1,s=t.allowEmpty??!0,r=new Set(["ー","-","−","‐","-","‒","–","—","―"]),a=new Set([".","。","。"]);function l(t){if(t>="0"&&t<="9")return t;if(e){const e=function(t){const e=t.charCodeAt(0);return 65296<=e&&e<=65305?String.fromCharCode(e-65296+48):null}(t);if(e)return e}return"."===t||e&&a.has(t)?n?".":"":"-"===t?i?"-":"":e&&r.has(t)&&i?"-":""}return{name:"numeric",targets:["input"],normalizeChar(t){let e=String(t);e=e.replace(/,/g,"");let i="";for(const t of e)i+=l(t);return i},normalizeStructure(t){let e="",s=!1,r=!1;for(const a of String(t))a>="0"&&a<="9"?e+=a:"-"===a&&i?s||0!==e.length||(e+="-",s=!0):"."===a&&n&&(r||(e+=".",r=!0));return e},fix(t){let e=String(t);if(""===e)return s?"":"0";if("-"===e||"."===e||"-."===e)return s?"":"0";e.startsWith("-.")&&(e="-0"+e.slice(1)),e.startsWith(".")&&(e="0"+e),e.endsWith(".")&&(e=e.slice(0,-1));let i="";e.startsWith("-")&&(i="-",e=e.slice(1));const n=e.indexOf(".");let r=n>=0?e.slice(0,n):e;const a=n>=0?e.slice(n+1):"";return r=r.replace(/^0+/,""),""===r&&(r="0"),"-"!==i||"0"!==r||a&&!/^0*$/.test(a)||(i=""),n>=0?`${i}${r}.${a}`:`${i}${r}`},validate(t,e){}}}function c(t){let e="",i=String(t);i.startsWith("-")&&(e="-",i=i.slice(1));const n=i.indexOf(".");if(!(n>=0))return{sign:e,intPart:i,fracPart:"",hasDot:!1};return{sign:e,intPart:i.slice(0,n),fracPart:i.slice(n+1),hasDot:!0}}function h(t){let e=1;const i=t.split("");for(let t=i.length-1;t>=0;t--){const n=i[t].charCodeAt(0)-48+e;if(!(n>=10)){i[t]=String.fromCharCode(48+n),e=0;break}i[t]="0",e=1}return 1===e&&i.unshift("1"),i.join("")}function d(t={}){const e={int:"number"==typeof t.int?t.int:void 0,frac:"number"==typeof t.frac?t.frac:void 0,countLeadingZeros:t.countLeadingZeros??!0,fixIntOnBlur:t.fixIntOnBlur??"none",fixFracOnBlur:t.fixFracOnBlur??"none",overflowInputInt:t.overflowInputInt??"none",overflowInputFrac:t.overflowInputFrac??"none",forceFracOnBlur:t.forceFracOnBlur??!1};return{name:"digits",targets:["input"],validate(t,i){const n=String(t);if(""===n||"-"===n||"."===n||"-."===n)return;const{intPart:s,fracPart:r}=c(n);if("number"==typeof e.int){const t=function(t,e){const i=t??"";if(0===i.length)return 0;if(e)return i.length;const n=i.replace(/^0+/,"");return 0===n.length?1:n.length}(s,e.countLeadingZeros);if(t>e.int){if("block"===e.overflowInputInt)return void i.requestRevert({reason:"digits.int_overflow",detail:{limit:e.int,actual:t}});i.pushError({code:"digits.int_overflow",rule:"digits",phase:"validate",detail:{limit:e.int,actual:t}})}}if("number"==typeof e.frac){const t=(r??"").length;if(t>e.frac){if("block"===e.overflowInputFrac)return void i.requestRevert({reason:"digits.frac_overflow",detail:{limit:e.frac,actual:t}});i.pushError({code:"digits.frac_overflow",rule:"digits",phase:"validate",detail:{limit:e.frac,actual:t}})}}},fix(t,i){const n=String(t);if(""===n||"-"===n||"."===n||"-."===n)return n;const s=c(n);let{intPart:r,fracPart:a}=s;const{sign:l,hasDot:o}=s;if("number"==typeof e.int&&"none"!==e.fixIntOnBlur){(r??"").length>e.int&&("truncateLeft"===e.fixIntOnBlur?r=r.slice(r.length-e.int):"truncateRight"===e.fixIntOnBlur?r=r.slice(0,e.int):"clamp"===e.fixIntOnBlur&&(r="9".repeat(e.int)))}if("number"==typeof e.frac&&"none"!==e.fixFracOnBlur&&o){const t=e.frac,i=a??"";if(i.length>t)if("truncate"===e.fixFracOnBlur)a=i.slice(0,t);else if("round"===e.fixFracOnBlur){const e=function(t,e,i){const n=e??"";if(n.length<=i)return{intPart:t,fracPart:n};const s=n.slice(0,i);if(n.charCodeAt(i)-48<5)return{intPart:t,fracPart:s};if(0===i)return{intPart:h(t.length?t:"0"),fracPart:""};let r=1;const a=s.split("");for(let t=a.length-1;t>=0;t--){const e=a[t].charCodeAt(0)-48+r;if(!(e>=10)){a[t]=String.fromCharCode(48+e),r=0;break}a[t]="0",r=1}const l=a.join("");let o=t;return 1===r&&(o=h(t.length?t:"0")),{intPart:o,fracPart:l}}(r,i,t);r=e.intPart,a=e.fracPart}}if(e.forceFracOnBlur&&"number"==typeof e.frac&&e.frac>0){const t=e.frac;o||(a="");const i=a??"";i.length<t&&(a=i+"0".repeat(t-i.length))}if("number"!=typeof e.frac)return`${l}${r}`;if(0===e.frac)return`${l}${r}`;if(o||e.forceFracOnBlur&&e.frac>0){return`${l}${r}.${a??""}`}return`${l}${r}`}}}function m(){return{name:"comma",targets:["input"],format(t){const e=String(t);if(""===e||"-"===e||"."===e||"-."===e)return e;let i="",n=e;n.startsWith("-")&&(i="-",n=n.slice(1));const s=n.indexOf("."),r=s>=0?n.slice(0,s):n,a=s>=0?n.slice(s+1):null,l=r.replace(/\B(?=(\d{3})+(?!\d))/g,",");return null!=a?`${i}${l}.${a}`:`${i}${l}`}}}u.fromDataset=function(t,e){if(null==t.tigRulesNumeric)return null;const i={},n=s(t.tigRulesNumericAllowFullWidth);null!=n&&(i.allowFullWidth=n);const r=s(t.tigRulesNumericAllowMinus);null!=r&&(i.allowMinus=r);const a=s(t.tigRulesNumericAllowDecimal);null!=a&&(i.allowDecimal=a);const l=s(t.tigRulesNumericAllowEmpty);return null!=l&&(i.allowEmpty=l),u(i)},d.fromDataset=function(t,e){if(null==t.tigRulesDigits)return null;const i={},n=r(t.tigRulesDigitsInt);null!=n&&(i.int=n);const l=r(t.tigRulesDigitsFrac);null!=l&&(i.frac=l);const o=s(t.tigRulesDigitsCountLeadingZeros);null!=o&&(i.countLeadingZeros=o);const u=a(t.tigRulesDigitsFixIntOnBlur,["none","truncateLeft","truncateRight","clamp"]);null!=u&&(i.fixIntOnBlur=u);const c=a(t.tigRulesDigitsFixFracOnBlur,["none","truncate","round"]);null!=c&&(i.fixFracOnBlur=c);const h=a(t.tigRulesDigitsOverflowInputInt,["none","block"]);null!=h&&(i.overflowInputInt=h);const m=a(t.tigRulesDigitsOverflowInputFrac,["none","block"]);null!=m&&(i.overflowInputFrac=m);const p=s(t.tigRulesDigitsForceFracOnBlur);return null!=p&&(i.forceFracOnBlur=p),d(i)},m.fromDataset=function(t,e){return null==t.tigRulesComma?null:m()};const p=new class{constructor(t,e){this.attachFn=t,this.ruleFactories=Array.isArray(e)?e:[]}register(t){this.ruleFactories.push(t)}autoAttach(t=document){const e=[],i=[];if(t.querySelectorAll){const e=t.querySelectorAll("input, textarea");for(const t of e)(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&i.push(t)}(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&(i.includes(t)||i.push(t));for(const t of i){const i=t.dataset;if("true"===i.tigAttached)continue;if(!o(i))continue;const n={},r=s(i.tigWarn);null!=r&&(n.warn=r),null!=i.tigInvalidClass&&""!==String(i.tigInvalidClass).trim()&&(n.invalidClass=String(i.tigInvalidClass)),n.separateValue={mode:l(i.tigSeparate)};const a=[];for(const e of this.ruleFactories)try{const n=e.fromDataset(i,t);n&&a.push(n)}catch(t){(n.warn??!0)&&console.warn(`[text-input-guard] autoAttach: rule "${e.name}" fromDataset() threw an error.`,t)}if(a.length>0&&(n.rules=a),!n.rules||0===n.rules.length)continue;const u=this.attachFn(t,n);e.push(u),t.dataset.tigAttached="true"}return{detach:()=>{for(const t of e)t.detach()},isValid:()=>e.every(t=>t.isValid()),getErrors:()=>e.flatMap(t=>t.getErrors()),getGuards:()=>e}}}(i,[{name:"numeric",fromDataset:u.fromDataset},{name:"digits",fromDataset:d.fromDataset},{name:"comma",fromDataset:m.fromDataset}]),f={numeric:u,digits:d,comma:m};exports.attach=i,exports.attachAll=function(t,e={}){const n=[];for(const s of t)n.push(i(s,e));return{detach:()=>{for(const t of n)t.detach()},isValid:()=>n.every(t=>t.isValid()),getErrors:()=>n.flatMap(t=>t.getErrors()),getGuards:()=>n}},exports.autoAttach=t=>p.autoAttach(t),exports.comma=m,exports.digits=d,exports.numeric=u,exports.rules=f,exports.version="0.1.2";