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