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.
@@ -206,7 +206,7 @@
206
206
 
207
207
  const kind = detectKind(element);
208
208
  if (!kind) {
209
- throw new TypeError("[jp-input-guard] attach() expects an <input> or <textarea> element.");
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('[jp-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.', this.warn);
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
- `[jp-input-guard] Rule "${rule.name}" is not supported for <${this.kind}>. skipped.`,
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("[jp-input-guard] compositionstart");
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("[jp-input-guard] compositionend");
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("[jp-input-guard] input");
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("[jp-input-guard] blur");
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 = parseBool(ds.tigWarn);
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(`[jp-input-guard] autoAttach: rule "${fac.name}" fromDataset() threw an error.`, e);
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
- // 組み立て(frac=0 のときは "." を残すか?は方針次第だが、ここでは消す)
1933
- if (!hasDot || typeof opt.frac !== "number") {
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
- return `${sign}${intPart}.${fracPart}`;
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.1",
4
- "description": "test",
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
  }