text-input-guard 1.0.2 → 1.2.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 +308 -68
- package/dist/cjs/text-input-guard.min.cjs +1 -1
- package/dist/esm/text-input-guard.js +308 -69
- package/dist/esm/text-input-guard.min.js +1 -1
- package/dist/types/text-input-guard.d.ts +65 -28
- package/dist/umd/text-input-guard.js +308 -68
- package/dist/umd/text-input-guard.min.js +1 -1
- package/package.json +1 -1
|
@@ -414,7 +414,10 @@ class SwapState {
|
|
|
414
414
|
* @property {boolean} [warn] - 非対応ルールなどを console.warn するか
|
|
415
415
|
* @property {string} [invalidClass="is-invalid"] - エラー時に付けるclass名
|
|
416
416
|
* @property {SeparateValueOptions} [separateValue] - 表示値と内部値の分離設定
|
|
417
|
+
* @property {number} [historySize = 50] - 記録する履歴の最大件数
|
|
417
418
|
* @property {(result: ValidateResult) => void} [onValidate] - 評価完了時の通知(input/commitごと)
|
|
419
|
+
* @property {(result: Guard) => void} [onInput] - 入力時に値が変更されていた場合の通知
|
|
420
|
+
* @property {(result: Guard) => void} [onChange] - フォーカスが外れた値が変更されていた場合の通知
|
|
418
421
|
*/
|
|
419
422
|
|
|
420
423
|
/**
|
|
@@ -525,6 +528,100 @@ function attachAll(elements, options = {}) {
|
|
|
525
528
|
};
|
|
526
529
|
}
|
|
527
530
|
|
|
531
|
+
/**
|
|
532
|
+
* UndoとRedoを実装用のキュー
|
|
533
|
+
*/
|
|
534
|
+
class HistoryQueue {
|
|
535
|
+
/**
|
|
536
|
+
* 初期化する
|
|
537
|
+
* @param {number} maxLength
|
|
538
|
+
*/
|
|
539
|
+
constructor(maxLength = 50) {
|
|
540
|
+
/**
|
|
541
|
+
* キューの最大長(これ以上は古い履歴から削除される)
|
|
542
|
+
*/
|
|
543
|
+
this.maxLength = maxLength;
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* @type {string[]} 履歴キュー(過去から未来の順で値が入る)
|
|
547
|
+
*/
|
|
548
|
+
this.queue = [];
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* 現在の位置を示すインデックス(queue内のどこにいるか、-1は履歴なしを意味する)
|
|
552
|
+
*/
|
|
553
|
+
this.index = -1;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* 値を追加(新しい履歴)
|
|
558
|
+
* @param {string} value
|
|
559
|
+
*/
|
|
560
|
+
push(value) {
|
|
561
|
+
// 現在位置の値と同じなら追加しない
|
|
562
|
+
if (this.queue[this.index] === value) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Undo後に新規入力した場合は、現在位置より後ろのRedo履歴を消す
|
|
567
|
+
if (this.index < this.queue.length - 1) {
|
|
568
|
+
this.queue = this.queue.slice(0, this.index + 1);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// 念のため、末尾と同じ値も追加しない
|
|
572
|
+
if (this.queue[this.queue.length - 1] === value) {
|
|
573
|
+
this.index = this.queue.length - 1;
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
this.queue.push(value);
|
|
578
|
+
|
|
579
|
+
// 最大長制限
|
|
580
|
+
if (this.queue.length > this.maxLength) {
|
|
581
|
+
this.queue.shift();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
this.index = this.queue.length - 1;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Undo(前の値)
|
|
589
|
+
* @returns {string|null}
|
|
590
|
+
*/
|
|
591
|
+
undo() {
|
|
592
|
+
if (this.index <= 0) { return null; }
|
|
593
|
+
this.index--;
|
|
594
|
+
return this.queue[this.index];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Redo(次の値)
|
|
599
|
+
* @returns {string|null}
|
|
600
|
+
*/
|
|
601
|
+
redo() {
|
|
602
|
+
if (this.index >= this.queue.length - 1) { return null; }
|
|
603
|
+
this.index++;
|
|
604
|
+
return this.queue[this.index];
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* 初期化して値をセット
|
|
609
|
+
* @param {string} value
|
|
610
|
+
*/
|
|
611
|
+
reset(value) {
|
|
612
|
+
this.queue = [value];
|
|
613
|
+
this.index = 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* デバッグ用の文字列化
|
|
618
|
+
* @returns {string}
|
|
619
|
+
*/
|
|
620
|
+
toString() {
|
|
621
|
+
return `HistoryQueue(index=${this.index}, queue=[${this.queue.join(" | ")}])`;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
528
625
|
class InputGuard {
|
|
529
626
|
/**
|
|
530
627
|
* InputGuard の内部状態を初期化する(DOM/設定/イベント/パイプラインを持つ)
|
|
@@ -578,7 +675,37 @@ class InputGuard {
|
|
|
578
675
|
* attach時に登録されたバリデーション結果コールバック
|
|
579
676
|
* @type {((result: ValidateResult) => void) | undefined}
|
|
580
677
|
*/
|
|
581
|
-
this.
|
|
678
|
+
this.onAttachValidate = options.onValidate;
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* input時に値が変更されていた場合の通知
|
|
682
|
+
* @type {((result: Guard) => void) | undefined}
|
|
683
|
+
*/
|
|
684
|
+
this.onAttachInput = options.onInput;
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* onInput 判定のための直前の値(input時にこれと比較して変化を検知する)
|
|
688
|
+
* @type {string}
|
|
689
|
+
*/
|
|
690
|
+
this.previousInputValue = "";
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* blur時に値が変更されていた場合の通知
|
|
694
|
+
* @type {((result: Guard) => void) | undefined}
|
|
695
|
+
*/
|
|
696
|
+
this.onAttachChange = options.onChange;
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* onChange 判定のための直前の値(blur時にこれと比較して変化を検知する)
|
|
700
|
+
* @type {string}
|
|
701
|
+
*/
|
|
702
|
+
this.previousBlurValue = "";
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Undo/Redo用の履歴キュー(入力値の履歴を保持して、revert要求に対応するために使用)
|
|
706
|
+
* @type {HistoryQueue}
|
|
707
|
+
*/
|
|
708
|
+
this.history = new HistoryQueue(options.historySize ?? 50);
|
|
582
709
|
|
|
583
710
|
/**
|
|
584
711
|
* 実際に送信を担う要素(swap時は hidden(raw) 側)
|
|
@@ -760,6 +887,10 @@ class InputGuard {
|
|
|
760
887
|
this.bindEvents();
|
|
761
888
|
// 初期値を評価
|
|
762
889
|
this.evaluateCommit();
|
|
890
|
+
// 初期値を記録(onInput/onChangeの比較用)
|
|
891
|
+
this.previousInputValue = this.getRawValue();
|
|
892
|
+
this.previousBlurValue = this.getDisplayValue();
|
|
893
|
+
this.history.push(this.getRawValue());
|
|
763
894
|
}
|
|
764
895
|
|
|
765
896
|
/**
|
|
@@ -992,11 +1123,49 @@ class InputGuard {
|
|
|
992
1123
|
if (this.warn) ;
|
|
993
1124
|
}
|
|
994
1125
|
|
|
1126
|
+
/**
|
|
1127
|
+
* 変更前後の文字列から置換範囲と挿入文字列を推測
|
|
1128
|
+
* @param {string} beforeText
|
|
1129
|
+
* @param {string} afterText
|
|
1130
|
+
* @returns {{ replaceStart: number, replaceEnd: number, insertedText: string }}
|
|
1131
|
+
*/
|
|
1132
|
+
detectTextDiff(beforeText, afterText) {
|
|
1133
|
+
let start = 0;
|
|
1134
|
+
|
|
1135
|
+
while (
|
|
1136
|
+
start < beforeText.length &&
|
|
1137
|
+
start < afterText.length &&
|
|
1138
|
+
beforeText[start] === afterText[start]
|
|
1139
|
+
) {
|
|
1140
|
+
start++;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
let beforeEnd = beforeText.length;
|
|
1144
|
+
let afterEnd = afterText.length;
|
|
1145
|
+
|
|
1146
|
+
while (
|
|
1147
|
+
beforeEnd > start &&
|
|
1148
|
+
afterEnd > start &&
|
|
1149
|
+
beforeText[beforeEnd - 1] === afterText[afterEnd - 1]
|
|
1150
|
+
) {
|
|
1151
|
+
beforeEnd--;
|
|
1152
|
+
afterEnd--;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return {
|
|
1156
|
+
replaceStart: start,
|
|
1157
|
+
replaceEnd: beforeEnd,
|
|
1158
|
+
insertedText: afterText.slice(start, afterEnd)
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
995
1162
|
/**
|
|
996
1163
|
* ルール実行に渡すコンテキストを作る(pushErrorで errors に積める)
|
|
997
1164
|
* @returns {GuardContext}
|
|
998
1165
|
*/
|
|
999
1166
|
createCtx({ useSnapshot = true } = {}) {
|
|
1167
|
+
// 入力後のテキストを取得
|
|
1168
|
+
let afterText = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement).value;
|
|
1000
1169
|
const snap = useSnapshot ? this.beforeInputSnapshot : null;
|
|
1001
1170
|
let inputType = snap?.inputType ?? "";
|
|
1002
1171
|
let insertedText = snap?.insertedText ?? "";
|
|
@@ -1039,23 +1208,23 @@ class InputGuard {
|
|
|
1039
1208
|
baseSel = lastSel;
|
|
1040
1209
|
}
|
|
1041
1210
|
|
|
1042
|
-
//
|
|
1211
|
+
// オートコンプリートの処理
|
|
1212
|
+
// inputType が取得できないため existBeforeInputEvent 情報で判断
|
|
1213
|
+
// 差分再構成の基準が「前回の受理値」しかないため、そこから今回の編集内容を推測する必要がある。
|
|
1043
1214
|
if (beforeText.length === 0 || !this.existBeforeInputEvent) {
|
|
1044
|
-
const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
|
|
1045
|
-
const current = display.value;
|
|
1046
1215
|
// 前回の値がとれないものの、何かしら入力情報がある状態
|
|
1047
|
-
if (
|
|
1216
|
+
if (afterText.length > 0) {
|
|
1048
1217
|
// 文字列の先頭が前回の受理値と同じなら、末尾に何かしら入力されたと考えられる(オートコンプリート等)
|
|
1049
|
-
if (
|
|
1050
|
-
if (!
|
|
1218
|
+
if (afterText.toLocaleLowerCase().startsWith(beforeText.toLocaleLowerCase())) {
|
|
1219
|
+
if (!afterText.startsWith(beforeText)) {
|
|
1051
1220
|
// 文字は同じだが、大文字と小文字の情報が替わっているなどのパターン
|
|
1052
1221
|
// 差し代わりが起きているため、前回値は基準にならないと判断して、差分全体を insertedText とする
|
|
1053
1222
|
beforeText = "";
|
|
1054
|
-
insertedText =
|
|
1223
|
+
insertedText = afterText;
|
|
1055
1224
|
} else {
|
|
1056
1225
|
// 末尾に追加されたと考えられる部分を insertedText とする
|
|
1057
|
-
// 例: beforeText="abc" →
|
|
1058
|
-
insertedText =
|
|
1226
|
+
// 例: beforeText="abc" → afterText="abcde" なら、"de" が insertedText
|
|
1227
|
+
insertedText = afterText.slice(beforeText.length);
|
|
1059
1228
|
}
|
|
1060
1229
|
// キャレットは前回値の末尾にあると推測する
|
|
1061
1230
|
baseSel = /** @type {SelectionState} */ {
|
|
@@ -1074,10 +1243,10 @@ class InputGuard {
|
|
|
1074
1243
|
let replaceStart = baseSel.start ?? 0;
|
|
1075
1244
|
let replaceEnd = baseSel.end ?? 0;
|
|
1076
1245
|
|
|
1246
|
+
// 削除操作の特殊処理
|
|
1077
1247
|
// Backspace / Delete は「挿入文字がない(dataがnull)」ことが多い。
|
|
1078
1248
|
// そのままだと差分再構成で “何も変わらない” 扱いになって削除が効かなくなるため、
|
|
1079
1249
|
// 選択範囲が無い場合は「削除される1文字ぶん」の置換範囲をここで作る。
|
|
1080
|
-
//
|
|
1081
1250
|
// ※ 選択範囲がある削除は replaceStart!=replaceEnd なので補正不要(その範囲を消すだけでよい)
|
|
1082
1251
|
if (replaceStart === replaceEnd) {
|
|
1083
1252
|
if (inputType === "deleteContentBackward") {
|
|
@@ -1093,6 +1262,25 @@ class InputGuard {
|
|
|
1093
1262
|
// deleteWordBackward / deleteWordForward / deleteByCut / deleteSoftLineBackward ... etc
|
|
1094
1263
|
}
|
|
1095
1264
|
|
|
1265
|
+
// アンドゥリドゥの特殊処理
|
|
1266
|
+
if (inputType === "historyUndo" || inputType === "historyRedo") {
|
|
1267
|
+
let newText = null;
|
|
1268
|
+
console.log(inputType);
|
|
1269
|
+
console.log(this.history.toString());
|
|
1270
|
+
if (inputType === "historyUndo") {
|
|
1271
|
+
newText = this.history.undo();
|
|
1272
|
+
} else if (inputType === "historyRedo") {
|
|
1273
|
+
newText = this.history.redo();
|
|
1274
|
+
}
|
|
1275
|
+
if (newText !== null) {
|
|
1276
|
+
afterText = newText;
|
|
1277
|
+
const diff = this.detectTextDiff(beforeText, afterText);
|
|
1278
|
+
replaceStart = diff.replaceStart;
|
|
1279
|
+
replaceEnd = diff.replaceEnd;
|
|
1280
|
+
insertedText = diff.insertedText;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1096
1284
|
return {
|
|
1097
1285
|
hostElement: this.hostElement,
|
|
1098
1286
|
displayElement: this.displayElement,
|
|
@@ -1106,7 +1294,7 @@ class InputGuard {
|
|
|
1106
1294
|
replaceStart,
|
|
1107
1295
|
replaceEnd,
|
|
1108
1296
|
insertedText,
|
|
1109
|
-
afterText
|
|
1297
|
+
afterText,
|
|
1110
1298
|
pushError: (e) => this.errors.push(e),
|
|
1111
1299
|
requestRevert: (req) => {
|
|
1112
1300
|
// 1回でもrevert要求が出たら採用(最初の理由を保持)
|
|
@@ -1136,13 +1324,13 @@ class InputGuard {
|
|
|
1136
1324
|
* @returns {void}
|
|
1137
1325
|
*/
|
|
1138
1326
|
notifyValidate(source) {
|
|
1139
|
-
if (!this.
|
|
1327
|
+
if (!this.onAttachValidate) {
|
|
1140
1328
|
return;
|
|
1141
1329
|
}
|
|
1142
1330
|
|
|
1143
1331
|
const errors = this.getErrors();
|
|
1144
1332
|
|
|
1145
|
-
this.
|
|
1333
|
+
this.onAttachValidate({
|
|
1146
1334
|
guard: this.getGuard(),
|
|
1147
1335
|
source,
|
|
1148
1336
|
errors,
|
|
@@ -1316,7 +1504,10 @@ class InputGuard {
|
|
|
1316
1504
|
/** @type {string|null} */
|
|
1317
1505
|
const inputType = typeof e.inputType === "string" ? e.inputType : null;
|
|
1318
1506
|
/** @type {string|null} */
|
|
1319
|
-
|
|
1507
|
+
let insertedText = typeof e.data === "string" ? e.data : null;
|
|
1508
|
+
if (insertedText === null && (inputType === "insertLineBreak" || inputType === "insertParagraph")) {
|
|
1509
|
+
insertedText = "\n";
|
|
1510
|
+
}
|
|
1320
1511
|
this.existBeforeInputEvent = true;
|
|
1321
1512
|
this.beforeInputSnapshot = { selection, inputType, insertedText };
|
|
1322
1513
|
}
|
|
@@ -1328,6 +1519,12 @@ class InputGuard {
|
|
|
1328
1519
|
onBlur() {
|
|
1329
1520
|
// console.log("[text-input-guard] blur");
|
|
1330
1521
|
this.evaluateCommit();
|
|
1522
|
+
if (this.previousBlurValue !== this.getDisplayValue()) {
|
|
1523
|
+
this.previousBlurValue = this.getDisplayValue();
|
|
1524
|
+
if (this.onAttachChange) {
|
|
1525
|
+
this.onAttachChange(this.getGuard());
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1331
1528
|
}
|
|
1332
1529
|
|
|
1333
1530
|
/**
|
|
@@ -1346,21 +1543,27 @@ class InputGuard {
|
|
|
1346
1543
|
ctx.beforeText = "";
|
|
1347
1544
|
ctx.afterText = current;
|
|
1348
1545
|
|
|
1349
|
-
let
|
|
1350
|
-
|
|
1351
|
-
|
|
1546
|
+
let raw = current;
|
|
1547
|
+
raw = this.runNormalizeChar(raw, ctx);
|
|
1548
|
+
raw = this.runNormalizeStructure(raw, ctx);
|
|
1352
1549
|
|
|
1353
|
-
if (
|
|
1354
|
-
this.setDisplayValuePreserveCaret(display,
|
|
1355
|
-
this.syncRaw(
|
|
1550
|
+
if (raw !== current) {
|
|
1551
|
+
this.setDisplayValuePreserveCaret(display, raw, ctx);
|
|
1552
|
+
this.syncRaw(raw);
|
|
1356
1553
|
}
|
|
1357
1554
|
|
|
1358
1555
|
// 受理値更新(blockで戻す位置も自然になる)
|
|
1359
|
-
this.lastAcceptedValue =
|
|
1556
|
+
this.lastAcceptedValue = raw;
|
|
1360
1557
|
this.lastAcceptedSelection = this.readSelection(display);
|
|
1361
1558
|
|
|
1362
1559
|
// キャレット/選択範囲の変化も反映しておく(blockで戻す位置も自然になる)
|
|
1363
1560
|
this.onSelectionChange();
|
|
1561
|
+
|
|
1562
|
+
// previousInputValueも更新(次の入力で差分再構成の基準になる)
|
|
1563
|
+
this.previousInputValue = raw;
|
|
1564
|
+
|
|
1565
|
+
// 中の値が替わっている可能性を考えて、historyも更新しておく(undoしたときに不自然にならないように)
|
|
1566
|
+
this.history.push(raw);
|
|
1364
1567
|
}
|
|
1365
1568
|
|
|
1366
1569
|
/**
|
|
@@ -1452,10 +1655,7 @@ class InputGuard {
|
|
|
1452
1655
|
* @returns {GuardContext}
|
|
1453
1656
|
*/
|
|
1454
1657
|
createCtxAndNormalize() {
|
|
1455
|
-
const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
|
|
1456
|
-
const current = display.value;
|
|
1457
1658
|
const ctx = this.createCtx();
|
|
1458
|
-
ctx.afterText = current;
|
|
1459
1659
|
|
|
1460
1660
|
// 元のテキスト
|
|
1461
1661
|
const beforeText = ctx.beforeText;
|
|
@@ -1467,7 +1667,7 @@ class InputGuard {
|
|
|
1467
1667
|
const replaceStart = ctx.replaceStart;
|
|
1468
1668
|
|
|
1469
1669
|
// 現状のテキスト
|
|
1470
|
-
const tempText =
|
|
1670
|
+
const tempText = ctx.afterText;
|
|
1471
1671
|
|
|
1472
1672
|
// 作成する全体のテキスト
|
|
1473
1673
|
let newText = beforeText;
|
|
@@ -1512,7 +1712,7 @@ class InputGuard {
|
|
|
1512
1712
|
|
|
1513
1713
|
// 画面を更新
|
|
1514
1714
|
this.syncDisplay(newText);
|
|
1515
|
-
this.writeSelection(
|
|
1715
|
+
this.writeSelection(this.displayElement, newSelection);
|
|
1516
1716
|
|
|
1517
1717
|
// CTX の情報を最新の情報へ更新する
|
|
1518
1718
|
ctx.afterText = newText;
|
|
@@ -1559,6 +1759,17 @@ class InputGuard {
|
|
|
1559
1759
|
|
|
1560
1760
|
// コールバック関数処理
|
|
1561
1761
|
this.notifyValidate("input");
|
|
1762
|
+
if (this.previousInputValue !== raw) {
|
|
1763
|
+
this.previousInputValue = raw;
|
|
1764
|
+
|
|
1765
|
+
// historyに積む(undoの基準になる)
|
|
1766
|
+
this.history.push(raw);
|
|
1767
|
+
|
|
1768
|
+
// 変更コールバック
|
|
1769
|
+
if (this.onAttachInput) {
|
|
1770
|
+
this.onAttachInput(this.getGuard());
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1562
1773
|
}
|
|
1563
1774
|
|
|
1564
1775
|
/**
|
|
@@ -6977,9 +7188,10 @@ width.fromDataset = function fromDataset(dataset, _el) {
|
|
|
6977
7188
|
/**
|
|
6978
7189
|
* bytes ルールのオプション
|
|
6979
7190
|
* @typedef {Object} BytesRuleOptions
|
|
6980
|
-
* @property {number} [max] -
|
|
7191
|
+
* @property {number} [max] - バイト数。未指定なら制限なし
|
|
6981
7192
|
* @property {"block"|"error"} [mode="block"] - 入力中に最大長を超えたときの挙動
|
|
6982
7193
|
* @property {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} [unit="utf-8"] - サイズの単位(sjis系を使用する場合はfilterも必須)
|
|
7194
|
+
* @property {"\n"|"\r"|"\r\n"} [newline="\n"] - 改行の扱い(バイト数計算に影響あり)
|
|
6983
7195
|
*/
|
|
6984
7196
|
|
|
6985
7197
|
/**
|
|
@@ -6988,25 +7200,28 @@ width.fromDataset = function fromDataset(dataset, _el) {
|
|
|
6988
7200
|
*/
|
|
6989
7201
|
|
|
6990
7202
|
/**
|
|
6991
|
-
*
|
|
7203
|
+
* テキストのバイト数を調べる
|
|
6992
7204
|
* @param {string} text
|
|
6993
7205
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7206
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
6994
7207
|
* @returns {number}
|
|
6995
7208
|
*/
|
|
6996
|
-
const getTextBytesByUnit = function(text, unit) {
|
|
7209
|
+
const getTextBytesByUnit = function(text, unit, newline) {
|
|
6997
7210
|
if (text.length === 0) {
|
|
6998
7211
|
return 0;
|
|
6999
7212
|
}
|
|
7213
|
+
|
|
7214
|
+
const normalizedText = text.replace(/\r?\n/g, newline);
|
|
7215
|
+
|
|
7000
7216
|
if (unit === "utf-8") {
|
|
7001
|
-
return Mojix.toUTF8Array(
|
|
7217
|
+
return Mojix.toUTF8Array(normalizedText).length;
|
|
7002
7218
|
} else if (unit === "utf-16") {
|
|
7003
|
-
return Mojix.toUTF16Array(
|
|
7219
|
+
return Mojix.toUTF16Array(normalizedText).length * 2;
|
|
7004
7220
|
} else if (unit === "utf-32") {
|
|
7005
|
-
return Mojix.toUTF32Array(
|
|
7221
|
+
return Mojix.toUTF32Array(normalizedText).length * 4;
|
|
7006
7222
|
} else if (unit === "sjis" || unit === "cp932") {
|
|
7007
|
-
return Mojix.encode(
|
|
7223
|
+
return Mojix.encode(normalizedText, "Shift_JIS").length;
|
|
7008
7224
|
} else {
|
|
7009
|
-
// ここには来ない
|
|
7010
7225
|
throw new Error(`Invalid unit: ${unit}`);
|
|
7011
7226
|
}
|
|
7012
7227
|
};
|
|
@@ -7016,9 +7231,10 @@ const getTextBytesByUnit = function(text, unit) {
|
|
|
7016
7231
|
* @param {string} text
|
|
7017
7232
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7018
7233
|
* @param {number} max
|
|
7234
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
7019
7235
|
* @returns {string}
|
|
7020
7236
|
*/
|
|
7021
|
-
const cutTextByUnit = function(text, unit, max) {
|
|
7237
|
+
const cutTextByUnit = function(text, unit, max, newline) {
|
|
7022
7238
|
/**
|
|
7023
7239
|
* グラフェムの配列
|
|
7024
7240
|
* @type {Grapheme[]}
|
|
@@ -7037,19 +7253,10 @@ const cutTextByUnit = function(text, unit, max) {
|
|
|
7037
7253
|
const outputGraphemeArray = [];
|
|
7038
7254
|
|
|
7039
7255
|
for (let i = 0; i < graphemeArray.length; i++) {
|
|
7040
|
-
const g = graphemeArray[i];
|
|
7041
|
-
|
|
7042
7256
|
// 1グラフェムあたりの長さ
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
} else if (unit === "utf-16") {
|
|
7047
|
-
byteCount = Mojix.toUTF16Array(Mojix.toStringFromMojiArray([g])).length * 2;
|
|
7048
|
-
} else if (unit === "utf-32") {
|
|
7049
|
-
byteCount = Mojix.toUTF32Array(Mojix.toStringFromMojiArray([g])).length * 4;
|
|
7050
|
-
} else if (unit === "sjis" || unit === "cp932") {
|
|
7051
|
-
byteCount = Mojix.encode(Mojix.toStringFromMojiArray([g]), "Shift_JIS").length;
|
|
7052
|
-
}
|
|
7257
|
+
const g = graphemeArray[i];
|
|
7258
|
+
const gText = Mojix.toStringFromMojiArray([g]);
|
|
7259
|
+
const byteCount = getTextBytesByUnit(gText, unit, newline);
|
|
7053
7260
|
|
|
7054
7261
|
if (count + byteCount > max) {
|
|
7055
7262
|
// 空配列を渡すとNUL文字を返すため、空配列のときは空文字を返す
|
|
@@ -7074,15 +7281,16 @@ const cutTextByUnit = function(text, unit, max) {
|
|
|
7074
7281
|
* @param {string} insertedText 追加するテキスト
|
|
7075
7282
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7076
7283
|
* @param {number} max
|
|
7284
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
7077
7285
|
* @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
|
|
7078
7286
|
*/
|
|
7079
|
-
const cutBytes = function(beforeText, insertedText, unit, max) {
|
|
7080
|
-
const beforeTextLen = getTextBytesByUnit(beforeText, unit);
|
|
7287
|
+
const cutBytes = function(beforeText, insertedText, unit, max, newline) {
|
|
7288
|
+
const beforeTextLen = getTextBytesByUnit(beforeText, unit, newline);
|
|
7081
7289
|
|
|
7082
7290
|
// すでに最大長を超えている場合は追加のテキストを全て切る
|
|
7083
7291
|
if (beforeTextLen >= max) { return ""; }
|
|
7084
7292
|
|
|
7085
|
-
const insertedTextLen = getTextBytesByUnit(insertedText, unit);
|
|
7293
|
+
const insertedTextLen = getTextBytesByUnit(insertedText, unit, newline);
|
|
7086
7294
|
const totalLen = beforeTextLen + insertedTextLen;
|
|
7087
7295
|
|
|
7088
7296
|
if (totalLen <= max) {
|
|
@@ -7092,7 +7300,7 @@ const cutBytes = function(beforeText, insertedText, unit, max) {
|
|
|
7092
7300
|
|
|
7093
7301
|
// 超える場合は追加のテキストを切る
|
|
7094
7302
|
const allowedAddLen = max - beforeTextLen;
|
|
7095
|
-
return cutTextByUnit(insertedText, unit, allowedAddLen);
|
|
7303
|
+
return cutTextByUnit(insertedText, unit, allowedAddLen, newline);
|
|
7096
7304
|
};
|
|
7097
7305
|
|
|
7098
7306
|
/**
|
|
@@ -7101,12 +7309,10 @@ const cutBytes = function(beforeText, insertedText, unit, max) {
|
|
|
7101
7309
|
* @returns {Rule}
|
|
7102
7310
|
*/
|
|
7103
7311
|
function bytes(options = {}) {
|
|
7104
|
-
|
|
7105
|
-
const
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
unit: options.unit ?? "utf-8"
|
|
7109
|
-
};
|
|
7312
|
+
const max = typeof options.max === "number" ? options.max : undefined;
|
|
7313
|
+
const mode = options.mode ?? "block";
|
|
7314
|
+
const unit = options.unit ?? "utf-8";
|
|
7315
|
+
const newline = options.newline ?? "\n";
|
|
7110
7316
|
|
|
7111
7317
|
return {
|
|
7112
7318
|
name: "bytes",
|
|
@@ -7114,35 +7320,35 @@ function bytes(options = {}) {
|
|
|
7114
7320
|
|
|
7115
7321
|
normalizeChar(value, ctx) {
|
|
7116
7322
|
// block 以外は何もしない
|
|
7117
|
-
if (
|
|
7323
|
+
if (mode !== "block") {
|
|
7118
7324
|
return value;
|
|
7119
7325
|
}
|
|
7120
7326
|
// max 未指定なら制限なし
|
|
7121
|
-
if (typeof
|
|
7327
|
+
if (typeof max !== "number") {
|
|
7122
7328
|
return value;
|
|
7123
7329
|
}
|
|
7124
7330
|
|
|
7125
|
-
const cutText = cutBytes(ctx.beforeText, value,
|
|
7331
|
+
const cutText = cutBytes(ctx.beforeText, value, unit, max, newline);
|
|
7126
7332
|
return cutText;
|
|
7127
7333
|
},
|
|
7128
7334
|
|
|
7129
7335
|
validate(value, ctx) {
|
|
7130
7336
|
// error 以外は何もしない
|
|
7131
|
-
if (
|
|
7337
|
+
if (mode !== "error") {
|
|
7132
7338
|
return;
|
|
7133
7339
|
}
|
|
7134
7340
|
// max 未指定なら制限なし
|
|
7135
|
-
if (typeof
|
|
7341
|
+
if (typeof max !== "number") {
|
|
7136
7342
|
return;
|
|
7137
7343
|
}
|
|
7138
7344
|
|
|
7139
|
-
const len = getTextBytesByUnit(value,
|
|
7140
|
-
if (len >
|
|
7345
|
+
const len = getTextBytesByUnit(value, unit, newline);
|
|
7346
|
+
if (len > max) {
|
|
7141
7347
|
ctx.pushError({
|
|
7142
7348
|
code: "bytes.max_overflow",
|
|
7143
7349
|
rule: "bytes",
|
|
7144
7350
|
phase: "validate",
|
|
7145
|
-
detail: { limit:
|
|
7351
|
+
detail: { limit: max, actual: len }
|
|
7146
7352
|
});
|
|
7147
7353
|
}
|
|
7148
7354
|
}
|
|
@@ -7159,6 +7365,7 @@ function bytes(options = {}) {
|
|
|
7159
7365
|
* - data-tig-rules-bytes-max -> dataset.tigRulesBytesMax
|
|
7160
7366
|
* - data-tig-rules-bytes-mode -> dataset.tigRulesBytesMode
|
|
7161
7367
|
* - data-tig-rules-bytes-unit -> dataset.tigRulesBytesUnit
|
|
7368
|
+
* - data-tig-rules-bytes-newline -> dataset.tigRulesBytesNewline
|
|
7162
7369
|
*
|
|
7163
7370
|
* @param {DOMStringMap} dataset
|
|
7164
7371
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -7191,6 +7398,14 @@ bytes.fromDataset = function fromDataset(dataset, _el) {
|
|
|
7191
7398
|
options.unit = unit;
|
|
7192
7399
|
}
|
|
7193
7400
|
|
|
7401
|
+
const newline = parseDatasetEnum(
|
|
7402
|
+
dataset.tigRulesBytesNewline,
|
|
7403
|
+
["\n", "\r", "\r\n"]
|
|
7404
|
+
);
|
|
7405
|
+
if (newline != null) {
|
|
7406
|
+
options.newline = newline;
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7194
7409
|
return bytes(options);
|
|
7195
7410
|
};
|
|
7196
7411
|
|
|
@@ -7488,12 +7703,37 @@ const rules = {
|
|
|
7488
7703
|
|
|
7489
7704
|
/**
|
|
7490
7705
|
* バージョン(ビルド時に置換したいならここを差し替える)
|
|
7491
|
-
* 例: rollup replace で ""1.0
|
|
7706
|
+
* 例: rollup replace で ""1.2.0"" を package.json の version に置換
|
|
7492
7707
|
*/
|
|
7493
7708
|
// @ts-ignore
|
|
7494
7709
|
// eslint-disable-next-line no-undef
|
|
7495
|
-
const version = "1.0
|
|
7710
|
+
const version = "1.2.0" ;
|
|
7711
|
+
|
|
7712
|
+
/**
|
|
7713
|
+
* UMD公開時のグローバルオブジェクト
|
|
7714
|
+
*/
|
|
7715
|
+
const TextInputGuard = {
|
|
7716
|
+
attach,
|
|
7717
|
+
attachAll,
|
|
7718
|
+
autoAttach,
|
|
7719
|
+
rules,
|
|
7720
|
+
numeric,
|
|
7721
|
+
digits,
|
|
7722
|
+
comma,
|
|
7723
|
+
imeOff,
|
|
7724
|
+
kana,
|
|
7725
|
+
ascii,
|
|
7726
|
+
filter,
|
|
7727
|
+
length,
|
|
7728
|
+
width,
|
|
7729
|
+
bytes,
|
|
7730
|
+
prefix,
|
|
7731
|
+
suffix,
|
|
7732
|
+
trim,
|
|
7733
|
+
version
|
|
7734
|
+
};
|
|
7496
7735
|
|
|
7736
|
+
exports.TextInputGuard = TextInputGuard;
|
|
7497
7737
|
exports.ascii = ascii;
|
|
7498
7738
|
exports.attach = attach;
|
|
7499
7739
|
exports.attachAll = attachAll;
|