text-input-guard 0.0.1 → 0.1.0
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.
- package/README.md +3 -129
- package/dist/cjs/text-input-guard.cjs +126 -87
- package/dist/cjs/text-input-guard.min.cjs +6 -0
- package/dist/esm/text-input-guard.js +126 -87
- package/dist/esm/text-input-guard.min.js +6 -0
- package/dist/umd/text-input-guard.js +126 -87
- package/dist/umd/text-input-guard.min.js +1 -1
- package/package.json +16 -6
package/README.md
CHANGED
|
@@ -4,134 +4,8 @@ TextInputGuard は、**開発中**の日本向けの入力欄ガードライブ
|
|
|
4
4
|
|
|
5
5
|
`<input>` / `<textarea>` に対して、数値入力や日本語特有の制約(全角混在、桁数、表示整形など)を扱いやすい形で提供します。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
シンプルな設計のため、機能追加も簡単に行えます。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- `attachAll()`:まとめて有効化し、まとめて `detach()` する
|
|
11
|
-
- `autoAttach()`:`data-tig-*` の指定から自動で `attach()` する
|
|
9
|
+
詳細は以下をご確認ください
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm i text-input-guard
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## 使い方
|
|
20
|
-
|
|
21
|
-
### 1) attach(単一要素に適用)
|
|
22
|
-
|
|
23
|
-
最も基本の使い方です。1つの要素に対してガードを適用します。
|
|
24
|
-
|
|
25
|
-
```js
|
|
26
|
-
import { attach, rules } from "text-input-guard";
|
|
27
|
-
|
|
28
|
-
const input = document.querySelector("#price");
|
|
29
|
-
|
|
30
|
-
const guard = attach(input, {
|
|
31
|
-
rules: [
|
|
32
|
-
rules.numeric({ allowFullWidth: true, allowMinus: true, allowDecimal: true }),
|
|
33
|
-
rules.digits({
|
|
34
|
-
int: 6,
|
|
35
|
-
frac: 2,
|
|
36
|
-
overflowInputInt: "block",
|
|
37
|
-
overflowInputFrac: "block",
|
|
38
|
-
fixFracOnBlur: "round"
|
|
39
|
-
}),
|
|
40
|
-
rules.comma()
|
|
41
|
-
]
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// 解除
|
|
45
|
-
// guard.detach();
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
`attach()` が返す `Guard` は、次のメソッドを持ちます。
|
|
49
|
-
|
|
50
|
-
- `detach()`:解除(イベント削除・swap復元)
|
|
51
|
-
- `isValid()`:現在エラーが無いか
|
|
52
|
-
- `getErrors()`:エラー一覧
|
|
53
|
-
- `getRawValue()`:送信用の正規化済み値
|
|
54
|
-
|
|
55
|
-
### 2) attachAll(複数要素にまとめて適用)
|
|
56
|
-
|
|
57
|
-
通常は入力欄が複数あるため、`querySelectorAll()` の戻り値に対してまとめて適用できます。
|
|
58
|
-
|
|
59
|
-
```js
|
|
60
|
-
import { attachAll, rules } from "text-input-guard";
|
|
61
|
-
|
|
62
|
-
const group = attachAll(document.querySelectorAll(".tig-price"), {
|
|
63
|
-
rules: [
|
|
64
|
-
rules.numeric({ allowFullWidth: true, allowMinus: true, allowDecimal: true }),
|
|
65
|
-
rules.digits({ int: 6, frac: 2 }),
|
|
66
|
-
rules.comma()
|
|
67
|
-
]
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// まとめて解除
|
|
71
|
-
// group.detach();
|
|
72
|
-
|
|
73
|
-
// 全部 valid なら true
|
|
74
|
-
// group.isValid();
|
|
75
|
-
|
|
76
|
-
// 全部のエラーを集約して取得
|
|
77
|
-
// group.getErrors();
|
|
78
|
-
|
|
79
|
-
// 個別 Guard 配列が欲しい場合
|
|
80
|
-
// const guards = group.getGuards();
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
`attachAll()` は `GuardGroup` を返します。
|
|
84
|
-
|
|
85
|
-
- `detach()`:全て解除
|
|
86
|
-
- `isValid()`:全て valid なら true
|
|
87
|
-
- `getErrors()`:全てのエラーを集約
|
|
88
|
-
- `getGuards()`:個別の `Guard[]` を返す
|
|
89
|
-
|
|
90
|
-
### 3) autoAttach(data属性から自動で適用)
|
|
91
|
-
|
|
92
|
-
HTML側に `data-tig-*` を書いておき、JS側では `autoAttach()` を呼ぶだけで適用できます。
|
|
93
|
-
|
|
94
|
-
```html
|
|
95
|
-
<input
|
|
96
|
-
class="price"
|
|
97
|
-
name="price"
|
|
98
|
-
data-tig-rules-numeric
|
|
99
|
-
data-tig-rules-numeric-allow-full-width="true"
|
|
100
|
-
data-tig-rules-numeric-allow-minus="true"
|
|
101
|
-
data-tig-rules-numeric-allow-decimal="true"
|
|
102
|
-
data-tig-rules-digits
|
|
103
|
-
data-tig-rules-digits-int="6"
|
|
104
|
-
data-tig-rules-digits-frac="2"
|
|
105
|
-
data-tig-rules-digits-overflow-input-int="block"
|
|
106
|
-
data-tig-rules-digits-overflow-input-frac="block"
|
|
107
|
-
data-tig-rules-digits-fix-frac-on-blur="round"
|
|
108
|
-
data-tig-rules-comma
|
|
109
|
-
/>
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
```js
|
|
113
|
-
import { autoAttach } from "text-input-guard";
|
|
114
|
-
|
|
115
|
-
// document 全体を対象に自動適用
|
|
116
|
-
const guards = autoAttach();
|
|
117
|
-
|
|
118
|
-
// 動的追加したコンテナだけ適用したい場合
|
|
119
|
-
// autoAttach(container);
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
`autoAttach()` は attachした `GuardGroup` を返します。
|
|
123
|
-
|
|
124
|
-
- 既に `data-tig-attached` が付いている要素はスキップします
|
|
125
|
-
- `data-tig-rules-*` を読み取って `rules` に変換します
|
|
126
|
-
|
|
127
|
-
## ルール
|
|
128
|
-
|
|
129
|
-
現在のルール例(公開API:`rules.xxx(...)`):
|
|
130
|
-
|
|
131
|
-
- `rules.numeric(...)`:数値入力の正規化(全角→半角、記号統一、不要文字除去)
|
|
132
|
-
- `rules.digits(...)`:整数部/小数部の桁数チェック、確定時の穏やか補正、入力ブロック
|
|
133
|
-
- `rules.comma()`:確定時のカンマ付与(表示整形)
|
|
134
|
-
|
|
135
|
-
## ライセンス
|
|
136
|
-
|
|
137
|
-
MIT
|
|
11
|
+
### **[TextInputGuard 紹介](https://natade-jp.github.io/text-input-guard/)**
|
|
@@ -202,7 +202,7 @@ class InputGuard {
|
|
|
202
202
|
|
|
203
203
|
const kind = detectKind(element);
|
|
204
204
|
if (!kind) {
|
|
205
|
-
throw new TypeError("[
|
|
205
|
+
throw new TypeError("[text-input-guard] attach() expects an <input> or <textarea> element.");
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
/**
|
|
@@ -434,7 +434,7 @@ class InputGuard {
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
if (this.kind !== "input") {
|
|
437
|
-
warnLog('[
|
|
437
|
+
warnLog('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.', this.warn);
|
|
438
438
|
return;
|
|
439
439
|
}
|
|
440
440
|
|
|
@@ -595,7 +595,7 @@ class InputGuard {
|
|
|
595
595
|
|
|
596
596
|
if (!supports) {
|
|
597
597
|
warnLog(
|
|
598
|
-
`[
|
|
598
|
+
`[text-input-guard] Rule "${rule.name}" is not supported for <${this.kind}>. skipped.`,
|
|
599
599
|
this.warn
|
|
600
600
|
);
|
|
601
601
|
continue;
|
|
@@ -680,9 +680,7 @@ class InputGuard {
|
|
|
680
680
|
// 連鎖防止(次の処理に持ち越さない)
|
|
681
681
|
this.revertRequest = null;
|
|
682
682
|
|
|
683
|
-
if (this.warn)
|
|
684
|
-
console.log(`[jp-input-guard] reverted: ${req.reason}`, req.detail);
|
|
685
|
-
}
|
|
683
|
+
if (this.warn) ;
|
|
686
684
|
}
|
|
687
685
|
|
|
688
686
|
/**
|
|
@@ -826,7 +824,7 @@ class InputGuard {
|
|
|
826
824
|
* @returns {void}
|
|
827
825
|
*/
|
|
828
826
|
onCompositionStart() {
|
|
829
|
-
console.log("[
|
|
827
|
+
// console.log("[text-input-guard] compositionstart");
|
|
830
828
|
this.composing = true;
|
|
831
829
|
}
|
|
832
830
|
|
|
@@ -836,7 +834,7 @@ class InputGuard {
|
|
|
836
834
|
* @returns {void}
|
|
837
835
|
*/
|
|
838
836
|
onCompositionEnd() {
|
|
839
|
-
console.log("[
|
|
837
|
+
// console.log("[text-input-guard] compositionend");
|
|
840
838
|
this.composing = false;
|
|
841
839
|
|
|
842
840
|
// compositionend後に input が来ない環境向けのフォールバック
|
|
@@ -856,7 +854,7 @@ class InputGuard {
|
|
|
856
854
|
* @returns {void}
|
|
857
855
|
*/
|
|
858
856
|
onInput() {
|
|
859
|
-
console.log("[
|
|
857
|
+
// console.log("[text-input-guard] input");
|
|
860
858
|
// compositionend後に input が来た場合、フォールバックを無効化
|
|
861
859
|
this.pendingCompositionCommit = false;
|
|
862
860
|
this.evaluateInput();
|
|
@@ -867,7 +865,7 @@ class InputGuard {
|
|
|
867
865
|
* @returns {void}
|
|
868
866
|
*/
|
|
869
867
|
onBlur() {
|
|
870
|
-
console.log("[
|
|
868
|
+
// console.log("[text-input-guard] blur");
|
|
871
869
|
this.evaluateCommit();
|
|
872
870
|
}
|
|
873
871
|
|
|
@@ -1116,6 +1114,63 @@ class InputGuard {
|
|
|
1116
1114
|
* The MIT license https://opensource.org/licenses/MIT
|
|
1117
1115
|
*/
|
|
1118
1116
|
|
|
1117
|
+
/**
|
|
1118
|
+
* datasetのboolean値を解釈する
|
|
1119
|
+
* - 未指定なら undefined
|
|
1120
|
+
* - "" / "true" / "1" / "yes" / "on" は true
|
|
1121
|
+
* - "false" / "0" / "no" / "off" は false
|
|
1122
|
+
* @param {string|undefined} v
|
|
1123
|
+
* @returns {boolean|undefined}
|
|
1124
|
+
*/
|
|
1125
|
+
function parseDatasetBool(v) {
|
|
1126
|
+
if (v == null) { return; }
|
|
1127
|
+
const s = String(v).trim().toLowerCase();
|
|
1128
|
+
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1129
|
+
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* datasetのnumber値を解釈する(整数想定)
|
|
1135
|
+
* - 未指定/空なら undefined
|
|
1136
|
+
* - 数値でなければ undefined
|
|
1137
|
+
* @param {string|undefined} v
|
|
1138
|
+
* @returns {number|undefined}
|
|
1139
|
+
*/
|
|
1140
|
+
function parseDatasetNumber(v) {
|
|
1141
|
+
if (v == null) { return; }
|
|
1142
|
+
const s = String(v).trim();
|
|
1143
|
+
if (s === "") { return; }
|
|
1144
|
+
const n = Number(s);
|
|
1145
|
+
return Number.isFinite(n) ? n : undefined;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* enumを解釈する(未指定なら undefined)
|
|
1150
|
+
* @template {string} T
|
|
1151
|
+
* @param {string|undefined} v
|
|
1152
|
+
* @param {readonly T[]} allowed
|
|
1153
|
+
* @returns {T|undefined}
|
|
1154
|
+
*/
|
|
1155
|
+
function parseDatasetEnum(v, allowed) {
|
|
1156
|
+
if (v == null) { return; }
|
|
1157
|
+
const s = String(v).trim();
|
|
1158
|
+
if (s === "") { return; }
|
|
1159
|
+
// 大文字小文字を区別したいならここを変える(今は厳密一致)
|
|
1160
|
+
return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* The script is part of TextInputGuard.
|
|
1165
|
+
*
|
|
1166
|
+
* AUTHOR:
|
|
1167
|
+
* natade-jp (https://github.com/natade-jp)
|
|
1168
|
+
*
|
|
1169
|
+
* LICENSE:
|
|
1170
|
+
* The MIT license https://opensource.org/licenses/MIT
|
|
1171
|
+
*/
|
|
1172
|
+
|
|
1173
|
+
|
|
1119
1174
|
/**
|
|
1120
1175
|
* @typedef {GuardGroup} GuardGroup
|
|
1121
1176
|
* @typedef {Guard} Guard
|
|
@@ -1130,19 +1185,6 @@ class InputGuard {
|
|
|
1130
1185
|
* @property {(dataset: DOMStringMap, el: HTMLInputElement|HTMLTextAreaElement) => Rule|null} fromDataset
|
|
1131
1186
|
*/
|
|
1132
1187
|
|
|
1133
|
-
/**
|
|
1134
|
-
* Boolean系のdata値を解釈する(未指定なら undefined を返す)
|
|
1135
|
-
* @param {string|undefined} v
|
|
1136
|
-
* @returns {boolean|undefined}
|
|
1137
|
-
*/
|
|
1138
|
-
function parseBool(v) {
|
|
1139
|
-
if (v == null) { return; }
|
|
1140
|
-
const s = String(v).trim().toLowerCase();
|
|
1141
|
-
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1142
|
-
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
1188
|
/**
|
|
1147
1189
|
* separate mode を解釈する(未指定は "auto")
|
|
1148
1190
|
* @param {string|undefined} v
|
|
@@ -1246,7 +1288,7 @@ class InputGuardAutoAttach {
|
|
|
1246
1288
|
const options = {};
|
|
1247
1289
|
|
|
1248
1290
|
// warn / invalidClass
|
|
1249
|
-
const warn =
|
|
1291
|
+
const warn = parseDatasetBool(ds.tigWarn);
|
|
1250
1292
|
if (warn != null) { options.warn = warn; }
|
|
1251
1293
|
|
|
1252
1294
|
if (ds.tigInvalidClass != null && String(ds.tigInvalidClass).trim() !== "") {
|
|
@@ -1266,7 +1308,7 @@ class InputGuardAutoAttach {
|
|
|
1266
1308
|
} catch (e) {
|
|
1267
1309
|
const w = options.warn ?? true;
|
|
1268
1310
|
if (w) {
|
|
1269
|
-
console.warn(`[
|
|
1311
|
+
console.warn(`[text-input-guard] autoAttach: rule "${fac.name}" fromDataset() threw an error.`, e);
|
|
1270
1312
|
}
|
|
1271
1313
|
}
|
|
1272
1314
|
}
|
|
@@ -1292,62 +1334,6 @@ class InputGuardAutoAttach {
|
|
|
1292
1334
|
}
|
|
1293
1335
|
}
|
|
1294
1336
|
|
|
1295
|
-
/**
|
|
1296
|
-
* The script is part of TextInputGuard.
|
|
1297
|
-
*
|
|
1298
|
-
* AUTHOR:
|
|
1299
|
-
* natade-jp (https://github.com/natade-jp)
|
|
1300
|
-
*
|
|
1301
|
-
* LICENSE:
|
|
1302
|
-
* The MIT license https://opensource.org/licenses/MIT
|
|
1303
|
-
*/
|
|
1304
|
-
|
|
1305
|
-
/**
|
|
1306
|
-
* datasetのboolean値を解釈する
|
|
1307
|
-
* - 未指定なら undefined
|
|
1308
|
-
* - "" / "true" / "1" / "yes" / "on" は true
|
|
1309
|
-
* - "false" / "0" / "no" / "off" は false
|
|
1310
|
-
* @param {string|undefined} v
|
|
1311
|
-
* @returns {boolean|undefined}
|
|
1312
|
-
*/
|
|
1313
|
-
function parseDatasetBool(v) {
|
|
1314
|
-
if (v == null) { return; }
|
|
1315
|
-
const s = String(v).trim().toLowerCase();
|
|
1316
|
-
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1317
|
-
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
/**
|
|
1322
|
-
* datasetのnumber値を解釈する(整数想定)
|
|
1323
|
-
* - 未指定/空なら undefined
|
|
1324
|
-
* - 数値でなければ undefined
|
|
1325
|
-
* @param {string|undefined} v
|
|
1326
|
-
* @returns {number|undefined}
|
|
1327
|
-
*/
|
|
1328
|
-
function parseDatasetNumber(v) {
|
|
1329
|
-
if (v == null) { return; }
|
|
1330
|
-
const s = String(v).trim();
|
|
1331
|
-
if (s === "") { return; }
|
|
1332
|
-
const n = Number(s);
|
|
1333
|
-
return Number.isFinite(n) ? n : undefined;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
/**
|
|
1337
|
-
* enumを解釈する(未指定なら undefined)
|
|
1338
|
-
* @template {string} T
|
|
1339
|
-
* @param {string|undefined} v
|
|
1340
|
-
* @param {readonly T[]} allowed
|
|
1341
|
-
* @returns {T|undefined}
|
|
1342
|
-
*/
|
|
1343
|
-
function parseDatasetEnum(v, allowed) {
|
|
1344
|
-
if (v == null) { return; }
|
|
1345
|
-
const s = String(v).trim();
|
|
1346
|
-
if (s === "") { return; }
|
|
1347
|
-
// 大文字小文字を区別したいならここを変える(今は厳密一致)
|
|
1348
|
-
return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
1337
|
/**
|
|
1352
1338
|
* The script is part of TextInputGuard.
|
|
1353
1339
|
*
|
|
@@ -1365,6 +1351,7 @@ function parseDatasetEnum(v, allowed) {
|
|
|
1365
1351
|
* @property {boolean} [allowFullWidth=true] - 全角数字/記号を許可して半角へ正規化する
|
|
1366
1352
|
* @property {boolean} [allowMinus=false] - マイナス記号を許可する(先頭のみ)
|
|
1367
1353
|
* @property {boolean} [allowDecimal=false] - 小数点を許可する(1つだけ)
|
|
1354
|
+
* @property {boolean} [allowEmpty=true] - 空文字を許可するか
|
|
1368
1355
|
*/
|
|
1369
1356
|
|
|
1370
1357
|
/**
|
|
@@ -1380,7 +1367,8 @@ function numeric(options = {}) {
|
|
|
1380
1367
|
const opt = {
|
|
1381
1368
|
allowFullWidth: options.allowFullWidth ?? true,
|
|
1382
1369
|
allowMinus: options.allowMinus ?? false,
|
|
1383
|
-
allowDecimal: options.allowDecimal ?? false
|
|
1370
|
+
allowDecimal: options.allowDecimal ?? false,
|
|
1371
|
+
allowEmpty: options.allowEmpty ?? true
|
|
1384
1372
|
};
|
|
1385
1373
|
|
|
1386
1374
|
/** @type {Set<string>} */
|
|
@@ -1538,9 +1526,14 @@ function numeric(options = {}) {
|
|
|
1538
1526
|
fix(value) {
|
|
1539
1527
|
let v = String(value);
|
|
1540
1528
|
|
|
1529
|
+
// 空文字の扱い
|
|
1530
|
+
if (v === "") {
|
|
1531
|
+
return opt.allowEmpty ? "" : "0";
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1541
1534
|
// 未完成な数値は空にする
|
|
1542
1535
|
if (v === "-" || v === "." || v === "-.") {
|
|
1543
|
-
return "";
|
|
1536
|
+
return opt.allowEmpty ? "" : "0";
|
|
1544
1537
|
}
|
|
1545
1538
|
|
|
1546
1539
|
// "-.1" → "-0.1"
|
|
@@ -1612,6 +1605,7 @@ function numeric(options = {}) {
|
|
|
1612
1605
|
* - data-tig-rules-numeric-allow-full-width -> dataset.tigRulesNumericAllowFullWidth
|
|
1613
1606
|
* - data-tig-rules-numeric-allow-minus -> dataset.tigRulesNumericAllowMinus
|
|
1614
1607
|
* - data-tig-rules-numeric-allow-decimal -> dataset.tigRulesNumericAllowDecimal
|
|
1608
|
+
* - data-tig-rules-numeric-allow-empty -> dataset.tigRulesNumericAllowEmpty
|
|
1615
1609
|
*
|
|
1616
1610
|
* @param {DOMStringMap} dataset
|
|
1617
1611
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -1644,6 +1638,12 @@ numeric.fromDataset = function fromDataset(dataset, _el) {
|
|
|
1644
1638
|
options.allowDecimal = allowDecimal;
|
|
1645
1639
|
}
|
|
1646
1640
|
|
|
1641
|
+
// data-tig-rules-numeric-allow-empty(未指定なら numeric側デフォルト true)
|
|
1642
|
+
const allowEmpty = parseDatasetBool(dataset.tigRulesNumericAllowEmpty);
|
|
1643
|
+
if (allowEmpty != null) {
|
|
1644
|
+
options.allowEmpty = allowEmpty;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
1647
|
return numeric(options);
|
|
1648
1648
|
};
|
|
1649
1649
|
|
|
@@ -1668,6 +1668,7 @@ numeric.fromDataset = function fromDataset(dataset, _el) {
|
|
|
1668
1668
|
* @property {"none"|"truncate"|"round"} [fixFracOnBlur="none"] - blur時の小数部補正
|
|
1669
1669
|
* @property {"none"|"block"} [overflowInputInt="none"] - 入力中:整数部が最大桁を超える入力をブロックする
|
|
1670
1670
|
* @property {"none"|"block"} [overflowInputFrac="none"] - 入力中:小数部が最大桁を超える入力をブロックする
|
|
1671
|
+
* @property {boolean} [forceFracOnBlur=false] - blur時に小数部を必ず表示(frac桁まで0埋め)
|
|
1671
1672
|
*/
|
|
1672
1673
|
|
|
1673
1674
|
/**
|
|
@@ -1808,7 +1809,8 @@ function digits(options = {}) {
|
|
|
1808
1809
|
fixIntOnBlur: options.fixIntOnBlur ?? "none",
|
|
1809
1810
|
fixFracOnBlur: options.fixFracOnBlur ?? "none",
|
|
1810
1811
|
overflowInputInt: options.overflowInputInt ?? "none",
|
|
1811
|
-
overflowInputFrac: options.overflowInputFrac ?? "none"
|
|
1812
|
+
overflowInputFrac: options.overflowInputFrac ?? "none",
|
|
1813
|
+
forceFracOnBlur: options.forceFracOnBlur ?? false
|
|
1812
1814
|
};
|
|
1813
1815
|
|
|
1814
1816
|
return {
|
|
@@ -1925,14 +1927,39 @@ function digits(options = {}) {
|
|
|
1925
1927
|
}
|
|
1926
1928
|
}
|
|
1927
1929
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
+
if (opt.forceFracOnBlur && typeof opt.frac === "number" && opt.frac > 0) {
|
|
1931
|
+
const limit = opt.frac;
|
|
1932
|
+
// "." が無いなら作る(12 → 12.00)
|
|
1933
|
+
if (!hasDot) {
|
|
1934
|
+
fracPart = "";
|
|
1935
|
+
}
|
|
1936
|
+
// 足りない分を 0 埋め(12.3 → 12.30 / 12. → 12.00)
|
|
1937
|
+
const f = fracPart ?? "";
|
|
1938
|
+
if (f.length < limit) {
|
|
1939
|
+
fracPart = f + "0".repeat(limit - f.length);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// 組み立て
|
|
1944
|
+
if (typeof opt.frac !== "number") {
|
|
1945
|
+
// frac未指定なら、dot があっても digits は触らず intだけ返す方針(現状維持)
|
|
1930
1946
|
return `${sign}${intPart}`;
|
|
1931
1947
|
}
|
|
1948
|
+
|
|
1932
1949
|
if (opt.frac === 0) {
|
|
1950
|
+
// 小数0桁なら常に整数表示
|
|
1933
1951
|
return `${sign}${intPart}`;
|
|
1934
1952
|
}
|
|
1935
|
-
|
|
1953
|
+
|
|
1954
|
+
// frac 指定あり(1以上)
|
|
1955
|
+
if (hasDot || (opt.forceFracOnBlur && opt.frac > 0)) {
|
|
1956
|
+
// "." が無いけど forceFracOnBlur の場合もここに来る
|
|
1957
|
+
const f = fracPart ?? "";
|
|
1958
|
+
return `${sign}${intPart}.${f}`;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// "." が無くて force もしないなら整数表示
|
|
1962
|
+
return `${sign}${intPart}`;
|
|
1936
1963
|
}
|
|
1937
1964
|
};
|
|
1938
1965
|
}
|
|
@@ -1951,6 +1978,7 @@ function digits(options = {}) {
|
|
|
1951
1978
|
* - data-tig-rules-digits-fix-frac-on-blur -> dataset.tigRulesDigitsFixFracOnBlur
|
|
1952
1979
|
* - data-tig-rules-digits-overflow-input-int -> dataset.tigRulesDigitsOverflowInputInt
|
|
1953
1980
|
* - data-tig-rules-digits-overflow-input-frac -> dataset.tigRulesDigitsOverflowInputFrac
|
|
1981
|
+
* - data-tig-rules-digits-force-frac-on-blur -> dataset.tigRulesDigitsForceFracOnBlur
|
|
1954
1982
|
*
|
|
1955
1983
|
* @param {DOMStringMap} dataset
|
|
1956
1984
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -2013,6 +2041,12 @@ digits.fromDataset = function fromDataset(dataset, _el) {
|
|
|
2013
2041
|
options.overflowInputFrac = ovFrac;
|
|
2014
2042
|
}
|
|
2015
2043
|
|
|
2044
|
+
// forceFracOnBlur
|
|
2045
|
+
const forceFrac = parseDatasetBool(dataset.tigRulesDigitsForceFracOnBlur);
|
|
2046
|
+
if (forceFrac != null) {
|
|
2047
|
+
options.forceFracOnBlur = forceFrac;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2016
2050
|
return digits(options);
|
|
2017
2051
|
};
|
|
2018
2052
|
|
|
@@ -2038,6 +2072,11 @@ function comma() {
|
|
|
2038
2072
|
|
|
2039
2073
|
/**
|
|
2040
2074
|
* 表示整形(確定時のみ)
|
|
2075
|
+
*
|
|
2076
|
+
* 前提:
|
|
2077
|
+
* - numeric / digits 等で正規化済みの数値文字列が渡される
|
|
2078
|
+
* - 整数部・小数部・符号のみを含む(カンマは含まない想定)
|
|
2079
|
+
*
|
|
2041
2080
|
* @param {string} value
|
|
2042
2081
|
* @returns {string}
|
|
2043
2082
|
*/
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* TextInputGuard
|
|
3
|
+
* AUTHOR: natade (https://github.com/natade-jp/)
|
|
4
|
+
* LICENSE: MIT https://opensource.org/licenses/MIT
|
|
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";
|