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