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