text-input-guard 1.0.1 → 1.1.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 +187 -94
- package/dist/cjs/text-input-guard.min.cjs +1 -1
- package/dist/esm/text-input-guard.js +187 -95
- package/dist/esm/text-input-guard.min.js +1 -1
- package/dist/types/text-input-guard.d.ts +57 -28
- package/dist/umd/text-input-guard.js +187 -94
- package/dist/umd/text-input-guard.min.js +1 -1
- package/package.json +1 -1
|
@@ -415,6 +415,7 @@ class SwapState {
|
|
|
415
415
|
* @property {string} [invalidClass="is-invalid"] - エラー時に付けるclass名
|
|
416
416
|
* @property {SeparateValueOptions} [separateValue] - 表示値と内部値の分離設定
|
|
417
417
|
* @property {(result: ValidateResult) => void} [onValidate] - 評価完了時の通知(input/commitごと)
|
|
418
|
+
* @property {(result: Guard) => void} [onChange] - フォーカスが外れた値が変更されていた場合の通知
|
|
418
419
|
*/
|
|
419
420
|
|
|
420
421
|
/**
|
|
@@ -580,6 +581,18 @@ class InputGuard {
|
|
|
580
581
|
*/
|
|
581
582
|
this.onValidate = options.onValidate;
|
|
582
583
|
|
|
584
|
+
/**
|
|
585
|
+
* blur時に値が変更されていた場合の通知
|
|
586
|
+
* @type {((result: Guard) => void) | undefined}
|
|
587
|
+
*/
|
|
588
|
+
this.onChange = options.onChange;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* onChange 判定のための直前の値(blur時にこれと比較して変化を検知する)
|
|
592
|
+
* @type {string}
|
|
593
|
+
*/
|
|
594
|
+
this.previousValue = "";
|
|
595
|
+
|
|
583
596
|
/**
|
|
584
597
|
* 実際に送信を担う要素(swap時は hidden(raw) 側)
|
|
585
598
|
* swapしない場合は originalElement と同一
|
|
@@ -737,6 +750,11 @@ class InputGuard {
|
|
|
737
750
|
*/
|
|
738
751
|
this.beforeInputSnapshot = null;
|
|
739
752
|
|
|
753
|
+
/**
|
|
754
|
+
* onBeforeInput イベントが発生したか否か
|
|
755
|
+
*/
|
|
756
|
+
this.existBeforeInputEvent = false;
|
|
757
|
+
|
|
740
758
|
/**
|
|
741
759
|
* ルールからのrevert要求
|
|
742
760
|
* @type {RevertRequest|null}
|
|
@@ -755,6 +773,8 @@ class InputGuard {
|
|
|
755
773
|
this.bindEvents();
|
|
756
774
|
// 初期値を評価
|
|
757
775
|
this.evaluateCommit();
|
|
776
|
+
// 初期値を記録
|
|
777
|
+
this.previousValue = this.getDisplayValue();
|
|
758
778
|
}
|
|
759
779
|
|
|
760
780
|
/**
|
|
@@ -987,18 +1007,56 @@ class InputGuard {
|
|
|
987
1007
|
if (this.warn) ;
|
|
988
1008
|
}
|
|
989
1009
|
|
|
1010
|
+
/**
|
|
1011
|
+
* 変更前後の文字列から置換範囲と挿入文字列を推測
|
|
1012
|
+
* @param {string} beforeText
|
|
1013
|
+
* @param {string} afterText
|
|
1014
|
+
* @returns {{ replaceStart: number, replaceEnd: number, insertedText: string }}
|
|
1015
|
+
*/
|
|
1016
|
+
detectTextDiff(beforeText, afterText) {
|
|
1017
|
+
let start = 0;
|
|
1018
|
+
|
|
1019
|
+
while (
|
|
1020
|
+
start < beforeText.length &&
|
|
1021
|
+
start < afterText.length &&
|
|
1022
|
+
beforeText[start] === afterText[start]
|
|
1023
|
+
) {
|
|
1024
|
+
start++;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
let beforeEnd = beforeText.length;
|
|
1028
|
+
let afterEnd = afterText.length;
|
|
1029
|
+
|
|
1030
|
+
while (
|
|
1031
|
+
beforeEnd > start &&
|
|
1032
|
+
afterEnd > start &&
|
|
1033
|
+
beforeText[beforeEnd - 1] === afterText[afterEnd - 1]
|
|
1034
|
+
) {
|
|
1035
|
+
beforeEnd--;
|
|
1036
|
+
afterEnd--;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
return {
|
|
1040
|
+
replaceStart: start,
|
|
1041
|
+
replaceEnd: beforeEnd,
|
|
1042
|
+
insertedText: afterText.slice(start, afterEnd)
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
990
1046
|
/**
|
|
991
1047
|
* ルール実行に渡すコンテキストを作る(pushErrorで errors に積める)
|
|
992
1048
|
* @returns {GuardContext}
|
|
993
1049
|
*/
|
|
994
1050
|
createCtx({ useSnapshot = true } = {}) {
|
|
1051
|
+
// 入力後のテキストを取得
|
|
1052
|
+
const afterText = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement).value;
|
|
995
1053
|
const snap = useSnapshot ? this.beforeInputSnapshot : null;
|
|
996
|
-
|
|
997
|
-
|
|
1054
|
+
let inputType = snap?.inputType ?? "";
|
|
1055
|
+
let insertedText = snap?.insertedText ?? "";
|
|
998
1056
|
|
|
999
1057
|
// 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
|
|
1000
1058
|
// display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
|
|
1001
|
-
|
|
1059
|
+
let beforeText = this.lastAcceptedValue ?? "";
|
|
1002
1060
|
|
|
1003
1061
|
// selection は2系統ある:
|
|
1004
1062
|
// - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
|
|
@@ -1034,13 +1092,45 @@ class InputGuard {
|
|
|
1034
1092
|
baseSel = lastSel;
|
|
1035
1093
|
}
|
|
1036
1094
|
|
|
1095
|
+
// オートコンプリートの処理
|
|
1096
|
+
// inputType が取得できないため existBeforeInputEvent 情報で判断
|
|
1097
|
+
// 差分再構成の基準が「前回の受理値」しかないため、そこから今回の編集内容を推測する必要がある。
|
|
1098
|
+
if (beforeText.length === 0 || !this.existBeforeInputEvent) {
|
|
1099
|
+
// 前回の値がとれないものの、何かしら入力情報がある状態
|
|
1100
|
+
if (afterText.length > 0) {
|
|
1101
|
+
// 文字列の先頭が前回の受理値と同じなら、末尾に何かしら入力されたと考えられる(オートコンプリート等)
|
|
1102
|
+
if (afterText.toLocaleLowerCase().startsWith(beforeText.toLocaleLowerCase())) {
|
|
1103
|
+
if (!afterText.startsWith(beforeText)) {
|
|
1104
|
+
// 文字は同じだが、大文字と小文字の情報が替わっているなどのパターン
|
|
1105
|
+
// 差し代わりが起きているため、前回値は基準にならないと判断して、差分全体を insertedText とする
|
|
1106
|
+
beforeText = "";
|
|
1107
|
+
insertedText = afterText;
|
|
1108
|
+
} else {
|
|
1109
|
+
// 末尾に追加されたと考えられる部分を insertedText とする
|
|
1110
|
+
// 例: beforeText="abc" → afterText="abcde" なら、"de" が insertedText
|
|
1111
|
+
insertedText = afterText.slice(beforeText.length);
|
|
1112
|
+
}
|
|
1113
|
+
// キャレットは前回値の末尾にあると推測する
|
|
1114
|
+
baseSel = /** @type {SelectionState} */ {
|
|
1115
|
+
start: beforeText.length,
|
|
1116
|
+
end: beforeText.length,
|
|
1117
|
+
direction: "none"
|
|
1118
|
+
};
|
|
1119
|
+
inputType = "insertText";
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
// existBeforeInputEvent は、少なくとも1回 beforeinput が発生したかどうかのフラグ
|
|
1124
|
+
// これが false の場合、上記のような「beforeinputがない環境での推測ロジック」を走らせる。
|
|
1125
|
+
this.existBeforeInputEvent = false;
|
|
1126
|
+
|
|
1037
1127
|
let replaceStart = baseSel.start ?? 0;
|
|
1038
1128
|
let replaceEnd = baseSel.end ?? 0;
|
|
1039
1129
|
|
|
1130
|
+
// 削除操作の特殊処理
|
|
1040
1131
|
// Backspace / Delete は「挿入文字がない(dataがnull)」ことが多い。
|
|
1041
1132
|
// そのままだと差分再構成で “何も変わらない” 扱いになって削除が効かなくなるため、
|
|
1042
1133
|
// 選択範囲が無い場合は「削除される1文字ぶん」の置換範囲をここで作る。
|
|
1043
|
-
//
|
|
1044
1134
|
// ※ 選択範囲がある削除は replaceStart!=replaceEnd なので補正不要(その範囲を消すだけでよい)
|
|
1045
1135
|
if (replaceStart === replaceEnd) {
|
|
1046
1136
|
if (inputType === "deleteContentBackward") {
|
|
@@ -1056,6 +1146,14 @@ class InputGuard {
|
|
|
1056
1146
|
// deleteWordBackward / deleteWordForward / deleteByCut / deleteSoftLineBackward ... etc
|
|
1057
1147
|
}
|
|
1058
1148
|
|
|
1149
|
+
// アンドゥリドゥの特殊処理
|
|
1150
|
+
if (inputType === "historyUndo" || inputType === "historyRedo") {
|
|
1151
|
+
const diff = this.detectTextDiff(beforeText, afterText);
|
|
1152
|
+
replaceStart = diff.replaceStart;
|
|
1153
|
+
replaceEnd = diff.replaceEnd;
|
|
1154
|
+
insertedText = diff.insertedText;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1059
1157
|
return {
|
|
1060
1158
|
hostElement: this.hostElement,
|
|
1061
1159
|
displayElement: this.displayElement,
|
|
@@ -1069,7 +1167,7 @@ class InputGuard {
|
|
|
1069
1167
|
replaceStart,
|
|
1070
1168
|
replaceEnd,
|
|
1071
1169
|
insertedText,
|
|
1072
|
-
afterText
|
|
1170
|
+
afterText,
|
|
1073
1171
|
pushError: (e) => this.errors.push(e),
|
|
1074
1172
|
requestRevert: (req) => {
|
|
1075
1173
|
// 1回でもrevert要求が出たら採用(最初の理由を保持)
|
|
@@ -1259,9 +1357,7 @@ class InputGuard {
|
|
|
1259
1357
|
try {
|
|
1260
1358
|
this.evaluateInput();
|
|
1261
1359
|
} finally {
|
|
1262
|
-
|
|
1263
|
-
// 古い snapshot を使い回さないよう、1イベントごとに破棄する
|
|
1264
|
-
this.beforeInputSnapshot = null;
|
|
1360
|
+
this.existBeforeInputEvent = false;
|
|
1265
1361
|
}
|
|
1266
1362
|
}
|
|
1267
1363
|
|
|
@@ -1281,7 +1377,11 @@ class InputGuard {
|
|
|
1281
1377
|
/** @type {string|null} */
|
|
1282
1378
|
const inputType = typeof e.inputType === "string" ? e.inputType : null;
|
|
1283
1379
|
/** @type {string|null} */
|
|
1284
|
-
|
|
1380
|
+
let insertedText = typeof e.data === "string" ? e.data : null;
|
|
1381
|
+
if (insertedText === null && (inputType === "insertLineBreak" || inputType === "insertParagraph")) {
|
|
1382
|
+
insertedText = "\n";
|
|
1383
|
+
}
|
|
1384
|
+
this.existBeforeInputEvent = true;
|
|
1285
1385
|
this.beforeInputSnapshot = { selection, inputType, insertedText };
|
|
1286
1386
|
}
|
|
1287
1387
|
|
|
@@ -1292,6 +1392,12 @@ class InputGuard {
|
|
|
1292
1392
|
onBlur() {
|
|
1293
1393
|
// console.log("[text-input-guard] blur");
|
|
1294
1394
|
this.evaluateCommit();
|
|
1395
|
+
if (this.previousValue !== this.getDisplayValue()) {
|
|
1396
|
+
this.previousValue = this.getDisplayValue();
|
|
1397
|
+
if (this.onChange) {
|
|
1398
|
+
this.onChange(this.getGuard());
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1295
1401
|
}
|
|
1296
1402
|
|
|
1297
1403
|
/**
|
|
@@ -1416,49 +1522,7 @@ class InputGuard {
|
|
|
1416
1522
|
* @returns {GuardContext}
|
|
1417
1523
|
*/
|
|
1418
1524
|
createCtxAndNormalize() {
|
|
1419
|
-
const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
|
|
1420
|
-
const current = display.value;
|
|
1421
1525
|
const ctx = this.createCtx();
|
|
1422
|
-
ctx.afterText = current;
|
|
1423
|
-
|
|
1424
|
-
/**
|
|
1425
|
-
* 入力値情報のみを使用するフォールバック
|
|
1426
|
-
* @returns {GuardContext}
|
|
1427
|
-
*/
|
|
1428
|
-
const applyFullNormalizeFromCurrent = () => {
|
|
1429
|
-
let newText = current;
|
|
1430
|
-
ctx.beforeText = "";
|
|
1431
|
-
newText = this.runNormalizeChar(newText, ctx);
|
|
1432
|
-
newText = this.runNormalizeStructure(newText, ctx);
|
|
1433
|
-
this.setDisplayValuePreserveCaret(display, newText, ctx);
|
|
1434
|
-
ctx.afterText = newText;
|
|
1435
|
-
return ctx;
|
|
1436
|
-
};
|
|
1437
|
-
|
|
1438
|
-
// beforeinput が取得できない経路(初回評価)では
|
|
1439
|
-
// 差分再構成を行うと lastAcceptedValue 基準で値を落とす可能性があるため、
|
|
1440
|
-
// 現在の全文を正規化して扱うフォールバックへ切り替える。
|
|
1441
|
-
if (!this.beforeInputSnapshot) {
|
|
1442
|
-
return applyFullNormalizeFromCurrent();
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
// オートコンプリート等では beforeinput は来ても data が空のことがあり、
|
|
1446
|
-
// 差分情報だけでは再構成不能になる。表示値がすでに変わっている場合は
|
|
1447
|
-
// 再構成を諦めて current 全体の正規化に切り替える。
|
|
1448
|
-
const isDeleteInput =
|
|
1449
|
-
ctx.inputType === "deleteContentBackward" ||
|
|
1450
|
-
ctx.inputType === "deleteContentForward";
|
|
1451
|
-
const isInsertLikeInput =
|
|
1452
|
-
ctx.inputType === "" ||
|
|
1453
|
-
ctx.inputType?.startsWith("insert");
|
|
1454
|
-
const lacksDelta =
|
|
1455
|
-
ctx.insertedText === "" &&
|
|
1456
|
-
ctx.beforeText !== current &&
|
|
1457
|
-
isInsertLikeInput &&
|
|
1458
|
-
!isDeleteInput;
|
|
1459
|
-
if (lacksDelta) {
|
|
1460
|
-
return applyFullNormalizeFromCurrent();
|
|
1461
|
-
}
|
|
1462
1526
|
|
|
1463
1527
|
// 元のテキスト
|
|
1464
1528
|
const beforeText = ctx.beforeText;
|
|
@@ -1470,7 +1534,7 @@ class InputGuard {
|
|
|
1470
1534
|
const replaceStart = ctx.replaceStart;
|
|
1471
1535
|
|
|
1472
1536
|
// 現状のテキスト
|
|
1473
|
-
const tempText =
|
|
1537
|
+
const tempText = ctx.afterText;
|
|
1474
1538
|
|
|
1475
1539
|
// 作成する全体のテキスト
|
|
1476
1540
|
let newText = beforeText;
|
|
@@ -1515,7 +1579,7 @@ class InputGuard {
|
|
|
1515
1579
|
|
|
1516
1580
|
// 画面を更新
|
|
1517
1581
|
this.syncDisplay(newText);
|
|
1518
|
-
this.writeSelection(
|
|
1582
|
+
this.writeSelection(this.displayElement, newSelection);
|
|
1519
1583
|
|
|
1520
1584
|
// CTX の情報を最新の情報へ更新する
|
|
1521
1585
|
ctx.afterText = newText;
|
|
@@ -6980,9 +7044,10 @@ width.fromDataset = function fromDataset(dataset, _el) {
|
|
|
6980
7044
|
/**
|
|
6981
7045
|
* bytes ルールのオプション
|
|
6982
7046
|
* @typedef {Object} BytesRuleOptions
|
|
6983
|
-
* @property {number} [max] -
|
|
7047
|
+
* @property {number} [max] - バイト数。未指定なら制限なし
|
|
6984
7048
|
* @property {"block"|"error"} [mode="block"] - 入力中に最大長を超えたときの挙動
|
|
6985
7049
|
* @property {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} [unit="utf-8"] - サイズの単位(sjis系を使用する場合はfilterも必須)
|
|
7050
|
+
* @property {"\n"|"\r"|"\r\n"} [newline="\n"] - 改行の扱い(バイト数計算に影響あり)
|
|
6986
7051
|
*/
|
|
6987
7052
|
|
|
6988
7053
|
/**
|
|
@@ -6991,25 +7056,28 @@ width.fromDataset = function fromDataset(dataset, _el) {
|
|
|
6991
7056
|
*/
|
|
6992
7057
|
|
|
6993
7058
|
/**
|
|
6994
|
-
*
|
|
7059
|
+
* テキストのバイト数を調べる
|
|
6995
7060
|
* @param {string} text
|
|
6996
7061
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7062
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
6997
7063
|
* @returns {number}
|
|
6998
7064
|
*/
|
|
6999
|
-
const getTextBytesByUnit = function(text, unit) {
|
|
7065
|
+
const getTextBytesByUnit = function(text, unit, newline) {
|
|
7000
7066
|
if (text.length === 0) {
|
|
7001
7067
|
return 0;
|
|
7002
7068
|
}
|
|
7069
|
+
|
|
7070
|
+
const normalizedText = text.replace(/\r?\n/g, newline);
|
|
7071
|
+
|
|
7003
7072
|
if (unit === "utf-8") {
|
|
7004
|
-
return Mojix.toUTF8Array(
|
|
7073
|
+
return Mojix.toUTF8Array(normalizedText).length;
|
|
7005
7074
|
} else if (unit === "utf-16") {
|
|
7006
|
-
return Mojix.toUTF16Array(
|
|
7075
|
+
return Mojix.toUTF16Array(normalizedText).length * 2;
|
|
7007
7076
|
} else if (unit === "utf-32") {
|
|
7008
|
-
return Mojix.toUTF32Array(
|
|
7077
|
+
return Mojix.toUTF32Array(normalizedText).length * 4;
|
|
7009
7078
|
} else if (unit === "sjis" || unit === "cp932") {
|
|
7010
|
-
return Mojix.encode(
|
|
7079
|
+
return Mojix.encode(normalizedText, "Shift_JIS").length;
|
|
7011
7080
|
} else {
|
|
7012
|
-
// ここには来ない
|
|
7013
7081
|
throw new Error(`Invalid unit: ${unit}`);
|
|
7014
7082
|
}
|
|
7015
7083
|
};
|
|
@@ -7019,9 +7087,10 @@ const getTextBytesByUnit = function(text, unit) {
|
|
|
7019
7087
|
* @param {string} text
|
|
7020
7088
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7021
7089
|
* @param {number} max
|
|
7090
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
7022
7091
|
* @returns {string}
|
|
7023
7092
|
*/
|
|
7024
|
-
const cutTextByUnit = function(text, unit, max) {
|
|
7093
|
+
const cutTextByUnit = function(text, unit, max, newline) {
|
|
7025
7094
|
/**
|
|
7026
7095
|
* グラフェムの配列
|
|
7027
7096
|
* @type {Grapheme[]}
|
|
@@ -7040,19 +7109,10 @@ const cutTextByUnit = function(text, unit, max) {
|
|
|
7040
7109
|
const outputGraphemeArray = [];
|
|
7041
7110
|
|
|
7042
7111
|
for (let i = 0; i < graphemeArray.length; i++) {
|
|
7043
|
-
const g = graphemeArray[i];
|
|
7044
|
-
|
|
7045
7112
|
// 1グラフェムあたりの長さ
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
} else if (unit === "utf-16") {
|
|
7050
|
-
byteCount = Mojix.toUTF16Array(Mojix.toStringFromMojiArray([g])).length * 2;
|
|
7051
|
-
} else if (unit === "utf-32") {
|
|
7052
|
-
byteCount = Mojix.toUTF32Array(Mojix.toStringFromMojiArray([g])).length * 4;
|
|
7053
|
-
} else if (unit === "sjis" || unit === "cp932") {
|
|
7054
|
-
byteCount = Mojix.encode(Mojix.toStringFromMojiArray([g]), "Shift_JIS").length;
|
|
7055
|
-
}
|
|
7113
|
+
const g = graphemeArray[i];
|
|
7114
|
+
const gText = Mojix.toStringFromMojiArray([g]);
|
|
7115
|
+
const byteCount = getTextBytesByUnit(gText, unit, newline);
|
|
7056
7116
|
|
|
7057
7117
|
if (count + byteCount > max) {
|
|
7058
7118
|
// 空配列を渡すとNUL文字を返すため、空配列のときは空文字を返す
|
|
@@ -7077,15 +7137,16 @@ const cutTextByUnit = function(text, unit, max) {
|
|
|
7077
7137
|
* @param {string} insertedText 追加するテキスト
|
|
7078
7138
|
* @param {"utf-8"|"utf-16"|"utf-32"|"sjis"|"cp932"} unit
|
|
7079
7139
|
* @param {number} max
|
|
7140
|
+
* @param {"\n"|"\r"|"\r\n"} newline
|
|
7080
7141
|
* @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
|
|
7081
7142
|
*/
|
|
7082
|
-
const cutBytes = function(beforeText, insertedText, unit, max) {
|
|
7083
|
-
const beforeTextLen = getTextBytesByUnit(beforeText, unit);
|
|
7143
|
+
const cutBytes = function(beforeText, insertedText, unit, max, newline) {
|
|
7144
|
+
const beforeTextLen = getTextBytesByUnit(beforeText, unit, newline);
|
|
7084
7145
|
|
|
7085
7146
|
// すでに最大長を超えている場合は追加のテキストを全て切る
|
|
7086
7147
|
if (beforeTextLen >= max) { return ""; }
|
|
7087
7148
|
|
|
7088
|
-
const insertedTextLen = getTextBytesByUnit(insertedText, unit);
|
|
7149
|
+
const insertedTextLen = getTextBytesByUnit(insertedText, unit, newline);
|
|
7089
7150
|
const totalLen = beforeTextLen + insertedTextLen;
|
|
7090
7151
|
|
|
7091
7152
|
if (totalLen <= max) {
|
|
@@ -7095,7 +7156,7 @@ const cutBytes = function(beforeText, insertedText, unit, max) {
|
|
|
7095
7156
|
|
|
7096
7157
|
// 超える場合は追加のテキストを切る
|
|
7097
7158
|
const allowedAddLen = max - beforeTextLen;
|
|
7098
|
-
return cutTextByUnit(insertedText, unit, allowedAddLen);
|
|
7159
|
+
return cutTextByUnit(insertedText, unit, allowedAddLen, newline);
|
|
7099
7160
|
};
|
|
7100
7161
|
|
|
7101
7162
|
/**
|
|
@@ -7104,12 +7165,10 @@ const cutBytes = function(beforeText, insertedText, unit, max) {
|
|
|
7104
7165
|
* @returns {Rule}
|
|
7105
7166
|
*/
|
|
7106
7167
|
function bytes(options = {}) {
|
|
7107
|
-
|
|
7108
|
-
const
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
unit: options.unit ?? "utf-8"
|
|
7112
|
-
};
|
|
7168
|
+
const max = typeof options.max === "number" ? options.max : undefined;
|
|
7169
|
+
const mode = options.mode ?? "block";
|
|
7170
|
+
const unit = options.unit ?? "utf-8";
|
|
7171
|
+
const newline = options.newline ?? "\n";
|
|
7113
7172
|
|
|
7114
7173
|
return {
|
|
7115
7174
|
name: "bytes",
|
|
@@ -7117,35 +7176,35 @@ function bytes(options = {}) {
|
|
|
7117
7176
|
|
|
7118
7177
|
normalizeChar(value, ctx) {
|
|
7119
7178
|
// block 以外は何もしない
|
|
7120
|
-
if (
|
|
7179
|
+
if (mode !== "block") {
|
|
7121
7180
|
return value;
|
|
7122
7181
|
}
|
|
7123
7182
|
// max 未指定なら制限なし
|
|
7124
|
-
if (typeof
|
|
7183
|
+
if (typeof max !== "number") {
|
|
7125
7184
|
return value;
|
|
7126
7185
|
}
|
|
7127
7186
|
|
|
7128
|
-
const cutText = cutBytes(ctx.beforeText, value,
|
|
7187
|
+
const cutText = cutBytes(ctx.beforeText, value, unit, max, newline);
|
|
7129
7188
|
return cutText;
|
|
7130
7189
|
},
|
|
7131
7190
|
|
|
7132
7191
|
validate(value, ctx) {
|
|
7133
7192
|
// error 以外は何もしない
|
|
7134
|
-
if (
|
|
7193
|
+
if (mode !== "error") {
|
|
7135
7194
|
return;
|
|
7136
7195
|
}
|
|
7137
7196
|
// max 未指定なら制限なし
|
|
7138
|
-
if (typeof
|
|
7197
|
+
if (typeof max !== "number") {
|
|
7139
7198
|
return;
|
|
7140
7199
|
}
|
|
7141
7200
|
|
|
7142
|
-
const len = getTextBytesByUnit(value,
|
|
7143
|
-
if (len >
|
|
7201
|
+
const len = getTextBytesByUnit(value, unit, newline);
|
|
7202
|
+
if (len > max) {
|
|
7144
7203
|
ctx.pushError({
|
|
7145
7204
|
code: "bytes.max_overflow",
|
|
7146
7205
|
rule: "bytes",
|
|
7147
7206
|
phase: "validate",
|
|
7148
|
-
detail: { limit:
|
|
7207
|
+
detail: { limit: max, actual: len }
|
|
7149
7208
|
});
|
|
7150
7209
|
}
|
|
7151
7210
|
}
|
|
@@ -7162,6 +7221,7 @@ function bytes(options = {}) {
|
|
|
7162
7221
|
* - data-tig-rules-bytes-max -> dataset.tigRulesBytesMax
|
|
7163
7222
|
* - data-tig-rules-bytes-mode -> dataset.tigRulesBytesMode
|
|
7164
7223
|
* - data-tig-rules-bytes-unit -> dataset.tigRulesBytesUnit
|
|
7224
|
+
* - data-tig-rules-bytes-newline -> dataset.tigRulesBytesNewline
|
|
7165
7225
|
*
|
|
7166
7226
|
* @param {DOMStringMap} dataset
|
|
7167
7227
|
* @param {HTMLInputElement|HTMLTextAreaElement} _el
|
|
@@ -7194,6 +7254,14 @@ bytes.fromDataset = function fromDataset(dataset, _el) {
|
|
|
7194
7254
|
options.unit = unit;
|
|
7195
7255
|
}
|
|
7196
7256
|
|
|
7257
|
+
const newline = parseDatasetEnum(
|
|
7258
|
+
dataset.tigRulesBytesNewline,
|
|
7259
|
+
["\n", "\r", "\r\n"]
|
|
7260
|
+
);
|
|
7261
|
+
if (newline != null) {
|
|
7262
|
+
options.newline = newline;
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7197
7265
|
return bytes(options);
|
|
7198
7266
|
};
|
|
7199
7267
|
|
|
@@ -7491,12 +7559,37 @@ const rules = {
|
|
|
7491
7559
|
|
|
7492
7560
|
/**
|
|
7493
7561
|
* バージョン(ビルド時に置換したいならここを差し替える)
|
|
7494
|
-
* 例: rollup replace で ""1.0
|
|
7562
|
+
* 例: rollup replace で ""1.1.0"" を package.json の version に置換
|
|
7495
7563
|
*/
|
|
7496
7564
|
// @ts-ignore
|
|
7497
7565
|
// eslint-disable-next-line no-undef
|
|
7498
|
-
const version = "1.0
|
|
7566
|
+
const version = "1.1.0" ;
|
|
7567
|
+
|
|
7568
|
+
/**
|
|
7569
|
+
* UMD公開時のグローバルオブジェクト
|
|
7570
|
+
*/
|
|
7571
|
+
const TextInputGuard = {
|
|
7572
|
+
attach,
|
|
7573
|
+
attachAll,
|
|
7574
|
+
autoAttach,
|
|
7575
|
+
rules,
|
|
7576
|
+
numeric,
|
|
7577
|
+
digits,
|
|
7578
|
+
comma,
|
|
7579
|
+
imeOff,
|
|
7580
|
+
kana,
|
|
7581
|
+
ascii,
|
|
7582
|
+
filter,
|
|
7583
|
+
length,
|
|
7584
|
+
width,
|
|
7585
|
+
bytes,
|
|
7586
|
+
prefix,
|
|
7587
|
+
suffix,
|
|
7588
|
+
trim,
|
|
7589
|
+
version
|
|
7590
|
+
};
|
|
7499
7591
|
|
|
7592
|
+
exports.TextInputGuard = TextInputGuard;
|
|
7500
7593
|
exports.ascii = ascii;
|
|
7501
7594
|
exports.attach = attach;
|
|
7502
7595
|
exports.attachAll = attachAll;
|