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
|
@@ -418,7 +418,10 @@
|
|
|
418
418
|
* @property {boolean} [warn] - 非対応ルールなどを console.warn するか
|
|
419
419
|
* @property {string} [invalidClass="is-invalid"] - エラー時に付けるclass名
|
|
420
420
|
* @property {SeparateValueOptions} [separateValue] - 表示値と内部値の分離設定
|
|
421
|
+
* @property {number} [historySize = 50] - 記録する履歴の最大件数
|
|
421
422
|
* @property {(result: ValidateResult) => void} [onValidate] - 評価完了時の通知(input/commitごと)
|
|
423
|
+
* @property {(result: Guard) => void} [onInput] - 入力時に値が変更されていた場合の通知
|
|
424
|
+
* @property {(result: Guard) => void} [onChange] - フォーカスが外れた値が変更されていた場合の通知
|
|
422
425
|
*/
|
|
423
426
|
|
|
424
427
|
/**
|
|
@@ -529,6 +532,100 @@
|
|
|
529
532
|
};
|
|
530
533
|
}
|
|
531
534
|
|
|
535
|
+
/**
|
|
536
|
+
* UndoとRedoを実装用のキュー
|
|
537
|
+
*/
|
|
538
|
+
class HistoryQueue {
|
|
539
|
+
/**
|
|
540
|
+
* 初期化する
|
|
541
|
+
* @param {number} maxLength
|
|
542
|
+
*/
|
|
543
|
+
constructor(maxLength = 50) {
|
|
544
|
+
/**
|
|
545
|
+
* キューの最大長(これ以上は古い履歴から削除される)
|
|
546
|
+
*/
|
|
547
|
+
this.maxLength = maxLength;
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* @type {string[]} 履歴キュー(過去から未来の順で値が入る)
|
|
551
|
+
*/
|
|
552
|
+
this.queue = [];
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* 現在の位置を示すインデックス(queue内のどこにいるか、-1は履歴なしを意味する)
|
|
556
|
+
*/
|
|
557
|
+
this.index = -1;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* 値を追加(新しい履歴)
|
|
562
|
+
* @param {string} value
|
|
563
|
+
*/
|
|
564
|
+
push(value) {
|
|
565
|
+
// 現在位置の値と同じなら追加しない
|
|
566
|
+
if (this.queue[this.index] === value) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Undo後に新規入力した場合は、現在位置より後ろのRedo履歴を消す
|
|
571
|
+
if (this.index < this.queue.length - 1) {
|
|
572
|
+
this.queue = this.queue.slice(0, this.index + 1);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// 念のため、末尾と同じ値も追加しない
|
|
576
|
+
if (this.queue[this.queue.length - 1] === value) {
|
|
577
|
+
this.index = this.queue.length - 1;
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.queue.push(value);
|
|
582
|
+
|
|
583
|
+
// 最大長制限
|
|
584
|
+
if (this.queue.length > this.maxLength) {
|
|
585
|
+
this.queue.shift();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
this.index = this.queue.length - 1;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Undo(前の値)
|
|
593
|
+
* @returns {string|null}
|
|
594
|
+
*/
|
|
595
|
+
undo() {
|
|
596
|
+
if (this.index <= 0) { return null; }
|
|
597
|
+
this.index--;
|
|
598
|
+
return this.queue[this.index];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Redo(次の値)
|
|
603
|
+
* @returns {string|null}
|
|
604
|
+
*/
|
|
605
|
+
redo() {
|
|
606
|
+
if (this.index >= this.queue.length - 1) { return null; }
|
|
607
|
+
this.index++;
|
|
608
|
+
return this.queue[this.index];
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* 初期化して値をセット
|
|
613
|
+
* @param {string} value
|
|
614
|
+
*/
|
|
615
|
+
reset(value) {
|
|
616
|
+
this.queue = [value];
|
|
617
|
+
this.index = 0;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* デバッグ用の文字列化
|
|
622
|
+
* @returns {string}
|
|
623
|
+
*/
|
|
624
|
+
toString() {
|
|
625
|
+
return `HistoryQueue(index=${this.index}, queue=[${this.queue.join(" | ")}])`;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
532
629
|
class InputGuard {
|
|
533
630
|
/**
|
|
534
631
|
* InputGuard の内部状態を初期化する(DOM/設定/イベント/パイプラインを持つ)
|
|
@@ -582,7 +679,37 @@
|
|
|
582
679
|
* attach時に登録されたバリデーション結果コールバック
|
|
583
680
|
* @type {((result: ValidateResult) => void) | undefined}
|
|
584
681
|
*/
|
|
585
|
-
this.
|
|
682
|
+
this.onAttachValidate = options.onValidate;
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* input時に値が変更されていた場合の通知
|
|
686
|
+
* @type {((result: Guard) => void) | undefined}
|
|
687
|
+
*/
|
|
688
|
+
this.onAttachInput = options.onInput;
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* onInput 判定のための直前の値(input時にこれと比較して変化を検知する)
|
|
692
|
+
* @type {string}
|
|
693
|
+
*/
|
|
694
|
+
this.previousInputValue = "";
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* blur時に値が変更されていた場合の通知
|
|
698
|
+
* @type {((result: Guard) => void) | undefined}
|
|
699
|
+
*/
|
|
700
|
+
this.onAttachChange = options.onChange;
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* onChange 判定のための直前の値(blur時にこれと比較して変化を検知する)
|
|
704
|
+
* @type {string}
|
|
705
|
+
*/
|
|
706
|
+
this.previousBlurValue = "";
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Undo/Redo用の履歴キュー(入力値の履歴を保持して、revert要求に対応するために使用)
|
|
710
|
+
* @type {HistoryQueue}
|
|
711
|
+
*/
|
|
712
|
+
this.history = new HistoryQueue(options.historySize ?? 50);
|
|
586
713
|
|
|
587
714
|
/**
|
|
588
715
|
* 実際に送信を担う要素(swap時は hidden(raw) 側)
|
|
@@ -764,6 +891,10 @@
|
|
|
764
891
|
this.bindEvents();
|
|
765
892
|
// 初期値を評価
|
|
766
893
|
this.evaluateCommit();
|
|
894
|
+
// 初期値を記録(onInput/onChangeの比較用)
|
|
895
|
+
this.previousInputValue = this.getRawValue();
|
|
896
|
+
this.previousBlurValue = this.getDisplayValue();
|
|
897
|
+
this.history.push(this.getRawValue());
|
|
767
898
|
}
|
|
768
899
|
|
|
769
900
|
/**
|
|
@@ -996,11 +1127,49 @@
|
|
|
996
1127
|
if (this.warn) ;
|
|
997
1128
|
}
|
|
998
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* 変更前後の文字列から置換範囲と挿入文字列を推測
|
|
1132
|
+
* @param {string} beforeText
|
|
1133
|
+
* @param {string} afterText
|
|
1134
|
+
* @returns {{ replaceStart: number, replaceEnd: number, insertedText: string }}
|
|
1135
|
+
*/
|
|
1136
|
+
detectTextDiff(beforeText, afterText) {
|
|
1137
|
+
let start = 0;
|
|
1138
|
+
|
|
1139
|
+
while (
|
|
1140
|
+
start < beforeText.length &&
|
|
1141
|
+
start < afterText.length &&
|
|
1142
|
+
beforeText[start] === afterText[start]
|
|
1143
|
+
) {
|
|
1144
|
+
start++;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
let beforeEnd = beforeText.length;
|
|
1148
|
+
let afterEnd = afterText.length;
|
|
1149
|
+
|
|
1150
|
+
while (
|
|
1151
|
+
beforeEnd > start &&
|
|
1152
|
+
afterEnd > start &&
|
|
1153
|
+
beforeText[beforeEnd - 1] === afterText[afterEnd - 1]
|
|
1154
|
+
) {
|
|
1155
|
+
beforeEnd--;
|
|
1156
|
+
afterEnd--;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
return {
|
|
1160
|
+
replaceStart: start,
|
|
1161
|
+
replaceEnd: beforeEnd,
|
|
1162
|
+
insertedText: afterText.slice(start, afterEnd)
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
999
1166
|
/**
|
|
1000
1167
|
* ルール実行に渡すコンテキストを作る(pushErrorで errors に積める)
|
|
1001
1168
|
* @returns {GuardContext}
|
|
1002
1169
|
*/
|
|
1003
1170
|
createCtx({ useSnapshot = true } = {}) {
|
|
1171
|
+
// 入力後のテキストを取得
|
|
1172
|
+
let afterText = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement).value;
|
|
1004
1173
|
const snap = useSnapshot ? this.beforeInputSnapshot : null;
|
|
1005
1174
|
let inputType = snap?.inputType ?? "";
|
|
1006
1175
|
let insertedText = snap?.insertedText ?? "";
|
|
@@ -1043,23 +1212,23 @@
|
|
|
1043
1212
|
baseSel = lastSel;
|
|
1044
1213
|
}
|
|
1045
1214
|
|
|
1046
|
-
//
|
|
1215
|
+
// オートコンプリートの処理
|
|
1216
|
+
// inputType が取得できないため existBeforeInputEvent 情報で判断
|
|
1217
|
+
// 差分再構成の基準が「前回の受理値」しかないため、そこから今回の編集内容を推測する必要がある。
|
|
1047
1218
|
if (beforeText.length === 0 || !this.existBeforeInputEvent) {
|
|
1048
|
-
const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
|
|
1049
|
-
const current = display.value;
|
|
1050
1219
|
// 前回の値がとれないものの、何かしら入力情報がある状態
|
|
1051
|
-
if (
|
|
1220
|
+
if (afterText.length > 0) {
|
|
1052
1221
|
// 文字列の先頭が前回の受理値と同じなら、末尾に何かしら入力されたと考えられる(オートコンプリート等)
|
|
1053
|
-
if (
|
|
1054
|
-
if (!
|
|
1222
|
+
if (afterText.toLocaleLowerCase().startsWith(beforeText.toLocaleLowerCase())) {
|
|
1223
|
+
if (!afterText.startsWith(beforeText)) {
|
|
1055
1224
|
// 文字は同じだが、大文字と小文字の情報が替わっているなどのパターン
|
|
1056
1225
|
// 差し代わりが起きているため、前回値は基準にならないと判断して、差分全体を insertedText とする
|
|
1057
1226
|
beforeText = "";
|
|
1058
|
-
insertedText =
|
|
1227
|
+
insertedText = afterText;
|
|
1059
1228
|
} else {
|
|
1060
1229
|
// 末尾に追加されたと考えられる部分を insertedText とする
|
|
1061
|
-
// 例: beforeText="abc" →
|
|
1062
|
-
insertedText =
|
|
1230
|
+
// 例: beforeText="abc" → afterText="abcde" なら、"de" が insertedText
|
|
1231
|
+
insertedText = afterText.slice(beforeText.length);
|
|
1063
1232
|
}
|
|
1064
1233
|
// キャレットは前回値の末尾にあると推測する
|
|
1065
1234
|
baseSel = /** @type {SelectionState} */ {
|
|
@@ -1078,10 +1247,10 @@
|
|
|
1078
1247
|
let replaceStart = baseSel.start ?? 0;
|
|
1079
1248
|
let replaceEnd = baseSel.end ?? 0;
|
|
1080
1249
|
|
|
1250
|
+
// 削除操作の特殊処理
|
|
1081
1251
|
// Backspace / Delete は「挿入文字がない(dataがnull)」ことが多い。
|
|
1082
1252
|
// そのままだと差分再構成で “何も変わらない” 扱いになって削除が効かなくなるため、
|
|
1083
1253
|
// 選択範囲が無い場合は「削除される1文字ぶん」の置換範囲をここで作る。
|
|
1084
|
-
//
|
|
1085
1254
|
// ※ 選択範囲がある削除は replaceStart!=replaceEnd なので補正不要(その範囲を消すだけでよい)
|
|
1086
1255
|
if (replaceStart === replaceEnd) {
|
|
1087
1256
|
if (inputType === "deleteContentBackward") {
|
|
@@ -1097,6 +1266,25 @@
|
|
|
1097
1266
|
// deleteWordBackward / deleteWordForward / deleteByCut / deleteSoftLineBackward ... etc
|
|
1098
1267
|
}
|
|
1099
1268
|
|
|
1269
|
+
// アンドゥリドゥの特殊処理
|
|
1270
|
+
if (inputType === "historyUndo" || inputType === "historyRedo") {
|
|
1271
|
+
let newText = null;
|
|
1272
|
+
console.log(inputType);
|
|
1273
|
+
console.log(this.history.toString());
|
|
1274
|
+
if (inputType === "historyUndo") {
|
|
1275
|
+
newText = this.history.undo();
|
|
1276
|
+
} else if (inputType === "historyRedo") {
|
|
1277
|
+
newText = this.history.redo();
|
|
1278
|
+
}
|
|
1279
|
+
if (newText !== null) {
|
|
1280
|
+
afterText = newText;
|
|
1281
|
+
const diff = this.detectTextDiff(beforeText, afterText);
|
|
1282
|
+
replaceStart = diff.replaceStart;
|
|
1283
|
+
replaceEnd = diff.replaceEnd;
|
|
1284
|
+
insertedText = diff.insertedText;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1100
1288
|
return {
|
|
1101
1289
|
hostElement: this.hostElement,
|
|
1102
1290
|
displayElement: this.displayElement,
|
|
@@ -1110,7 +1298,7 @@
|
|
|
1110
1298
|
replaceStart,
|
|
1111
1299
|
replaceEnd,
|
|
1112
1300
|
insertedText,
|
|
1113
|
-
afterText
|
|
1301
|
+
afterText,
|
|
1114
1302
|
pushError: (e) => this.errors.push(e),
|
|
1115
1303
|
requestRevert: (req) => {
|
|
1116
1304
|
// 1回でもrevert要求が出たら採用(最初の理由を保持)
|
|
@@ -1140,13 +1328,13 @@
|
|
|
1140
1328
|
* @returns {void}
|
|
1141
1329
|
*/
|
|
1142
1330
|
notifyValidate(source) {
|
|
1143
|
-
if (!this.
|
|
1331
|
+
if (!this.onAttachValidate) {
|
|
1144
1332
|
return;
|
|
1145
1333
|
}
|
|
1146
1334
|
|
|
1147
1335
|
const errors = this.getErrors();
|
|
1148
1336
|
|
|
1149
|
-
this.
|
|
1337
|
+
this.onAttachValidate({
|
|
1150
1338
|
guard: this.getGuard(),
|
|
1151
1339
|
source,
|
|
1152
1340
|
errors,
|
|
@@ -1320,7 +1508,10 @@
|
|
|
1320
1508
|
/** @type {string|null} */
|
|
1321
1509
|
const inputType = typeof e.inputType === "string" ? e.inputType : null;
|
|
1322
1510
|
/** @type {string|null} */
|
|
1323
|
-
|
|
1511
|
+
let insertedText = typeof e.data === "string" ? e.data : null;
|
|
1512
|
+
if (insertedText === null && (inputType === "insertLineBreak" || inputType === "insertParagraph")) {
|
|
1513
|
+
insertedText = "\n";
|
|
1514
|
+
}
|
|
1324
1515
|
this.existBeforeInputEvent = true;
|
|
1325
1516
|
this.beforeInputSnapshot = { selection, inputType, insertedText };
|
|
1326
1517
|
}
|
|
@@ -1332,6 +1523,12 @@
|
|
|
1332
1523
|
onBlur() {
|
|
1333
1524
|
// console.log("[text-input-guard] blur");
|
|
1334
1525
|
this.evaluateCommit();
|
|
1526
|
+
if (this.previousBlurValue !== this.getDisplayValue()) {
|
|
1527
|
+
this.previousBlurValue = this.getDisplayValue();
|
|
1528
|
+
if (this.onAttachChange) {
|
|
1529
|
+
this.onAttachChange(this.getGuard());
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1335
1532
|
}
|
|
1336
1533
|
|
|
1337
1534
|
/**
|
|
@@ -1350,21 +1547,27 @@
|
|
|
1350
1547
|
ctx.beforeText = "";
|
|
1351
1548
|
ctx.afterText = current;
|
|
1352
1549
|
|
|
1353
|
-
let
|
|
1354
|
-
|
|
1355
|
-
|
|
1550
|
+
let raw = current;
|
|
1551
|
+
raw = this.runNormalizeChar(raw, ctx);
|
|
1552
|
+
raw = this.runNormalizeStructure(raw, ctx);
|
|
1356
1553
|
|
|
1357
|
-
if (
|
|
1358
|
-
this.setDisplayValuePreserveCaret(display,
|
|
1359
|
-
this.syncRaw(
|
|
1554
|
+
if (raw !== current) {
|
|
1555
|
+
this.setDisplayValuePreserveCaret(display, raw, ctx);
|
|
1556
|
+
this.syncRaw(raw);
|
|
1360
1557
|
}
|
|
1361
1558
|
|
|
1362
1559
|
// 受理値更新(blockで戻す位置も自然になる)
|
|
1363
|
-
this.lastAcceptedValue =
|
|
1560
|
+
this.lastAcceptedValue = raw;
|
|
1364
1561
|
this.lastAcceptedSelection = this.readSelection(display);
|
|
1365
1562
|
|
|
1366
1563
|
// キャレット/選択範囲の変化も反映しておく(blockで戻す位置も自然になる)
|
|
1367
1564
|
this.onSelectionChange();
|
|
1565
|
+
|
|
1566
|
+
// previousInputValueも更新(次の入力で差分再構成の基準になる)
|
|
1567
|
+
this.previousInputValue = raw;
|
|
1568
|
+
|
|
1569
|
+
// 中の値が替わっている可能性を考えて、historyも更新しておく(undoしたときに不自然にならないように)
|
|
1570
|
+
this.history.push(raw);
|
|
1368
1571
|
}
|
|
1369
1572
|
|
|
1370
1573
|
/**
|
|
@@ -1456,10 +1659,7 @@
|
|
|
1456
1659
|
* @returns {GuardContext}
|
|
1457
1660
|
*/
|
|
1458
1661
|
createCtxAndNormalize() {
|
|
1459
|
-
const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
|
|
1460
|
-
const current = display.value;
|
|
1461
1662
|
const ctx = this.createCtx();
|
|
1462
|
-
ctx.afterText = current;
|
|
1463
1663
|
|
|
1464
1664
|
// 元のテキスト
|
|
1465
1665
|
const beforeText = ctx.beforeText;
|
|
@@ -1471,7 +1671,7 @@
|
|
|
1471
1671
|
const replaceStart = ctx.replaceStart;
|
|
1472
1672
|
|
|
1473
1673
|
// 現状のテキスト
|
|
1474
|
-
const tempText =
|
|
1674
|
+
const tempText = ctx.afterText;
|
|
1475
1675
|
|
|
1476
1676
|
// 作成する全体のテキスト
|
|
1477
1677
|
let newText = beforeText;
|
|
@@ -1516,7 +1716,7 @@
|
|
|
1516
1716
|
|
|
1517
1717
|
// 画面を更新
|
|
1518
1718
|
this.syncDisplay(newText);
|
|
1519
|
-
this.writeSelection(
|
|
1719
|
+
this.writeSelection(this.displayElement, newSelection);
|
|
1520
1720
|
|
|
1521
1721
|
// CTX の情報を最新の情報へ更新する
|
|
1522
1722
|
ctx.afterText = newText;
|
|
@@ -1563,6 +1763,17 @@
|
|
|
1563
1763
|
|
|
1564
1764
|
// コールバック関数処理
|
|
1565
1765
|
this.notifyValidate("input");
|
|
1766
|
+
if (this.previousInputValue !== raw) {
|
|
1767
|
+
this.previousInputValue = raw;
|
|
1768
|
+
|
|
1769
|
+
// historyに積む(undoの基準になる)
|
|
1770
|
+
this.history.push(raw);
|
|
1771
|
+
|
|
1772
|
+
// 変更コールバック
|
|
1773
|
+
if (this.onAttachInput) {
|
|
1774
|
+
this.onAttachInput(this.getGuard());
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1566
1777
|
}
|
|
1567
1778
|
|
|
1568
1779
|
/**
|
|
@@ -6981,9 +7192,10 @@
|
|
|
6981
7192
|
/**
|
|
6982
7193
|
* bytes ルールのオプション
|
|
6983
7194
|
* @typedef {Object} BytesRuleOptions
|
|
6984
|
-
* @property {number} [max] -
|
|
7195
|
+
* @property {number} [max] - バイト数。未指定なら制限なし
|
|
6985
7196
|
* @property {"block"|"error"} [mode="block"] - 入力中に最大長を超えたときの挙動
|
|
6986
7197
|
* @property {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} [unit="utf-8"] - サイズの単位(sjis系を使用する場合はfilterも必須)
|
|
7198
|
+
* @property {"\n"|"\r"|"\r\n"} [newline="\n"] - 改行の扱い(バイト数計算に影響あり)
|
|
6987
7199
|
*/
|
|
6988
7200
|
|
|
6989
7201
|
/**
|
|
@@ -6992,25 +7204,28 @@
|
|
|
6992
7204
|
*/
|
|
6993
7205
|
|
|
6994
7206
|
/**
|
|
6995
|
-
*
|
|
7207
|
+
* テキストのバイト数を調べる
|
|
6996
7208
|
* @param {string} text
|
|
6997
7209
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7210
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
6998
7211
|
* @returns {number}
|
|
6999
7212
|
*/
|
|
7000
|
-
const getTextBytesByUnit = function(text, unit) {
|
|
7213
|
+
const getTextBytesByUnit = function(text, unit, newline) {
|
|
7001
7214
|
if (text.length === 0) {
|
|
7002
7215
|
return 0;
|
|
7003
7216
|
}
|
|
7217
|
+
|
|
7218
|
+
const normalizedText = text.replace(/\r?\n/g, newline);
|
|
7219
|
+
|
|
7004
7220
|
if (unit === "utf-8") {
|
|
7005
|
-
return Mojix.toUTF8Array(
|
|
7221
|
+
return Mojix.toUTF8Array(normalizedText).length;
|
|
7006
7222
|
} else if (unit === "utf-16") {
|
|
7007
|
-
return Mojix.toUTF16Array(
|
|
7223
|
+
return Mojix.toUTF16Array(normalizedText).length * 2;
|
|
7008
7224
|
} else if (unit === "utf-32") {
|
|
7009
|
-
return Mojix.toUTF32Array(
|
|
7225
|
+
return Mojix.toUTF32Array(normalizedText).length * 4;
|
|
7010
7226
|
} else if (unit === "sjis" || unit === "cp932") {
|
|
7011
|
-
return Mojix.encode(
|
|
7227
|
+
return Mojix.encode(normalizedText, "Shift_JIS").length;
|
|
7012
7228
|
} else {
|
|
7013
|
-
// ここには来ない
|
|
7014
7229
|
throw new Error(`Invalid unit: ${unit}`);
|
|
7015
7230
|
}
|
|
7016
7231
|
};
|
|
@@ -7020,9 +7235,10 @@
|
|
|
7020
7235
|
* @param {string} text
|
|
7021
7236
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7022
7237
|
* @param {number} max
|
|
7238
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
7023
7239
|
* @returns {string}
|
|
7024
7240
|
*/
|
|
7025
|
-
const cutTextByUnit = function(text, unit, max) {
|
|
7241
|
+
const cutTextByUnit = function(text, unit, max, newline) {
|
|
7026
7242
|
/**
|
|
7027
7243
|
* グラフェムの配列
|
|
7028
7244
|
* @type {Grapheme[]}
|
|
@@ -7041,19 +7257,10 @@
|
|
|
7041
7257
|
const outputGraphemeArray = [];
|
|
7042
7258
|
|
|
7043
7259
|
for (let i = 0; i < graphemeArray.length; i++) {
|
|
7044
|
-
const g = graphemeArray[i];
|
|
7045
|
-
|
|
7046
7260
|
// 1グラフェムあたりの長さ
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
} else if (unit === "utf-16") {
|
|
7051
|
-
byteCount = Mojix.toUTF16Array(Mojix.toStringFromMojiArray([g])).length * 2;
|
|
7052
|
-
} else if (unit === "utf-32") {
|
|
7053
|
-
byteCount = Mojix.toUTF32Array(Mojix.toStringFromMojiArray([g])).length * 4;
|
|
7054
|
-
} else if (unit === "sjis" || unit === "cp932") {
|
|
7055
|
-
byteCount = Mojix.encode(Mojix.toStringFromMojiArray([g]), "Shift_JIS").length;
|
|
7056
|
-
}
|
|
7261
|
+
const g = graphemeArray[i];
|
|
7262
|
+
const gText = Mojix.toStringFromMojiArray([g]);
|
|
7263
|
+
const byteCount = getTextBytesByUnit(gText, unit, newline);
|
|
7057
7264
|
|
|
7058
7265
|
if (count + byteCount > max) {
|
|
7059
7266
|
// 空配列を渡すとNUL文字を返すため、空配列のときは空文字を返す
|
|
@@ -7078,15 +7285,16 @@
|
|
|
7078
7285
|
* @param {string} insertedText 追加するテキスト
|
|
7079
7286
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7080
7287
|
* @param {number} max
|
|
7288
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
7081
7289
|
* @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
|
|
7082
7290
|
*/
|
|
7083
|
-
const cutBytes = function(beforeText, insertedText, unit, max) {
|
|
7084
|
-
const beforeTextLen = getTextBytesByUnit(beforeText, unit);
|
|
7291
|
+
const cutBytes = function(beforeText, insertedText, unit, max, newline) {
|
|
7292
|
+
const beforeTextLen = getTextBytesByUnit(beforeText, unit, newline);
|
|
7085
7293
|
|
|
7086
7294
|
// すでに最大長を超えている場合は追加のテキストを全て切る
|
|
7087
7295
|
if (beforeTextLen >= max) { return ""; }
|
|
7088
7296
|
|
|
7089
|
-
const insertedTextLen = getTextBytesByUnit(insertedText, unit);
|
|
7297
|
+
const insertedTextLen = getTextBytesByUnit(insertedText, unit, newline);
|
|
7090
7298
|
const totalLen = beforeTextLen + insertedTextLen;
|
|
7091
7299
|
|
|
7092
7300
|
if (totalLen <= max) {
|
|
@@ -7096,7 +7304,7 @@
|
|
|
7096
7304
|
|
|
7097
7305
|
// 超える場合は追加のテキストを切る
|
|
7098
7306
|
const allowedAddLen = max - beforeTextLen;
|
|
7099
|
-
return cutTextByUnit(insertedText, unit, allowedAddLen);
|
|
7307
|
+
return cutTextByUnit(insertedText, unit, allowedAddLen, newline);
|
|
7100
7308
|
};
|
|
7101
7309
|
|
|
7102
7310
|
/**
|
|
@@ -7105,12 +7313,10 @@
|
|
|
7105
7313
|
* @returns {Rule}
|
|
7106
7314
|
*/
|
|
7107
7315
|
function bytes(options = {}) {
|
|
7108
|
-
|
|
7109
|
-
const
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
unit: options.unit ?? "utf-8"
|
|
7113
|
-
};
|
|
7316
|
+
const max = typeof options.max === "number" ? options.max : undefined;
|
|
7317
|
+
const mode = options.mode ?? "block";
|
|
7318
|
+
const unit = options.unit ?? "utf-8";
|
|
7319
|
+
const newline = options.newline ?? "\n";
|
|
7114
7320
|
|
|
7115
7321
|
return {
|
|
7116
7322
|
name: "bytes",
|
|
@@ -7118,35 +7324,35 @@
|
|
|
7118
7324
|
|
|
7119
7325
|
normalizeChar(value, ctx) {
|
|
7120
7326
|
// block 以外は何もしない
|
|
7121
|
-
if (
|
|
7327
|
+
if (mode !== "block") {
|
|
7122
7328
|
return value;
|
|
7123
7329
|
}
|
|
7124
7330
|
// max 未指定なら制限なし
|
|
7125
|
-
if (typeof
|
|
7331
|
+
if (typeof max !== "number") {
|
|
7126
7332
|
return value;
|
|
7127
7333
|
}
|
|
7128
7334
|
|
|
7129
|
-
const cutText = cutBytes(ctx.beforeText, value,
|
|
7335
|
+
const cutText = cutBytes(ctx.beforeText, value, unit, max, newline);
|
|
7130
7336
|
return cutText;
|
|
7131
7337
|
},
|
|
7132
7338
|
|
|
7133
7339
|
validate(value, ctx) {
|
|
7134
7340
|
// error 以外は何もしない
|
|
7135
|
-
if (
|
|
7341
|
+
if (mode !== "error") {
|
|
7136
7342
|
return;
|
|
7137
7343
|
}
|
|
7138
7344
|
// max 未指定なら制限なし
|
|
7139
|
-
if (typeof
|
|
7345
|
+
if (typeof max !== "number") {
|
|
7140
7346
|
return;
|
|
7141
7347
|
}
|
|
7142
7348
|
|
|
7143
|
-
const len = getTextBytesByUnit(value,
|
|
7144
|
-
if (len >
|
|
7349
|
+
const len = getTextBytesByUnit(value, unit, newline);
|
|
7350
|
+
if (len > max) {
|
|
7145
7351
|
ctx.pushError({
|
|
7146
7352
|
code: "bytes.max_overflow",
|
|
7147
7353
|
rule: "bytes",
|
|
7148
7354
|
phase: "validate",
|
|
7149
|
-
detail: { limit:
|
|
7355
|
+
detail: { limit: max, actual: len }
|
|
7150
7356
|
});
|
|
7151
7357
|
}
|
|
7152
7358
|
}
|
|
@@ -7163,6 +7369,7 @@
|
|
|
7163
7369
|
* - data-tig-rules-bytes-max -> dataset.tigRulesBytesMax
|
|
7164
7370
|
* - data-tig-rules-bytes-mode -> dataset.tigRulesBytesMode
|
|
7165
7371
|
* - data-tig-rules-bytes-unit -> dataset.tigRulesBytesUnit
|
|
7372
|
+
* - data-tig-rules-bytes-newline -> dataset.tigRulesBytesNewline
|
|
7166
7373
|
*
|
|
7167
7374
|
* @param {DOMStringMap} dataset
|
|
7168
7375
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -7195,6 +7402,14 @@
|
|
|
7195
7402
|
options.unit = unit;
|
|
7196
7403
|
}
|
|
7197
7404
|
|
|
7405
|
+
const newline = parseDatasetEnum(
|
|
7406
|
+
dataset.tigRulesBytesNewline,
|
|
7407
|
+
["\n", "\r", "\r\n"]
|
|
7408
|
+
);
|
|
7409
|
+
if (newline != null) {
|
|
7410
|
+
options.newline = newline;
|
|
7411
|
+
}
|
|
7412
|
+
|
|
7198
7413
|
return bytes(options);
|
|
7199
7414
|
};
|
|
7200
7415
|
|
|
@@ -7492,12 +7707,37 @@
|
|
|
7492
7707
|
|
|
7493
7708
|
/**
|
|
7494
7709
|
* バージョン(ビルド時に置換したいならここを差し替える)
|
|
7495
|
-
* 例: rollup replace で ""1.0
|
|
7710
|
+
* 例: rollup replace で ""1.2.0"" を package.json の version に置換
|
|
7496
7711
|
*/
|
|
7497
7712
|
// @ts-ignore
|
|
7498
7713
|
// eslint-disable-next-line no-undef
|
|
7499
|
-
const version = "1.0
|
|
7714
|
+
const version = "1.2.0" ;
|
|
7715
|
+
|
|
7716
|
+
/**
|
|
7717
|
+
* UMD公開時のグローバルオブジェクト
|
|
7718
|
+
*/
|
|
7719
|
+
const TextInputGuard = {
|
|
7720
|
+
attach,
|
|
7721
|
+
attachAll,
|
|
7722
|
+
autoAttach,
|
|
7723
|
+
rules,
|
|
7724
|
+
numeric,
|
|
7725
|
+
digits,
|
|
7726
|
+
comma,
|
|
7727
|
+
imeOff,
|
|
7728
|
+
kana,
|
|
7729
|
+
ascii,
|
|
7730
|
+
filter,
|
|
7731
|
+
length,
|
|
7732
|
+
width,
|
|
7733
|
+
bytes,
|
|
7734
|
+
prefix,
|
|
7735
|
+
suffix,
|
|
7736
|
+
trim,
|
|
7737
|
+
version
|
|
7738
|
+
};
|
|
7500
7739
|
|
|
7740
|
+
exports.TextInputGuard = TextInputGuard;
|
|
7501
7741
|
exports.ascii = ascii;
|
|
7502
7742
|
exports.attach = attach;
|
|
7503
7743
|
exports.attachAll = attachAll;
|