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