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.
@@ -925,8 +925,8 @@
925
925
  * ルール実行に渡すコンテキストを作る(pushErrorで errors に積める)
926
926
  * @returns {GuardContext}
927
927
  */
928
- createCtx() {
929
- const snap = this.beforeInputSnapshot;
928
+ createCtx({ useSnapshot = true } = {}) {
929
+ const snap = useSnapshot ? this.beforeInputSnapshot : null;
930
930
  const inputType = snap?.inputType ?? "";
931
931
  const insertedText = snap?.insertedText ?? "";
932
932
 
@@ -1206,6 +1206,8 @@
1206
1206
  const current = display.value;
1207
1207
 
1208
1208
  const ctx = this.createCtx();
1209
+
1210
+ ctx.beforeText = "";
1209
1211
  ctx.afterText = current;
1210
1212
 
1211
1213
  let v = current;
@@ -1406,10 +1408,11 @@
1406
1408
  this.revertRequest = null;
1407
1409
 
1408
1410
  const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1409
- const ctx = this.createCtx();
1411
+ const ctx = this.createCtx({ useSnapshot: false });
1410
1412
 
1411
1413
  // 1) raw候補(displayから取得)
1412
1414
  let raw = display.value;
1415
+ ctx.beforeText = "";
1413
1416
  ctx.afterText = raw;
1414
1417
 
1415
1418
  // 2) 正規化(rawとして扱う形に揃える)
@@ -6390,7 +6393,7 @@
6390
6393
  * @param {number} max
6391
6394
  * @returns {string}
6392
6395
  */
6393
- const cutTextByUnit = function(text, unit, max) {
6396
+ const cutTextByUnit$1 = function(text, unit, max) {
6394
6397
  /**
6395
6398
  * グラフェムの配列
6396
6399
  * @type {Grapheme[]}
@@ -6450,13 +6453,13 @@
6450
6453
  * @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
6451
6454
  */
6452
6455
  const cutLength = function(beforeText, insertedText, unit, max) {
6453
- const orgLen = getTextLengthByUnit(beforeText, unit);
6456
+ const beforeTextLen = getTextLengthByUnit(beforeText, unit);
6454
6457
 
6455
6458
  // すでに最大長を超えている場合は追加のテキストを全て切る
6456
- if (orgLen >= max) { return ""; }
6459
+ if (beforeTextLen >= max) { return ""; }
6457
6460
 
6458
- const addLen = getTextLengthByUnit(insertedText, unit);
6459
- const totalLen = orgLen + addLen;
6461
+ const insertedTextLen = getTextLengthByUnit(insertedText, unit);
6462
+ const totalLen = beforeTextLen + insertedTextLen;
6460
6463
 
6461
6464
  if (totalLen <= max) {
6462
6465
  // 今回の追加で範囲内に収まるなら何もしない
@@ -6464,8 +6467,8 @@
6464
6467
  }
6465
6468
 
6466
6469
  // 超える場合は追加のテキストを切る
6467
- const allowedAddLen = max - orgLen;
6468
- return cutTextByUnit(insertedText, unit, allowedAddLen);
6470
+ const allowedAddLen = max - beforeTextLen;
6471
+ return cutTextByUnit$1(insertedText, unit, allowedAddLen);
6469
6472
  };
6470
6473
 
6471
6474
  /**
@@ -6495,13 +6498,7 @@
6495
6498
  return value;
6496
6499
  }
6497
6500
 
6498
- const beforeText = ctx.beforeText;
6499
- const insertedText = ctx.insertedText;
6500
- if (insertedText === "") {
6501
- return value;
6502
- }
6503
-
6504
- const cutText = cutLength(beforeText, insertedText, opt.unit, opt.max);
6501
+ const cutText = cutLength(ctx.beforeText, value, opt.unit, opt.max);
6505
6502
  return cutText;
6506
6503
  },
6507
6504
 
@@ -6576,6 +6573,376 @@
6576
6573
  return length(options);
6577
6574
  };
6578
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
+
6579
6946
  /**
6580
6947
  * The script is part of TextInputGuard.
6581
6948
  *
@@ -6835,6 +7202,8 @@
6835
7202
  { name: "ascii", fromDataset: ascii.fromDataset },
6836
7203
  { name: "filter", fromDataset: filter.fromDataset },
6837
7204
  { name: "length", fromDataset: length.fromDataset },
7205
+ { name: "width", fromDataset: width.fromDataset },
7206
+ { name: "bytes", fromDataset: bytes.fromDataset },
6838
7207
  { name: "prefix", fromDataset: prefix.fromDataset },
6839
7208
  { name: "suffix", fromDataset: suffix.fromDataset },
6840
7209
  { name: "trim", fromDataset: trim.fromDataset }
@@ -6857,6 +7226,8 @@
6857
7226
  ascii,
6858
7227
  filter,
6859
7228
  length,
7229
+ width,
7230
+ bytes,
6860
7231
  prefix,
6861
7232
  suffix,
6862
7233
  trim
@@ -6864,16 +7235,17 @@
6864
7235
 
6865
7236
  /**
6866
7237
  * バージョン(ビルド時に置換したいならここを差し替える)
6867
- * 例: rollup replace で ""0.1.5"" を package.json の version に置換
7238
+ * 例: rollup replace で ""0.1.6"" を package.json の version に置換
6868
7239
  */
6869
7240
  // @ts-ignore
6870
7241
  // eslint-disable-next-line no-undef
6871
- const version = "0.1.5" ;
7242
+ const version = "0.1.6" ;
6872
7243
 
6873
7244
  exports.ascii = ascii;
6874
7245
  exports.attach = attach;
6875
7246
  exports.attachAll = attachAll;
6876
7247
  exports.autoAttach = autoAttach;
7248
+ exports.bytes = bytes;
6877
7249
  exports.comma = comma;
6878
7250
  exports.digits = digits;
6879
7251
  exports.filter = filter;
@@ -6885,5 +7257,6 @@
6885
7257
  exports.suffix = suffix;
6886
7258
  exports.trim = trim;
6887
7259
  exports.version = version;
7260
+ exports.width = width;
6888
7261
 
6889
7262
  }));