text-input-guard 0.1.5 → 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.
@@ -921,8 +921,8 @@ class InputGuard {
921
921
  * ルール実行に渡すコンテキストを作る(pushErrorで errors に積める)
922
922
  * @returns {GuardContext}
923
923
  */
924
- createCtx() {
925
- const snap = this.beforeInputSnapshot;
924
+ createCtx({ useSnapshot = true } = {}) {
925
+ const snap = useSnapshot ? this.beforeInputSnapshot : null;
926
926
  const inputType = snap?.inputType ?? "";
927
927
  const insertedText = snap?.insertedText ?? "";
928
928
 
@@ -1202,6 +1202,8 @@ class InputGuard {
1202
1202
  const current = display.value;
1203
1203
 
1204
1204
  const ctx = this.createCtx();
1205
+
1206
+ ctx.beforeText = "";
1205
1207
  ctx.afterText = current;
1206
1208
 
1207
1209
  let v = current;
@@ -1402,10 +1404,11 @@ class InputGuard {
1402
1404
  this.revertRequest = null;
1403
1405
 
1404
1406
  const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1405
- const ctx = this.createCtx();
1407
+ const ctx = this.createCtx({ useSnapshot: false });
1406
1408
 
1407
1409
  // 1) raw候補(displayから取得)
1408
1410
  let raw = display.value;
1411
+ ctx.beforeText = "";
1409
1412
  ctx.afterText = raw;
1410
1413
 
1411
1414
  // 2) 正規化(rawとして扱う形に揃える)
@@ -6386,7 +6389,7 @@ const getTextLengthByUnit = function(text, unit) {
6386
6389
  * @param {number} max
6387
6390
  * @returns {string}
6388
6391
  */
6389
- const cutTextByUnit = function(text, unit, max) {
6392
+ const cutTextByUnit$1 = function(text, unit, max) {
6390
6393
  /**
6391
6394
  * グラフェムの配列
6392
6395
  * @type {Grapheme[]}
@@ -6446,13 +6449,13 @@ const cutTextByUnit = function(text, unit, max) {
6446
6449
  * @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
6447
6450
  */
6448
6451
  const cutLength = function(beforeText, insertedText, unit, max) {
6449
- const orgLen = getTextLengthByUnit(beforeText, unit);
6452
+ const beforeTextLen = getTextLengthByUnit(beforeText, unit);
6450
6453
 
6451
6454
  // すでに最大長を超えている場合は追加のテキストを全て切る
6452
- if (orgLen >= max) { return ""; }
6455
+ if (beforeTextLen >= max) { return ""; }
6453
6456
 
6454
- const addLen = getTextLengthByUnit(insertedText, unit);
6455
- const totalLen = orgLen + addLen;
6457
+ const insertedTextLen = getTextLengthByUnit(insertedText, unit);
6458
+ const totalLen = beforeTextLen + insertedTextLen;
6456
6459
 
6457
6460
  if (totalLen <= max) {
6458
6461
  // 今回の追加で範囲内に収まるなら何もしない
@@ -6460,8 +6463,8 @@ const cutLength = function(beforeText, insertedText, unit, max) {
6460
6463
  }
6461
6464
 
6462
6465
  // 超える場合は追加のテキストを切る
6463
- const allowedAddLen = max - orgLen;
6464
- return cutTextByUnit(insertedText, unit, allowedAddLen);
6466
+ const allowedAddLen = max - beforeTextLen;
6467
+ return cutTextByUnit$1(insertedText, unit, allowedAddLen);
6465
6468
  };
6466
6469
 
6467
6470
  /**
@@ -6491,13 +6494,7 @@ function length(options = {}) {
6491
6494
  return value;
6492
6495
  }
6493
6496
 
6494
- const beforeText = ctx.beforeText;
6495
- const insertedText = ctx.insertedText;
6496
- if (insertedText === "") {
6497
- return value;
6498
- }
6499
-
6500
- const cutText = cutLength(beforeText, insertedText, opt.unit, opt.max);
6497
+ const cutText = cutLength(ctx.beforeText, value, opt.unit, opt.max);
6501
6498
  return cutText;
6502
6499
  },
6503
6500
 
@@ -6572,6 +6569,376 @@ length.fromDataset = function fromDataset(dataset, _el) {
6572
6569
  return length(options);
6573
6570
  };
6574
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
+
6575
6942
  /**
6576
6943
  * The script is part of TextInputGuard.
6577
6944
  *
@@ -6831,6 +7198,8 @@ const auto = new InputGuardAutoAttach(attach, [
6831
7198
  { name: "ascii", fromDataset: ascii.fromDataset },
6832
7199
  { name: "filter", fromDataset: filter.fromDataset },
6833
7200
  { name: "length", fromDataset: length.fromDataset },
7201
+ { name: "width", fromDataset: width.fromDataset },
7202
+ { name: "bytes", fromDataset: bytes.fromDataset },
6834
7203
  { name: "prefix", fromDataset: prefix.fromDataset },
6835
7204
  { name: "suffix", fromDataset: suffix.fromDataset },
6836
7205
  { name: "trim", fromDataset: trim.fromDataset }
@@ -6853,6 +7222,8 @@ const rules = {
6853
7222
  ascii,
6854
7223
  filter,
6855
7224
  length,
7225
+ width,
7226
+ bytes,
6856
7227
  prefix,
6857
7228
  suffix,
6858
7229
  trim
@@ -6860,16 +7231,17 @@ const rules = {
6860
7231
 
6861
7232
  /**
6862
7233
  * バージョン(ビルド時に置換したいならここを差し替える)
6863
- * 例: rollup replace で ""0.1.5"" を package.json の version に置換
7234
+ * 例: rollup replace で ""0.1.6"" を package.json の version に置換
6864
7235
  */
6865
7236
  // @ts-ignore
6866
7237
  // eslint-disable-next-line no-undef
6867
- const version = "0.1.5" ;
7238
+ const version = "0.1.6" ;
6868
7239
 
6869
7240
  exports.ascii = ascii;
6870
7241
  exports.attach = attach;
6871
7242
  exports.attachAll = attachAll;
6872
7243
  exports.autoAttach = autoAttach;
7244
+ exports.bytes = bytes;
6873
7245
  exports.comma = comma;
6874
7246
  exports.digits = digits;
6875
7247
  exports.filter = filter;
@@ -6881,3 +7253,4 @@ exports.rules = rules;
6881
7253
  exports.suffix = suffix;
6882
7254
  exports.trim = trim;
6883
7255
  exports.version = version;
7256
+ exports.width = width;