text-input-guard 0.2.1 → 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 +87 -27
- package/dist/cjs/text-input-guard.min.cjs +1 -1
- package/dist/esm/text-input-guard.js +87 -27
- package/dist/esm/text-input-guard.min.js +1 -1
- package/dist/umd/text-input-guard.js +87 -27
- package/dist/umd/text-input-guard.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://natade-jp.github.io/text-input-guard/ogp-dark.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://natade-jp.github.io/text-input-guard/ogp-light.svg">
|
|
5
|
+
<img src="https://natade-jp.github.io/text-input-guard/ogp-light.svg" width="700" alt="TextInputGuard">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
2
8
|
|
|
3
|
-
TextInputGuard
|
|
9
|
+
TextInputGuard は、**開発中**の日本語入力環境を前提に設計された入力補助ライブラリです。
|
|
4
10
|
|
|
5
|
-
`<input>` / `<textarea>`
|
|
11
|
+
`<input>` / `<textarea>` に対して、全角混在・桁数制限・小数処理・表示整形など、日本語環境特有の入力制御を扱いやすい形で提供します。業務系フォームや金額入力など、IME の影響を受けやすい入力欄でも、表示用の値と送信用の値を分離しながら、安定した入力制御を実現できます。
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
---
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
## Documentation
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
詳しいドキュメントはこちら
|
|
18
|
+
|
|
19
|
+
👉 https://natade-jp.github.io/text-input-guard/
|
|
@@ -165,6 +165,7 @@ class SwapState {
|
|
|
165
165
|
// raw化(送信担当)
|
|
166
166
|
input.type = "hidden";
|
|
167
167
|
input.removeAttribute("id");
|
|
168
|
+
input.removeAttribute("class");
|
|
168
169
|
input.className = "";
|
|
169
170
|
input.dataset.tigRole = "raw";
|
|
170
171
|
|
|
@@ -172,6 +173,9 @@ class SwapState {
|
|
|
172
173
|
if (this.originalId) {
|
|
173
174
|
input.dataset.tigOriginalId = this.originalId;
|
|
174
175
|
}
|
|
176
|
+
if (this.originalClass) {
|
|
177
|
+
input.dataset.tigOriginalClass = this.originalClass;
|
|
178
|
+
}
|
|
175
179
|
if (this.originalName) {
|
|
176
180
|
input.dataset.tigOriginalName = this.originalName;
|
|
177
181
|
}
|
|
@@ -192,7 +196,10 @@ class SwapState {
|
|
|
192
196
|
display.id = this.originalId;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
|
|
199
|
+
if (this.originalClass) {
|
|
200
|
+
display.className = this.originalClass;
|
|
201
|
+
}
|
|
202
|
+
|
|
196
203
|
display.value = raw.value;
|
|
197
204
|
|
|
198
205
|
for (const [name, v] of Object.entries(this.originalUiAttrs)) {
|
|
@@ -253,6 +260,7 @@ class SwapState {
|
|
|
253
260
|
|
|
254
261
|
delete raw.dataset.tigRole;
|
|
255
262
|
delete raw.dataset.tigOriginalId;
|
|
263
|
+
delete raw.dataset.tigOriginalClass;
|
|
256
264
|
delete raw.dataset.tigOriginalName;
|
|
257
265
|
}
|
|
258
266
|
}
|
|
@@ -446,6 +454,28 @@ function warnLog(msg, warn) {
|
|
|
446
454
|
}
|
|
447
455
|
}
|
|
448
456
|
|
|
457
|
+
/**
|
|
458
|
+
* input / textarea 要素と内部 Guard インスタンスの対応表
|
|
459
|
+
*
|
|
460
|
+
* - key: displayElement
|
|
461
|
+
* - value: InputGuard(内部実装)
|
|
462
|
+
*
|
|
463
|
+
* @type {WeakMap<HTMLInputElement|HTMLTextAreaElement, InputGuard>}
|
|
464
|
+
*/
|
|
465
|
+
const guardMap = new WeakMap();
|
|
466
|
+
|
|
467
|
+
document.addEventListener("selectionchange", () => {
|
|
468
|
+
const el = document.activeElement;
|
|
469
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const inputGuard = guardMap.get(el);
|
|
473
|
+
if (!inputGuard) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
inputGuard.onSelectionChange();
|
|
477
|
+
});
|
|
478
|
+
|
|
449
479
|
/**
|
|
450
480
|
* 指定した1要素に対してガードを適用し、Guard API を返す
|
|
451
481
|
* @param {HTMLInputElement|HTMLTextAreaElement} element
|
|
@@ -453,9 +483,12 @@ function warnLog(msg, warn) {
|
|
|
453
483
|
* @returns {Guard}
|
|
454
484
|
*/
|
|
455
485
|
function attach(element, options = {}) {
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
486
|
+
const inputGuard = new InputGuard(element, options);
|
|
487
|
+
inputGuard.init();
|
|
488
|
+
const guard = inputGuard.getGuard();
|
|
489
|
+
const display = guard.getDisplayElement();
|
|
490
|
+
guardMap.set(display, inputGuard);
|
|
491
|
+
return guard;
|
|
459
492
|
}
|
|
460
493
|
|
|
461
494
|
/**
|
|
@@ -551,7 +584,7 @@ class InputGuard {
|
|
|
551
584
|
/**
|
|
552
585
|
* ユーザーが直接入力する表示側要素
|
|
553
586
|
* swapしない場合は originalElement と同一
|
|
554
|
-
* @type {
|
|
587
|
+
* @type {HTMLInputElement|HTMLTextAreaElement}
|
|
555
588
|
*/
|
|
556
589
|
this.displayElement = element;
|
|
557
590
|
|
|
@@ -673,6 +706,12 @@ class InputGuard {
|
|
|
673
706
|
*/
|
|
674
707
|
this.pendingCompositionCommit = false;
|
|
675
708
|
|
|
709
|
+
/**
|
|
710
|
+
* selection 更新のフレーム予約ID
|
|
711
|
+
* @type {number|null}
|
|
712
|
+
*/
|
|
713
|
+
this.selectionFrameId = null;
|
|
714
|
+
|
|
676
715
|
/**
|
|
677
716
|
* 直前に受理した表示値、正しい情報のスナップショットのような情報(block時の戻し先)
|
|
678
717
|
* @type {string}
|
|
@@ -718,9 +757,11 @@ class InputGuard {
|
|
|
718
757
|
* @returns {SelectionState}
|
|
719
758
|
*/
|
|
720
759
|
readSelection(el) {
|
|
760
|
+
const start = el.selectionStart ?? 0;
|
|
761
|
+
const end = el.selectionEnd ?? start;
|
|
721
762
|
return {
|
|
722
|
-
start
|
|
723
|
-
end
|
|
763
|
+
start,
|
|
764
|
+
end,
|
|
724
765
|
direction: el.selectionDirection
|
|
725
766
|
};
|
|
726
767
|
}
|
|
@@ -834,6 +875,8 @@ class InputGuard {
|
|
|
834
875
|
* @returns {void}
|
|
835
876
|
*/
|
|
836
877
|
detach() {
|
|
878
|
+
// 管理マップから削除
|
|
879
|
+
guardMap.delete(this.displayElement);
|
|
837
880
|
// イベント解除(displayElementがswap後の可能性があるので先に外す)
|
|
838
881
|
this.unbindEvents();
|
|
839
882
|
// swap復元
|
|
@@ -894,15 +937,7 @@ class InputGuard {
|
|
|
894
937
|
this.displayElement.addEventListener("input", this.onInput);
|
|
895
938
|
this.displayElement.addEventListener("beforeinput", this.onBeforeInput);
|
|
896
939
|
this.displayElement.addEventListener("blur", this.onBlur);
|
|
897
|
-
|
|
898
|
-
// フォーカスで編集用に戻す
|
|
899
940
|
this.displayElement.addEventListener("focus", this.onFocus);
|
|
900
|
-
|
|
901
|
-
// キャレット/選択範囲の変化を拾う(block時の不自然ジャンプ対策)
|
|
902
|
-
this.displayElement.addEventListener("keyup", this.onSelectionChange);
|
|
903
|
-
this.displayElement.addEventListener("mouseup", this.onSelectionChange);
|
|
904
|
-
this.displayElement.addEventListener("select", this.onSelectionChange);
|
|
905
|
-
this.displayElement.addEventListener("focus", this.onSelectionChange);
|
|
906
941
|
}
|
|
907
942
|
|
|
908
943
|
/**
|
|
@@ -916,10 +951,6 @@ class InputGuard {
|
|
|
916
951
|
this.displayElement.removeEventListener("beforeinput", this.onBeforeInput);
|
|
917
952
|
this.displayElement.removeEventListener("blur", this.onBlur);
|
|
918
953
|
this.displayElement.removeEventListener("focus", this.onFocus);
|
|
919
|
-
this.displayElement.removeEventListener("keyup", this.onSelectionChange);
|
|
920
|
-
this.displayElement.removeEventListener("mouseup", this.onSelectionChange);
|
|
921
|
-
this.displayElement.removeEventListener("select", this.onSelectionChange);
|
|
922
|
-
this.displayElement.removeEventListener("focus", this.onSelectionChange);
|
|
923
954
|
}
|
|
924
955
|
|
|
925
956
|
/**
|
|
@@ -1287,12 +1318,41 @@ class InputGuard {
|
|
|
1287
1318
|
* @returns {void}
|
|
1288
1319
|
*/
|
|
1289
1320
|
onSelectionChange() {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1321
|
+
const requestFrame =
|
|
1322
|
+
typeof requestAnimationFrame === "function"
|
|
1323
|
+
? requestAnimationFrame
|
|
1324
|
+
: (
|
|
1325
|
+
/** @param {FrameRequestCallback} cb */
|
|
1326
|
+
(cb) => setTimeout(cb, 0)
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
const cancelFrame =
|
|
1330
|
+
typeof cancelAnimationFrame === "function"
|
|
1331
|
+
? cancelAnimationFrame
|
|
1332
|
+
: clearTimeout;
|
|
1333
|
+
|
|
1334
|
+
// すでに予約済みならキャンセル(selectionchange は連続発火するため)
|
|
1335
|
+
if (this.selectionFrameId != null) {
|
|
1336
|
+
cancelFrame(this.selectionFrameId);
|
|
1293
1337
|
}
|
|
1294
|
-
|
|
1295
|
-
this.
|
|
1338
|
+
|
|
1339
|
+
this.selectionFrameId = requestFrame(() => {
|
|
1340
|
+
this.selectionFrameId = null;
|
|
1341
|
+
|
|
1342
|
+
// IME変換中は無視(キャレット位置が不安定になるため)
|
|
1343
|
+
if (this.composing) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
|
|
1348
|
+
|
|
1349
|
+
// 要素がフォーカスされていない場合は無視
|
|
1350
|
+
if (document.activeElement !== el) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
this.lastAcceptedSelection = this.readSelection(el);
|
|
1355
|
+
});
|
|
1296
1356
|
}
|
|
1297
1357
|
|
|
1298
1358
|
/**
|
|
@@ -7031,7 +7091,7 @@ function bytes(options = {}) {
|
|
|
7031
7091
|
code: "bytes.max_overflow",
|
|
7032
7092
|
rule: "bytes",
|
|
7033
7093
|
phase: "validate",
|
|
7034
|
-
detail: {
|
|
7094
|
+
detail: { limit: opt.max, actual: len }
|
|
7035
7095
|
});
|
|
7036
7096
|
}
|
|
7037
7097
|
}
|
|
@@ -7377,11 +7437,11 @@ const rules = {
|
|
|
7377
7437
|
|
|
7378
7438
|
/**
|
|
7379
7439
|
* バージョン(ビルド時に置換したいならここを差し替える)
|
|
7380
|
-
* 例: rollup replace で ""0.2.
|
|
7440
|
+
* 例: rollup replace で ""0.2.2"" を package.json の version に置換
|
|
7381
7441
|
*/
|
|
7382
7442
|
// @ts-ignore
|
|
7383
7443
|
// eslint-disable-next-line no-undef
|
|
7384
|
-
const version = "0.2.
|
|
7444
|
+
const version = "0.2.2" ;
|
|
7385
7445
|
|
|
7386
7446
|
exports.ascii = ascii;
|
|
7387
7447
|
exports.attach = attach;
|