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.
@@ -200,7 +200,7 @@ class InputGuard {
200
200
 
201
201
  const kind = detectKind(element);
202
202
  if (!kind) {
203
- throw new TypeError("[jp-input-guard] attach() expects an <input> or <textarea> element.");
203
+ throw new TypeError("[text-input-guard] attach() expects an <input> or <textarea> element.");
204
204
  }
205
205
 
206
206
  /**
@@ -432,7 +432,7 @@ class InputGuard {
432
432
  }
433
433
 
434
434
  if (this.kind !== "input") {
435
- warnLog('[jp-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.', this.warn);
435
+ warnLog('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.', this.warn);
436
436
  return;
437
437
  }
438
438
 
@@ -593,7 +593,7 @@ class InputGuard {
593
593
 
594
594
  if (!supports) {
595
595
  warnLog(
596
- `[jp-input-guard] Rule "${rule.name}" is not supported for <${this.kind}>. skipped.`,
596
+ `[text-input-guard] Rule "${rule.name}" is not supported for <${this.kind}>. skipped.`,
597
597
  this.warn
598
598
  );
599
599
  continue;
@@ -678,9 +678,7 @@ class InputGuard {
678
678
  // 連鎖防止(次の処理に持ち越さない)
679
679
  this.revertRequest = null;
680
680
 
681
- if (this.warn) {
682
- console.log(`[jp-input-guard] reverted: ${req.reason}`, req.detail);
683
- }
681
+ if (this.warn) ;
684
682
  }
685
683
 
686
684
  /**
@@ -824,7 +822,7 @@ class InputGuard {
824
822
  * @returns {void}
825
823
  */
826
824
  onCompositionStart() {
827
- console.log("[jp-input-guard] compositionstart");
825
+ // console.log("[text-input-guard] compositionstart");
828
826
  this.composing = true;
829
827
  }
830
828
 
@@ -834,7 +832,7 @@ class InputGuard {
834
832
  * @returns {void}
835
833
  */
836
834
  onCompositionEnd() {
837
- console.log("[jp-input-guard] compositionend");
835
+ // console.log("[text-input-guard] compositionend");
838
836
  this.composing = false;
839
837
 
840
838
  // compositionend後に input が来ない環境向けのフォールバック
@@ -854,7 +852,7 @@ class InputGuard {
854
852
  * @returns {void}
855
853
  */
856
854
  onInput() {
857
- console.log("[jp-input-guard] input");
855
+ // console.log("[text-input-guard] input");
858
856
  // compositionend後に input が来た場合、フォールバックを無効化
859
857
  this.pendingCompositionCommit = false;
860
858
  this.evaluateInput();
@@ -865,7 +863,7 @@ class InputGuard {
865
863
  * @returns {void}
866
864
  */
867
865
  onBlur() {
868
- console.log("[jp-input-guard] blur");
866
+ // console.log("[text-input-guard] blur");
869
867
  this.evaluateCommit();
870
868
  }
871
869
 
@@ -1114,6 +1112,63 @@ class InputGuard {
1114
1112
  * The MIT license https://opensource.org/licenses/MIT
1115
1113
  */
1116
1114
 
1115
+ /**
1116
+ * datasetのboolean値を解釈する
1117
+ * - 未指定なら undefined
1118
+ * - "" / "true" / "1" / "yes" / "on" は true
1119
+ * - "false" / "0" / "no" / "off" は false
1120
+ * @param {string|undefined} v
1121
+ * @returns {boolean|undefined}
1122
+ */
1123
+ function parseDatasetBool(v) {
1124
+ if (v == null) { return; }
1125
+ const s = String(v).trim().toLowerCase();
1126
+ if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
1127
+ if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
1128
+ return;
1129
+ }
1130
+
1131
+ /**
1132
+ * datasetのnumber値を解釈する(整数想定)
1133
+ * - 未指定/空なら undefined
1134
+ * - 数値でなければ undefined
1135
+ * @param {string|undefined} v
1136
+ * @returns {number|undefined}
1137
+ */
1138
+ function parseDatasetNumber(v) {
1139
+ if (v == null) { return; }
1140
+ const s = String(v).trim();
1141
+ if (s === "") { return; }
1142
+ const n = Number(s);
1143
+ return Number.isFinite(n) ? n : undefined;
1144
+ }
1145
+
1146
+ /**
1147
+ * enumを解釈する(未指定なら undefined)
1148
+ * @template {string} T
1149
+ * @param {string|undefined} v
1150
+ * @param {readonly T[]} allowed
1151
+ * @returns {T|undefined}
1152
+ */
1153
+ function parseDatasetEnum(v, allowed) {
1154
+ if (v == null) { return; }
1155
+ const s = String(v).trim();
1156
+ if (s === "") { return; }
1157
+ // 大文字小文字を区別したいならここを変える(今は厳密一致)
1158
+ return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
1159
+ }
1160
+
1161
+ /**
1162
+ * The script is part of TextInputGuard.
1163
+ *
1164
+ * AUTHOR:
1165
+ * natade-jp (https://github.com/natade-jp)
1166
+ *
1167
+ * LICENSE:
1168
+ * The MIT license https://opensource.org/licenses/MIT
1169
+ */
1170
+
1171
+
1117
1172
  /**
1118
1173
  * @typedef {GuardGroup} GuardGroup
1119
1174
  * @typedef {Guard} Guard
@@ -1128,19 +1183,6 @@ class InputGuard {
1128
1183
  * @property {(dataset: DOMStringMap, el: HTMLInputElement|HTMLTextAreaElement) => Rule|null} fromDataset
1129
1184
  */
1130
1185
 
1131
- /**
1132
- * Boolean系のdata値を解釈する(未指定なら undefined を返す)
1133
- * @param {string|undefined} v
1134
- * @returns {boolean|undefined}
1135
- */
1136
- function parseBool(v) {
1137
- if (v == null) { return; }
1138
- const s = String(v).trim().toLowerCase();
1139
- if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
1140
- if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
1141
- return;
1142
- }
1143
-
1144
1186
  /**
1145
1187
  * separate mode を解釈する(未指定は "auto")
1146
1188
  * @param {string|undefined} v
@@ -1244,7 +1286,7 @@ class InputGuardAutoAttach {
1244
1286
  const options = {};
1245
1287
 
1246
1288
  // warn / invalidClass
1247
- const warn = parseBool(ds.tigWarn);
1289
+ const warn = parseDatasetBool(ds.tigWarn);
1248
1290
  if (warn != null) { options.warn = warn; }
1249
1291
 
1250
1292
  if (ds.tigInvalidClass != null && String(ds.tigInvalidClass).trim() !== "") {
@@ -1264,7 +1306,7 @@ class InputGuardAutoAttach {
1264
1306
  } catch (e) {
1265
1307
  const w = options.warn ?? true;
1266
1308
  if (w) {
1267
- console.warn(`[jp-input-guard] autoAttach: rule "${fac.name}" fromDataset() threw an error.`, e);
1309
+ console.warn(`[text-input-guard] autoAttach: rule "${fac.name}" fromDataset() threw an error.`, e);
1268
1310
  }
1269
1311
  }
1270
1312
  }
@@ -1290,62 +1332,6 @@ class InputGuardAutoAttach {
1290
1332
  }
1291
1333
  }
1292
1334
 
1293
- /**
1294
- * The script is part of TextInputGuard.
1295
- *
1296
- * AUTHOR:
1297
- * natade-jp (https://github.com/natade-jp)
1298
- *
1299
- * LICENSE:
1300
- * The MIT license https://opensource.org/licenses/MIT
1301
- */
1302
-
1303
- /**
1304
- * datasetのboolean値を解釈する
1305
- * - 未指定なら undefined
1306
- * - "" / "true" / "1" / "yes" / "on" は true
1307
- * - "false" / "0" / "no" / "off" は false
1308
- * @param {string|undefined} v
1309
- * @returns {boolean|undefined}
1310
- */
1311
- function parseDatasetBool(v) {
1312
- if (v == null) { return; }
1313
- const s = String(v).trim().toLowerCase();
1314
- if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
1315
- if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
1316
- return;
1317
- }
1318
-
1319
- /**
1320
- * datasetのnumber値を解釈する(整数想定)
1321
- * - 未指定/空なら undefined
1322
- * - 数値でなければ undefined
1323
- * @param {string|undefined} v
1324
- * @returns {number|undefined}
1325
- */
1326
- function parseDatasetNumber(v) {
1327
- if (v == null) { return; }
1328
- const s = String(v).trim();
1329
- if (s === "") { return; }
1330
- const n = Number(s);
1331
- return Number.isFinite(n) ? n : undefined;
1332
- }
1333
-
1334
- /**
1335
- * enumを解釈する(未指定なら undefined)
1336
- * @template {string} T
1337
- * @param {string|undefined} v
1338
- * @param {readonly T[]} allowed
1339
- * @returns {T|undefined}
1340
- */
1341
- function parseDatasetEnum(v, allowed) {
1342
- if (v == null) { return; }
1343
- const s = String(v).trim();
1344
- if (s === "") { return; }
1345
- // 大文字小文字を区別したいならここを変える(今は厳密一致)
1346
- return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
1347
- }
1348
-
1349
1335
  /**
1350
1336
  * The script is part of TextInputGuard.
1351
1337
  *
@@ -1363,6 +1349,7 @@ function parseDatasetEnum(v, allowed) {
1363
1349
  * @property {boolean} [allowFullWidth=true] - 全角数字/記号を許可して半角へ正規化する
1364
1350
  * @property {boolean} [allowMinus=false] - マイナス記号を許可する(先頭のみ)
1365
1351
  * @property {boolean} [allowDecimal=false] - 小数点を許可する(1つだけ)
1352
+ * @property {boolean} [allowEmpty=true] - 空文字を許可するか
1366
1353
  */
1367
1354
 
1368
1355
  /**
@@ -1378,7 +1365,8 @@ function numeric(options = {}) {
1378
1365
  const opt = {
1379
1366
  allowFullWidth: options.allowFullWidth ?? true,
1380
1367
  allowMinus: options.allowMinus ?? false,
1381
- allowDecimal: options.allowDecimal ?? false
1368
+ allowDecimal: options.allowDecimal ?? false,
1369
+ allowEmpty: options.allowEmpty ?? true
1382
1370
  };
1383
1371
 
1384
1372
  /** @type {Set<string>} */
@@ -1536,9 +1524,14 @@ function numeric(options = {}) {
1536
1524
  fix(value) {
1537
1525
  let v = String(value);
1538
1526
 
1527
+ // 空文字の扱い
1528
+ if (v === "") {
1529
+ return opt.allowEmpty ? "" : "0";
1530
+ }
1531
+
1539
1532
  // 未完成な数値は空にする
1540
1533
  if (v === "-" || v === "." || v === "-.") {
1541
- return "";
1534
+ return opt.allowEmpty ? "" : "0";
1542
1535
  }
1543
1536
 
1544
1537
  // "-.1" → "-0.1"
@@ -1610,6 +1603,7 @@ function numeric(options = {}) {
1610
1603
  * - data-tig-rules-numeric-allow-full-width -> dataset.tigRulesNumericAllowFullWidth
1611
1604
  * - data-tig-rules-numeric-allow-minus -> dataset.tigRulesNumericAllowMinus
1612
1605
  * - data-tig-rules-numeric-allow-decimal -> dataset.tigRulesNumericAllowDecimal
1606
+ * - data-tig-rules-numeric-allow-empty -> dataset.tigRulesNumericAllowEmpty
1613
1607
  *
1614
1608
  * @param {DOMStringMap} dataset
1615
1609
  * @param {HTMLInputElement|HTMLTextAreaElement} _el
@@ -1642,6 +1636,12 @@ numeric.fromDataset = function fromDataset(dataset, _el) {
1642
1636
  options.allowDecimal = allowDecimal;
1643
1637
  }
1644
1638
 
1639
+ // data-tig-rules-numeric-allow-empty(未指定なら numeric側デフォルト true)
1640
+ const allowEmpty = parseDatasetBool(dataset.tigRulesNumericAllowEmpty);
1641
+ if (allowEmpty != null) {
1642
+ options.allowEmpty = allowEmpty;
1643
+ }
1644
+
1645
1645
  return numeric(options);
1646
1646
  };
1647
1647
 
@@ -1666,6 +1666,7 @@ numeric.fromDataset = function fromDataset(dataset, _el) {
1666
1666
  * @property {"none"|"truncate"|"round"} [fixFracOnBlur="none"] - blur時の小数部補正
1667
1667
  * @property {"none"|"block"} [overflowInputInt="none"] - 入力中:整数部が最大桁を超える入力をブロックする
1668
1668
  * @property {"none"|"block"} [overflowInputFrac="none"] - 入力中:小数部が最大桁を超える入力をブロックする
1669
+ * @property {boolean} [forceFracOnBlur=false] - blur時に小数部を必ず表示(frac桁まで0埋め)
1669
1670
  */
1670
1671
 
1671
1672
  /**
@@ -1806,7 +1807,8 @@ function digits(options = {}) {
1806
1807
  fixIntOnBlur: options.fixIntOnBlur ?? "none",
1807
1808
  fixFracOnBlur: options.fixFracOnBlur ?? "none",
1808
1809
  overflowInputInt: options.overflowInputInt ?? "none",
1809
- overflowInputFrac: options.overflowInputFrac ?? "none"
1810
+ overflowInputFrac: options.overflowInputFrac ?? "none",
1811
+ forceFracOnBlur: options.forceFracOnBlur ?? false
1810
1812
  };
1811
1813
 
1812
1814
  return {
@@ -1923,14 +1925,39 @@ function digits(options = {}) {
1923
1925
  }
1924
1926
  }
1925
1927
 
1926
- // 組み立て(frac=0 のときは "." を残すか?は方針次第だが、ここでは消す)
1927
- if (!hasDot || typeof opt.frac !== "number") {
1928
+ if (opt.forceFracOnBlur && typeof opt.frac === "number" && opt.frac > 0) {
1929
+ const limit = opt.frac;
1930
+ // "." が無いなら作る(12 → 12.00)
1931
+ if (!hasDot) {
1932
+ fracPart = "";
1933
+ }
1934
+ // 足りない分を 0 埋め(12.3 → 12.30 / 12. → 12.00)
1935
+ const f = fracPart ?? "";
1936
+ if (f.length < limit) {
1937
+ fracPart = f + "0".repeat(limit - f.length);
1938
+ }
1939
+ }
1940
+
1941
+ // 組み立て
1942
+ if (typeof opt.frac !== "number") {
1943
+ // frac未指定なら、dot があっても digits は触らず intだけ返す方針(現状維持)
1928
1944
  return `${sign}${intPart}`;
1929
1945
  }
1946
+
1930
1947
  if (opt.frac === 0) {
1948
+ // 小数0桁なら常に整数表示
1931
1949
  return `${sign}${intPart}`;
1932
1950
  }
1933
- return `${sign}${intPart}.${fracPart}`;
1951
+
1952
+ // frac 指定あり(1以上)
1953
+ if (hasDot || (opt.forceFracOnBlur && opt.frac > 0)) {
1954
+ // "." が無いけど forceFracOnBlur の場合もここに来る
1955
+ const f = fracPart ?? "";
1956
+ return `${sign}${intPart}.${f}`;
1957
+ }
1958
+
1959
+ // "." が無くて force もしないなら整数表示
1960
+ return `${sign}${intPart}`;
1934
1961
  }
1935
1962
  };
1936
1963
  }
@@ -1949,6 +1976,7 @@ function digits(options = {}) {
1949
1976
  * - data-tig-rules-digits-fix-frac-on-blur -> dataset.tigRulesDigitsFixFracOnBlur
1950
1977
  * - data-tig-rules-digits-overflow-input-int -> dataset.tigRulesDigitsOverflowInputInt
1951
1978
  * - data-tig-rules-digits-overflow-input-frac -> dataset.tigRulesDigitsOverflowInputFrac
1979
+ * - data-tig-rules-digits-force-frac-on-blur -> dataset.tigRulesDigitsForceFracOnBlur
1952
1980
  *
1953
1981
  * @param {DOMStringMap} dataset
1954
1982
  * @param {HTMLInputElement|HTMLTextAreaElement} _el
@@ -2011,6 +2039,12 @@ digits.fromDataset = function fromDataset(dataset, _el) {
2011
2039
  options.overflowInputFrac = ovFrac;
2012
2040
  }
2013
2041
 
2042
+ // forceFracOnBlur
2043
+ const forceFrac = parseDatasetBool(dataset.tigRulesDigitsForceFracOnBlur);
2044
+ if (forceFrac != null) {
2045
+ options.forceFracOnBlur = forceFrac;
2046
+ }
2047
+
2014
2048
  return digits(options);
2015
2049
  };
2016
2050
 
@@ -2036,6 +2070,11 @@ function comma() {
2036
2070
 
2037
2071
  /**
2038
2072
  * 表示整形(確定時のみ)
2073
+ *
2074
+ * 前提:
2075
+ * - numeric / digits 等で正規化済みの数値文字列が渡される
2076
+ * - 整数部・小数部・符号のみを含む(カンマは含まない想定)
2077
+ *
2039
2078
  * @param {string} value
2040
2079
  * @returns {string}
2041
2080
  */
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * TextInputGuard
3
+ * AUTHOR: natade (https://github.com/natade-jp/)
4
+ * LICENSE: MIT https://opensource.org/licenses/MIT
5
+ */
6
+ function t(t,e){e&&console.warn(t)}function e(t,e={}){const i=new n(t,e);return i.init(),i.toGuard()}function i(t,i={}){const n=[];for(const r of t)n.push(e(r,i));return{detach:()=>{for(const t of n)t.detach()},isValid:()=>n.every(t=>t.isValid()),getErrors:()=>n.flatMap(t=>t.getErrors()),getGuards:()=>n}}class n{constructor(t,e){this.originalElement=t,this.options=e;const i=(n=t)instanceof HTMLInputElement?"input":n instanceof HTMLTextAreaElement?"textarea":null;var n;if(!i)throw new TypeError("[text-input-guard] attach() expects an <input> or <textarea> element.");this.kind=i,this.warn=e.warn??!0,this.invalidClass=e.invalidClass??"is-invalid",this.rules=Array.isArray(e.rules)?e.rules:[],this.hostElement=t,this.displayElement=t,this.rawElement=null,this.composing=!1,this.errors=[],this.normalizeCharRules=[],this.normalizeStructureRules=[],this.validateRules=[],this.fixRules=[],this.formatRules=[],this.onCompositionStart=this.onCompositionStart.bind(this),this.onCompositionEnd=this.onCompositionEnd.bind(this),this.onInput=this.onInput.bind(this),this.onBlur=this.onBlur.bind(this),this.onFocus=this.onFocus.bind(this),this.onSelectionChange=this.onSelectionChange.bind(this),this.swapState=null,this.pendingCompositionCommit=!1,this.lastAcceptedValue="",this.lastAcceptedSelection={start:null,end:null,direction:null},this.revertRequest=null}init(){this.buildPipeline(),this.applySeparateValue(),this.bindEvents(),this.evaluateInput()}readSelection(t){return{start:t.selectionStart,end:t.selectionEnd,direction:t.selectionDirection}}writeSelection(t,e){if(null!=e.start&&null!=e.end)try{e.direction?t.setSelectionRange(e.start,e.end,e.direction):t.setSelectionRange(e.start,e.end)}catch(t){}}applySeparateValue(){const e=this.options.separateValue?.mode??"auto";if("swap"!==("auto"===e?this.formatRules.length>0?"swap":"off":e))return;if("input"!==this.kind)return void t('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.',this.warn);const i=this.originalElement;this.swapState={originalType:i.type,originalId:i.getAttribute("id"),originalName:i.getAttribute("name"),originalClass:i.className,createdDisplay:null},i.type="hidden",i.removeAttribute("id"),i.dataset.tigRole="raw",this.swapState.originalId&&(i.dataset.tigOriginalId=this.swapState.originalId),this.swapState.originalName&&(i.dataset.tigOriginalName=this.swapState.originalName);const n=document.createElement("input");n.type="text",n.dataset.tigRole="display",this.swapState.originalId&&(n.id=this.swapState.originalId),n.removeAttribute("name"),n.className=this.swapState.originalClass,i.className="",n.value=i.value,i.after(n),this.hostElement=i,this.displayElement=n,this.rawElement=i,this.swapState.createdDisplay=n,this.lastAcceptedValue=n.value,this.lastAcceptedSelection=this.readSelection(n)}restoreSeparateValue(){if(!this.swapState)return;const t=this.swapState,e=this.hostElement,i=t.createdDisplay;if(i)try{e.value=e.value||i.value}catch(t){}i&&i.parentNode&&i.parentNode.removeChild(i),e.type=t.originalType,t.originalId?e.setAttribute("id",t.originalId):e.removeAttribute("id"),t.originalName?e.setAttribute("name",t.originalName):e.removeAttribute("name"),e.className=t.originalClass??"",delete e.dataset.tigRole,delete e.dataset.tigOriginalId,delete e.dataset.tigOriginalName,this.hostElement=this.originalElement,this.displayElement=this.originalElement,this.rawElement=null}detach(){this.unbindEvents(),this.restoreSeparateValue(),this.swapState=null}buildPipeline(){this.normalizeCharRules=[],this.normalizeStructureRules=[],this.validateRules=[],this.fixRules=[],this.formatRules=[];for(const e of this.rules){"input"===this.kind&&e.targets.includes("input")||"textarea"===this.kind&&e.targets.includes("textarea")?(e.normalizeChar&&this.normalizeCharRules.push(e),e.normalizeStructure&&this.normalizeStructureRules.push(e),e.validate&&this.validateRules.push(e),e.fix&&this.fixRules.push(e),e.format&&this.formatRules.push(e)):t(`[text-input-guard] Rule "${e.name}" is not supported for <${this.kind}>. skipped.`,this.warn)}}bindEvents(){this.displayElement.addEventListener("compositionstart",this.onCompositionStart),this.displayElement.addEventListener("compositionend",this.onCompositionEnd),this.displayElement.addEventListener("input",this.onInput),this.displayElement.addEventListener("blur",this.onBlur),this.displayElement.addEventListener("focus",this.onFocus),this.displayElement.addEventListener("keyup",this.onSelectionChange),this.displayElement.addEventListener("mouseup",this.onSelectionChange),this.displayElement.addEventListener("select",this.onSelectionChange),this.displayElement.addEventListener("focus",this.onSelectionChange)}unbindEvents(){this.displayElement.removeEventListener("compositionstart",this.onCompositionStart),this.displayElement.removeEventListener("compositionend",this.onCompositionEnd),this.displayElement.removeEventListener("input",this.onInput),this.displayElement.removeEventListener("blur",this.onBlur),this.displayElement.removeEventListener("focus",this.onFocus),this.displayElement.removeEventListener("keyup",this.onSelectionChange),this.displayElement.removeEventListener("mouseup",this.onSelectionChange),this.displayElement.removeEventListener("select",this.onSelectionChange),this.displayElement.removeEventListener("focus",this.onSelectionChange)}revertDisplay(t){const e=this.displayElement;e.value=this.lastAcceptedValue,this.writeSelection(e,this.lastAcceptedSelection),this.syncRaw(this.lastAcceptedValue),this.clearErrors(),this.applyInvalidClass(),this.revertRequest=null,this.warn}createCtx(){return{hostElement:this.hostElement,displayElement:this.displayElement,rawElement:this.rawElement,kind:this.kind,warn:this.warn,invalidClass:this.invalidClass,composing:this.composing,pushError:t=>this.errors.push(t),requestRevert:t=>{this.revertRequest||(this.revertRequest=t)}}}clearErrors(){this.errors=[]}runNormalizeChar(t,e){let i=t;for(const t of this.normalizeCharRules)i=t.normalizeChar?t.normalizeChar(i,e):i;return i}runNormalizeStructure(t,e){let i=t;for(const t of this.normalizeStructureRules)i=t.normalizeStructure?t.normalizeStructure(i,e):i;return i}runValidate(t,e){for(const i of this.validateRules)i.validate&&i.validate(t,e)}runFix(t,e){let i=t;for(const t of this.fixRules)i=t.fix?t.fix(i,e):i;return i}runFormat(t,e){let i=t;for(const t of this.formatRules)i=t.format?t.format(i,e):i;return i}applyInvalidClass(){const t=this.displayElement;this.errors.length>0?t.classList.add(this.invalidClass):t.classList.remove(this.invalidClass)}syncRaw(t){this.rawElement&&(this.rawElement.value=t)}syncDisplay(t){(this.displayElement instanceof HTMLInputElement||this.displayElement instanceof HTMLTextAreaElement)&&(this.displayElement.value=t)}onCompositionStart(){this.composing=!0}onCompositionEnd(){this.composing=!1,this.pendingCompositionCommit=!0,queueMicrotask(()=>{this.pendingCompositionCommit&&(this.pendingCompositionCommit=!1,this.evaluateInput())})}onInput(){this.pendingCompositionCommit=!1,this.evaluateInput()}onBlur(){this.evaluateCommit()}onFocus(){if(this.composing)return;const t=this.displayElement,e=t.value,i=this.createCtx();let n=e;n=this.runNormalizeChar(n,i),n=this.runNormalizeStructure(n,i),n!==e&&(this.setDisplayValuePreserveCaret(t,n,i),this.syncRaw(n)),this.lastAcceptedValue=n,this.lastAcceptedSelection=this.readSelection(t),this.onSelectionChange()}onSelectionChange(){if(this.composing)return;const t=this.displayElement;this.lastAcceptedSelection=this.readSelection(t)}setDisplayValuePreserveCaret(t,e,i){const n=t.value;if(n===e)return;const r=t.selectionStart,s=t.selectionEnd;if(null==r||null==s)return void(t.value=e);let a=n.slice(0,r);a=this.runNormalizeChar(a,i),a=this.runNormalizeStructure(a,i),t.value=e;const l=Math.min(a.length,e.length);try{t.setSelectionRange(l,l)}catch(t){}}evaluateInput(){if(this.composing)return;this.clearErrors(),this.revertRequest=null;const t=this.displayElement,e=t.value,i=this.createCtx();let n=e;n=this.runNormalizeChar(n,i),n=this.runNormalizeStructure(n,i),n!==e&&this.setDisplayValuePreserveCaret(t,n,i),this.runValidate(n,i),this.revertRequest?this.revertDisplay(this.revertRequest):(this.syncRaw(n),this.applyInvalidClass(),this.lastAcceptedValue=n,this.lastAcceptedSelection=this.readSelection(t))}evaluateCommit(){if(this.composing)return;this.clearErrors(),this.revertRequest=null;const t=this.displayElement,e=this.createCtx();let i=t.value;if(i=this.runNormalizeChar(i,e),i=this.runNormalizeStructure(i,e),this.runValidate(i,e),this.revertRequest)return void this.revertDisplay(this.revertRequest);if(i=this.runFix(i,e),this.clearErrors(),this.revertRequest=null,this.runValidate(i,e),this.revertRequest)return void this.revertDisplay(this.revertRequest);this.syncRaw(i);let n=i;n=this.runFormat(n,e),this.syncDisplay(n),this.applyInvalidClass(),this.lastAcceptedValue=i,this.lastAcceptedSelection=this.readSelection(t)}isValid(){return 0===this.errors.length}getErrors(){return this.errors.slice()}getRawValue(){return this.rawElement?this.rawElement.value:this.displayElement.value}toGuard(){return{detach:()=>this.detach(),isValid:()=>this.isValid(),getErrors:()=>this.getErrors(),getRawValue:()=>this.getRawValue(),getDisplayElement:()=>this.displayElement}}}function r(t){if(null==t)return;const e=String(t).trim().toLowerCase();return""===e||"true"===e||"1"===e||"yes"===e||"on"===e||"false"!==e&&"0"!==e&&"no"!==e&&"off"!==e&&void 0}function s(t){if(null==t)return;const e=String(t).trim();if(""===e)return;const i=Number(e);return Number.isFinite(i)?i:void 0}function a(t,e){if(null==t)return;const i=String(t).trim();return""!==i&&e.includes(i)?i:void 0}function l(t){if(null==t||""===String(t).trim())return"auto";const e=String(t).trim().toLowerCase();return"auto"===e||"swap"===e||"off"===e?e:"auto"}function o(t){if(null!=t.tigSeparate)return!0;if(null!=t.tigWarn)return!0;if(null!=t.tigInvalidClass)return!0;for(const e in t)if(e.startsWith("tigRules"))return!0;return!1}function u(t={}){const e=t.allowFullWidth??!0,i=t.allowMinus??!1,n=t.allowDecimal??!1,r=t.allowEmpty??!0,s=new Set(["ー","-","−","‐","-","‒","–","—","―"]),a=new Set([".","。","。"]);function l(t){if(t>="0"&&t<="9")return t;if(e){const e=function(t){const e=t.charCodeAt(0);return 65296<=e&&e<=65305?String.fromCharCode(e-65296+48):null}(t);if(e)return e}return"."===t||e&&a.has(t)?n?".":"":"-"===t?i?"-":"":e&&s.has(t)&&i?"-":""}return{name:"numeric",targets:["input"],normalizeChar(t){let e=String(t);e=e.replace(/,/g,"");let i="";for(const t of e)i+=l(t);return i},normalizeStructure(t){let e="",r=!1,s=!1;for(const a of String(t))a>="0"&&a<="9"?e+=a:"-"===a&&i?r||0!==e.length||(e+="-",r=!0):"."===a&&n&&(s||(e+=".",s=!0));return e},fix(t){let e=String(t);if(""===e)return r?"":"0";if("-"===e||"."===e||"-."===e)return r?"":"0";e.startsWith("-.")&&(e="-0"+e.slice(1)),e.startsWith(".")&&(e="0"+e),e.endsWith(".")&&(e=e.slice(0,-1));let i="";e.startsWith("-")&&(i="-",e=e.slice(1));const n=e.indexOf(".");let s=n>=0?e.slice(0,n):e;const a=n>=0?e.slice(n+1):"";return s=s.replace(/^0+/,""),""===s&&(s="0"),"-"!==i||"0"!==s||a&&!/^0*$/.test(a)||(i=""),n>=0?`${i}${s}.${a}`:`${i}${s}`},validate(t,e){}}}function c(t){let e="",i=String(t);i.startsWith("-")&&(e="-",i=i.slice(1));const n=i.indexOf(".");if(!(n>=0))return{sign:e,intPart:i,fracPart:"",hasDot:!1};return{sign:e,intPart:i.slice(0,n),fracPart:i.slice(n+1),hasDot:!0}}function h(t){let e=1;const i=t.split("");for(let t=i.length-1;t>=0;t--){const n=i[t].charCodeAt(0)-48+e;if(!(n>=10)){i[t]=String.fromCharCode(48+n),e=0;break}i[t]="0",e=1}return 1===e&&i.unshift("1"),i.join("")}function d(t={}){const e={int:"number"==typeof t.int?t.int:void 0,frac:"number"==typeof t.frac?t.frac:void 0,countLeadingZeros:t.countLeadingZeros??!0,fixIntOnBlur:t.fixIntOnBlur??"none",fixFracOnBlur:t.fixFracOnBlur??"none",overflowInputInt:t.overflowInputInt??"none",overflowInputFrac:t.overflowInputFrac??"none",forceFracOnBlur:t.forceFracOnBlur??!1};return{name:"digits",targets:["input"],validate(t,i){const n=String(t);if(""===n||"-"===n||"."===n||"-."===n)return;const{intPart:r,fracPart:s}=c(n);if("number"==typeof e.int){const t=function(t,e){const i=t??"";if(0===i.length)return 0;if(e)return i.length;const n=i.replace(/^0+/,"");return 0===n.length?1:n.length}(r,e.countLeadingZeros);if(t>e.int){if("block"===e.overflowInputInt)return void i.requestRevert({reason:"digits.int_overflow",detail:{limit:e.int,actual:t}});i.pushError({code:"digits.int_overflow",rule:"digits",phase:"validate",detail:{limit:e.int,actual:t}})}}if("number"==typeof e.frac){const t=(s??"").length;if(t>e.frac){if("block"===e.overflowInputFrac)return void i.requestRevert({reason:"digits.frac_overflow",detail:{limit:e.frac,actual:t}});i.pushError({code:"digits.frac_overflow",rule:"digits",phase:"validate",detail:{limit:e.frac,actual:t}})}}},fix(t,i){const n=String(t);if(""===n||"-"===n||"."===n||"-."===n)return n;const r=c(n);let{intPart:s,fracPart:a}=r;const{sign:l,hasDot:o}=r;if("number"==typeof e.int&&"none"!==e.fixIntOnBlur){(s??"").length>e.int&&("truncateLeft"===e.fixIntOnBlur?s=s.slice(s.length-e.int):"truncateRight"===e.fixIntOnBlur?s=s.slice(0,e.int):"clamp"===e.fixIntOnBlur&&(s="9".repeat(e.int)))}if("number"==typeof e.frac&&"none"!==e.fixFracOnBlur&&o){const t=e.frac,i=a??"";if(i.length>t)if("truncate"===e.fixFracOnBlur)a=i.slice(0,t);else if("round"===e.fixFracOnBlur){const e=function(t,e,i){const n=e??"";if(n.length<=i)return{intPart:t,fracPart:n};const r=n.slice(0,i);if(n.charCodeAt(i)-48<5)return{intPart:t,fracPart:r};if(0===i)return{intPart:h(t.length?t:"0"),fracPart:""};let s=1;const a=r.split("");for(let t=a.length-1;t>=0;t--){const e=a[t].charCodeAt(0)-48+s;if(!(e>=10)){a[t]=String.fromCharCode(48+e),s=0;break}a[t]="0",s=1}const l=a.join("");let o=t;return 1===s&&(o=h(t.length?t:"0")),{intPart:o,fracPart:l}}(s,i,t);s=e.intPart,a=e.fracPart}}if(e.forceFracOnBlur&&"number"==typeof e.frac&&e.frac>0){const t=e.frac;o||(a="");const i=a??"";i.length<t&&(a=i+"0".repeat(t-i.length))}if("number"!=typeof e.frac)return`${l}${s}`;if(0===e.frac)return`${l}${s}`;if(o||e.forceFracOnBlur&&e.frac>0){return`${l}${s}.${a??""}`}return`${l}${s}`}}}function m(){return{name:"comma",targets:["input"],format(t){const e=String(t);if(""===e||"-"===e||"."===e||"-."===e)return e;let i="",n=e;n.startsWith("-")&&(i="-",n=n.slice(1));const r=n.indexOf("."),s=r>=0?n.slice(0,r):n,a=r>=0?n.slice(r+1):null,l=s.replace(/\B(?=(\d{3})+(?!\d))/g,",");return null!=a?`${i}${l}.${a}`:`${i}${l}`}}}u.fromDataset=function(t,e){if(null==t.tigRulesNumeric)return null;const i={},n=r(t.tigRulesNumericAllowFullWidth);null!=n&&(i.allowFullWidth=n);const s=r(t.tigRulesNumericAllowMinus);null!=s&&(i.allowMinus=s);const a=r(t.tigRulesNumericAllowDecimal);null!=a&&(i.allowDecimal=a);const l=r(t.tigRulesNumericAllowEmpty);return null!=l&&(i.allowEmpty=l),u(i)},d.fromDataset=function(t,e){if(null==t.tigRulesDigits)return null;const i={},n=s(t.tigRulesDigitsInt);null!=n&&(i.int=n);const l=s(t.tigRulesDigitsFrac);null!=l&&(i.frac=l);const o=r(t.tigRulesDigitsCountLeadingZeros);null!=o&&(i.countLeadingZeros=o);const u=a(t.tigRulesDigitsFixIntOnBlur,["none","truncateLeft","truncateRight","clamp"]);null!=u&&(i.fixIntOnBlur=u);const c=a(t.tigRulesDigitsFixFracOnBlur,["none","truncate","round"]);null!=c&&(i.fixFracOnBlur=c);const h=a(t.tigRulesDigitsOverflowInputInt,["none","block"]);null!=h&&(i.overflowInputInt=h);const m=a(t.tigRulesDigitsOverflowInputFrac,["none","block"]);null!=m&&(i.overflowInputFrac=m);const f=r(t.tigRulesDigitsForceFracOnBlur);return null!=f&&(i.forceFracOnBlur=f),d(i)},m.fromDataset=function(t,e){return null==t.tigRulesComma?null:m()};const f=new class{constructor(t,e){this.attachFn=t,this.ruleFactories=Array.isArray(e)?e:[]}register(t){this.ruleFactories.push(t)}autoAttach(t=document){const e=[],i=[];if(t.querySelectorAll){const e=t.querySelectorAll("input, textarea");for(const t of e)(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&i.push(t)}(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&(i.includes(t)||i.push(t));for(const t of i){const i=t.dataset;if("true"===i.tigAttached)continue;if(!o(i))continue;const n={},s=r(i.tigWarn);null!=s&&(n.warn=s),null!=i.tigInvalidClass&&""!==String(i.tigInvalidClass).trim()&&(n.invalidClass=String(i.tigInvalidClass)),n.separateValue={mode:l(i.tigSeparate)};const a=[];for(const e of this.ruleFactories)try{const n=e.fromDataset(i,t);n&&a.push(n)}catch(t){(n.warn??!0)&&console.warn(`[text-input-guard] autoAttach: rule "${e.name}" fromDataset() threw an error.`,t)}if(a.length>0&&(n.rules=a),!n.rules||0===n.rules.length)continue;const u=this.attachFn(t,n);e.push(u),t.dataset.tigAttached="true"}return{detach:()=>{for(const t of e)t.detach()},isValid:()=>e.every(t=>t.isValid()),getErrors:()=>e.flatMap(t=>t.getErrors()),getGuards:()=>e}}}(e,[{name:"numeric",fromDataset:u.fromDataset},{name:"digits",fromDataset:d.fromDataset},{name:"comma",fromDataset:m.fromDataset}]),p=t=>f.autoAttach(t),g={numeric:u,digits:d,comma:m},v="0.0.1";export{e as attach,i as attachAll,p as autoAttach,m as comma,d as digits,u as numeric,g as rules,v as version};