text-input-guard 0.2.0 → 0.2.2
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.
- package/README.md +14 -6
- package/dist/cjs/text-input-guard.cjs +194 -29
- package/dist/cjs/text-input-guard.min.cjs +1 -1
- package/dist/esm/text-input-guard.js +194 -30
- package/dist/esm/text-input-guard.min.js +1 -1
- package/dist/types/text-input-guard.d.ts +26 -1
- package/dist/umd/text-input-guard.js +194 -29
- package/dist/umd/text-input-guard.min.js +1 -1
- package/package.json +1 -1
|
@@ -169,6 +169,7 @@
|
|
|
169
169
|
// raw化(送信担当)
|
|
170
170
|
input.type = "hidden";
|
|
171
171
|
input.removeAttribute("id");
|
|
172
|
+
input.removeAttribute("class");
|
|
172
173
|
input.className = "";
|
|
173
174
|
input.dataset.tigRole = "raw";
|
|
174
175
|
|
|
@@ -176,6 +177,9 @@
|
|
|
176
177
|
if (this.originalId) {
|
|
177
178
|
input.dataset.tigOriginalId = this.originalId;
|
|
178
179
|
}
|
|
180
|
+
if (this.originalClass) {
|
|
181
|
+
input.dataset.tigOriginalClass = this.originalClass;
|
|
182
|
+
}
|
|
179
183
|
if (this.originalName) {
|
|
180
184
|
input.dataset.tigOriginalName = this.originalName;
|
|
181
185
|
}
|
|
@@ -196,7 +200,10 @@
|
|
|
196
200
|
display.id = this.originalId;
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
|
|
203
|
+
if (this.originalClass) {
|
|
204
|
+
display.className = this.originalClass;
|
|
205
|
+
}
|
|
206
|
+
|
|
200
207
|
display.value = raw.value;
|
|
201
208
|
|
|
202
209
|
for (const [name, v] of Object.entries(this.originalUiAttrs)) {
|
|
@@ -257,6 +264,7 @@
|
|
|
257
264
|
|
|
258
265
|
delete raw.dataset.tigRole;
|
|
259
266
|
delete raw.dataset.tigOriginalId;
|
|
267
|
+
delete raw.dataset.tigOriginalClass;
|
|
260
268
|
delete raw.dataset.tigOriginalName;
|
|
261
269
|
}
|
|
262
270
|
}
|
|
@@ -402,7 +410,7 @@
|
|
|
402
410
|
* @typedef {Object} AttachOptions
|
|
403
411
|
* @property {Rule[]} [rules] - 適用するルール配列(順番がフェーズ内実行順になる)
|
|
404
412
|
* @property {boolean} [warn] - 非対応ルールなどを console.warn するか
|
|
405
|
-
* @property {string} [invalidClass] - エラー時に付けるclass名
|
|
413
|
+
* @property {string} [invalidClass="is-invalid"] - エラー時に付けるclass名
|
|
406
414
|
* @property {SeparateValueOptions} [separateValue] - 表示値と内部値の分離設定
|
|
407
415
|
* @property {(result: ValidateResult) => void} [onValidate] - 評価完了時の通知(input/commitごと)
|
|
408
416
|
*/
|
|
@@ -450,6 +458,28 @@
|
|
|
450
458
|
}
|
|
451
459
|
}
|
|
452
460
|
|
|
461
|
+
/**
|
|
462
|
+
* input / textarea 要素と内部 Guard インスタンスの対応表
|
|
463
|
+
*
|
|
464
|
+
* - key: displayElement
|
|
465
|
+
* - value: InputGuard(内部実装)
|
|
466
|
+
*
|
|
467
|
+
* @type {WeakMap<HTMLInputElement|HTMLTextAreaElement, InputGuard>}
|
|
468
|
+
*/
|
|
469
|
+
const guardMap = new WeakMap();
|
|
470
|
+
|
|
471
|
+
document.addEventListener("selectionchange", () => {
|
|
472
|
+
const el = document.activeElement;
|
|
473
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const inputGuard = guardMap.get(el);
|
|
477
|
+
if (!inputGuard) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
inputGuard.onSelectionChange();
|
|
481
|
+
});
|
|
482
|
+
|
|
453
483
|
/**
|
|
454
484
|
* 指定した1要素に対してガードを適用し、Guard API を返す
|
|
455
485
|
* @param {HTMLInputElement|HTMLTextAreaElement} element
|
|
@@ -457,9 +487,12 @@
|
|
|
457
487
|
* @returns {Guard}
|
|
458
488
|
*/
|
|
459
489
|
function attach(element, options = {}) {
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
490
|
+
const inputGuard = new InputGuard(element, options);
|
|
491
|
+
inputGuard.init();
|
|
492
|
+
const guard = inputGuard.getGuard();
|
|
493
|
+
const display = guard.getDisplayElement();
|
|
494
|
+
guardMap.set(display, inputGuard);
|
|
495
|
+
return guard;
|
|
463
496
|
}
|
|
464
497
|
|
|
465
498
|
/**
|
|
@@ -555,7 +588,7 @@
|
|
|
555
588
|
/**
|
|
556
589
|
* ユーザーが直接入力する表示側要素
|
|
557
590
|
* swapしない場合は originalElement と同一
|
|
558
|
-
* @type {
|
|
591
|
+
* @type {HTMLInputElement|HTMLTextAreaElement}
|
|
559
592
|
*/
|
|
560
593
|
this.displayElement = element;
|
|
561
594
|
|
|
@@ -677,6 +710,12 @@
|
|
|
677
710
|
*/
|
|
678
711
|
this.pendingCompositionCommit = false;
|
|
679
712
|
|
|
713
|
+
/**
|
|
714
|
+
* selection 更新のフレーム予約ID
|
|
715
|
+
* @type {number|null}
|
|
716
|
+
*/
|
|
717
|
+
this.selectionFrameId = null;
|
|
718
|
+
|
|
680
719
|
/**
|
|
681
720
|
* 直前に受理した表示値、正しい情報のスナップショットのような情報(block時の戻し先)
|
|
682
721
|
* @type {string}
|
|
@@ -722,9 +761,11 @@
|
|
|
722
761
|
* @returns {SelectionState}
|
|
723
762
|
*/
|
|
724
763
|
readSelection(el) {
|
|
764
|
+
const start = el.selectionStart ?? 0;
|
|
765
|
+
const end = el.selectionEnd ?? start;
|
|
725
766
|
return {
|
|
726
|
-
start
|
|
727
|
-
end
|
|
767
|
+
start,
|
|
768
|
+
end,
|
|
728
769
|
direction: el.selectionDirection
|
|
729
770
|
};
|
|
730
771
|
}
|
|
@@ -838,6 +879,8 @@
|
|
|
838
879
|
* @returns {void}
|
|
839
880
|
*/
|
|
840
881
|
detach() {
|
|
882
|
+
// 管理マップから削除
|
|
883
|
+
guardMap.delete(this.displayElement);
|
|
841
884
|
// イベント解除(displayElementがswap後の可能性があるので先に外す)
|
|
842
885
|
this.unbindEvents();
|
|
843
886
|
// swap復元
|
|
@@ -898,15 +941,7 @@
|
|
|
898
941
|
this.displayElement.addEventListener("input", this.onInput);
|
|
899
942
|
this.displayElement.addEventListener("beforeinput", this.onBeforeInput);
|
|
900
943
|
this.displayElement.addEventListener("blur", this.onBlur);
|
|
901
|
-
|
|
902
|
-
// フォーカスで編集用に戻す
|
|
903
944
|
this.displayElement.addEventListener("focus", this.onFocus);
|
|
904
|
-
|
|
905
|
-
// キャレット/選択範囲の変化を拾う(block時の不自然ジャンプ対策)
|
|
906
|
-
this.displayElement.addEventListener("keyup", this.onSelectionChange);
|
|
907
|
-
this.displayElement.addEventListener("mouseup", this.onSelectionChange);
|
|
908
|
-
this.displayElement.addEventListener("select", this.onSelectionChange);
|
|
909
|
-
this.displayElement.addEventListener("focus", this.onSelectionChange);
|
|
910
945
|
}
|
|
911
946
|
|
|
912
947
|
/**
|
|
@@ -920,10 +955,6 @@
|
|
|
920
955
|
this.displayElement.removeEventListener("beforeinput", this.onBeforeInput);
|
|
921
956
|
this.displayElement.removeEventListener("blur", this.onBlur);
|
|
922
957
|
this.displayElement.removeEventListener("focus", this.onFocus);
|
|
923
|
-
this.displayElement.removeEventListener("keyup", this.onSelectionChange);
|
|
924
|
-
this.displayElement.removeEventListener("mouseup", this.onSelectionChange);
|
|
925
|
-
this.displayElement.removeEventListener("select", this.onSelectionChange);
|
|
926
|
-
this.displayElement.removeEventListener("focus", this.onSelectionChange);
|
|
927
958
|
}
|
|
928
959
|
|
|
929
960
|
/**
|
|
@@ -1291,12 +1322,41 @@
|
|
|
1291
1322
|
* @returns {void}
|
|
1292
1323
|
*/
|
|
1293
1324
|
onSelectionChange() {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1325
|
+
const requestFrame =
|
|
1326
|
+
typeof requestAnimationFrame === "function"
|
|
1327
|
+
? requestAnimationFrame
|
|
1328
|
+
: (
|
|
1329
|
+
/** @param {FrameRequestCallback} cb */
|
|
1330
|
+
(cb) => setTimeout(cb, 0)
|
|
1331
|
+
);
|
|
1332
|
+
|
|
1333
|
+
const cancelFrame =
|
|
1334
|
+
typeof cancelAnimationFrame === "function"
|
|
1335
|
+
? cancelAnimationFrame
|
|
1336
|
+
: clearTimeout;
|
|
1337
|
+
|
|
1338
|
+
// すでに予約済みならキャンセル(selectionchange は連続発火するため)
|
|
1339
|
+
if (this.selectionFrameId != null) {
|
|
1340
|
+
cancelFrame(this.selectionFrameId);
|
|
1297
1341
|
}
|
|
1298
|
-
|
|
1299
|
-
this.
|
|
1342
|
+
|
|
1343
|
+
this.selectionFrameId = requestFrame(() => {
|
|
1344
|
+
this.selectionFrameId = null;
|
|
1345
|
+
|
|
1346
|
+
// IME変換中は無視(キャレット位置が不安定になるため)
|
|
1347
|
+
if (this.composing) {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
|
|
1352
|
+
|
|
1353
|
+
// 要素がフォーカスされていない場合は無視
|
|
1354
|
+
if (document.activeElement !== el) {
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
this.lastAcceptedSelection = this.readSelection(el);
|
|
1359
|
+
});
|
|
1300
1360
|
}
|
|
1301
1361
|
|
|
1302
1362
|
/**
|
|
@@ -2705,6 +2765,104 @@
|
|
|
2705
2765
|
return comma();
|
|
2706
2766
|
};
|
|
2707
2767
|
|
|
2768
|
+
/**
|
|
2769
|
+
* The script is part of TextInputGuard.
|
|
2770
|
+
*
|
|
2771
|
+
* AUTHOR:
|
|
2772
|
+
* natade-jp (https://github.com/natade-jp)
|
|
2773
|
+
*
|
|
2774
|
+
* LICENSE:
|
|
2775
|
+
* The MIT license https://opensource.org/licenses/MIT
|
|
2776
|
+
*/
|
|
2777
|
+
|
|
2778
|
+
/**
|
|
2779
|
+
* IMEオフ入力相当の文字変換テーブル
|
|
2780
|
+
* @type {Record<string, string>}
|
|
2781
|
+
*/
|
|
2782
|
+
/* eslint-disable quote-props */
|
|
2783
|
+
const IME_OFF_MAP = {
|
|
2784
|
+
"\u3000": "\u0020", // 全角スペース → space
|
|
2785
|
+
"\u3001": "\u002C", // 、 → ,
|
|
2786
|
+
"\u3002": "\u002E", // 。 → .
|
|
2787
|
+
"\u300C": "\u005B", // 「 → [
|
|
2788
|
+
"\u300D": "\u005D", // 」 → ]
|
|
2789
|
+
"\u301C": "\u007E", // 〜 → ~
|
|
2790
|
+
"\u30FC": "\u002D", // ー → -
|
|
2791
|
+
"\uFFE5": "\u005C" // ¥ → \
|
|
2792
|
+
};
|
|
2793
|
+
/* eslint-enable quote-props */
|
|
2794
|
+
|
|
2795
|
+
/**
|
|
2796
|
+
* ASCII入力欄に日本語IMEで入った文字をASCIIへ矯正する
|
|
2797
|
+
* @param {string} text - 変換したいテキスト
|
|
2798
|
+
* @returns {string} 変換後のテキスト
|
|
2799
|
+
*/
|
|
2800
|
+
const toImeOff = function (text) {
|
|
2801
|
+
return Array.from(String(text), (ch) => {
|
|
2802
|
+
// 個別マップ
|
|
2803
|
+
if (ch in IME_OFF_MAP) {
|
|
2804
|
+
return IME_OFF_MAP[ch];
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
const code = ch.charCodeAt(0);
|
|
2808
|
+
|
|
2809
|
+
// 全角ASCII
|
|
2810
|
+
if (code >= 0xFF01 && code <= 0xFF5E) {
|
|
2811
|
+
return String.fromCharCode(code - 0xFEE0);
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
// シングルクォート系
|
|
2815
|
+
if (code >= 0x2018 && code <= 0x201B) {
|
|
2816
|
+
return "'";
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// ダブルクォート系
|
|
2820
|
+
if (code >= 0x201C && code <= 0x201F) {
|
|
2821
|
+
return '"';
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
return ch;
|
|
2825
|
+
}).join("");
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2828
|
+
/**
|
|
2829
|
+
* ASCII入力欄に日本語IMEで入った文字をASCIIへ矯正する
|
|
2830
|
+
*
|
|
2831
|
+
* 注意:
|
|
2832
|
+
* - これは「半角化」ではなく「IMEオフ入力相当への寄せ」
|
|
2833
|
+
* - ascii() とは責務が異なる
|
|
2834
|
+
*
|
|
2835
|
+
* @returns {Rule}
|
|
2836
|
+
*/
|
|
2837
|
+
function imeOff() {
|
|
2838
|
+
return {
|
|
2839
|
+
name: "imeOff",
|
|
2840
|
+
targets: ["input", "textarea"],
|
|
2841
|
+
|
|
2842
|
+
normalizeChar(value, ctx) {
|
|
2843
|
+
return toImeOff(value);
|
|
2844
|
+
}
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
/**
|
|
2849
|
+
* dataset から imeOff ルールを生成する
|
|
2850
|
+
*
|
|
2851
|
+
* 対応する data 属性
|
|
2852
|
+
* - data-tig-rules-ime-off
|
|
2853
|
+
*
|
|
2854
|
+
* @param {DOMStringMap} dataset
|
|
2855
|
+
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
2856
|
+
* @returns {Rule|null}
|
|
2857
|
+
*/
|
|
2858
|
+
imeOff.fromDataset = function fromDataset(dataset, _el) {
|
|
2859
|
+
if (dataset.tigRulesImeOff == null) {
|
|
2860
|
+
return null;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
return imeOff();
|
|
2864
|
+
};
|
|
2865
|
+
|
|
2708
2866
|
/**
|
|
2709
2867
|
* The script is part of Mojix for TextInputGuard.
|
|
2710
2868
|
*
|
|
@@ -5926,7 +6084,7 @@
|
|
|
5926
6084
|
function ascii(options = {}) {
|
|
5927
6085
|
/** @type {AsciiRuleOptions} */
|
|
5928
6086
|
const opt = {
|
|
5929
|
-
case: options.case ??
|
|
6087
|
+
case: options.case ?? "none"
|
|
5930
6088
|
};
|
|
5931
6089
|
|
|
5932
6090
|
return {
|
|
@@ -5939,6 +6097,10 @@
|
|
|
5939
6097
|
// まず半角へ正規化
|
|
5940
6098
|
s = Mojix.toHalfWidthAsciiCode(s);
|
|
5941
6099
|
|
|
6100
|
+
// toHalfWidthAsciiCode で対応できていない文字も実施
|
|
6101
|
+
s = s.replace(/\uFFE5/g, "\u005C"); //¥
|
|
6102
|
+
s = s.replace(/[\u2010-\u2015\u2212\u30FC\uFF0D\uFF70]/g, "\u002D"); //ハイフンに似ている記号
|
|
6103
|
+
|
|
5942
6104
|
// 英字の大文字/小文字統一
|
|
5943
6105
|
if (opt.case === "upper") {
|
|
5944
6106
|
s = s.toUpperCase();
|
|
@@ -6933,7 +7095,7 @@
|
|
|
6933
7095
|
code: "bytes.max_overflow",
|
|
6934
7096
|
rule: "bytes",
|
|
6935
7097
|
phase: "validate",
|
|
6936
|
-
detail: {
|
|
7098
|
+
detail: { limit: opt.max, actual: len }
|
|
6937
7099
|
});
|
|
6938
7100
|
}
|
|
6939
7101
|
}
|
|
@@ -7240,6 +7402,7 @@
|
|
|
7240
7402
|
{ name: "numeric", fromDataset: numeric.fromDataset },
|
|
7241
7403
|
{ name: "digits", fromDataset: digits.fromDataset },
|
|
7242
7404
|
{ name: "comma", fromDataset: comma.fromDataset },
|
|
7405
|
+
{ name: "imeOff", fromDataset: imeOff.fromDataset },
|
|
7243
7406
|
{ name: "kana", fromDataset: kana.fromDataset },
|
|
7244
7407
|
{ name: "ascii", fromDataset: ascii.fromDataset },
|
|
7245
7408
|
{ name: "filter", fromDataset: filter.fromDataset },
|
|
@@ -7264,6 +7427,7 @@
|
|
|
7264
7427
|
numeric,
|
|
7265
7428
|
digits,
|
|
7266
7429
|
comma,
|
|
7430
|
+
imeOff,
|
|
7267
7431
|
kana,
|
|
7268
7432
|
ascii,
|
|
7269
7433
|
filter,
|
|
@@ -7277,11 +7441,11 @@
|
|
|
7277
7441
|
|
|
7278
7442
|
/**
|
|
7279
7443
|
* バージョン(ビルド時に置換したいならここを差し替える)
|
|
7280
|
-
* 例: rollup replace で ""0.2.
|
|
7444
|
+
* 例: rollup replace で ""0.2.2"" を package.json の version に置換
|
|
7281
7445
|
*/
|
|
7282
7446
|
// @ts-ignore
|
|
7283
7447
|
// eslint-disable-next-line no-undef
|
|
7284
|
-
const version = "0.2.
|
|
7448
|
+
const version = "0.2.2" ;
|
|
7285
7449
|
|
|
7286
7450
|
exports.ascii = ascii;
|
|
7287
7451
|
exports.attach = attach;
|
|
@@ -7291,6 +7455,7 @@
|
|
|
7291
7455
|
exports.comma = comma;
|
|
7292
7456
|
exports.digits = digits;
|
|
7293
7457
|
exports.filter = filter;
|
|
7458
|
+
exports.imeOff = imeOff;
|
|
7294
7459
|
exports.kana = kana;
|
|
7295
7460
|
exports.length = length;
|
|
7296
7461
|
exports.numeric = numeric;
|