text-input-guard 0.1.4 → 0.1.6

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.
@@ -332,10 +332,25 @@ class SwapState {
332
332
  * @property {boolean} warn - warnログを出すかどうか
333
333
  * @property {string} invalidClass - エラー時に付与するclass名
334
334
  * @property {boolean} composing - IME変換中かどうか
335
+ * @property {string|null} inputType - 直前の入力操作種別(insertText / insertFromPaste / insertCompositionText 等)
336
+ * @property {string} beforeText - 挿入前の全文字列(置換範囲は除去済み)
337
+ * @property {number} replaceStart - 挿入位置/置換開始位置(selectionStart)
338
+ * @property {number} replaceEnd - 置換終了位置(selectionEnd)
339
+ * @property {string} insertedText - 挿入された文字列
340
+ * @property {string} afterText - 挿入後の全文字列(後で代入する)
335
341
  * @property {(e: TigError) => void} pushError - エラーを登録する関数
336
342
  * @property {(req: RevertRequest) => void} requestRevert - 入力を直前の受理値へ巻き戻す要求
337
343
  */
338
344
 
345
+ /**
346
+ * beforeinput で採取する「入力直前スナップショット」
347
+ * - 入力反映前の状態を保持し、差分判定や挿入位置特定に利用する
348
+ * @typedef {Object} BeforeInputSnapshot
349
+ * @property {SelectionState} selection - 入力反映前の選択範囲(挿入/置換位置の判定に使用)
350
+ * @property {string|null} inputType - 入力種別(insertText / insertFromPaste / deleteContentBackward 等)
351
+ * @property {string|null} insertedText - 挿入された文字列(通常入力時に取得できる場合あり)
352
+ */
353
+
339
354
  /**
340
355
  * 1つの入力制御ルール定義
341
356
  * - 各フェーズの処理を必要に応じて実装する
@@ -592,6 +607,11 @@ class InputGuard {
592
607
  */
593
608
  this.onInput = this.onInput.bind(this);
594
609
 
610
+ /**
611
+ * beforeinputイベントハンドラ(this固定)
612
+ */
613
+ this.onBeforeInput = this.onBeforeInput.bind(this);
614
+
595
615
  /**
596
616
  * blurイベントハンドラ(this固定)
597
617
  */
@@ -621,17 +641,24 @@ class InputGuard {
621
641
  this.pendingCompositionCommit = false;
622
642
 
623
643
  /**
624
- * 直前に受理した表示値(block時の戻し先)
644
+ * 直前に受理した表示値、正しい情報のスナップショットのような情報(block時の戻し先)
625
645
  * @type {string}
626
646
  */
627
647
  this.lastAcceptedValue = "";
628
648
 
629
649
  /**
630
- * 直前に受理したselectionblock時の戻し先)
650
+ * 直前に受理したselection、正しい情報のスナップショットのような情報(block時の戻し先)
631
651
  * @type {SelectionState}
632
652
  */
633
653
  this.lastAcceptedSelection = { start: null, end: null, direction: null };
634
654
 
655
+ /**
656
+ * 入力直前スナップショット(beforeinputで更新)
657
+ * length等の「挿入位置優先」ロジックで使用する
658
+ * @type {BeforeInputSnapshot|null}
659
+ */
660
+ this.beforeInputSnapshot = null;
661
+
635
662
  /**
636
663
  * ルールからのrevert要求
637
664
  * @type {RevertRequest|null}
@@ -832,6 +859,7 @@ class InputGuard {
832
859
  this.displayElement.addEventListener("compositionstart", this.onCompositionStart);
833
860
  this.displayElement.addEventListener("compositionend", this.onCompositionEnd);
834
861
  this.displayElement.addEventListener("input", this.onInput);
862
+ this.displayElement.addEventListener("beforeinput", this.onBeforeInput);
835
863
  this.displayElement.addEventListener("blur", this.onBlur);
836
864
 
837
865
  // フォーカスで編集用に戻す
@@ -852,6 +880,7 @@ class InputGuard {
852
880
  this.displayElement.removeEventListener("compositionstart", this.onCompositionStart);
853
881
  this.displayElement.removeEventListener("compositionend", this.onCompositionEnd);
854
882
  this.displayElement.removeEventListener("input", this.onInput);
883
+ this.displayElement.removeEventListener("beforeinput", this.onBeforeInput);
855
884
  this.displayElement.removeEventListener("blur", this.onBlur);
856
885
  this.displayElement.removeEventListener("focus", this.onFocus);
857
886
  this.displayElement.removeEventListener("keyup", this.onSelectionChange);
@@ -892,7 +921,71 @@ class InputGuard {
892
921
  * ルール実行に渡すコンテキストを作る(pushErrorで errors に積める)
893
922
  * @returns {GuardContext}
894
923
  */
895
- createCtx() {
924
+ createCtx({ useSnapshot = true } = {}) {
925
+ const snap = useSnapshot ? this.beforeInputSnapshot : null;
926
+ const inputType = snap?.inputType ?? "";
927
+ const insertedText = snap?.insertedText ?? "";
928
+
929
+ // 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
930
+ // display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
931
+ const beforeText = this.lastAcceptedValue ?? "";
932
+
933
+ // selection は2系統ある:
934
+ // - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
935
+ // - lastSel: ユーザー操作(keyup/mouseup/select 等)で追跡している selection(常にUIの見た目に近い)
936
+ //
937
+ // 基本は snapSel を優先するが、IME が絡むと snapSel が「変換中の範囲(composition range)」を指して
938
+ // “本当のキャレット位置” と一致しないことがあるため、その場合は lastSel を採用する。
939
+ const snapSel = snap?.selection ?? null;
940
+ const lastSel = this.lastAcceptedSelection;
941
+
942
+ // 通常は beforeinput の selection(snapSel)を使うのが一番正確
943
+ let baseSel = snapSel ?? lastSel;
944
+
945
+ // IME由来の入力(変換中の確定/更新など)は、beforeinput の selection が
946
+ // 「IMEが管理する範囲」になってしまうことがある。
947
+ // この場合 snapSel を使うと “勝手に上書き” が起きやすいので lastSel に寄せる。
948
+ const isCompositionInput =
949
+ this.composing ||
950
+ inputType === "insertCompositionText" ||
951
+ inputType === "deleteCompositionText" ||
952
+ inputType === "insertFromComposition";
953
+
954
+ // もう一つの検知:snapSel が「範囲選択」なのに lastSel が「キャレットのみ」なら、
955
+ // その範囲はユーザーが選択したのではなく、IMEが作っている範囲である可能性が高い。
956
+ // (例:12|34 に 5 を入れたいのに、IME範囲を置換して 1254 になる、など)
957
+ const looksLikeImeRange =
958
+ snapSel &&
959
+ (snapSel.start !== snapSel.end) &&
960
+ (lastSel.start === lastSel.end) &&
961
+ (inputType === "insertText" || inputType === "insertCompositionText");
962
+
963
+ if (isCompositionInput || looksLikeImeRange) {
964
+ baseSel = lastSel;
965
+ }
966
+
967
+ let replaceStart = baseSel.start ?? 0;
968
+ let replaceEnd = baseSel.end ?? 0;
969
+
970
+ // Backspace / Delete は「挿入文字がない(dataがnull)」ことが多い。
971
+ // そのままだと差分再構成で “何も変わらない” 扱いになって削除が効かなくなるため、
972
+ // 選択範囲が無い場合は「削除される1文字ぶん」の置換範囲をここで作る。
973
+ //
974
+ // ※ 選択範囲がある削除は replaceStart!=replaceEnd なので補正不要(その範囲を消すだけでよい)
975
+ if (replaceStart === replaceEnd) {
976
+ if (inputType === "deleteContentBackward") {
977
+ // Backspace: キャレットの左側1文字を削除
978
+ replaceStart = Math.max(0, replaceStart - 1);
979
+ replaceEnd = snapSel.start ?? replaceEnd;
980
+ } else if (inputType === "deleteContentForward") {
981
+ // Delete: キャレットの右側1文字を削除
982
+ replaceStart = snapSel.start ?? replaceStart;
983
+ replaceEnd = Math.min(beforeText.length, replaceEnd + 1);
984
+ }
985
+ // 追加で拾うならここ:
986
+ // deleteWordBackward / deleteWordForward / deleteByCut / deleteSoftLineBackward ... etc
987
+ }
988
+
896
989
  return {
897
990
  hostElement: this.hostElement,
898
991
  displayElement: this.displayElement,
@@ -901,6 +994,12 @@ class InputGuard {
901
994
  warn: this.warn,
902
995
  invalidClass: this.invalidClass,
903
996
  composing: this.composing,
997
+ inputType,
998
+ beforeText,
999
+ replaceStart,
1000
+ replaceEnd,
1001
+ insertedText,
1002
+ afterText: null, // 後で代入する
904
1003
  pushError: (e) => this.errors.push(e),
905
1004
  requestRevert: (req) => {
906
1005
  // 1回でもrevert要求が出たら採用(最初の理由を保持)
@@ -1065,6 +1164,23 @@ class InputGuard {
1065
1164
  this.evaluateInput();
1066
1165
  }
1067
1166
 
1167
+ /**
1168
+ * beforeinput:入力が反映される直前に呼ばれる
1169
+ * - ここでの value/selection が「今回の編集の基準点」になる
1170
+ * @param {InputEvent} e
1171
+ * @returns {void}
1172
+ */
1173
+ onBeforeInput(e) {
1174
+ const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1175
+ // 現時点(反映前)の選択範囲
1176
+ const selection = this.readSelection(el);
1177
+ /** @type {string|null} */
1178
+ const inputType = typeof e.inputType === "string" ? e.inputType : null;
1179
+ /** @type {string|null} */
1180
+ const insertedText = typeof e.data === "string" ? e.data : null;
1181
+ this.beforeInputSnapshot = { selection, inputType, insertedText };
1182
+ }
1183
+
1068
1184
  /**
1069
1185
  * blurイベント:確定時評価(normalize → validate → fix → format、同期、class更新)
1070
1186
  * @returns {void}
@@ -1075,7 +1191,7 @@ class InputGuard {
1075
1191
  }
1076
1192
 
1077
1193
  /**
1078
- * focusイベント:表示整形(カンマ等)を剥がして編集しやすい状態にする
1194
+ * focusイベント:表示整形を剥がして編集しやすい状態にする
1079
1195
  * - validate は走らせない(触っただけで赤くしたくないため)
1080
1196
  * @returns {void}
1081
1197
  */
@@ -1087,8 +1203,11 @@ class InputGuard {
1087
1203
 
1088
1204
  const ctx = this.createCtx();
1089
1205
 
1206
+ ctx.beforeText = "";
1207
+ ctx.afterText = current;
1208
+
1090
1209
  let v = current;
1091
- v = this.runNormalizeChar(v, ctx); // カンマ除去が効く
1210
+ v = this.runNormalizeChar(v, ctx);
1092
1211
  v = this.runNormalizeStructure(v, ctx);
1093
1212
 
1094
1213
  if (v !== current) {
@@ -1155,6 +1274,83 @@ class InputGuard {
1155
1274
  }
1156
1275
  }
1157
1276
 
1277
+ /**
1278
+ * evaluateInput専用createCtx(ルール実行に渡すコンテキストを作り、正規箇所も実行する)
1279
+ *
1280
+ * - CTX を作成する中で、文字の正規化と構造の正規化を行い、CTXのその情報に合わせる
1281
+ * - runNormalizeChar に対して入力した文字のみを入れることで、処理の高速化とキャレットズレが起きないように制御する
1282
+ * - runNormalizeStructure は全体の文字を入れるため、ここでの処理はキャレットズレが起きる可能性がある
1283
+ * @returns {GuardContext}
1284
+ */
1285
+ createCtxAndNormalize() {
1286
+ const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1287
+ const current = display.value;
1288
+ const ctx = this.createCtx();
1289
+ ctx.afterText = current;
1290
+
1291
+ // 元のテキスト
1292
+ const beforeText = ctx.beforeText;
1293
+
1294
+ // 追加入力したテキスト
1295
+ let insertedText = ctx.insertedText;
1296
+
1297
+ // 左端の挿入箇所
1298
+ const replaceStart = ctx.replaceStart;
1299
+
1300
+ // 現状のテキスト
1301
+ const tempText = current;
1302
+
1303
+ // 作成する全体のテキスト
1304
+ let newText = beforeText;
1305
+
1306
+ if (ctx.replaceStart !== ctx.replaceEnd) {
1307
+ // 選択範囲の前までと、選択範囲の後ろを結合して、間(選択部分)を削除する
1308
+ newText = beforeText.slice(0, ctx.replaceStart) + beforeText.slice(ctx.replaceEnd);
1309
+ }
1310
+
1311
+ // CTX の情報を最新の情報へ更新する
1312
+ ctx.beforeText = newText;
1313
+
1314
+ // 挿入するテキストのみ文字チェックを行う
1315
+ const normalizeCharText = this.runNormalizeChar(insertedText, ctx);
1316
+ insertedText = normalizeCharText;
1317
+
1318
+ // 作成したテキストを挿入する
1319
+ newText = newText.slice(0, replaceStart) + insertedText + newText.slice(replaceStart);
1320
+
1321
+ // 挿入したテキストの右側にカーソル位置をずらす
1322
+ // insertedText は UTF-16 code unit 長なので、
1323
+ // Selection は JS の index(UTF-16)前提で計算
1324
+ /**
1325
+ * @type {SelectionState}
1326
+ */
1327
+ let newSelection = { start: replaceStart + insertedText.length, end: replaceStart + insertedText.length, direction: "forward" };
1328
+
1329
+ // 挿入後文章全体に構造チェックを行う
1330
+ const normalizeStructureText = this.runNormalizeStructure(newText, ctx);
1331
+
1332
+ // 構成した文章がずれていた場合、カーソル位置の見直しを行う
1333
+ if (newText !== normalizeStructureText) {
1334
+ newText = normalizeStructureText;
1335
+ // 入力した実際のテキスト(tempText)から、現在位置から左側のみ切り出して、左側のみ再チェックする
1336
+ // 文章の長さに依存した変更があった場合は厳しいが、それ以外は以下の方法で切り抜けられる可能性が高い
1337
+ let leftText = tempText.slice(0, replaceStart);
1338
+ leftText = this.runNormalizeChar(leftText, ctx);
1339
+ leftText = this.runNormalizeStructure(leftText, ctx);
1340
+ const newPos = Math.min(leftText.length, newText.length);
1341
+ newSelection = { start: newPos, end: newPos, direction: "forward" };
1342
+ }
1343
+
1344
+ // 画面を更新
1345
+ this.syncDisplay(newText);
1346
+ this.writeSelection(display, newSelection);
1347
+
1348
+ // CTX の情報を最新の情報へ更新する
1349
+ ctx.afterText = newText;
1350
+
1351
+ return ctx;
1352
+ }
1353
+
1158
1354
  /**
1159
1355
  * 入力中の評価(IME中は何もしない)
1160
1356
  * - 固定順:normalize.char → normalize.structure → validate
@@ -1170,26 +1366,15 @@ class InputGuard {
1170
1366
  this.revertRequest = null;
1171
1367
 
1172
1368
  const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1173
- const current = display.value;
1174
1369
 
1175
- const ctx = this.createCtx();
1176
-
1177
- // raw候補(入力中は表示値=rawとして扱う)
1178
- let raw = current;
1179
-
1180
- raw = this.runNormalizeChar(raw, ctx);
1181
- raw = this.runNormalizeStructure(raw, ctx);
1182
-
1183
- // normalizeで変わったら反映(selection補正)
1184
- if (raw !== current) {
1185
- this.setDisplayValuePreserveCaret(display, raw, ctx);
1186
- }
1370
+ const ctx = this.createCtxAndNormalize();
1371
+ const raw = ctx.afterText;
1187
1372
 
1188
1373
  // validate(入力中:エラー出すだけ)
1189
1374
  this.runValidate(raw, ctx);
1190
1375
 
1191
- // revert要求が出たら巻き戻して終了
1192
1376
  if (this.revertRequest) {
1377
+ // revert要求が出たら巻き戻して終了
1193
1378
  this.revertDisplay(this.revertRequest);
1194
1379
  return;
1195
1380
  }
@@ -1219,10 +1404,12 @@ class InputGuard {
1219
1404
  this.revertRequest = null;
1220
1405
 
1221
1406
  const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1222
- const ctx = this.createCtx();
1407
+ const ctx = this.createCtx({ useSnapshot: false });
1223
1408
 
1224
1409
  // 1) raw候補(displayから取得)
1225
1410
  let raw = display.value;
1411
+ ctx.beforeText = "";
1412
+ ctx.afterText = raw;
1226
1413
 
1227
1414
  // 2) 正規化(rawとして扱う形に揃える)
1228
1415
  raw = this.runNormalizeChar(raw, ctx);
@@ -1356,83 +1543,111 @@ class InputGuard {
1356
1543
  }
1357
1544
 
1358
1545
  /**
1359
- * The script is part of TextInputGuard.
1546
+ * dataset/option boolean 値を解釈する
1547
+ * - 未指定(null/undefined)の場合は defaultValue を返す
1548
+ * - 空文字 "" は常に true(HTML属性文化)
1549
+ * - 指定があるが解釈できない場合は undefined
1360
1550
  *
1361
- * AUTHOR:
1362
- * natade-jp (https://github.com/natade-jp)
1551
+ * true : true / 1 / "true" / "1" / "yes" / "on" / ""
1552
+ * false : false / 0 / "false" / "0" / "no" / "off"
1363
1553
  *
1364
- * LICENSE:
1365
- * The MIT license https://opensource.org/licenses/MIT
1366
- */
1367
-
1368
- /**
1369
- * datasetのboolean値を解釈する
1370
- * - 未指定なら undefined
1371
- * - "" / "true" / "1" / "yes" / "on" は true
1372
- * - "false" / "0" / "no" / "off" は false
1373
- * @param {string|undefined} v
1554
+ * @param {string|number|boolean|undefined|null} v
1555
+ * @param {boolean} [defaultValue]
1374
1556
  * @returns {boolean|undefined}
1375
1557
  */
1376
- function parseDatasetBool(v) {
1377
- if (v == null) { return; }
1558
+ function parseDatasetBool(v, defaultValue) {
1559
+ if (v === null || v === undefined) { return defaultValue; }
1560
+
1561
+ if (typeof v === "boolean") { return v; }
1562
+
1563
+ if (typeof v === "number") {
1564
+ if (v === 1) { return true; }
1565
+ if (v === 0) { return false; }
1566
+ return;
1567
+ }
1568
+
1378
1569
  const s = String(v).trim().toLowerCase();
1379
- if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
1570
+
1571
+ // dataset の属性存在を true とみなす(例: data-xxx="")
1572
+ if (s === "") { return true; }
1573
+
1574
+ if (s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
1380
1575
  if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
1576
+
1381
1577
  return;
1382
1578
  }
1383
1579
 
1384
1580
  /**
1385
- * datasetのnumber値を解釈する(整数想定)
1386
- * - 未指定/空なら undefined
1581
+ * dataset/option number 値を解釈する
1582
+ * - 未指定(null/undefined/空文字)の場合は defaultValue を返す
1387
1583
  * - 数値でなければ undefined
1388
- * @param {string|undefined} v
1584
+ * @param {string|number|undefined|null} v
1585
+ * @param {number} [defaultValue]
1389
1586
  * @returns {number|undefined}
1390
1587
  */
1391
- function parseDatasetNumber(v) {
1392
- if (v == null) { return; }
1588
+ function parseDatasetNumber(v, defaultValue) {
1589
+ if (v === null || v === undefined) { return defaultValue; }
1590
+
1591
+ if (typeof v === "number") {
1592
+ return Number.isFinite(v) ? v : undefined;
1593
+ }
1594
+
1393
1595
  const s = String(v).trim();
1394
- if (s === "") { return; }
1596
+ if (s === "") { return defaultValue; }
1597
+
1395
1598
  const n = Number(s);
1396
1599
  return Number.isFinite(n) ? n : undefined;
1397
1600
  }
1398
1601
 
1399
1602
  /**
1400
- * enumを解釈する(未指定なら undefined)
1603
+ * enumを解釈する
1604
+ * - 未指定(null/undefined/空文字)の場合は defaultValue を返す
1605
+ * - 値が指定されているが allowed に含まれない場合は undefined を返す
1606
+ *
1401
1607
  * @template {string} T
1402
- * @param {string|undefined} v
1608
+ * @param {string|undefined|null} v
1403
1609
  * @param {readonly T[]} allowed
1610
+ * @param {T} [defaultValue]
1404
1611
  * @returns {T|undefined}
1405
1612
  */
1406
- function parseDatasetEnum(v, allowed) {
1407
- if (v == null) { return; }
1613
+ function parseDatasetEnum(v, allowed, defaultValue) {
1614
+ if (v === null || v === undefined) { return defaultValue; }
1615
+
1408
1616
  const s = String(v).trim();
1409
- if (s === "") { return; }
1410
- // 大文字小文字を区別したいならここを変える(今は厳密一致)
1411
- return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
1617
+ if (s === "") { return defaultValue; }
1618
+
1619
+ return /** @type {T|undefined} */ (
1620
+ allowed.includes(/** @type {any} */ (s)) ? s : undefined
1621
+ );
1412
1622
  }
1413
1623
 
1414
1624
  /**
1415
- * enum のカンマ区切り複数指定を解釈する(未指定なら undefined)
1416
- * - 未指定なら undefined
1625
+ * enum のカンマ区切り複数指定を解釈する
1626
+ * - 未指定(null/undefined/空文字)の場合は defaultValue を返す
1417
1627
  * - 空要素は無視
1418
1628
  * - allowed に含まれないものは除外
1419
1629
  *
1420
- * 例:
1421
- * - "a,b,c" -> ["a","b","c"](allowed に含まれるもののみ)
1422
- * - "" / " " -> undefined
1423
- * - "x,y"(どちらも allowed 外)-> []
1424
- *
1425
1630
  * @template {string} T
1426
- * @param {string|undefined} v
1631
+ * @param {string|T[]|undefined|null} v
1427
1632
  * @param {readonly T[]} allowed
1633
+ * @param {T[]} [defaultValue]
1428
1634
  * @returns {T[]|undefined}
1429
1635
  */
1430
- function parseDatasetEnumList(v, allowed) {
1431
- if (v == null) { return; }
1636
+ function parseDatasetEnumList(v, allowed, defaultValue) {
1637
+ if (v === null || v === undefined) { return defaultValue; }
1638
+
1639
+ // JSオプションで配列直渡しも許可
1640
+ if (Array.isArray(v)) {
1641
+ const result = v.filter(
1642
+ /** @returns {x is T} */
1643
+ (x) => allowed.includes(/** @type {any} */ (x))
1644
+ );
1645
+ return result;
1646
+ }
1647
+
1432
1648
  const s = String(v).trim();
1433
- if (s === "") { return; }
1649
+ if (s === "") { return defaultValue; }
1434
1650
 
1435
- /** @type {string[]} */
1436
1651
  const list = s
1437
1652
  .split(",")
1438
1653
  .map((x) => x.trim())
@@ -5890,7 +6105,7 @@ const createCategoryTester = function (categories) {
5890
6105
  /**
5891
6106
  * 1文字が許可されるか判定する関数を作る
5892
6107
  * @param {FilterCategory[]} categoryList
5893
- * @param {(graphem: Grapheme, s: string) => boolean} categoryTest
6108
+ * @param {(g: Grapheme, s: string) => boolean} categoryTest
5894
6109
  * @param {RegExp|undefined} allowRe
5895
6110
  * @param {RegExp|undefined} denyRe
5896
6111
  * @returns {(g: Grapheme, s: string) => boolean}
@@ -5943,10 +6158,10 @@ const scanByAllowed = function (value, isAllowed, maxInvalidChars = 20) {
5943
6158
  * グラフェムの配列
5944
6159
  * @type {Grapheme[]}
5945
6160
  */
5946
- const graphemArray = Mojix.toMojiArrayFromString(v);
6161
+ const graphemeArray = Mojix.toMojiArrayFromString(v);
5947
6162
 
5948
6163
  // JS の文字列イテレータはコードポイント単位で回るので Array.from は不要
5949
- for (const g of graphemArray) {
6164
+ for (const g of graphemeArray) {
5950
6165
  const s = Mojix.toStringFromMojiArray([g]);
5951
6166
  if (isAllowed(g, s)) {
5952
6167
  filtered += s;
@@ -6121,6 +6336,609 @@ filter.fromDataset = function fromDataset(dataset, _el) {
6121
6336
  return filter(options);
6122
6337
  };
6123
6338
 
6339
+ /**
6340
+ * The script is part of TextInputGuard.
6341
+ *
6342
+ * AUTHOR:
6343
+ * natade-jp (https://github.com/natade-jp)
6344
+ *
6345
+ * LICENSE:
6346
+ * The MIT license https://opensource.org/licenses/MIT
6347
+ */
6348
+
6349
+
6350
+ /**
6351
+ * length ルールのオプション
6352
+ * @typedef {Object} LengthRuleOptions
6353
+ * @property {number} [max] - 最大長(グラフェム数)。未指定なら制限なし
6354
+ * @property {"block"|"error"} [overflowInput="block"] - 入力中に最大長を超えたときの挙動
6355
+ * @property {"grapheme"|"utf-16"|"utf-32"} [unit="grapheme"] - 長さの単位
6356
+ *
6357
+ * block : 最大長を超える部分を切る
6358
+ * error : エラーを積むだけ(値は変更しない)
6359
+ */
6360
+
6361
+ /**
6362
+ * グラフェム(1グラフェムは、UTF-32の配列)
6363
+ * @typedef {number[]} Grapheme
6364
+ */
6365
+
6366
+ /**
6367
+ * グラフェム/UTF-16コード単位/UTF-32コード単位の長さを調べる
6368
+ * @param {string} text
6369
+ * @param {"grapheme"|"utf-16"|"utf-32"} unit
6370
+ * @returns {number}
6371
+ */
6372
+ const getTextLengthByUnit = function(text, unit) {
6373
+ if (unit === "grapheme") {
6374
+ return Mojix.toMojiArrayFromString(text).length;
6375
+ } else if (unit === "utf-16") {
6376
+ return Mojix.toUTF16Array(text).length;
6377
+ } else if (unit === "utf-32") {
6378
+ return Mojix.toUTF32Array(text).length;
6379
+ } else {
6380
+ // ここには来ない
6381
+ throw new Error(`Invalid unit: ${unit}`);
6382
+ }
6383
+ };
6384
+
6385
+ /**
6386
+ * グラフェム/UTF-16コード単位/UTF-32コード単位でテキストを切る
6387
+ * @param {string} text
6388
+ * @param {"grapheme"|"utf-16"|"utf-32"} unit
6389
+ * @param {number} max
6390
+ * @returns {string}
6391
+ */
6392
+ const cutTextByUnit$1 = function(text, unit, max) {
6393
+ /**
6394
+ * グラフェムの配列
6395
+ * @type {Grapheme[]}
6396
+ */
6397
+ const graphemeArray = Mojix.toMojiArrayFromString(text);
6398
+
6399
+ /**
6400
+ * 現在の位置
6401
+ */
6402
+ let count = 0;
6403
+
6404
+ /**
6405
+ * グラフェムの配列(出力用)
6406
+ * @type {Grapheme[]}
6407
+ */
6408
+ const outputGraphemeArray = [];
6409
+
6410
+ for (let i = 0; i < graphemeArray.length; i++) {
6411
+ const g = graphemeArray[i];
6412
+
6413
+ // 1グラフェムあたりの長さ
6414
+ let graphemeCount = 0;
6415
+ if (unit === "grapheme") {
6416
+ graphemeCount = 1;
6417
+ } else if (unit === "utf-16") {
6418
+ graphemeCount = 0;
6419
+ for (let i = 0; i < g.length; i++) {
6420
+ graphemeCount += (g[i] > 0xFFFF) ? 2 : 1;
6421
+ }
6422
+ } else if (unit === "utf-32") {
6423
+ graphemeCount = g.length;
6424
+ }
6425
+
6426
+ if (count + graphemeCount > max) {
6427
+ // 空配列を渡すとNUL文字を返すため、空配列のときは空文字を返す
6428
+ if (outputGraphemeArray.length === 0) {
6429
+ return "";
6430
+ }
6431
+ // 超える前の位置で文字列化して返す
6432
+ return Mojix.toStringFromMojiArray(outputGraphemeArray);
6433
+ }
6434
+
6435
+ count += graphemeCount;
6436
+ outputGraphemeArray.push(g);
6437
+ }
6438
+
6439
+ // 全部入るなら元の text を返す
6440
+ return text;
6441
+ };
6442
+
6443
+ /**
6444
+ * 元のテキストと追加のテキストの合計が max を超える場合、追加のテキストを切って合計が max に収まるようにする
6445
+ * @param {string} beforeText 元のテキスト
6446
+ * @param {string} insertedText 追加するテキスト
6447
+ * @param {"grapheme"|"utf-16"|"utf-32"} unit
6448
+ * @param {number} max
6449
+ * @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
6450
+ */
6451
+ const cutLength = function(beforeText, insertedText, unit, max) {
6452
+ const beforeTextLen = getTextLengthByUnit(beforeText, unit);
6453
+
6454
+ // すでに最大長を超えている場合は追加のテキストを全て切る
6455
+ if (beforeTextLen >= max) { return ""; }
6456
+
6457
+ const insertedTextLen = getTextLengthByUnit(insertedText, unit);
6458
+ const totalLen = beforeTextLen + insertedTextLen;
6459
+
6460
+ if (totalLen <= max) {
6461
+ // 今回の追加で範囲内に収まるなら何もしない
6462
+ return insertedText;
6463
+ }
6464
+
6465
+ // 超える場合は追加のテキストを切る
6466
+ const allowedAddLen = max - beforeTextLen;
6467
+ return cutTextByUnit$1(insertedText, unit, allowedAddLen);
6468
+ };
6469
+
6470
+ /**
6471
+ * length ルールを生成する
6472
+ * @param {LengthRuleOptions} [options]
6473
+ * @returns {Rule}
6474
+ */
6475
+ function length(options = {}) {
6476
+ /** @type {LengthRuleOptions} */
6477
+ const opt = {
6478
+ max: typeof options.max === "number" ? options.max : undefined,
6479
+ overflowInput: options.overflowInput ?? "block",
6480
+ unit: options.unit ?? "grapheme"
6481
+ };
6482
+
6483
+ return {
6484
+ name: "length",
6485
+ targets: ["input", "textarea"],
6486
+
6487
+ normalizeChar(value, ctx) {
6488
+ // block 以外は何もしない
6489
+ if (opt.overflowInput !== "block") {
6490
+ return value;
6491
+ }
6492
+ // max 未指定なら制限なし
6493
+ if (typeof opt.max !== "number") {
6494
+ return value;
6495
+ }
6496
+
6497
+ const cutText = cutLength(ctx.beforeText, value, opt.unit, opt.max);
6498
+ return cutText;
6499
+ },
6500
+
6501
+ validate(value, ctx) {
6502
+ // error 以外は何もしない
6503
+ if (opt.overflowInput !== "error") {
6504
+ return value;
6505
+ }
6506
+ // max 未指定なら制限なし
6507
+ if (typeof opt.max !== "number") {
6508
+ return;
6509
+ }
6510
+
6511
+ const len = getTextLengthByUnit(value, opt.unit);
6512
+ if (len > opt.max) {
6513
+ ctx.pushError({
6514
+ code: "length.max_overflow",
6515
+ rule: "length",
6516
+ phase: "validate",
6517
+ detail: { max: opt.max, actual: len }
6518
+ });
6519
+ }
6520
+ }
6521
+ };
6522
+ }
6523
+
6524
+ /**
6525
+ * datasetから length ルールを生成する
6526
+ * - data-tig-rules-length が無ければ null
6527
+ * - オプションは data-tig-rules-length-xxx から読む
6528
+ *
6529
+ * 対応する data 属性(dataset 名)
6530
+ * - data-tig-rules-length -> dataset.tigRulesLength
6531
+ * - data-tig-rules-length-max -> dataset.tigRulesLengthMax
6532
+ * - data-tig-rules-length-overflow-input -> dataset.tigRulesLengthOverflowInput
6533
+ * - data-tig-rules-length-unit -> dataset.tigRulesLengthUnit
6534
+ *
6535
+ * @param {DOMStringMap} dataset
6536
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6537
+ * @returns {Rule|null}
6538
+ */
6539
+ length.fromDataset = function fromDataset(dataset, _el) {
6540
+ // ON判定
6541
+ if (dataset.tigRulesLength == null) {
6542
+ return null;
6543
+ }
6544
+
6545
+ /** @type {LengthRuleOptions} */
6546
+ const options = {};
6547
+
6548
+ const max = parseDatasetNumber(dataset.tigRulesLengthMax);
6549
+ if (max != null) {
6550
+ options.max = max;
6551
+ }
6552
+
6553
+ const overflowInput = parseDatasetEnum(
6554
+ dataset.tigRulesLengthOverflowInput,
6555
+ ["block", "error"]
6556
+ );
6557
+ if (overflowInput != null) {
6558
+ options.overflowInput = overflowInput;
6559
+ }
6560
+
6561
+ const unit = parseDatasetEnum(
6562
+ dataset.tigRulesLengthUnit,
6563
+ ["grapheme", "utf-16", "utf-32"]
6564
+ );
6565
+ if (unit != null) {
6566
+ options.unit = unit;
6567
+ }
6568
+
6569
+ return length(options);
6570
+ };
6571
+
6572
+ /**
6573
+ * The script is part of TextInputGuard.
6574
+ *
6575
+ * AUTHOR:
6576
+ * natade-jp (https://github.com/natade-jp)
6577
+ *
6578
+ * LICENSE:
6579
+ * The MIT license https://opensource.org/licenses/MIT
6580
+ */
6581
+
6582
+
6583
+ /**
6584
+ * width ルールのオプション
6585
+ * @typedef {Object} WidthRuleOptions
6586
+ * @property {number} [max] - 最大長(全角は2, 半角は1)
6587
+ * @property {"block"|"error"} [overflowInput="block"] - 入力中に最大長を超えたときの挙動
6588
+ *
6589
+ * block : 最大長を超える部分を切る
6590
+ * error : エラーを積むだけ(値は変更しない)
6591
+ */
6592
+
6593
+ /**
6594
+ * width ルールを生成する
6595
+ * @param {WidthRuleOptions} [options]
6596
+ * @returns {Rule}
6597
+ */
6598
+ function width(options = {}) {
6599
+ /** @type {WidthRuleOptions} */
6600
+ const opt = {
6601
+ max: typeof options.max === "number" ? options.max : undefined,
6602
+ overflowInput: options.overflowInput ?? "block"
6603
+ };
6604
+
6605
+ return {
6606
+ name: "length",
6607
+ targets: ["input", "textarea"],
6608
+
6609
+ normalizeChar(value, ctx) {
6610
+ // block 以外は何もしない
6611
+ if (opt.overflowInput !== "block") {
6612
+ return value;
6613
+ }
6614
+ // max 未指定なら制限なし
6615
+ if (typeof opt.max !== "number") {
6616
+ return value;
6617
+ }
6618
+
6619
+ /*
6620
+ * 指定したテキストを切り出す
6621
+ * - 0幅 ... グラフェムを構成する要素
6622
+ * (結合文字, 異体字セレクタ, スキントーン修飾子,
6623
+ * Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
6624
+ * - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
6625
+ * - 2幅 ... 上記以外
6626
+ * ※ Unicode が配布してる EastAsianWidth.txt は使用していません。
6627
+ * (目的としては Shift_JIS 時代の半角全角だと思われるため)
6628
+ */
6629
+ const cutText = Mojix.cutTextForWidth(value, 0, opt.max);
6630
+ return cutText;
6631
+ },
6632
+
6633
+ validate(value, ctx) {
6634
+ // error 以外は何もしない
6635
+ if (opt.overflowInput !== "error") {
6636
+ return value;
6637
+ }
6638
+ // max 未指定なら制限なし
6639
+ if (typeof opt.max !== "number") {
6640
+ return;
6641
+ }
6642
+
6643
+ /*
6644
+ * 指定したテキストの横幅を半角/全角でカウント
6645
+ * - 0幅 ... グラフェムを構成する要素
6646
+ * (結合文字, 異体字セレクタ, スキントーン修飾子,
6647
+ * Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
6648
+ * - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
6649
+ * - 2幅 ... 上記以外
6650
+ * ※ Unicode が配布してる EastAsianWidth.txt は使用していません。
6651
+ * (目的としては Shift_JIS 時代の半角全角だと思われるため)
6652
+ */
6653
+ const len = Mojix.getWidth(value);
6654
+ if (len > opt.max) {
6655
+ ctx.pushError({
6656
+ code: "length.max_overflow",
6657
+ rule: "length",
6658
+ phase: "validate",
6659
+ detail: { max: opt.max, actual: len }
6660
+ });
6661
+ }
6662
+ }
6663
+ };
6664
+ }
6665
+
6666
+ /**
6667
+ * datasetから length ルールを生成する
6668
+ * - data-tig-rules-length が無ければ null
6669
+ * - オプションは data-tig-rules-length-xxx から読む
6670
+ *
6671
+ * 対応する data 属性(dataset 名)
6672
+ * - data-tig-rules-length -> dataset.tigRulesWidth
6673
+ * - data-tig-rules-length-max -> dataset.tigRulesWidthMax
6674
+ * - data-tig-rules-length-overflow-input -> dataset.tigRulesWidthOverflowInput
6675
+ *
6676
+ * @param {DOMStringMap} dataset
6677
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6678
+ * @returns {Rule|null}
6679
+ */
6680
+ width.fromDataset = function fromDataset(dataset, _el) {
6681
+ // ON判定
6682
+ if (dataset.tigRulesWidth == null) {
6683
+ return null;
6684
+ }
6685
+
6686
+ /** @type {WidthRuleOptions} */
6687
+ const options = {};
6688
+
6689
+ const max = parseDatasetNumber(dataset.tigRulesWidthMax);
6690
+ if (max != null) {
6691
+ options.max = max;
6692
+ }
6693
+
6694
+ const overflowInput = parseDatasetEnum(
6695
+ dataset.tigRulesWidthOverflowInput,
6696
+ ["block", "error"]
6697
+ );
6698
+ if (overflowInput != null) {
6699
+ options.overflowInput = overflowInput;
6700
+ }
6701
+
6702
+ return width(options);
6703
+ };
6704
+
6705
+ /**
6706
+ * The script is part of TextInputGuard.
6707
+ *
6708
+ * AUTHOR:
6709
+ * natade-jp (https://github.com/natade-jp)
6710
+ *
6711
+ * LICENSE:
6712
+ * The MIT license https://opensource.org/licenses/MIT
6713
+ */
6714
+
6715
+
6716
+ /**
6717
+ * bytes ルールのオプション
6718
+ * @typedef {Object} BytesRuleOptions
6719
+ * @property {number} [max] - 最大長(グラフェム数)。未指定なら制限なし
6720
+ * @property {"block"|"error"} [overflowInput="block"] - 入力中に最大長を超えたときの挙動
6721
+ * @property {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} [unit="utf-8"] - サイズの単位(sjis系を使用する場合はfilterも必須)
6722
+ *
6723
+ * block : 最大長を超える部分を切る
6724
+ * error : エラーを積むだけ(値は変更しない)
6725
+ */
6726
+
6727
+ /**
6728
+ * グラフェム(1グラフェムは、UTF-32の配列)
6729
+ * @typedef {number[]} Grapheme
6730
+ */
6731
+
6732
+ /**
6733
+ * グラフェム/UTF-16コード単位/UTF-32コード単位の長さを調べる
6734
+ * @param {string} text
6735
+ * @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
6736
+ * @returns {number}
6737
+ */
6738
+ const getTextBytesByUnit = function(text, unit) {
6739
+ if (text.length === 0) {
6740
+ return 0;
6741
+ }
6742
+ if (unit === "utf-8") {
6743
+ return Mojix.toUTF8Array(text).length;
6744
+ } else if (unit === "utf-16") {
6745
+ return Mojix.toUTF16Array(text).length * 2;
6746
+ } else if (unit === "utf-32") {
6747
+ return Mojix.toUTF32Array(text).length * 4;
6748
+ } else if (unit === "sjis" || unit === "cp932") {
6749
+ return Mojix.encode(text, "Shift_JIS").length;
6750
+ } else {
6751
+ // ここには来ない
6752
+ throw new Error(`Invalid unit: ${unit}`);
6753
+ }
6754
+ };
6755
+
6756
+ /**
6757
+ * グラフェム/UTF-16コード単位/UTF-32コード単位でテキストを切る
6758
+ * @param {string} text
6759
+ * @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
6760
+ * @param {number} max
6761
+ * @returns {string}
6762
+ */
6763
+ const cutTextByUnit = function(text, unit, max) {
6764
+ /**
6765
+ * グラフェムの配列
6766
+ * @type {Grapheme[]}
6767
+ */
6768
+ const graphemeArray = Mojix.toMojiArrayFromString(text);
6769
+
6770
+ /**
6771
+ * 現在の位置
6772
+ */
6773
+ let count = 0;
6774
+
6775
+ /**
6776
+ * グラフェムの配列(出力用)
6777
+ * @type {Grapheme[]}
6778
+ */
6779
+ const outputGraphemeArray = [];
6780
+
6781
+ for (let i = 0; i < graphemeArray.length; i++) {
6782
+ const g = graphemeArray[i];
6783
+
6784
+ // 1グラフェムあたりの長さ
6785
+ let byteCount = 0;
6786
+ if (unit === "utf-8") {
6787
+ byteCount = Mojix.toUTF8Array(Mojix.toStringFromMojiArray([g])).length;
6788
+ } else if (unit === "utf-16") {
6789
+ byteCount = Mojix.toUTF16Array(Mojix.toStringFromMojiArray([g])).length * 2;
6790
+ } else if (unit === "utf-32") {
6791
+ byteCount = Mojix.toUTF32Array(Mojix.toStringFromMojiArray([g])).length * 4;
6792
+ } else if (unit === "sjis" || unit === "cp932") {
6793
+ byteCount = Mojix.encode(Mojix.toStringFromMojiArray([g]), "Shift_JIS").length;
6794
+ }
6795
+
6796
+ if (count + byteCount > max) {
6797
+ // 空配列を渡すとNUL文字を返すため、空配列のときは空文字を返す
6798
+ if (outputGraphemeArray.length === 0) {
6799
+ return "";
6800
+ }
6801
+ // 超える前の位置で文字列化して返す
6802
+ return Mojix.toStringFromMojiArray(outputGraphemeArray);
6803
+ }
6804
+
6805
+ count += byteCount;
6806
+ outputGraphemeArray.push(g);
6807
+ }
6808
+
6809
+ // 全部入るなら元の text を返す
6810
+ return text;
6811
+ };
6812
+
6813
+ /**
6814
+ * 元のテキストと追加のテキストの合計が max を超える場合、追加のテキストを切って合計が max に収まるようにする
6815
+ * @param {string} beforeText 元のテキスト
6816
+ * @param {string} insertedText 追加するテキスト
6817
+ * @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
6818
+ * @param {number} max
6819
+ * @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
6820
+ */
6821
+ const cutBytes = function(beforeText, insertedText, unit, max) {
6822
+ const beforeTextLen = getTextBytesByUnit(beforeText, unit);
6823
+
6824
+ // すでに最大長を超えている場合は追加のテキストを全て切る
6825
+ if (beforeTextLen >= max) { return ""; }
6826
+
6827
+ const insertedTextLen = getTextBytesByUnit(insertedText, unit);
6828
+ const totalLen = beforeTextLen + insertedTextLen;
6829
+
6830
+ if (totalLen <= max) {
6831
+ // 今回の追加で範囲内に収まるなら何もしない
6832
+ return insertedText;
6833
+ }
6834
+
6835
+ // 超える場合は追加のテキストを切る
6836
+ const allowedAddLen = max - beforeTextLen;
6837
+ return cutTextByUnit(insertedText, unit, allowedAddLen);
6838
+ };
6839
+
6840
+ /**
6841
+ * bytes ルールを生成する
6842
+ * @param {BytesRuleOptions} [options]
6843
+ * @returns {Rule}
6844
+ */
6845
+ function bytes(options = {}) {
6846
+ /** @type {BytesRuleOptions} */
6847
+ const opt = {
6848
+ max: typeof options.max === "number" ? options.max : undefined,
6849
+ overflowInput: options.overflowInput ?? "block",
6850
+ unit: options.unit ?? "utf-8"
6851
+ };
6852
+
6853
+ return {
6854
+ name: "bytes",
6855
+ targets: ["input", "textarea"],
6856
+
6857
+ normalizeChar(value, ctx) {
6858
+ // block 以外は何もしない
6859
+ if (opt.overflowInput !== "block") {
6860
+ return value;
6861
+ }
6862
+ // max 未指定なら制限なし
6863
+ if (typeof opt.max !== "number") {
6864
+ return value;
6865
+ }
6866
+
6867
+ const cutText = cutBytes(ctx.beforeText, value, opt.unit, opt.max);
6868
+ return cutText;
6869
+ },
6870
+
6871
+ validate(value, ctx) {
6872
+ // error 以外は何もしない
6873
+ if (opt.overflowInput !== "error") {
6874
+ return value;
6875
+ }
6876
+ // max 未指定なら制限なし
6877
+ if (typeof opt.max !== "number") {
6878
+ return;
6879
+ }
6880
+
6881
+ const len = getTextBytesByUnit(value, opt.unit);
6882
+ if (len > opt.max) {
6883
+ ctx.pushError({
6884
+ code: "bytes.max_overflow",
6885
+ rule: "bytes",
6886
+ phase: "validate",
6887
+ detail: { max: opt.max, actual: len }
6888
+ });
6889
+ }
6890
+ }
6891
+ };
6892
+ }
6893
+
6894
+ /**
6895
+ * datasetから bytes ルールを生成する
6896
+ * - data-tig-rules-bytes が無ければ null
6897
+ * - オプションは data-tig-rules-bytes-xxx から読む
6898
+ *
6899
+ * 対応する data 属性(dataset 名)
6900
+ * - data-tig-rules-bytes -> dataset.tigRulesBytes
6901
+ * - data-tig-rules-bytes-max -> dataset.tigRulesBytesMax
6902
+ * - data-tig-rules-bytes-overflow-input -> dataset.tigRulesBytesOverflowInput
6903
+ * - data-tig-rules-bytes-unit -> dataset.tigRulesBytesUnit
6904
+ *
6905
+ * @param {DOMStringMap} dataset
6906
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6907
+ * @returns {Rule|null}
6908
+ */
6909
+ bytes.fromDataset = function fromDataset(dataset, _el) {
6910
+ // ON判定
6911
+ if (dataset.tigRulesBytes == null) {
6912
+ return null;
6913
+ }
6914
+
6915
+ /** @type {BytesRuleOptions} */
6916
+ const options = {};
6917
+
6918
+ const max = parseDatasetNumber(dataset.tigRulesBytesMax);
6919
+ if (max != null) {
6920
+ options.max = max;
6921
+ }
6922
+
6923
+ const overflowInput = parseDatasetEnum(
6924
+ dataset.tigRulesBytesOverflowInput,
6925
+ ["block", "error"]
6926
+ );
6927
+ if (overflowInput != null) {
6928
+ options.overflowInput = overflowInput;
6929
+ }
6930
+
6931
+ const unit = parseDatasetEnum(
6932
+ dataset.tigRulesBytesUnit,
6933
+ ["utf-8", "utf-16", "utf-32", "sjis", "cp932"]
6934
+ );
6935
+ if (unit != null) {
6936
+ options.unit = unit;
6937
+ }
6938
+
6939
+ return bytes(options);
6940
+ };
6941
+
6124
6942
  /**
6125
6943
  * The script is part of TextInputGuard.
6126
6944
  *
@@ -6379,6 +7197,9 @@ const auto = new InputGuardAutoAttach(attach, [
6379
7197
  { name: "kana", fromDataset: kana.fromDataset },
6380
7198
  { name: "ascii", fromDataset: ascii.fromDataset },
6381
7199
  { name: "filter", fromDataset: filter.fromDataset },
7200
+ { name: "length", fromDataset: length.fromDataset },
7201
+ { name: "width", fromDataset: width.fromDataset },
7202
+ { name: "bytes", fromDataset: bytes.fromDataset },
6382
7203
  { name: "prefix", fromDataset: prefix.fromDataset },
6383
7204
  { name: "suffix", fromDataset: suffix.fromDataset },
6384
7205
  { name: "trim", fromDataset: trim.fromDataset }
@@ -6400,6 +7221,9 @@ const rules = {
6400
7221
  kana,
6401
7222
  ascii,
6402
7223
  filter,
7224
+ length,
7225
+ width,
7226
+ bytes,
6403
7227
  prefix,
6404
7228
  suffix,
6405
7229
  trim
@@ -6407,23 +7231,26 @@ const rules = {
6407
7231
 
6408
7232
  /**
6409
7233
  * バージョン(ビルド時に置換したいならここを差し替える)
6410
- * 例: rollup replace で ""0.1.4"" を package.json の version に置換
7234
+ * 例: rollup replace で ""0.1.6"" を package.json の version に置換
6411
7235
  */
6412
7236
  // @ts-ignore
6413
7237
  // eslint-disable-next-line no-undef
6414
- const version = "0.1.4" ;
7238
+ const version = "0.1.6" ;
6415
7239
 
6416
7240
  exports.ascii = ascii;
6417
7241
  exports.attach = attach;
6418
7242
  exports.attachAll = attachAll;
6419
7243
  exports.autoAttach = autoAttach;
7244
+ exports.bytes = bytes;
6420
7245
  exports.comma = comma;
6421
7246
  exports.digits = digits;
6422
7247
  exports.filter = filter;
6423
7248
  exports.kana = kana;
7249
+ exports.length = length;
6424
7250
  exports.numeric = numeric;
6425
7251
  exports.prefix = prefix;
6426
7252
  exports.rules = rules;
6427
7253
  exports.suffix = suffix;
6428
7254
  exports.trim = trim;
6429
7255
  exports.version = version;
7256
+ exports.width = width;