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.
@@ -163,6 +163,7 @@ class SwapState {
163
163
  // raw化(送信担当)
164
164
  input.type = "hidden";
165
165
  input.removeAttribute("id");
166
+ input.removeAttribute("class");
166
167
  input.className = "";
167
168
  input.dataset.tigRole = "raw";
168
169
 
@@ -170,6 +171,9 @@ class SwapState {
170
171
  if (this.originalId) {
171
172
  input.dataset.tigOriginalId = this.originalId;
172
173
  }
174
+ if (this.originalClass) {
175
+ input.dataset.tigOriginalClass = this.originalClass;
176
+ }
173
177
  if (this.originalName) {
174
178
  input.dataset.tigOriginalName = this.originalName;
175
179
  }
@@ -190,7 +194,10 @@ class SwapState {
190
194
  display.id = this.originalId;
191
195
  }
192
196
 
193
- display.className = this.originalClass ?? "";
197
+ if (this.originalClass) {
198
+ display.className = this.originalClass;
199
+ }
200
+
194
201
  display.value = raw.value;
195
202
 
196
203
  for (const [name, v] of Object.entries(this.originalUiAttrs)) {
@@ -251,6 +258,7 @@ class SwapState {
251
258
 
252
259
  delete raw.dataset.tigRole;
253
260
  delete raw.dataset.tigOriginalId;
261
+ delete raw.dataset.tigOriginalClass;
254
262
  delete raw.dataset.tigOriginalName;
255
263
  }
256
264
  }
@@ -444,6 +452,28 @@ function warnLog(msg, warn) {
444
452
  }
445
453
  }
446
454
 
455
+ /**
456
+ * input / textarea 要素と内部 Guard インスタンスの対応表
457
+ *
458
+ * - key: displayElement
459
+ * - value: InputGuard(内部実装)
460
+ *
461
+ * @type {WeakMap<HTMLInputElement|HTMLTextAreaElement, InputGuard>}
462
+ */
463
+ const guardMap = new WeakMap();
464
+
465
+ document.addEventListener("selectionchange", () => {
466
+ const el = document.activeElement;
467
+ if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
468
+ return;
469
+ }
470
+ const inputGuard = guardMap.get(el);
471
+ if (!inputGuard) {
472
+ return;
473
+ }
474
+ inputGuard.onSelectionChange();
475
+ });
476
+
447
477
  /**
448
478
  * 指定した1要素に対してガードを適用し、Guard API を返す
449
479
  * @param {HTMLInputElement|HTMLTextAreaElement} element
@@ -451,9 +481,12 @@ function warnLog(msg, warn) {
451
481
  * @returns {Guard}
452
482
  */
453
483
  function attach(element, options = {}) {
454
- const guard = new InputGuard(element, options);
455
- guard.init();
456
- return guard.getGuard();
484
+ const inputGuard = new InputGuard(element, options);
485
+ inputGuard.init();
486
+ const guard = inputGuard.getGuard();
487
+ const display = guard.getDisplayElement();
488
+ guardMap.set(display, inputGuard);
489
+ return guard;
457
490
  }
458
491
 
459
492
  /**
@@ -549,7 +582,7 @@ class InputGuard {
549
582
  /**
550
583
  * ユーザーが直接入力する表示側要素
551
584
  * swapしない場合は originalElement と同一
552
- * @type {HTMLElement}
585
+ * @type {HTMLInputElement|HTMLTextAreaElement}
553
586
  */
554
587
  this.displayElement = element;
555
588
 
@@ -671,6 +704,12 @@ class InputGuard {
671
704
  */
672
705
  this.pendingCompositionCommit = false;
673
706
 
707
+ /**
708
+ * selection 更新のフレーム予約ID
709
+ * @type {number|null}
710
+ */
711
+ this.selectionFrameId = null;
712
+
674
713
  /**
675
714
  * 直前に受理した表示値、正しい情報のスナップショットのような情報(block時の戻し先)
676
715
  * @type {string}
@@ -716,9 +755,11 @@ class InputGuard {
716
755
  * @returns {SelectionState}
717
756
  */
718
757
  readSelection(el) {
758
+ const start = el.selectionStart ?? 0;
759
+ const end = el.selectionEnd ?? start;
719
760
  return {
720
- start: el.selectionStart,
721
- end: el.selectionEnd,
761
+ start,
762
+ end,
722
763
  direction: el.selectionDirection
723
764
  };
724
765
  }
@@ -832,6 +873,8 @@ class InputGuard {
832
873
  * @returns {void}
833
874
  */
834
875
  detach() {
876
+ // 管理マップから削除
877
+ guardMap.delete(this.displayElement);
835
878
  // イベント解除(displayElementがswap後の可能性があるので先に外す)
836
879
  this.unbindEvents();
837
880
  // swap復元
@@ -892,15 +935,7 @@ class InputGuard {
892
935
  this.displayElement.addEventListener("input", this.onInput);
893
936
  this.displayElement.addEventListener("beforeinput", this.onBeforeInput);
894
937
  this.displayElement.addEventListener("blur", this.onBlur);
895
-
896
- // フォーカスで編集用に戻す
897
938
  this.displayElement.addEventListener("focus", this.onFocus);
898
-
899
- // キャレット/選択範囲の変化を拾う(block時の不自然ジャンプ対策)
900
- this.displayElement.addEventListener("keyup", this.onSelectionChange);
901
- this.displayElement.addEventListener("mouseup", this.onSelectionChange);
902
- this.displayElement.addEventListener("select", this.onSelectionChange);
903
- this.displayElement.addEventListener("focus", this.onSelectionChange);
904
939
  }
905
940
 
906
941
  /**
@@ -914,10 +949,6 @@ class InputGuard {
914
949
  this.displayElement.removeEventListener("beforeinput", this.onBeforeInput);
915
950
  this.displayElement.removeEventListener("blur", this.onBlur);
916
951
  this.displayElement.removeEventListener("focus", this.onFocus);
917
- this.displayElement.removeEventListener("keyup", this.onSelectionChange);
918
- this.displayElement.removeEventListener("mouseup", this.onSelectionChange);
919
- this.displayElement.removeEventListener("select", this.onSelectionChange);
920
- this.displayElement.removeEventListener("focus", this.onSelectionChange);
921
952
  }
922
953
 
923
954
  /**
@@ -1285,12 +1316,41 @@ class InputGuard {
1285
1316
  * @returns {void}
1286
1317
  */
1287
1318
  onSelectionChange() {
1288
- // IME変換中は無視(この間はキャレット位置が不安定になることがあるため)
1289
- if (this.composing) {
1290
- return;
1319
+ const requestFrame =
1320
+ typeof requestAnimationFrame === "function"
1321
+ ? requestAnimationFrame
1322
+ : (
1323
+ /** @param {FrameRequestCallback} cb */
1324
+ (cb) => setTimeout(cb, 0)
1325
+ );
1326
+
1327
+ const cancelFrame =
1328
+ typeof cancelAnimationFrame === "function"
1329
+ ? cancelAnimationFrame
1330
+ : clearTimeout;
1331
+
1332
+ // すでに予約済みならキャンセル(selectionchange は連続発火するため)
1333
+ if (this.selectionFrameId != null) {
1334
+ cancelFrame(this.selectionFrameId);
1291
1335
  }
1292
- const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1293
- this.lastAcceptedSelection = this.readSelection(el);
1336
+
1337
+ this.selectionFrameId = requestFrame(() => {
1338
+ this.selectionFrameId = null;
1339
+
1340
+ // IME変換中は無視(キャレット位置が不安定になるため)
1341
+ if (this.composing) {
1342
+ return;
1343
+ }
1344
+
1345
+ const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1346
+
1347
+ // 要素がフォーカスされていない場合は無視
1348
+ if (document.activeElement !== el) {
1349
+ return;
1350
+ }
1351
+
1352
+ this.lastAcceptedSelection = this.readSelection(el);
1353
+ });
1294
1354
  }
1295
1355
 
1296
1356
  /**
@@ -7029,7 +7089,7 @@ function bytes(options = {}) {
7029
7089
  code: "bytes.max_overflow",
7030
7090
  rule: "bytes",
7031
7091
  phase: "validate",
7032
- detail: { max: opt.max, actual: len }
7092
+ detail: { limit: opt.max, actual: len }
7033
7093
  });
7034
7094
  }
7035
7095
  }
@@ -7375,10 +7435,10 @@ const rules = {
7375
7435
 
7376
7436
  /**
7377
7437
  * バージョン(ビルド時に置換したいならここを差し替える)
7378
- * 例: rollup replace で ""0.2.1"" を package.json の version に置換
7438
+ * 例: rollup replace で ""0.2.2"" を package.json の version に置換
7379
7439
  */
7380
7440
  // @ts-ignore
7381
7441
  // eslint-disable-next-line no-undef
7382
- const version = "0.2.1" ;
7442
+ const version = "0.2.2" ;
7383
7443
 
7384
7444
  export { ascii, attach, attachAll, autoAttach, bytes, comma, digits, filter, imeOff, kana, length, numeric, prefix, rules, suffix, trim, version, width };