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
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
|
|
207
207
|
const kind = detectKind(element);
|
|
208
208
|
if (!kind) {
|
|
209
|
-
throw new TypeError("[
|
|
209
|
+
throw new TypeError("[text-input-guard] attach() expects an <input> or <textarea> element.");
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
/**
|
|
@@ -438,7 +438,7 @@
|
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
if (this.kind !== "input") {
|
|
441
|
-
warnLog('[
|
|
441
|
+
warnLog('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.', this.warn);
|
|
442
442
|
return;
|
|
443
443
|
}
|
|
444
444
|
|
|
@@ -599,7 +599,7 @@
|
|
|
599
599
|
|
|
600
600
|
if (!supports) {
|
|
601
601
|
warnLog(
|
|
602
|
-
`[
|
|
602
|
+
`[text-input-guard] Rule "${rule.name}" is not supported for <${this.kind}>. skipped.`,
|
|
603
603
|
this.warn
|
|
604
604
|
);
|
|
605
605
|
continue;
|
|
@@ -684,9 +684,7 @@
|
|
|
684
684
|
// 連鎖防止(次の処理に持ち越さない)
|
|
685
685
|
this.revertRequest = null;
|
|
686
686
|
|
|
687
|
-
if (this.warn)
|
|
688
|
-
console.log(`[jp-input-guard] reverted: ${req.reason}`, req.detail);
|
|
689
|
-
}
|
|
687
|
+
if (this.warn) ;
|
|
690
688
|
}
|
|
691
689
|
|
|
692
690
|
/**
|
|
@@ -830,7 +828,7 @@
|
|
|
830
828
|
* @returns {void}
|
|
831
829
|
*/
|
|
832
830
|
onCompositionStart() {
|
|
833
|
-
console.log("[
|
|
831
|
+
// console.log("[text-input-guard] compositionstart");
|
|
834
832
|
this.composing = true;
|
|
835
833
|
}
|
|
836
834
|
|
|
@@ -840,7 +838,7 @@
|
|
|
840
838
|
* @returns {void}
|
|
841
839
|
*/
|
|
842
840
|
onCompositionEnd() {
|
|
843
|
-
console.log("[
|
|
841
|
+
// console.log("[text-input-guard] compositionend");
|
|
844
842
|
this.composing = false;
|
|
845
843
|
|
|
846
844
|
// compositionend後に input が来ない環境向けのフォールバック
|
|
@@ -860,7 +858,7 @@
|
|
|
860
858
|
* @returns {void}
|
|
861
859
|
*/
|
|
862
860
|
onInput() {
|
|
863
|
-
console.log("[
|
|
861
|
+
// console.log("[text-input-guard] input");
|
|
864
862
|
// compositionend後に input が来た場合、フォールバックを無効化
|
|
865
863
|
this.pendingCompositionCommit = false;
|
|
866
864
|
this.evaluateInput();
|
|
@@ -871,7 +869,7 @@
|
|
|
871
869
|
* @returns {void}
|
|
872
870
|
*/
|
|
873
871
|
onBlur() {
|
|
874
|
-
console.log("[
|
|
872
|
+
// console.log("[text-input-guard] blur");
|
|
875
873
|
this.evaluateCommit();
|
|
876
874
|
}
|
|
877
875
|
|
|
@@ -1120,6 +1118,63 @@
|
|
|
1120
1118
|
* The MIT license https://opensource.org/licenses/MIT
|
|
1121
1119
|
*/
|
|
1122
1120
|
|
|
1121
|
+
/**
|
|
1122
|
+
* datasetのboolean値を解釈する
|
|
1123
|
+
* - 未指定なら undefined
|
|
1124
|
+
* - "" / "true" / "1" / "yes" / "on" は true
|
|
1125
|
+
* - "false" / "0" / "no" / "off" は false
|
|
1126
|
+
* @param {string|undefined} v
|
|
1127
|
+
* @returns {boolean|undefined}
|
|
1128
|
+
*/
|
|
1129
|
+
function parseDatasetBool(v) {
|
|
1130
|
+
if (v == null) { return; }
|
|
1131
|
+
const s = String(v).trim().toLowerCase();
|
|
1132
|
+
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1133
|
+
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* datasetのnumber値を解釈する(整数想定)
|
|
1139
|
+
* - 未指定/空なら undefined
|
|
1140
|
+
* - 数値でなければ undefined
|
|
1141
|
+
* @param {string|undefined} v
|
|
1142
|
+
* @returns {number|undefined}
|
|
1143
|
+
*/
|
|
1144
|
+
function parseDatasetNumber(v) {
|
|
1145
|
+
if (v == null) { return; }
|
|
1146
|
+
const s = String(v).trim();
|
|
1147
|
+
if (s === "") { return; }
|
|
1148
|
+
const n = Number(s);
|
|
1149
|
+
return Number.isFinite(n) ? n : undefined;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* enumを解釈する(未指定なら undefined)
|
|
1154
|
+
* @template {string} T
|
|
1155
|
+
* @param {string|undefined} v
|
|
1156
|
+
* @param {readonly T[]} allowed
|
|
1157
|
+
* @returns {T|undefined}
|
|
1158
|
+
*/
|
|
1159
|
+
function parseDatasetEnum(v, allowed) {
|
|
1160
|
+
if (v == null) { return; }
|
|
1161
|
+
const s = String(v).trim();
|
|
1162
|
+
if (s === "") { return; }
|
|
1163
|
+
// 大文字小文字を区別したいならここを変える(今は厳密一致)
|
|
1164
|
+
return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* The script is part of TextInputGuard.
|
|
1169
|
+
*
|
|
1170
|
+
* AUTHOR:
|
|
1171
|
+
* natade-jp (https://github.com/natade-jp)
|
|
1172
|
+
*
|
|
1173
|
+
* LICENSE:
|
|
1174
|
+
* The MIT license https://opensource.org/licenses/MIT
|
|
1175
|
+
*/
|
|
1176
|
+
|
|
1177
|
+
|
|
1123
1178
|
/**
|
|
1124
1179
|
* @typedef {GuardGroup} GuardGroup
|
|
1125
1180
|
* @typedef {Guard} Guard
|
|
@@ -1134,19 +1189,6 @@
|
|
|
1134
1189
|
* @property {(dataset: DOMStringMap, el: HTMLInputElement|HTMLTextAreaElement) => Rule|null} fromDataset
|
|
1135
1190
|
*/
|
|
1136
1191
|
|
|
1137
|
-
/**
|
|
1138
|
-
* Boolean系のdata値を解釈する(未指定なら undefined を返す)
|
|
1139
|
-
* @param {string|undefined} v
|
|
1140
|
-
* @returns {boolean|undefined}
|
|
1141
|
-
*/
|
|
1142
|
-
function parseBool(v) {
|
|
1143
|
-
if (v == null) { return; }
|
|
1144
|
-
const s = String(v).trim().toLowerCase();
|
|
1145
|
-
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1146
|
-
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
1192
|
/**
|
|
1151
1193
|
* separate mode を解釈する(未指定は "auto")
|
|
1152
1194
|
* @param {string|undefined} v
|
|
@@ -1250,7 +1292,7 @@
|
|
|
1250
1292
|
const options = {};
|
|
1251
1293
|
|
|
1252
1294
|
// warn / invalidClass
|
|
1253
|
-
const warn =
|
|
1295
|
+
const warn = parseDatasetBool(ds.tigWarn);
|
|
1254
1296
|
if (warn != null) { options.warn = warn; }
|
|
1255
1297
|
|
|
1256
1298
|
if (ds.tigInvalidClass != null && String(ds.tigInvalidClass).trim() !== "") {
|
|
@@ -1270,7 +1312,7 @@
|
|
|
1270
1312
|
} catch (e) {
|
|
1271
1313
|
const w = options.warn ?? true;
|
|
1272
1314
|
if (w) {
|
|
1273
|
-
console.warn(`[
|
|
1315
|
+
console.warn(`[text-input-guard] autoAttach: rule "${fac.name}" fromDataset() threw an error.`, e);
|
|
1274
1316
|
}
|
|
1275
1317
|
}
|
|
1276
1318
|
}
|
|
@@ -1296,62 +1338,6 @@
|
|
|
1296
1338
|
}
|
|
1297
1339
|
}
|
|
1298
1340
|
|
|
1299
|
-
/**
|
|
1300
|
-
* The script is part of TextInputGuard.
|
|
1301
|
-
*
|
|
1302
|
-
* AUTHOR:
|
|
1303
|
-
* natade-jp (https://github.com/natade-jp)
|
|
1304
|
-
*
|
|
1305
|
-
* LICENSE:
|
|
1306
|
-
* The MIT license https://opensource.org/licenses/MIT
|
|
1307
|
-
*/
|
|
1308
|
-
|
|
1309
|
-
/**
|
|
1310
|
-
* datasetのboolean値を解釈する
|
|
1311
|
-
* - 未指定なら undefined
|
|
1312
|
-
* - "" / "true" / "1" / "yes" / "on" は true
|
|
1313
|
-
* - "false" / "0" / "no" / "off" は false
|
|
1314
|
-
* @param {string|undefined} v
|
|
1315
|
-
* @returns {boolean|undefined}
|
|
1316
|
-
*/
|
|
1317
|
-
function parseDatasetBool(v) {
|
|
1318
|
-
if (v == null) { return; }
|
|
1319
|
-
const s = String(v).trim().toLowerCase();
|
|
1320
|
-
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1321
|
-
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
/**
|
|
1326
|
-
* datasetのnumber値を解釈する(整数想定)
|
|
1327
|
-
* - 未指定/空なら undefined
|
|
1328
|
-
* - 数値でなければ undefined
|
|
1329
|
-
* @param {string|undefined} v
|
|
1330
|
-
* @returns {number|undefined}
|
|
1331
|
-
*/
|
|
1332
|
-
function parseDatasetNumber(v) {
|
|
1333
|
-
if (v == null) { return; }
|
|
1334
|
-
const s = String(v).trim();
|
|
1335
|
-
if (s === "") { return; }
|
|
1336
|
-
const n = Number(s);
|
|
1337
|
-
return Number.isFinite(n) ? n : undefined;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* enumを解釈する(未指定なら undefined)
|
|
1342
|
-
* @template {string} T
|
|
1343
|
-
* @param {string|undefined} v
|
|
1344
|
-
* @param {readonly T[]} allowed
|
|
1345
|
-
* @returns {T|undefined}
|
|
1346
|
-
*/
|
|
1347
|
-
function parseDatasetEnum(v, allowed) {
|
|
1348
|
-
if (v == null) { return; }
|
|
1349
|
-
const s = String(v).trim();
|
|
1350
|
-
if (s === "") { return; }
|
|
1351
|
-
// 大文字小文字を区別したいならここを変える(今は厳密一致)
|
|
1352
|
-
return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
1341
|
/**
|
|
1356
1342
|
* The script is part of TextInputGuard.
|
|
1357
1343
|
*
|
|
@@ -1369,6 +1355,7 @@
|
|
|
1369
1355
|
* @property {boolean} [allowFullWidth=true] - 全角数字/記号を許可して半角へ正規化する
|
|
1370
1356
|
* @property {boolean} [allowMinus=false] - マイナス記号を許可する(先頭のみ)
|
|
1371
1357
|
* @property {boolean} [allowDecimal=false] - 小数点を許可する(1つだけ)
|
|
1358
|
+
* @property {boolean} [allowEmpty=true] - 空文字を許可するか
|
|
1372
1359
|
*/
|
|
1373
1360
|
|
|
1374
1361
|
/**
|
|
@@ -1384,7 +1371,8 @@
|
|
|
1384
1371
|
const opt = {
|
|
1385
1372
|
allowFullWidth: options.allowFullWidth ?? true,
|
|
1386
1373
|
allowMinus: options.allowMinus ?? false,
|
|
1387
|
-
allowDecimal: options.allowDecimal ?? false
|
|
1374
|
+
allowDecimal: options.allowDecimal ?? false,
|
|
1375
|
+
allowEmpty: options.allowEmpty ?? true
|
|
1388
1376
|
};
|
|
1389
1377
|
|
|
1390
1378
|
/** @type {Set<string>} */
|
|
@@ -1542,9 +1530,14 @@
|
|
|
1542
1530
|
fix(value) {
|
|
1543
1531
|
let v = String(value);
|
|
1544
1532
|
|
|
1533
|
+
// 空文字の扱い
|
|
1534
|
+
if (v === "") {
|
|
1535
|
+
return opt.allowEmpty ? "" : "0";
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1545
1538
|
// 未完成な数値は空にする
|
|
1546
1539
|
if (v === "-" || v === "." || v === "-.") {
|
|
1547
|
-
return "";
|
|
1540
|
+
return opt.allowEmpty ? "" : "0";
|
|
1548
1541
|
}
|
|
1549
1542
|
|
|
1550
1543
|
// "-.1" → "-0.1"
|
|
@@ -1616,6 +1609,7 @@
|
|
|
1616
1609
|
* - data-tig-rules-numeric-allow-full-width -> dataset.tigRulesNumericAllowFullWidth
|
|
1617
1610
|
* - data-tig-rules-numeric-allow-minus -> dataset.tigRulesNumericAllowMinus
|
|
1618
1611
|
* - data-tig-rules-numeric-allow-decimal -> dataset.tigRulesNumericAllowDecimal
|
|
1612
|
+
* - data-tig-rules-numeric-allow-empty -> dataset.tigRulesNumericAllowEmpty
|
|
1619
1613
|
*
|
|
1620
1614
|
* @param {DOMStringMap} dataset
|
|
1621
1615
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -1648,6 +1642,12 @@
|
|
|
1648
1642
|
options.allowDecimal = allowDecimal;
|
|
1649
1643
|
}
|
|
1650
1644
|
|
|
1645
|
+
// data-tig-rules-numeric-allow-empty(未指定なら numeric側デフォルト true)
|
|
1646
|
+
const allowEmpty = parseDatasetBool(dataset.tigRulesNumericAllowEmpty);
|
|
1647
|
+
if (allowEmpty != null) {
|
|
1648
|
+
options.allowEmpty = allowEmpty;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
1651
|
return numeric(options);
|
|
1652
1652
|
};
|
|
1653
1653
|
|
|
@@ -1672,6 +1672,7 @@
|
|
|
1672
1672
|
* @property {"none"|"truncate"|"round"} [fixFracOnBlur="none"] - blur時の小数部補正
|
|
1673
1673
|
* @property {"none"|"block"} [overflowInputInt="none"] - 入力中:整数部が最大桁を超える入力をブロックする
|
|
1674
1674
|
* @property {"none"|"block"} [overflowInputFrac="none"] - 入力中:小数部が最大桁を超える入力をブロックする
|
|
1675
|
+
* @property {boolean} [forceFracOnBlur=false] - blur時に小数部を必ず表示(frac桁まで0埋め)
|
|
1675
1676
|
*/
|
|
1676
1677
|
|
|
1677
1678
|
/**
|
|
@@ -1812,7 +1813,8 @@
|
|
|
1812
1813
|
fixIntOnBlur: options.fixIntOnBlur ?? "none",
|
|
1813
1814
|
fixFracOnBlur: options.fixFracOnBlur ?? "none",
|
|
1814
1815
|
overflowInputInt: options.overflowInputInt ?? "none",
|
|
1815
|
-
overflowInputFrac: options.overflowInputFrac ?? "none"
|
|
1816
|
+
overflowInputFrac: options.overflowInputFrac ?? "none",
|
|
1817
|
+
forceFracOnBlur: options.forceFracOnBlur ?? false
|
|
1816
1818
|
};
|
|
1817
1819
|
|
|
1818
1820
|
return {
|
|
@@ -1929,14 +1931,39 @@
|
|
|
1929
1931
|
}
|
|
1930
1932
|
}
|
|
1931
1933
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
+
if (opt.forceFracOnBlur && typeof opt.frac === "number" && opt.frac > 0) {
|
|
1935
|
+
const limit = opt.frac;
|
|
1936
|
+
// "." が無いなら作る(12 → 12.00)
|
|
1937
|
+
if (!hasDot) {
|
|
1938
|
+
fracPart = "";
|
|
1939
|
+
}
|
|
1940
|
+
// 足りない分を 0 埋め(12.3 → 12.30 / 12. → 12.00)
|
|
1941
|
+
const f = fracPart ?? "";
|
|
1942
|
+
if (f.length < limit) {
|
|
1943
|
+
fracPart = f + "0".repeat(limit - f.length);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// 組み立て
|
|
1948
|
+
if (typeof opt.frac !== "number") {
|
|
1949
|
+
// frac未指定なら、dot があっても digits は触らず intだけ返す方針(現状維持)
|
|
1934
1950
|
return `${sign}${intPart}`;
|
|
1935
1951
|
}
|
|
1952
|
+
|
|
1936
1953
|
if (opt.frac === 0) {
|
|
1954
|
+
// 小数0桁なら常に整数表示
|
|
1937
1955
|
return `${sign}${intPart}`;
|
|
1938
1956
|
}
|
|
1939
|
-
|
|
1957
|
+
|
|
1958
|
+
// frac 指定あり(1以上)
|
|
1959
|
+
if (hasDot || (opt.forceFracOnBlur && opt.frac > 0)) {
|
|
1960
|
+
// "." が無いけど forceFracOnBlur の場合もここに来る
|
|
1961
|
+
const f = fracPart ?? "";
|
|
1962
|
+
return `${sign}${intPart}.${f}`;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// "." が無くて force もしないなら整数表示
|
|
1966
|
+
return `${sign}${intPart}`;
|
|
1940
1967
|
}
|
|
1941
1968
|
};
|
|
1942
1969
|
}
|
|
@@ -1955,6 +1982,7 @@
|
|
|
1955
1982
|
* - data-tig-rules-digits-fix-frac-on-blur -> dataset.tigRulesDigitsFixFracOnBlur
|
|
1956
1983
|
* - data-tig-rules-digits-overflow-input-int -> dataset.tigRulesDigitsOverflowInputInt
|
|
1957
1984
|
* - data-tig-rules-digits-overflow-input-frac -> dataset.tigRulesDigitsOverflowInputFrac
|
|
1985
|
+
* - data-tig-rules-digits-force-frac-on-blur -> dataset.tigRulesDigitsForceFracOnBlur
|
|
1958
1986
|
*
|
|
1959
1987
|
* @param {DOMStringMap} dataset
|
|
1960
1988
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -2017,6 +2045,12 @@
|
|
|
2017
2045
|
options.overflowInputFrac = ovFrac;
|
|
2018
2046
|
}
|
|
2019
2047
|
|
|
2048
|
+
// forceFracOnBlur
|
|
2049
|
+
const forceFrac = parseDatasetBool(dataset.tigRulesDigitsForceFracOnBlur);
|
|
2050
|
+
if (forceFrac != null) {
|
|
2051
|
+
options.forceFracOnBlur = forceFrac;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2020
2054
|
return digits(options);
|
|
2021
2055
|
};
|
|
2022
2056
|
|
|
@@ -2042,6 +2076,11 @@
|
|
|
2042
2076
|
|
|
2043
2077
|
/**
|
|
2044
2078
|
* 表示整形(確定時のみ)
|
|
2079
|
+
*
|
|
2080
|
+
* 前提:
|
|
2081
|
+
* - numeric / digits 等で正規化済みの数値文字列が渡される
|
|
2082
|
+
* - 整数部・小数部・符号のみを含む(カンマは含まない想定)
|
|
2083
|
+
*
|
|
2045
2084
|
* @param {string} value
|
|
2046
2085
|
* @returns {string}
|
|
2047
2086
|
*/
|
|
@@ -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,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TextInputGuard={})}(this,function(t){"use strict";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("[jp-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 t=this.options.separateValue?.mode??"auto";if("swap"!==("auto"===t?this.formatRules.length>0?"swap":"off":t))return;if("input"!==this.kind)return void e('[jp-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 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(`[jp-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&&console.log(`[jp-input-guard] reverted: ${t.reason}`,t.detail)}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(){console.log("[jp-input-guard] compositionstart"),this.composing=!0}onCompositionEnd(){console.log("[jp-input-guard] compositionend"),this.composing=!1,this.pendingCompositionCommit=!0,queueMicrotask(()=>{this.pendingCompositionCommit&&(this.pendingCompositionCommit=!1,this.evaluateInput())})}onInput(){console.log("[jp-input-guard] input"),this.pendingCompositionCommit=!1,this.evaluateInput()}onBlur(){console.log("[jp-input-guard] blur"),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.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 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||""===String(t).trim())return"auto";const e=String(t).trim().toLowerCase();return"auto"===e||"swap"===e||"off"===e?e:"auto"}function a(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 l(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 o(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 u(t,e){if(null==t)return;const i=String(t).trim();return""!==i&&e.includes(i)?i:void 0}function c(t={}){const e=t.allowFullWidth??!0,i=t.allowMinus??!1,n=t.allowDecimal??!1,s=new Set(["ー","-","−","‐","-","‒","–","—","―"]),r=new Set([".","。","。"]);function a(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&&r.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+=a(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||"."===e||"-."===e)return"";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 r=n>=0?e.slice(n+1):"";return s=s.replace(/^0+/,""),""===s&&(s="0"),"-"!==i||"0"!==s||r&&!/^0*$/.test(r)||(i=""),n>=0?`${i}${s}.${r}`:`${i}${s}`},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 p(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"};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}}return o&&"number"==typeof e.frac?0===e.frac?`${l}${r}`:`${l}${r}.${a}`:`${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}`}}}c.fromDataset=function(t,e){if(null==t.tigRulesNumeric)return null;const i={},n=l(t.tigRulesNumericAllowFullWidth);null!=n&&(i.allowFullWidth=n);const s=l(t.tigRulesNumericAllowMinus);null!=s&&(i.allowMinus=s);const r=l(t.tigRulesNumericAllowDecimal);return null!=r&&(i.allowDecimal=r),c(i)},p.fromDataset=function(t,e){if(null==t.tigRulesDigits)return null;const i={},n=o(t.tigRulesDigitsInt);null!=n&&(i.int=n);const s=o(t.tigRulesDigitsFrac);null!=s&&(i.frac=s);const r=l(t.tigRulesDigitsCountLeadingZeros);null!=r&&(i.countLeadingZeros=r);const a=u(t.tigRulesDigitsFixIntOnBlur,["none","truncateLeft","truncateRight","clamp"]);null!=a&&(i.fixIntOnBlur=a);const c=u(t.tigRulesDigitsFixFracOnBlur,["none","truncate","round"]);null!=c&&(i.fixFracOnBlur=c);const h=u(t.tigRulesDigitsOverflowInputInt,["none","block"]);null!=h&&(i.overflowInputInt=h);const d=u(t.tigRulesDigitsOverflowInputFrac,["none","block"]);return null!=d&&(i.overflowInputFrac=d),p(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(!a(i))continue;const n={},l=s(i.tigWarn);null!=l&&(n.warn=l),null!=i.tigInvalidClass&&""!==String(i.tigInvalidClass).trim()&&(n.invalidClass=String(i.tigInvalidClass)),n.separateValue={mode:r(i.tigSeparate)};const o=[];for(const e of this.ruleFactories)try{const n=e.fromDataset(i,t);n&&o.push(n)}catch(t){(n.warn??!0)&&console.warn(`[jp-input-guard] autoAttach: rule "${e.name}" fromDataset() threw an error.`,t)}if(o.length>0&&(n.rules=o),!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:c.fromDataset},{name:"digits",fromDataset:p.fromDataset},{name:"comma",fromDataset:m.fromDataset}]),g={numeric:c,digits:p,comma:m};t.attach=i,t.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}},t.autoAttach=t=>f.autoAttach(t),t.comma=m,t.digits=p,t.numeric=c,t.rules=g,t.version="0.0.1"});
|
|
6
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TextInputGuard={})}(this,function(t){"use strict";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 t=this.options.separateValue?.mode??"auto";if("swap"!==("auto"===t?this.formatRules.length>0?"swap":"off":t))return;if("input"!==this.kind)return void e('[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 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.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 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 f(){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 f=a(t.tigRulesDigitsOverflowInputFrac,["none","block"]);null!=f&&(i.overflowInputFrac=f);const m=s(t.tigRulesDigitsForceFracOnBlur);return null!=m&&(i.forceFracOnBlur=m),d(i)},f.fromDataset=function(t,e){return null==t.tigRulesComma?null:f()};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(!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:f.fromDataset}]),p={numeric:u,digits:d,comma:f};t.attach=i,t.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}},t.autoAttach=t=>m.autoAttach(t),t.comma=f,t.digits=d,t.numeric=u,t.rules=p,t.version="0.0.1"});
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "text-input-guard",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
5
|
-
"keywords": [
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A JavaScript input guard library for Japanese web apps, handling full-width digits, numeric rules, digit limits, and formatted display.",
|
|
5
|
+
"keywords": ["input-validation",
|
|
6
|
+
"numeric-input",
|
|
7
|
+
"digit-limit",
|
|
8
|
+
"fullwidth",
|
|
9
|
+
"zenkaku",
|
|
10
|
+
"japanese"],
|
|
7
11
|
"author": "natade-jp <natade3@gmail.com> (https://github.com/natade-jp)",
|
|
8
12
|
"license": "MIT",
|
|
9
13
|
"type": "module",
|
|
@@ -35,7 +39,11 @@
|
|
|
35
39
|
],
|
|
36
40
|
"scripts": {
|
|
37
41
|
"build": "node ./scripts/package.build.js",
|
|
38
|
-
"test": "node --test",
|
|
42
|
+
"test": "node --test \"src/**/*.test.js\"",
|
|
43
|
+
"doc": "node ./scripts/package.doc.js",
|
|
44
|
+
"docs:dev": "vitepress dev docs",
|
|
45
|
+
"docs:build": "vitepress build docs",
|
|
46
|
+
"docs:preview": "vitepress preview docs",
|
|
39
47
|
"lint": "eslint .",
|
|
40
48
|
"lint:fix": "eslint . --fix",
|
|
41
49
|
"format": "prettier . --write",
|
|
@@ -58,11 +66,13 @@
|
|
|
58
66
|
"eslint-plugin-jsonc": "^2.21.0",
|
|
59
67
|
"eslint-plugin-unicorn": "^62.0.0",
|
|
60
68
|
"globals": "^17.1.0",
|
|
69
|
+
"jsdom": "^28.1.0",
|
|
61
70
|
"jsonc-eslint-parser": "^2.4.2",
|
|
62
71
|
"ntfile": "^2.0.0",
|
|
63
72
|
"prettier": "^3.8.1",
|
|
64
73
|
"rollup": "^4.56.0",
|
|
65
74
|
"rollup-plugin-dts": "^6.3.0",
|
|
66
|
-
"typescript": "^5.9.3"
|
|
75
|
+
"typescript": "^5.9.3",
|
|
76
|
+
"vitepress": "^1.6.4"
|
|
67
77
|
}
|
|
68
78
|
}
|