text-input-guard 1.2.1 → 1.3.0
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/dist/cjs/text-input-guard.cjs +146 -43
- package/dist/cjs/text-input-guard.min.cjs +1 -1
- package/dist/esm/text-input-guard.js +146 -43
- package/dist/esm/text-input-guard.min.js +1 -1
- package/dist/types/text-input-guard.d.ts +1 -1
- package/dist/umd/text-input-guard.js +146 -43
- package/dist/umd/text-input-guard.min.js +1 -1
- package/package.json +1 -1
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* SwapState
|
|
19
19
|
*
|
|
20
20
|
* separateValue.mode="swap" のときに使用する
|
|
21
|
-
* 元 input 要素の状態スナップショットおよび復元ロジックを管理するクラス
|
|
21
|
+
* 元 input/textarea 要素の状態スナップショットおよび復元ロジックを管理するクラス
|
|
22
22
|
*
|
|
23
23
|
* 役割
|
|
24
|
-
* - swap前の input の属性状態を保持する
|
|
24
|
+
* - swap前の input/textarea の属性状態を保持する
|
|
25
25
|
* - raw化および display生成時に必要な属性を適用する
|
|
26
26
|
* - detach時に元の状態へ復元する
|
|
27
27
|
*
|
|
@@ -97,16 +97,16 @@
|
|
|
97
97
|
* swap時に生成された display 用 input 要素
|
|
98
98
|
* detach時に削除するため保持する
|
|
99
99
|
*
|
|
100
|
-
* @type {HTMLInputElement|null}
|
|
100
|
+
* @type {HTMLInputElement|HTMLTextAreaElement|null}
|
|
101
101
|
*/
|
|
102
102
|
createdDisplay;
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* @param {HTMLInputElement} input
|
|
105
|
+
* @param {HTMLInputElement|HTMLTextAreaElement} input
|
|
106
106
|
* swap前の元 input 要素
|
|
107
107
|
*/
|
|
108
108
|
constructor(input) {
|
|
109
|
-
this.originalType = input.type;
|
|
109
|
+
this.originalType = input.type || "";
|
|
110
110
|
this.originalId = input.getAttribute("id");
|
|
111
111
|
this.originalName = input.getAttribute("name");
|
|
112
112
|
this.originalClass = input.className;
|
|
@@ -117,22 +117,30 @@
|
|
|
117
117
|
this.createdDisplay = null;
|
|
118
118
|
|
|
119
119
|
const UI_ATTRS = [
|
|
120
|
-
|
|
120
|
+
// input 有
|
|
121
121
|
"list",
|
|
122
|
+
"size",
|
|
123
|
+
"pattern",
|
|
124
|
+
|
|
125
|
+
// input / textarea 共有
|
|
126
|
+
"placeholder",
|
|
122
127
|
"inputmode",
|
|
123
128
|
"autocomplete",
|
|
124
129
|
"autocapitalize",
|
|
125
130
|
"autocorrect",
|
|
126
131
|
"minlength",
|
|
127
132
|
"maxlength",
|
|
128
|
-
"size",
|
|
129
|
-
"pattern",
|
|
130
133
|
"dir",
|
|
131
134
|
"title",
|
|
132
135
|
"tabindex",
|
|
133
136
|
"style",
|
|
134
137
|
"enterkeyhint",
|
|
135
|
-
"spellcheck"
|
|
138
|
+
"spellcheck",
|
|
139
|
+
|
|
140
|
+
// textarea 用
|
|
141
|
+
"rows",
|
|
142
|
+
"cols",
|
|
143
|
+
"wrap"
|
|
136
144
|
];
|
|
137
145
|
|
|
138
146
|
const UI_BOOL_ATTRS = [
|
|
@@ -168,12 +176,17 @@
|
|
|
168
176
|
* raw 元input を hidden 化する
|
|
169
177
|
* 送信担当要素として扱う
|
|
170
178
|
*
|
|
171
|
-
* @param {HTMLInputElement} input
|
|
179
|
+
* @param {HTMLInputElement|HTMLTextAreaElement} input
|
|
172
180
|
* @returns {void}
|
|
173
181
|
*/
|
|
174
182
|
applyToRaw(input) {
|
|
175
183
|
// raw化(送信担当)
|
|
176
|
-
input
|
|
184
|
+
if (input instanceof HTMLInputElement) {
|
|
185
|
+
input.type = "hidden";
|
|
186
|
+
} else if (input instanceof HTMLTextAreaElement) {
|
|
187
|
+
input.setAttribute("hidden", "");
|
|
188
|
+
input.style.display = "none";
|
|
189
|
+
}
|
|
177
190
|
input.removeAttribute("id");
|
|
178
191
|
input.removeAttribute("class");
|
|
179
192
|
input.className = "";
|
|
@@ -194,12 +207,22 @@
|
|
|
194
207
|
/**
|
|
195
208
|
* display用 input を生成し UI属性 aria属性 data属性を適用
|
|
196
209
|
*
|
|
197
|
-
* @param {HTMLInputElement} raw hidden化された元input
|
|
198
|
-
* @returns {HTMLInputElement}
|
|
210
|
+
* @param {HTMLInputElement|HTMLTextAreaElement} raw hidden化された元input
|
|
211
|
+
* @returns {HTMLInputElement|HTMLTextAreaElement}
|
|
199
212
|
*/
|
|
200
213
|
createDisplay(raw) {
|
|
201
|
-
|
|
202
|
-
|
|
214
|
+
/**
|
|
215
|
+
* @type {HTMLInputElement|HTMLTextAreaElement}
|
|
216
|
+
*/
|
|
217
|
+
let display;
|
|
218
|
+
if (raw instanceof HTMLInputElement) {
|
|
219
|
+
display = document.createElement("input");
|
|
220
|
+
display.type = "text";
|
|
221
|
+
} else if (raw instanceof HTMLTextAreaElement) {
|
|
222
|
+
display = document.createElement("textarea");
|
|
223
|
+
} else {
|
|
224
|
+
throw new Error("Unsupported element type for display creation");
|
|
225
|
+
}
|
|
203
226
|
display.dataset.tigRole = "display";
|
|
204
227
|
|
|
205
228
|
if (this.originalId) {
|
|
@@ -248,11 +271,16 @@
|
|
|
248
271
|
/**
|
|
249
272
|
* raw hidden化された元input を元の状態へ復元する
|
|
250
273
|
*
|
|
251
|
-
* @param {HTMLInputElement} raw
|
|
274
|
+
* @param {HTMLInputElement|HTMLTextAreaElement} raw
|
|
252
275
|
* @returns {void}
|
|
253
276
|
*/
|
|
254
277
|
restoreRaw(raw) {
|
|
255
|
-
raw
|
|
278
|
+
if (raw instanceof HTMLInputElement) {
|
|
279
|
+
raw.type = this.originalType;
|
|
280
|
+
} else if (raw instanceof HTMLTextAreaElement) {
|
|
281
|
+
raw.removeAttribute("hidden");
|
|
282
|
+
raw.style.display = "";
|
|
283
|
+
}
|
|
256
284
|
|
|
257
285
|
if (this.originalId) {
|
|
258
286
|
raw.setAttribute("id", this.originalId);
|
|
@@ -276,7 +304,7 @@
|
|
|
276
304
|
}
|
|
277
305
|
|
|
278
306
|
/**
|
|
279
|
-
* The script is part of
|
|
307
|
+
* The script is part of TextInputGuard.
|
|
280
308
|
*
|
|
281
309
|
* AUTHOR:
|
|
282
310
|
* natade-jp (https://github.com/natade-jp)
|
|
@@ -365,7 +393,7 @@
|
|
|
365
393
|
* @typedef {Object} GuardContext
|
|
366
394
|
* @property {HTMLElement} hostElement - 元の要素(swap時はraw側)
|
|
367
395
|
* @property {HTMLElement} displayElement - ユーザーが操作する表示要素
|
|
368
|
-
* @property {HTMLInputElement|null} rawElement - 送信用hidden要素(swap時のみ)
|
|
396
|
+
* @property {HTMLInputElement|HTMLTextAreaElement|null} rawElement - 送信用hidden要素(swap時のみ)
|
|
369
397
|
* @property {ElementKind} kind - 要素種別(input / textarea)
|
|
370
398
|
* @property {boolean} warn - warnログを出すかどうか
|
|
371
399
|
* @property {string} invalidClass - エラー時に付与するclass名
|
|
@@ -626,6 +654,8 @@
|
|
|
626
654
|
}
|
|
627
655
|
}
|
|
628
656
|
|
|
657
|
+
let globalGuardId = 0; // デバッグ用のガードID生成
|
|
658
|
+
|
|
629
659
|
class InputGuard {
|
|
630
660
|
/**
|
|
631
661
|
* InputGuard の内部状態を初期化する(DOM/設定/イベント/パイプラインを持つ)
|
|
@@ -633,6 +663,12 @@
|
|
|
633
663
|
* @param {AttachOptions} options
|
|
634
664
|
*/
|
|
635
665
|
constructor(element, options) {
|
|
666
|
+
/**
|
|
667
|
+
* ガードID(デバッグ用、インスタンスごとにユニーク)
|
|
668
|
+
* @type {number}
|
|
669
|
+
*/
|
|
670
|
+
this.id = ++globalGuardId;
|
|
671
|
+
|
|
636
672
|
/**
|
|
637
673
|
* attach対象の元の要素(swap前の原本)
|
|
638
674
|
* detach時の復元や基準参照に使う
|
|
@@ -714,7 +750,7 @@
|
|
|
714
750
|
/**
|
|
715
751
|
* 実際に送信を担う要素(swap時は hidden(raw) 側)
|
|
716
752
|
* swapしない場合は originalElement と同一
|
|
717
|
-
* @type {
|
|
753
|
+
* @type {HTMLInputElement|HTMLTextAreaElement}
|
|
718
754
|
*/
|
|
719
755
|
this.hostElement = element;
|
|
720
756
|
|
|
@@ -728,7 +764,7 @@
|
|
|
728
764
|
/**
|
|
729
765
|
* swap時に生成される hidden(raw) input
|
|
730
766
|
* swapしない場合は null
|
|
731
|
-
* @type {HTMLInputElement|null}
|
|
767
|
+
* @type {HTMLInputElement|HTMLTextAreaElement|null}
|
|
732
768
|
*/
|
|
733
769
|
this.rawElement = null;
|
|
734
770
|
|
|
@@ -825,6 +861,11 @@
|
|
|
825
861
|
*/
|
|
826
862
|
this.onFocus = this.onFocus.bind(this);
|
|
827
863
|
|
|
864
|
+
/**
|
|
865
|
+
* keydownイベントハンドラ(this固定)
|
|
866
|
+
*/
|
|
867
|
+
this.onKeyDown = this.onKeyDown.bind(this);
|
|
868
|
+
|
|
828
869
|
/**
|
|
829
870
|
* キャレット/選択範囲の変化イベントハンドラ(this固定)
|
|
830
871
|
*/
|
|
@@ -880,6 +921,14 @@
|
|
|
880
921
|
this.revertRequest = null;
|
|
881
922
|
}
|
|
882
923
|
|
|
924
|
+
/**
|
|
925
|
+
* デバッグ用の文字列化
|
|
926
|
+
* @returns {string}
|
|
927
|
+
*/
|
|
928
|
+
toString() {
|
|
929
|
+
return `[TextInputGuard#${this.id} kind=${this.kind} host=${this.hostElement.tagName.toLowerCase()}#${this.hostElement.id}] value=${this.hostElement.value}]`;
|
|
930
|
+
}
|
|
931
|
+
|
|
883
932
|
/**
|
|
884
933
|
* 初期化処理(swap適用 → パイプライン構築 → イベント登録 → 初回評価)
|
|
885
934
|
* @returns {void}
|
|
@@ -934,7 +983,7 @@
|
|
|
934
983
|
|
|
935
984
|
/**
|
|
936
985
|
* separateValue.mode="swap" のとき、input を hidden(raw) にして display(input[type=text]) を生成する
|
|
937
|
-
* - textarea
|
|
986
|
+
* - textarea も対応(hidden属性とdisplay:noneを使用)
|
|
938
987
|
* @returns {void}
|
|
939
988
|
*/
|
|
940
989
|
applySeparateValue() {
|
|
@@ -950,25 +999,20 @@
|
|
|
950
999
|
return;
|
|
951
1000
|
}
|
|
952
1001
|
|
|
953
|
-
|
|
954
|
-
warnLog('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.', this.warn);
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
1002
|
+
const element = this.originalElement;
|
|
957
1003
|
|
|
958
|
-
const
|
|
1004
|
+
const state = new SwapState(element);
|
|
1005
|
+
state.applyToRaw(element);
|
|
959
1006
|
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
const display = state.createDisplay(input);
|
|
964
|
-
input.after(display);
|
|
1007
|
+
const display = state.createDisplay(element);
|
|
1008
|
+
element.after(display);
|
|
965
1009
|
|
|
966
1010
|
this.swapState = state;
|
|
967
1011
|
|
|
968
1012
|
// elements更新
|
|
969
|
-
this.hostElement =
|
|
1013
|
+
this.hostElement = element; // raw
|
|
970
1014
|
this.displayElement = display; // display
|
|
971
|
-
this.rawElement =
|
|
1015
|
+
this.rawElement = element;
|
|
972
1016
|
|
|
973
1017
|
// revert 機構
|
|
974
1018
|
this.lastAcceptedValue = display.value;
|
|
@@ -1084,6 +1128,7 @@
|
|
|
1084
1128
|
this.displayElement.addEventListener("beforeinput", this.onBeforeInput);
|
|
1085
1129
|
this.displayElement.addEventListener("blur", this.onBlur);
|
|
1086
1130
|
this.displayElement.addEventListener("focus", this.onFocus);
|
|
1131
|
+
this.displayElement.addEventListener("keydown", this.onKeyDown);
|
|
1087
1132
|
}
|
|
1088
1133
|
|
|
1089
1134
|
/**
|
|
@@ -1097,6 +1142,7 @@
|
|
|
1097
1142
|
this.displayElement.removeEventListener("beforeinput", this.onBeforeInput);
|
|
1098
1143
|
this.displayElement.removeEventListener("blur", this.onBlur);
|
|
1099
1144
|
this.displayElement.removeEventListener("focus", this.onFocus);
|
|
1145
|
+
this.displayElement.removeEventListener("keydown", this.onKeyDown);
|
|
1100
1146
|
}
|
|
1101
1147
|
|
|
1102
1148
|
/**
|
|
@@ -1514,6 +1560,19 @@
|
|
|
1514
1560
|
}
|
|
1515
1561
|
this.existBeforeInputEvent = true;
|
|
1516
1562
|
this.beforeInputSnapshot = { selection, inputType, insertedText };
|
|
1563
|
+
|
|
1564
|
+
// アンドゥリドゥの beforeinput はフォーカスされている要素以外にも発生することがあるため、
|
|
1565
|
+
// 正しく判定するために onKeyDown で捕まえて、必要なときだけ onBeforeInput のスナップを作る
|
|
1566
|
+
if (inputType === "historyUndo" || inputType === "historyRedo") {
|
|
1567
|
+
e.preventDefault();
|
|
1568
|
+
|
|
1569
|
+
// フォーカス中ではない要素に飛んできたUndo/Redoは止めるだけ
|
|
1570
|
+
if (document.activeElement !== this.displayElement) {
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
this.evaluateInput();
|
|
1575
|
+
}
|
|
1517
1576
|
}
|
|
1518
1577
|
|
|
1519
1578
|
/**
|
|
@@ -1570,6 +1629,51 @@
|
|
|
1570
1629
|
this.history.push(raw);
|
|
1571
1630
|
}
|
|
1572
1631
|
|
|
1632
|
+
/**
|
|
1633
|
+
* keydownイベント:特殊な用途向けに提供(例:Enterで確定させたいなど)
|
|
1634
|
+
* @param {Event} e
|
|
1635
|
+
* @returns {void}
|
|
1636
|
+
*/
|
|
1637
|
+
onKeyDown(e) {
|
|
1638
|
+
if (!(e instanceof KeyboardEvent)) {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// アンドゥ及びリドゥの onBeforeInput はフォーカスされている要素以外にも発生することがあるため
|
|
1643
|
+
// 正しく判定するために onKeyDown で捕まえて、必要なときだけ onBeforeInput のスナップを作る
|
|
1644
|
+
|
|
1645
|
+
const isUndo = (e.ctrlKey || e.metaKey) && !e.shiftKey && e.key.toLowerCase() === "z";
|
|
1646
|
+
const isRedo =
|
|
1647
|
+
((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "z") ||
|
|
1648
|
+
((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "y");
|
|
1649
|
+
|
|
1650
|
+
if (!isUndo && !isRedo) {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// ここでチェックする
|
|
1655
|
+
if (document.activeElement !== this.displayElement) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// ブラウザのデフォルトの undo/redo をキャンセルして、自前でUndo/Redo を発生させる
|
|
1660
|
+
e.preventDefault();
|
|
1661
|
+
|
|
1662
|
+
// 擬似beforeinputのスナップを作る
|
|
1663
|
+
this.beforeInputSnapshot = {
|
|
1664
|
+
selection: this.readSelection(this.displayElement),
|
|
1665
|
+
inputType: isUndo ? "historyUndo" : "historyRedo",
|
|
1666
|
+
insertedText: ""
|
|
1667
|
+
};
|
|
1668
|
+
|
|
1669
|
+
this.existBeforeInputEvent = true;
|
|
1670
|
+
try {
|
|
1671
|
+
this.evaluateInput();
|
|
1672
|
+
} finally {
|
|
1673
|
+
this.existBeforeInputEvent = false;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1573
1677
|
/**
|
|
1574
1678
|
* キャレット/選択範囲の変化を lastAcceptedSelection に反映する
|
|
1575
1679
|
* - 値が変わっていない状態でもキャレットは動くため、block時に自然な位置へ戻すために使う
|
|
@@ -1913,7 +2017,7 @@
|
|
|
1913
2017
|
|
|
1914
2018
|
/**
|
|
1915
2019
|
* 外部に公開する Guard API を生成して返す
|
|
1916
|
-
* -
|
|
2020
|
+
* - TextInputGuard 自体を公開せず、最小の操作だけを渡す
|
|
1917
2021
|
* @returns {Guard}
|
|
1918
2022
|
*/
|
|
1919
2023
|
getGuard() {
|
|
@@ -2352,7 +2456,7 @@
|
|
|
2352
2456
|
|
|
2353
2457
|
return {
|
|
2354
2458
|
name: "numeric",
|
|
2355
|
-
targets: ["input"],
|
|
2459
|
+
targets: ["input", "textarea"],
|
|
2356
2460
|
|
|
2357
2461
|
/**
|
|
2358
2462
|
* 文字単位の正規化(全角→半角、記号統一、不要文字の除去)
|
|
@@ -2701,7 +2805,6 @@
|
|
|
2701
2805
|
* @returns {Rule}
|
|
2702
2806
|
*/
|
|
2703
2807
|
function digits(options = {}) {
|
|
2704
|
-
/** @type {DigitsRuleOptions} */
|
|
2705
2808
|
const opt = {
|
|
2706
2809
|
int: typeof options.int === "number" ? options.int : undefined,
|
|
2707
2810
|
frac: typeof options.frac === "number" ? options.frac : undefined,
|
|
@@ -2715,7 +2818,7 @@
|
|
|
2715
2818
|
|
|
2716
2819
|
return {
|
|
2717
2820
|
name: "digits",
|
|
2718
|
-
targets: ["input"],
|
|
2821
|
+
targets: ["input", "textarea"],
|
|
2719
2822
|
|
|
2720
2823
|
/**
|
|
2721
2824
|
* 桁数チェック(入力中:エラーを積むだけ)
|
|
@@ -2968,7 +3071,7 @@
|
|
|
2968
3071
|
function comma() {
|
|
2969
3072
|
return {
|
|
2970
3073
|
name: "comma",
|
|
2971
|
-
targets: ["input"],
|
|
3074
|
+
targets: ["input", "textarea"],
|
|
2972
3075
|
|
|
2973
3076
|
/**
|
|
2974
3077
|
* 表示整形(確定時のみ)
|
|
@@ -7447,7 +7550,7 @@
|
|
|
7447
7550
|
|
|
7448
7551
|
return {
|
|
7449
7552
|
name: "prefix",
|
|
7450
|
-
targets: ["input"],
|
|
7553
|
+
targets: ["input", "textarea"],
|
|
7451
7554
|
|
|
7452
7555
|
/**
|
|
7453
7556
|
* 手動入力された prefix を除去
|
|
@@ -7541,7 +7644,7 @@
|
|
|
7541
7644
|
|
|
7542
7645
|
return {
|
|
7543
7646
|
name: "suffix",
|
|
7544
|
-
targets: ["input"],
|
|
7647
|
+
targets: ["input", "textarea"],
|
|
7545
7648
|
|
|
7546
7649
|
/**
|
|
7547
7650
|
* 手動入力された suffix を除去
|
|
@@ -7707,11 +7810,11 @@
|
|
|
7707
7810
|
|
|
7708
7811
|
/**
|
|
7709
7812
|
* バージョン(ビルド時に置換したいならここを差し替える)
|
|
7710
|
-
* 例: rollup replace で ""1.
|
|
7813
|
+
* 例: rollup replace で ""1.3.0"" を package.json の version に置換
|
|
7711
7814
|
*/
|
|
7712
7815
|
// @ts-ignore
|
|
7713
7816
|
// eslint-disable-next-line no-undef
|
|
7714
|
-
const version = "1.
|
|
7817
|
+
const version = "1.3.0" ;
|
|
7715
7818
|
|
|
7716
7819
|
/**
|
|
7717
7820
|
* UMD公開時のグローバルオブジェクト
|