text-input-guard 0.0.1 → 0.1.1
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 +3 -129
- package/dist/cjs/text-input-guard.cjs +398 -182
- package/dist/cjs/text-input-guard.min.cjs +6 -0
- package/dist/esm/text-input-guard.js +398 -182
- package/dist/esm/text-input-guard.min.js +6 -0
- package/dist/types/text-input-guard.d.ts +10 -28
- package/dist/umd/text-input-guard.js +398 -182
- package/dist/umd/text-input-guard.min.js +1 -1
- package/package.json +17 -7
|
@@ -4,6 +4,249 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.TextInputGuard = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The script is part of TextInputGuard.
|
|
9
|
+
*
|
|
10
|
+
* AUTHOR:
|
|
11
|
+
* natade-jp (https://github.com/natade-jp)
|
|
12
|
+
*
|
|
13
|
+
* LICENSE:
|
|
14
|
+
* The MIT license https://opensource.org/licenses/MIT
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* SwapState
|
|
19
|
+
*
|
|
20
|
+
* separateValue.mode="swap" のときに使用する
|
|
21
|
+
* 元 input 要素の状態スナップショットおよび復元ロジックを管理するクラス
|
|
22
|
+
*
|
|
23
|
+
* 役割
|
|
24
|
+
* - swap前の input の属性状態を保持する
|
|
25
|
+
* - raw化および display生成時に必要な属性を適用する
|
|
26
|
+
* - detach時に元の状態へ復元する
|
|
27
|
+
*
|
|
28
|
+
* 設計方針
|
|
29
|
+
* - 送信用属性は raw に残す
|
|
30
|
+
* - UIおよびアクセシビリティ属性は display に適用する
|
|
31
|
+
* - tig内部用の data-* は display にコピーしない
|
|
32
|
+
*/
|
|
33
|
+
class SwapState {
|
|
34
|
+
/**
|
|
35
|
+
* 元 input の type 属性
|
|
36
|
+
* detach時に復元するため保持する
|
|
37
|
+
* @type {string}
|
|
38
|
+
*/
|
|
39
|
+
originalType;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 元 input の id 属性
|
|
43
|
+
* swap時に display へ移し detach時に rawへ戻す
|
|
44
|
+
* @type {string|null}
|
|
45
|
+
*/
|
|
46
|
+
originalId;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 元 input の name 属性
|
|
50
|
+
* 送信用属性のため raw側に残すが
|
|
51
|
+
* detach時の整合性のため保持する
|
|
52
|
+
* @type {string|null}
|
|
53
|
+
*/
|
|
54
|
+
originalName;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 元 input の class 属性
|
|
58
|
+
* swap時に display へ移す
|
|
59
|
+
* @type {string}
|
|
60
|
+
*/
|
|
61
|
+
originalClass;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* UI系属性のスナップショット
|
|
65
|
+
* placeholder inputmode required などを保持する
|
|
66
|
+
*
|
|
67
|
+
* key 属性名
|
|
68
|
+
* value 属性値 未指定の場合は null
|
|
69
|
+
*
|
|
70
|
+
* @type {Object.<string, string|null>}
|
|
71
|
+
*/
|
|
72
|
+
originalUiAttrs;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* aria-* 属性のスナップショット
|
|
76
|
+
* アクセシビリティ維持のため display に適用する
|
|
77
|
+
*
|
|
78
|
+
* key aria属性名 例 aria-label
|
|
79
|
+
* value 属性値
|
|
80
|
+
*
|
|
81
|
+
* @type {Object.<string, string>}
|
|
82
|
+
*/
|
|
83
|
+
originalAriaAttrs;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* tig 以外の data-* 属性のスナップショット
|
|
87
|
+
* swap後も display へ引き継ぐ
|
|
88
|
+
*
|
|
89
|
+
* key datasetキー camelCase
|
|
90
|
+
* value 属性値
|
|
91
|
+
*
|
|
92
|
+
* @type {Object.<string, string>}
|
|
93
|
+
*/
|
|
94
|
+
originalDataset;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* swap時に生成された display 用 input 要素
|
|
98
|
+
* detach時に削除するため保持する
|
|
99
|
+
*
|
|
100
|
+
* @type {HTMLInputElement|null}
|
|
101
|
+
*/
|
|
102
|
+
createdDisplay;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {HTMLInputElement} input
|
|
106
|
+
* swap前の元 input 要素
|
|
107
|
+
*/
|
|
108
|
+
constructor(input) {
|
|
109
|
+
this.originalType = input.type;
|
|
110
|
+
this.originalId = input.getAttribute("id");
|
|
111
|
+
this.originalName = input.getAttribute("name");
|
|
112
|
+
this.originalClass = input.className;
|
|
113
|
+
|
|
114
|
+
this.originalUiAttrs = {};
|
|
115
|
+
this.originalAriaAttrs = {};
|
|
116
|
+
this.originalDataset = {};
|
|
117
|
+
this.createdDisplay = null;
|
|
118
|
+
|
|
119
|
+
const UI_ATTRS = [
|
|
120
|
+
"placeholder",
|
|
121
|
+
"inputmode",
|
|
122
|
+
"autocomplete",
|
|
123
|
+
"required",
|
|
124
|
+
"minlength",
|
|
125
|
+
"maxlength",
|
|
126
|
+
"pattern",
|
|
127
|
+
"title",
|
|
128
|
+
"tabindex"
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
for (const name of UI_ATTRS) {
|
|
132
|
+
this.originalUiAttrs[name] =
|
|
133
|
+
input.hasAttribute(name) ? input.getAttribute(name) : null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const attr of input.attributes) {
|
|
137
|
+
if (attr.name.startsWith("aria-")) {
|
|
138
|
+
this.originalAriaAttrs[attr.name] = attr.value ?? "";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const [k, v] of Object.entries(input.dataset)) {
|
|
143
|
+
if (k.startsWith("tig")) { continue; }
|
|
144
|
+
this.originalDataset[k] = v;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* raw 元input を hidden 化する
|
|
150
|
+
* 送信担当要素として扱う
|
|
151
|
+
*
|
|
152
|
+
* @param {HTMLInputElement} input
|
|
153
|
+
* @returns {void}
|
|
154
|
+
*/
|
|
155
|
+
applyToRaw(input) {
|
|
156
|
+
// raw化(送信担当)
|
|
157
|
+
input.type = "hidden";
|
|
158
|
+
input.removeAttribute("id");
|
|
159
|
+
input.className = "";
|
|
160
|
+
input.dataset.tigRole = "raw";
|
|
161
|
+
|
|
162
|
+
// 元idのメタを残す(デバッグ/参照用)
|
|
163
|
+
if (this.originalId) {
|
|
164
|
+
input.dataset.tigOriginalId = this.originalId;
|
|
165
|
+
}
|
|
166
|
+
if (this.originalName) {
|
|
167
|
+
input.dataset.tigOriginalName = this.originalName;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* display用 input を生成し UI属性 aria属性 data属性を適用
|
|
173
|
+
*
|
|
174
|
+
* @param {HTMLInputElement} raw hidden化された元input
|
|
175
|
+
* @returns {HTMLInputElement}
|
|
176
|
+
*/
|
|
177
|
+
createDisplay(raw) {
|
|
178
|
+
const display = document.createElement("input");
|
|
179
|
+
display.type = "text";
|
|
180
|
+
display.dataset.tigRole = "display";
|
|
181
|
+
|
|
182
|
+
if (this.originalId) {
|
|
183
|
+
display.id = this.originalId;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
display.className = this.originalClass ?? "";
|
|
187
|
+
display.value = raw.value;
|
|
188
|
+
|
|
189
|
+
for (const [name, v] of Object.entries(this.originalUiAttrs)) {
|
|
190
|
+
if (v == null) {
|
|
191
|
+
display.removeAttribute(name);
|
|
192
|
+
} else {
|
|
193
|
+
display.setAttribute(name, v);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const [name, v] of Object.entries(this.originalAriaAttrs)) {
|
|
198
|
+
display.setAttribute(name, v);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const [k, v] of Object.entries(this.originalDataset)) {
|
|
202
|
+
display.dataset[k] = v;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.createdDisplay = display;
|
|
206
|
+
return display;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* detach時に display 要素を削除する
|
|
211
|
+
*
|
|
212
|
+
* @returns {void}
|
|
213
|
+
*/
|
|
214
|
+
removeDisplay() {
|
|
215
|
+
if (this.createdDisplay?.parentNode) {
|
|
216
|
+
this.createdDisplay.parentNode.removeChild(this.createdDisplay);
|
|
217
|
+
}
|
|
218
|
+
this.createdDisplay = null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* raw hidden化された元input を元の状態へ復元する
|
|
223
|
+
*
|
|
224
|
+
* @param {HTMLInputElement} raw
|
|
225
|
+
* @returns {void}
|
|
226
|
+
*/
|
|
227
|
+
restoreRaw(raw) {
|
|
228
|
+
raw.type = this.originalType;
|
|
229
|
+
|
|
230
|
+
if (this.originalId) {
|
|
231
|
+
raw.setAttribute("id", this.originalId);
|
|
232
|
+
} else {
|
|
233
|
+
raw.removeAttribute("id");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (this.originalName) {
|
|
237
|
+
raw.setAttribute("name", this.originalName);
|
|
238
|
+
} else {
|
|
239
|
+
raw.removeAttribute("name");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
raw.className = this.originalClass ?? "";
|
|
243
|
+
|
|
244
|
+
delete raw.dataset.tigRole;
|
|
245
|
+
delete raw.dataset.tigOriginalId;
|
|
246
|
+
delete raw.dataset.tigOriginalName;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
7
250
|
/**
|
|
8
251
|
* The script is part of JPInputGuard.
|
|
9
252
|
*
|
|
@@ -14,6 +257,7 @@
|
|
|
14
257
|
* The MIT license https://opensource.org/licenses/MIT
|
|
15
258
|
*/
|
|
16
259
|
|
|
260
|
+
|
|
17
261
|
/**
|
|
18
262
|
* 対象要素の種別(現在は input と textarea のみ対応)
|
|
19
263
|
* @typedef {"input"|"textarea"} ElementKind
|
|
@@ -41,7 +285,9 @@
|
|
|
41
285
|
* @property {() => boolean} isValid - 現在エラーが無いかどうか
|
|
42
286
|
* @property {() => TigError[]} getErrors - エラー一覧を取得
|
|
43
287
|
* @property {() => string} getRawValue - 送信用の正規化済み値を取得
|
|
44
|
-
* @property {() =>
|
|
288
|
+
* @property {() => string} getDisplayValue - ユーザーが実際に操作している要素の値を取得
|
|
289
|
+
* @property {() => HTMLInputElement|HTMLTextAreaElement} getRawElement - 送信用の正規化済み値の要素
|
|
290
|
+
* @property {() => HTMLInputElement|HTMLTextAreaElement} getDisplayElement - ユーザーが実際に操作している要素(swap時はdisplay専用)
|
|
45
291
|
*/
|
|
46
292
|
|
|
47
293
|
/**
|
|
@@ -90,17 +336,6 @@
|
|
|
90
336
|
* @property {SeparateValueOptions} [separateValue] - 表示値と内部値の分離設定
|
|
91
337
|
*/
|
|
92
338
|
|
|
93
|
-
/**
|
|
94
|
-
* swap時に退避する元inputの情報
|
|
95
|
-
* detach時に元の状態へ復元するために使用する
|
|
96
|
-
* @typedef {Object} SwapState
|
|
97
|
-
* @property {string} originalType - 元のinput.type
|
|
98
|
-
* @property {string|null} originalId - 元のid属性
|
|
99
|
-
* @property {string|null} originalName - 元のname属性
|
|
100
|
-
* @property {string} originalClass - 元のclass文字列
|
|
101
|
-
* @property {HTMLInputElement} createdDisplay - 生成した表示用input
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
339
|
/**
|
|
105
340
|
* selection(カーソル/選択範囲)の退避情報
|
|
106
341
|
* @typedef {Object} SelectionState
|
|
@@ -206,7 +441,7 @@
|
|
|
206
441
|
|
|
207
442
|
const kind = detectKind(element);
|
|
208
443
|
if (!kind) {
|
|
209
|
-
throw new TypeError("[
|
|
444
|
+
throw new TypeError("[text-input-guard] attach() expects an <input> or <textarea> element.");
|
|
210
445
|
}
|
|
211
446
|
|
|
212
447
|
/**
|
|
@@ -438,67 +673,25 @@
|
|
|
438
673
|
}
|
|
439
674
|
|
|
440
675
|
if (this.kind !== "input") {
|
|
441
|
-
warnLog('[
|
|
676
|
+
warnLog('[text-input-guard] separateValue.mode="swap" is not supported for <textarea>. ignored.', this.warn);
|
|
442
677
|
return;
|
|
443
678
|
}
|
|
444
679
|
|
|
445
680
|
const input = /** @type {HTMLInputElement} */ (this.originalElement);
|
|
446
681
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
this.swapState = {
|
|
450
|
-
originalType: input.type,
|
|
451
|
-
originalId: input.getAttribute("id"),
|
|
452
|
-
originalName: input.getAttribute("name"),
|
|
453
|
-
originalClass: input.className,
|
|
454
|
-
// 必要になったらここに placeholder/aria/data を追加していく
|
|
455
|
-
createdDisplay: null
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
// raw化(送信担当)
|
|
459
|
-
input.type = "hidden";
|
|
460
|
-
input.removeAttribute("id"); // displayに引き継ぐため
|
|
461
|
-
input.dataset.tigRole = "raw";
|
|
462
|
-
|
|
463
|
-
// 元idのメタを残す(デバッグ/参照用)
|
|
464
|
-
if (this.swapState.originalId) {
|
|
465
|
-
input.dataset.tigOriginalId = this.swapState.originalId;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (this.swapState.originalName) {
|
|
469
|
-
input.dataset.tigOriginalName = this.swapState.originalName;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// display生成(ユーザー入力担当)
|
|
473
|
-
const display = document.createElement("input");
|
|
474
|
-
display.type = "text";
|
|
475
|
-
display.dataset.tigRole = "display";
|
|
476
|
-
|
|
477
|
-
// id は display に移す
|
|
478
|
-
if (this.swapState.originalId) {
|
|
479
|
-
display.id = this.swapState.originalId;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// name は付けない(送信しない)
|
|
483
|
-
display.removeAttribute("name");
|
|
682
|
+
const state = new SwapState(input);
|
|
683
|
+
state.applyToRaw(input);
|
|
484
684
|
|
|
485
|
-
|
|
486
|
-
display.className = this.swapState.originalClass;
|
|
487
|
-
input.className = "";
|
|
488
|
-
|
|
489
|
-
// value 初期同期
|
|
490
|
-
display.value = input.value;
|
|
491
|
-
|
|
492
|
-
// DOMに挿入(rawの直後)
|
|
685
|
+
const display = state.createDisplay(input);
|
|
493
686
|
input.after(display);
|
|
494
687
|
|
|
688
|
+
this.swapState = state;
|
|
689
|
+
|
|
495
690
|
// elements更新
|
|
496
|
-
this.hostElement = input;
|
|
497
|
-
this.displayElement = display;
|
|
691
|
+
this.hostElement = input; // raw
|
|
692
|
+
this.displayElement = display; // display
|
|
498
693
|
this.rawElement = input;
|
|
499
694
|
|
|
500
|
-
this.swapState.createdDisplay = display;
|
|
501
|
-
|
|
502
695
|
// revert 機構
|
|
503
696
|
this.lastAcceptedValue = display.value;
|
|
504
697
|
this.lastAcceptedSelection = this.readSelection(display);
|
|
@@ -518,10 +711,10 @@
|
|
|
518
711
|
|
|
519
712
|
// rawは元の input(hidden化されている)
|
|
520
713
|
const raw = /** @type {HTMLInputElement} */ (this.hostElement);
|
|
521
|
-
const display = state.createdDisplay;
|
|
522
714
|
|
|
523
715
|
// displayが存在するなら、最新表示値をrawに同期してから消す(安全策)
|
|
524
716
|
// ※ rawは常に正規化済みを持つ設計だけど、念のため
|
|
717
|
+
const display = state.createdDisplay;
|
|
525
718
|
if (display) {
|
|
526
719
|
try {
|
|
527
720
|
raw.value = raw.value || display.value;
|
|
@@ -531,39 +724,18 @@
|
|
|
531
724
|
}
|
|
532
725
|
|
|
533
726
|
// display削除
|
|
534
|
-
|
|
535
|
-
display.parentNode.removeChild(display);
|
|
536
|
-
}
|
|
727
|
+
state.removeDisplay();
|
|
537
728
|
|
|
538
729
|
// rawを元に戻す(type)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
// id を戻す
|
|
542
|
-
if (state.originalId) {
|
|
543
|
-
raw.setAttribute("id", state.originalId);
|
|
544
|
-
} else {
|
|
545
|
-
raw.removeAttribute("id");
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// name を戻す(swap中は残している想定だが、念のため)
|
|
549
|
-
if (state.originalName) {
|
|
550
|
-
raw.setAttribute("name", state.originalName);
|
|
551
|
-
} else {
|
|
552
|
-
raw.removeAttribute("name");
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// class を戻す
|
|
556
|
-
raw.className = state.originalClass ?? "";
|
|
557
|
-
|
|
558
|
-
// data属性(tig用)は消しておく
|
|
559
|
-
delete raw.dataset.tigRole;
|
|
560
|
-
delete raw.dataset.tigOriginalId;
|
|
561
|
-
delete raw.dataset.tigOriginalName;
|
|
730
|
+
state.restoreRaw(raw);
|
|
562
731
|
|
|
563
732
|
// elements参照を original に戻す
|
|
564
733
|
this.hostElement = this.originalElement;
|
|
565
734
|
this.displayElement = this.originalElement;
|
|
566
735
|
this.rawElement = null;
|
|
736
|
+
|
|
737
|
+
// swapState破棄
|
|
738
|
+
this.swapState = null;
|
|
567
739
|
}
|
|
568
740
|
|
|
569
741
|
/**
|
|
@@ -575,8 +747,6 @@
|
|
|
575
747
|
this.unbindEvents();
|
|
576
748
|
// swap復元
|
|
577
749
|
this.restoreSeparateValue();
|
|
578
|
-
// swapState破棄
|
|
579
|
-
this.swapState = null;
|
|
580
750
|
// 以後このインスタンスは利用不能にしてもいいが、今回は明示しない
|
|
581
751
|
}
|
|
582
752
|
|
|
@@ -599,7 +769,7 @@
|
|
|
599
769
|
|
|
600
770
|
if (!supports) {
|
|
601
771
|
warnLog(
|
|
602
|
-
`[
|
|
772
|
+
`[text-input-guard] Rule "${rule.name}" is not supported for <${this.kind}>. skipped.`,
|
|
603
773
|
this.warn
|
|
604
774
|
);
|
|
605
775
|
continue;
|
|
@@ -684,9 +854,7 @@
|
|
|
684
854
|
// 連鎖防止(次の処理に持ち越さない)
|
|
685
855
|
this.revertRequest = null;
|
|
686
856
|
|
|
687
|
-
if (this.warn)
|
|
688
|
-
console.log(`[jp-input-guard] reverted: ${req.reason}`, req.detail);
|
|
689
|
-
}
|
|
857
|
+
if (this.warn) ;
|
|
690
858
|
}
|
|
691
859
|
|
|
692
860
|
/**
|
|
@@ -830,7 +998,7 @@
|
|
|
830
998
|
* @returns {void}
|
|
831
999
|
*/
|
|
832
1000
|
onCompositionStart() {
|
|
833
|
-
console.log("[
|
|
1001
|
+
// console.log("[text-input-guard] compositionstart");
|
|
834
1002
|
this.composing = true;
|
|
835
1003
|
}
|
|
836
1004
|
|
|
@@ -840,7 +1008,7 @@
|
|
|
840
1008
|
* @returns {void}
|
|
841
1009
|
*/
|
|
842
1010
|
onCompositionEnd() {
|
|
843
|
-
console.log("[
|
|
1011
|
+
// console.log("[text-input-guard] compositionend");
|
|
844
1012
|
this.composing = false;
|
|
845
1013
|
|
|
846
1014
|
// compositionend後に input が来ない環境向けのフォールバック
|
|
@@ -860,7 +1028,7 @@
|
|
|
860
1028
|
* @returns {void}
|
|
861
1029
|
*/
|
|
862
1030
|
onInput() {
|
|
863
|
-
console.log("[
|
|
1031
|
+
// console.log("[text-input-guard] input");
|
|
864
1032
|
// compositionend後に input が来た場合、フォールバックを無効化
|
|
865
1033
|
this.pendingCompositionCommit = false;
|
|
866
1034
|
this.evaluateInput();
|
|
@@ -871,7 +1039,7 @@
|
|
|
871
1039
|
* @returns {void}
|
|
872
1040
|
*/
|
|
873
1041
|
onBlur() {
|
|
874
|
-
console.log("[
|
|
1042
|
+
// console.log("[text-input-guard] blur");
|
|
875
1043
|
this.evaluateCommit();
|
|
876
1044
|
}
|
|
877
1045
|
|
|
@@ -1088,9 +1256,14 @@
|
|
|
1088
1256
|
* @returns {string}
|
|
1089
1257
|
*/
|
|
1090
1258
|
getRawValue() {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1259
|
+
return /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.hostElement).value;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* 表示用の値を返す(displayの値)
|
|
1264
|
+
* @returns {string}
|
|
1265
|
+
*/
|
|
1266
|
+
getDisplayValue() {
|
|
1094
1267
|
return /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement).value;
|
|
1095
1268
|
}
|
|
1096
1269
|
|
|
@@ -1105,6 +1278,8 @@
|
|
|
1105
1278
|
isValid: () => this.isValid(),
|
|
1106
1279
|
getErrors: () => this.getErrors(),
|
|
1107
1280
|
getRawValue: () => this.getRawValue(),
|
|
1281
|
+
getDisplayValue: () => this.getDisplayValue(),
|
|
1282
|
+
getRawElement: () => /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.hostElement),
|
|
1108
1283
|
getDisplayElement: () => /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement)
|
|
1109
1284
|
};
|
|
1110
1285
|
}
|
|
@@ -1120,6 +1295,63 @@
|
|
|
1120
1295
|
* The MIT license https://opensource.org/licenses/MIT
|
|
1121
1296
|
*/
|
|
1122
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* datasetのboolean値を解釈する
|
|
1300
|
+
* - 未指定なら undefined
|
|
1301
|
+
* - "" / "true" / "1" / "yes" / "on" は true
|
|
1302
|
+
* - "false" / "0" / "no" / "off" は false
|
|
1303
|
+
* @param {string|undefined} v
|
|
1304
|
+
* @returns {boolean|undefined}
|
|
1305
|
+
*/
|
|
1306
|
+
function parseDatasetBool(v) {
|
|
1307
|
+
if (v == null) { return; }
|
|
1308
|
+
const s = String(v).trim().toLowerCase();
|
|
1309
|
+
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1310
|
+
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* datasetのnumber値を解釈する(整数想定)
|
|
1316
|
+
* - 未指定/空なら undefined
|
|
1317
|
+
* - 数値でなければ undefined
|
|
1318
|
+
* @param {string|undefined} v
|
|
1319
|
+
* @returns {number|undefined}
|
|
1320
|
+
*/
|
|
1321
|
+
function parseDatasetNumber(v) {
|
|
1322
|
+
if (v == null) { return; }
|
|
1323
|
+
const s = String(v).trim();
|
|
1324
|
+
if (s === "") { return; }
|
|
1325
|
+
const n = Number(s);
|
|
1326
|
+
return Number.isFinite(n) ? n : undefined;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* enumを解釈する(未指定なら undefined)
|
|
1331
|
+
* @template {string} T
|
|
1332
|
+
* @param {string|undefined} v
|
|
1333
|
+
* @param {readonly T[]} allowed
|
|
1334
|
+
* @returns {T|undefined}
|
|
1335
|
+
*/
|
|
1336
|
+
function parseDatasetEnum(v, allowed) {
|
|
1337
|
+
if (v == null) { return; }
|
|
1338
|
+
const s = String(v).trim();
|
|
1339
|
+
if (s === "") { return; }
|
|
1340
|
+
// 大文字小文字を区別したいならここを変える(今は厳密一致)
|
|
1341
|
+
return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
/**
|
|
1345
|
+
* The script is part of TextInputGuard.
|
|
1346
|
+
*
|
|
1347
|
+
* AUTHOR:
|
|
1348
|
+
* natade-jp (https://github.com/natade-jp)
|
|
1349
|
+
*
|
|
1350
|
+
* LICENSE:
|
|
1351
|
+
* The MIT license https://opensource.org/licenses/MIT
|
|
1352
|
+
*/
|
|
1353
|
+
|
|
1354
|
+
|
|
1123
1355
|
/**
|
|
1124
1356
|
* @typedef {GuardGroup} GuardGroup
|
|
1125
1357
|
* @typedef {Guard} Guard
|
|
@@ -1134,19 +1366,6 @@
|
|
|
1134
1366
|
* @property {(dataset: DOMStringMap, el: HTMLInputElement|HTMLTextAreaElement) => Rule|null} fromDataset
|
|
1135
1367
|
*/
|
|
1136
1368
|
|
|
1137
|
-
/**
|
|
1138
|
-
* Boolean系のdata値を解釈する(未指定なら undefined を返す)
|
|
1139
|
-
* @param {string|undefined} v
|
|
1140
|
-
* @returns {boolean|undefined}
|
|
1141
|
-
*/
|
|
1142
|
-
function parseBool(v) {
|
|
1143
|
-
if (v == null) { return; }
|
|
1144
|
-
const s = String(v).trim().toLowerCase();
|
|
1145
|
-
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1146
|
-
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
1369
|
/**
|
|
1151
1370
|
* separate mode を解釈する(未指定は "auto")
|
|
1152
1371
|
* @param {string|undefined} v
|
|
@@ -1250,7 +1469,7 @@
|
|
|
1250
1469
|
const options = {};
|
|
1251
1470
|
|
|
1252
1471
|
// warn / invalidClass
|
|
1253
|
-
const warn =
|
|
1472
|
+
const warn = parseDatasetBool(ds.tigWarn);
|
|
1254
1473
|
if (warn != null) { options.warn = warn; }
|
|
1255
1474
|
|
|
1256
1475
|
if (ds.tigInvalidClass != null && String(ds.tigInvalidClass).trim() !== "") {
|
|
@@ -1270,7 +1489,7 @@
|
|
|
1270
1489
|
} catch (e) {
|
|
1271
1490
|
const w = options.warn ?? true;
|
|
1272
1491
|
if (w) {
|
|
1273
|
-
console.warn(`[
|
|
1492
|
+
console.warn(`[text-input-guard] autoAttach: rule "${fac.name}" fromDataset() threw an error.`, e);
|
|
1274
1493
|
}
|
|
1275
1494
|
}
|
|
1276
1495
|
}
|
|
@@ -1296,62 +1515,6 @@
|
|
|
1296
1515
|
}
|
|
1297
1516
|
}
|
|
1298
1517
|
|
|
1299
|
-
/**
|
|
1300
|
-
* The script is part of TextInputGuard.
|
|
1301
|
-
*
|
|
1302
|
-
* AUTHOR:
|
|
1303
|
-
* natade-jp (https://github.com/natade-jp)
|
|
1304
|
-
*
|
|
1305
|
-
* LICENSE:
|
|
1306
|
-
* The MIT license https://opensource.org/licenses/MIT
|
|
1307
|
-
*/
|
|
1308
|
-
|
|
1309
|
-
/**
|
|
1310
|
-
* datasetのboolean値を解釈する
|
|
1311
|
-
* - 未指定なら undefined
|
|
1312
|
-
* - "" / "true" / "1" / "yes" / "on" は true
|
|
1313
|
-
* - "false" / "0" / "no" / "off" は false
|
|
1314
|
-
* @param {string|undefined} v
|
|
1315
|
-
* @returns {boolean|undefined}
|
|
1316
|
-
*/
|
|
1317
|
-
function parseDatasetBool(v) {
|
|
1318
|
-
if (v == null) { return; }
|
|
1319
|
-
const s = String(v).trim().toLowerCase();
|
|
1320
|
-
if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
|
|
1321
|
-
if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
/**
|
|
1326
|
-
* datasetのnumber値を解釈する(整数想定)
|
|
1327
|
-
* - 未指定/空なら undefined
|
|
1328
|
-
* - 数値でなければ undefined
|
|
1329
|
-
* @param {string|undefined} v
|
|
1330
|
-
* @returns {number|undefined}
|
|
1331
|
-
*/
|
|
1332
|
-
function parseDatasetNumber(v) {
|
|
1333
|
-
if (v == null) { return; }
|
|
1334
|
-
const s = String(v).trim();
|
|
1335
|
-
if (s === "") { return; }
|
|
1336
|
-
const n = Number(s);
|
|
1337
|
-
return Number.isFinite(n) ? n : undefined;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* enumを解釈する(未指定なら undefined)
|
|
1342
|
-
* @template {string} T
|
|
1343
|
-
* @param {string|undefined} v
|
|
1344
|
-
* @param {readonly T[]} allowed
|
|
1345
|
-
* @returns {T|undefined}
|
|
1346
|
-
*/
|
|
1347
|
-
function parseDatasetEnum(v, allowed) {
|
|
1348
|
-
if (v == null) { return; }
|
|
1349
|
-
const s = String(v).trim();
|
|
1350
|
-
if (s === "") { return; }
|
|
1351
|
-
// 大文字小文字を区別したいならここを変える(今は厳密一致)
|
|
1352
|
-
return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
1518
|
/**
|
|
1356
1519
|
* The script is part of TextInputGuard.
|
|
1357
1520
|
*
|
|
@@ -1369,6 +1532,7 @@
|
|
|
1369
1532
|
* @property {boolean} [allowFullWidth=true] - 全角数字/記号を許可して半角へ正規化する
|
|
1370
1533
|
* @property {boolean} [allowMinus=false] - マイナス記号を許可する(先頭のみ)
|
|
1371
1534
|
* @property {boolean} [allowDecimal=false] - 小数点を許可する(1つだけ)
|
|
1535
|
+
* @property {boolean} [allowEmpty=true] - 空文字を許可するか
|
|
1372
1536
|
*/
|
|
1373
1537
|
|
|
1374
1538
|
/**
|
|
@@ -1384,7 +1548,8 @@
|
|
|
1384
1548
|
const opt = {
|
|
1385
1549
|
allowFullWidth: options.allowFullWidth ?? true,
|
|
1386
1550
|
allowMinus: options.allowMinus ?? false,
|
|
1387
|
-
allowDecimal: options.allowDecimal ?? false
|
|
1551
|
+
allowDecimal: options.allowDecimal ?? false,
|
|
1552
|
+
allowEmpty: options.allowEmpty ?? true
|
|
1388
1553
|
};
|
|
1389
1554
|
|
|
1390
1555
|
/** @type {Set<string>} */
|
|
@@ -1542,9 +1707,14 @@
|
|
|
1542
1707
|
fix(value) {
|
|
1543
1708
|
let v = String(value);
|
|
1544
1709
|
|
|
1710
|
+
// 空文字の扱い
|
|
1711
|
+
if (v === "") {
|
|
1712
|
+
return opt.allowEmpty ? "" : "0";
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1545
1715
|
// 未完成な数値は空にする
|
|
1546
1716
|
if (v === "-" || v === "." || v === "-.") {
|
|
1547
|
-
return "";
|
|
1717
|
+
return opt.allowEmpty ? "" : "0";
|
|
1548
1718
|
}
|
|
1549
1719
|
|
|
1550
1720
|
// "-.1" → "-0.1"
|
|
@@ -1616,6 +1786,7 @@
|
|
|
1616
1786
|
* - data-tig-rules-numeric-allow-full-width -> dataset.tigRulesNumericAllowFullWidth
|
|
1617
1787
|
* - data-tig-rules-numeric-allow-minus -> dataset.tigRulesNumericAllowMinus
|
|
1618
1788
|
* - data-tig-rules-numeric-allow-decimal -> dataset.tigRulesNumericAllowDecimal
|
|
1789
|
+
* - data-tig-rules-numeric-allow-empty -> dataset.tigRulesNumericAllowEmpty
|
|
1619
1790
|
*
|
|
1620
1791
|
* @param {DOMStringMap} dataset
|
|
1621
1792
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -1648,6 +1819,12 @@
|
|
|
1648
1819
|
options.allowDecimal = allowDecimal;
|
|
1649
1820
|
}
|
|
1650
1821
|
|
|
1822
|
+
// data-tig-rules-numeric-allow-empty(未指定なら numeric側デフォルト true)
|
|
1823
|
+
const allowEmpty = parseDatasetBool(dataset.tigRulesNumericAllowEmpty);
|
|
1824
|
+
if (allowEmpty != null) {
|
|
1825
|
+
options.allowEmpty = allowEmpty;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1651
1828
|
return numeric(options);
|
|
1652
1829
|
};
|
|
1653
1830
|
|
|
@@ -1672,6 +1849,7 @@
|
|
|
1672
1849
|
* @property {"none"|"truncate"|"round"} [fixFracOnBlur="none"] - blur時の小数部補正
|
|
1673
1850
|
* @property {"none"|"block"} [overflowInputInt="none"] - 入力中:整数部が最大桁を超える入力をブロックする
|
|
1674
1851
|
* @property {"none"|"block"} [overflowInputFrac="none"] - 入力中:小数部が最大桁を超える入力をブロックする
|
|
1852
|
+
* @property {boolean} [forceFracOnBlur=false] - blur時に小数部を必ず表示(frac桁まで0埋め)
|
|
1675
1853
|
*/
|
|
1676
1854
|
|
|
1677
1855
|
/**
|
|
@@ -1812,7 +1990,8 @@
|
|
|
1812
1990
|
fixIntOnBlur: options.fixIntOnBlur ?? "none",
|
|
1813
1991
|
fixFracOnBlur: options.fixFracOnBlur ?? "none",
|
|
1814
1992
|
overflowInputInt: options.overflowInputInt ?? "none",
|
|
1815
|
-
overflowInputFrac: options.overflowInputFrac ?? "none"
|
|
1993
|
+
overflowInputFrac: options.overflowInputFrac ?? "none",
|
|
1994
|
+
forceFracOnBlur: options.forceFracOnBlur ?? false
|
|
1816
1995
|
};
|
|
1817
1996
|
|
|
1818
1997
|
return {
|
|
@@ -1929,14 +2108,39 @@
|
|
|
1929
2108
|
}
|
|
1930
2109
|
}
|
|
1931
2110
|
|
|
1932
|
-
|
|
1933
|
-
|
|
2111
|
+
if (opt.forceFracOnBlur && typeof opt.frac === "number" && opt.frac > 0) {
|
|
2112
|
+
const limit = opt.frac;
|
|
2113
|
+
// "." が無いなら作る(12 → 12.00)
|
|
2114
|
+
if (!hasDot) {
|
|
2115
|
+
fracPart = "";
|
|
2116
|
+
}
|
|
2117
|
+
// 足りない分を 0 埋め(12.3 → 12.30 / 12. → 12.00)
|
|
2118
|
+
const f = fracPart ?? "";
|
|
2119
|
+
if (f.length < limit) {
|
|
2120
|
+
fracPart = f + "0".repeat(limit - f.length);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// 組み立て
|
|
2125
|
+
if (typeof opt.frac !== "number") {
|
|
2126
|
+
// frac未指定なら、dot があっても digits は触らず intだけ返す方針(現状維持)
|
|
1934
2127
|
return `${sign}${intPart}`;
|
|
1935
2128
|
}
|
|
2129
|
+
|
|
1936
2130
|
if (opt.frac === 0) {
|
|
2131
|
+
// 小数0桁なら常に整数表示
|
|
1937
2132
|
return `${sign}${intPart}`;
|
|
1938
2133
|
}
|
|
1939
|
-
|
|
2134
|
+
|
|
2135
|
+
// frac 指定あり(1以上)
|
|
2136
|
+
if (hasDot || (opt.forceFracOnBlur && opt.frac > 0)) {
|
|
2137
|
+
// "." が無いけど forceFracOnBlur の場合もここに来る
|
|
2138
|
+
const f = fracPart ?? "";
|
|
2139
|
+
return `${sign}${intPart}.${f}`;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// "." が無くて force もしないなら整数表示
|
|
2143
|
+
return `${sign}${intPart}`;
|
|
1940
2144
|
}
|
|
1941
2145
|
};
|
|
1942
2146
|
}
|
|
@@ -1955,6 +2159,7 @@
|
|
|
1955
2159
|
* - data-tig-rules-digits-fix-frac-on-blur -> dataset.tigRulesDigitsFixFracOnBlur
|
|
1956
2160
|
* - data-tig-rules-digits-overflow-input-int -> dataset.tigRulesDigitsOverflowInputInt
|
|
1957
2161
|
* - data-tig-rules-digits-overflow-input-frac -> dataset.tigRulesDigitsOverflowInputFrac
|
|
2162
|
+
* - data-tig-rules-digits-force-frac-on-blur -> dataset.tigRulesDigitsForceFracOnBlur
|
|
1958
2163
|
*
|
|
1959
2164
|
* @param {DOMStringMap} dataset
|
|
1960
2165
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -2017,6 +2222,12 @@
|
|
|
2017
2222
|
options.overflowInputFrac = ovFrac;
|
|
2018
2223
|
}
|
|
2019
2224
|
|
|
2225
|
+
// forceFracOnBlur
|
|
2226
|
+
const forceFrac = parseDatasetBool(dataset.tigRulesDigitsForceFracOnBlur);
|
|
2227
|
+
if (forceFrac != null) {
|
|
2228
|
+
options.forceFracOnBlur = forceFrac;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2020
2231
|
return digits(options);
|
|
2021
2232
|
};
|
|
2022
2233
|
|
|
@@ -2042,6 +2253,11 @@
|
|
|
2042
2253
|
|
|
2043
2254
|
/**
|
|
2044
2255
|
* 表示整形(確定時のみ)
|
|
2256
|
+
*
|
|
2257
|
+
* 前提:
|
|
2258
|
+
* - numeric / digits 等で正規化済みの数値文字列が渡される
|
|
2259
|
+
* - 整数部・小数部・符号のみを含む(カンマは含まない想定)
|
|
2260
|
+
*
|
|
2045
2261
|
* @param {string} value
|
|
2046
2262
|
* @returns {string}
|
|
2047
2263
|
*/
|
|
@@ -2129,11 +2345,11 @@
|
|
|
2129
2345
|
|
|
2130
2346
|
/**
|
|
2131
2347
|
* バージョン(ビルド時に置換したいならここを差し替える)
|
|
2132
|
-
* 例: rollup replace で ""0.0
|
|
2348
|
+
* 例: rollup replace で ""0.1.0"" を package.json の version に置換
|
|
2133
2349
|
*/
|
|
2134
2350
|
// @ts-ignore
|
|
2135
2351
|
// eslint-disable-next-line no-undef
|
|
2136
|
-
const version = "0.0
|
|
2352
|
+
const version = "0.1.0" ;
|
|
2137
2353
|
|
|
2138
2354
|
exports.attach = attach;
|
|
2139
2355
|
exports.attachAll = attachAll;
|