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.
@@ -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
- display.className = this.originalClass ?? "";
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
  }
@@ -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 guard = new InputGuard(element, options);
461
- guard.init();
462
- return guard.getGuard();
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 {HTMLElement}
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: el.selectionStart,
727
- end: el.selectionEnd,
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
- // IME変換中は無視(この間はキャレット位置が不安定になることがあるため)
1295
- if (this.composing) {
1296
- return;
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
- const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1299
- this.lastAcceptedSelection = this.readSelection(el);
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
  /**
@@ -7035,7 +7095,7 @@
7035
7095
  code: "bytes.max_overflow",
7036
7096
  rule: "bytes",
7037
7097
  phase: "validate",
7038
- detail: { max: opt.max, actual: len }
7098
+ detail: { limit: opt.max, actual: len }
7039
7099
  });
7040
7100
  }
7041
7101
  }
@@ -7381,11 +7441,11 @@
7381
7441
 
7382
7442
  /**
7383
7443
  * バージョン(ビルド時に置換したいならここを差し替える)
7384
- * 例: rollup replace で ""0.2.1"" を package.json の version に置換
7444
+ * 例: rollup replace で ""0.2.2"" を package.json の version に置換
7385
7445
  */
7386
7446
  // @ts-ignore
7387
7447
  // eslint-disable-next-line no-undef
7388
- const version = "0.2.1" ;
7448
+ const version = "0.2.2" ;
7389
7449
 
7390
7450
  exports.ascii = ascii;
7391
7451
  exports.attach = attach;