124 lines
3.6 KiB
JavaScript
124 lines
3.6 KiB
JavaScript
/* global module */
|
|
|
|
const TOUCH_SHIFT_MODE_OFF = "off";
|
|
const TOUCH_SHIFT_MODE_ONCE = "once";
|
|
const TOUCH_SHIFT_MODE_LOCK = "lock";
|
|
const TOUCH_SHIFT_DOUBLE_TAP_MS = 320;
|
|
|
|
function normalizeTouchShiftMode(value) {
|
|
const normalized = String(value || "")
|
|
.trim()
|
|
.toLowerCase();
|
|
if (normalized === TOUCH_SHIFT_MODE_ONCE) return TOUCH_SHIFT_MODE_ONCE;
|
|
if (normalized === TOUCH_SHIFT_MODE_LOCK) return TOUCH_SHIFT_MODE_LOCK;
|
|
return TOUCH_SHIFT_MODE_OFF;
|
|
}
|
|
|
|
function isTouchShiftActive(mode) {
|
|
const normalized = normalizeTouchShiftMode(mode);
|
|
return normalized === TOUCH_SHIFT_MODE_ONCE || normalized === TOUCH_SHIFT_MODE_LOCK;
|
|
}
|
|
|
|
function isAsciiLetter(value) {
|
|
return /[A-Za-z]/.test(String(value || ""));
|
|
}
|
|
|
|
function resolveTouchShiftModeOnTap(currentMode, lastTapAt, now, doubleTapWindowMs) {
|
|
const normalizedMode = normalizeTouchShiftMode(currentMode);
|
|
const currentTime = Number.isFinite(Number(now)) ? Number(now) : Date.now();
|
|
const previousTapAt = Number(lastTapAt);
|
|
const windowMs = Number.isFinite(Number(doubleTapWindowMs))
|
|
? Math.max(0, Number(doubleTapWindowMs))
|
|
: TOUCH_SHIFT_DOUBLE_TAP_MS;
|
|
|
|
if (normalizedMode === TOUCH_SHIFT_MODE_LOCK) {
|
|
return TOUCH_SHIFT_MODE_OFF;
|
|
}
|
|
if (
|
|
normalizedMode === TOUCH_SHIFT_MODE_ONCE &&
|
|
Number.isFinite(previousTapAt) &&
|
|
currentTime >= previousTapAt &&
|
|
currentTime - previousTapAt <= windowMs
|
|
) {
|
|
return TOUCH_SHIFT_MODE_LOCK;
|
|
}
|
|
return TOUCH_SHIFT_MODE_ONCE;
|
|
}
|
|
|
|
function findCommonPrefixLength(previousValue, nextValue) {
|
|
const max = Math.min(previousValue.length, nextValue.length);
|
|
let index = 0;
|
|
while (index < max && previousValue[index] === nextValue[index]) {
|
|
index += 1;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
function findCommonSuffixLength(previousValue, nextValue, prefixLength) {
|
|
const previousRemain = previousValue.length - prefixLength;
|
|
const nextRemain = nextValue.length - prefixLength;
|
|
const max = Math.min(previousRemain, nextRemain);
|
|
let index = 0;
|
|
while (
|
|
index < max &&
|
|
previousValue[previousValue.length - 1 - index] === nextValue[nextValue.length - 1 - index]
|
|
) {
|
|
index += 1;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
function applyTouchShiftToValue(previousValue, nextValue, mode) {
|
|
const previousText = String(previousValue || "");
|
|
const nextText = String(nextValue || "");
|
|
const normalizedMode = normalizeTouchShiftMode(mode);
|
|
|
|
if (!isTouchShiftActive(normalizedMode) || !nextText) {
|
|
return {
|
|
value: nextText,
|
|
consumedOnce: false,
|
|
touchedLetter: false,
|
|
transformed: false
|
|
};
|
|
}
|
|
|
|
const prefixLength = findCommonPrefixLength(previousText, nextText);
|
|
const suffixLength = findCommonSuffixLength(previousText, nextText, prefixLength);
|
|
const insertedEnd = nextText.length - suffixLength;
|
|
const insertedText = nextText.slice(prefixLength, insertedEnd);
|
|
|
|
if (!insertedText) {
|
|
return {
|
|
value: nextText,
|
|
consumedOnce: false,
|
|
touchedLetter: false,
|
|
transformed: false
|
|
};
|
|
}
|
|
|
|
const touchedLetter = isAsciiLetter(insertedText);
|
|
const transformedInserted = insertedText.replace(/[a-z]/g, (match) => match.toUpperCase());
|
|
const transformed = transformedInserted !== insertedText;
|
|
const value = transformed
|
|
? `${nextText.slice(0, prefixLength)}${transformedInserted}${nextText.slice(insertedEnd)}`
|
|
: nextText;
|
|
|
|
return {
|
|
value,
|
|
consumedOnce: normalizedMode === TOUCH_SHIFT_MODE_ONCE && touchedLetter,
|
|
touchedLetter,
|
|
transformed
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
TOUCH_SHIFT_DOUBLE_TAP_MS,
|
|
TOUCH_SHIFT_MODE_LOCK,
|
|
TOUCH_SHIFT_MODE_OFF,
|
|
TOUCH_SHIFT_MODE_ONCE,
|
|
applyTouchShiftToValue,
|
|
isTouchShiftActive,
|
|
normalizeTouchShiftMode,
|
|
resolveTouchShiftModeOnTap
|
|
};
|