@true-directive/base
Version:
The set of base classes for the TrueDirective Grid
628 lines (627 loc) • 27.5 kB
JavaScript
import { MaskSectionValue } from './mask-section-value.class';
import { MaskValue } from './mask-value.class';
import { Keys } from '../common/keys.class';
// Actions which are initialized by section
var MaskSectionAction = /** @class */ (function () {
function MaskSectionAction(name) {
this.name = name;
}
MaskSectionAction.NONE = new MaskSectionAction('NONE');
MaskSectionAction.APPLY = new MaskSectionAction('APPLY');
MaskSectionAction.SKIP = new MaskSectionAction('SKIP');
MaskSectionAction.GO_FWD = new MaskSectionAction('GO_FWD');
MaskSectionAction.GO_BACK = new MaskSectionAction('GO_BACK');
MaskSectionAction.GO_BACK_AND_DELETE = new MaskSectionAction('GO_BACK_AND_DELETE');
MaskSectionAction.GO_BACK_AND_BACKSPACE = new MaskSectionAction('GO_BACK_AND_BACKSPACE');
return MaskSectionAction;
}());
export { MaskSectionAction };
// Result of user input
var MaskResult = /** @class */ (function () {
function MaskResult(newValue, action, nextSectionPos) {
this.newValue = newValue;
this.action = action;
this.nextSectionPos = nextSectionPos;
}
return MaskResult;
}());
export { MaskResult };
// Section of pattern
var MaskSection = /** @class */ (function () {
function MaskSection(settings, section, // Value of the mask
delimiter, sectionType) {
if (sectionType === void 0) { sectionType = null; }
this.settings = settings;
this.section = section;
this.delimiter = delimiter;
this.sectionType = sectionType;
}
Object.defineProperty(MaskSection.prototype, "length", {
// Minimum length of value
get: function () {
if (this.hasOptions()) {
var min_1 = 99;
this.sectionType.options.forEach(function (v) {
if (v.length < min_1) {
min_1 = v.length;
}
});
return min_1;
}
return this.section.length;
},
enumerable: true,
configurable: true
});
Object.defineProperty(MaskSection.prototype, "maxLength", {
// Maximum length
get: function () {
if (this.hasOptions()) {
var ml_1 = 0;
this.sectionType.options.forEach(function (v) {
if (v.length > ml_1) {
ml_1 = v.length;
}
});
return ml_1;
}
if (this.sectionType && this.sectionType.max && ('' + this.sectionType.max).length > this.section.length) {
return ('' + this.sectionType.max).length;
}
else {
return this.section.length;
}
},
enumerable: true,
configurable: true
});
MaskSection.prototype.isEmptySection = function () {
return this.section === '';
};
MaskSection.prototype.hasOptions = function () {
return this.sectionType && this.sectionType.options && this.sectionType.options.length > 0;
};
MaskSection.prototype.hasRegExp = function () {
return this.sectionType && this.sectionType.regExp !== undefined;
};
MaskSection.prototype.isNumeric = function () {
return this.sectionType && this.sectionType.numeric;
};
MaskSection.prototype.numericValue = function (value) {
return +value;
};
MaskSection.prototype.checkMinMax = function (n) {
if (n === null) {
return null;
}
if (!this.sectionType) {
return n;
}
if (this.sectionType.min !== undefined && n < this.sectionType.min) {
n = this.sectionType.min;
}
if (this.sectionType.max !== undefined && n > this.sectionType.max) {
n = this.sectionType.max;
}
return n;
};
// String placeholders cleanup method
MaskSection.prototype.removePlaceholders = function (txt) {
return txt.split(this.settings.placeholder).join('');
};
// Increasing section's value upon Up button press
MaskSection.prototype.incValue = function (value) {
// Next option
if (this.hasOptions()) {
var i = this.sectionType.options.indexOf(value);
return i < this.sectionType.options.length - 1 ? this.sectionType.options[i + 1] : this.sectionType.options[0];
}
// Incrementing numeric value
if (this.isNumeric() && this.sectionType.max !== undefined) {
var n = this.numericValue(value);
if (isNaN(n)) {
n = this.sectionType.min === undefined ? 0 : this.sectionType.min;
}
else {
n++;
}
var res = '' + this.checkMinMax(n);
while (res.length < this.length) {
res = '0' + res;
}
return res;
}
// Returning current value if none found
return value;
};
// Предыдущее значение секции при нажатии стрелки вниз
MaskSection.prototype.decValue = function (value) {
// Previous option
if (this.hasOptions()) {
var i = this.sectionType.options.indexOf(value);
return i > 0 ? this.sectionType.options[i - 1] : this.sectionType.options[this.sectionType.options.length - 1];
}
// Previous numeric value
if (this.isNumeric() && this.sectionType.min !== undefined) {
var n = this.numericValue(value);
if (isNaN(n)) {
n = this.sectionType.min === undefined ? 0 : this.sectionType.max;
}
else {
n--;
}
var res = '' + this.checkMinMax(n);
while (res.length < this.length) {
res = '0' + res;
}
return res;
}
// Returning current value if none found
return value;
};
// Auto-correction of section's value
MaskSection.prototype.autoCorrectVal = function (s) {
if (!this.sectionType) {
return s;
}
if (this.hasOptions()) {
var variant = this.sectionType.options.find(function (v) { return v.toLowerCase() === s.toLowerCase(); });
if (!variant) {
s = this.sectionType.options.length > 0 ? this.sectionType.options[0] : '';
}
return s;
}
var res = s;
if (this.isNumeric()) {
var n = this.numericValue(res);
// Numeric value is expected
if (isNaN(n) || s === '') {
return s; // Bad or empty value
}
// Year
if (this.sectionType.datePart === 'yyyy') {
if (s.length === 2) {
n += n < 50 ? 2000 : 1900;
}
else {
if (s.length !== 4) {
return s;
}
}
}
// Numeric value recognized. Identifying min and max values.
// But only if autoCorrect is enabled.
// Otherwise numeric value of defined length is returned
if (this.settings.autoCorrect) {
n = this.checkMinMax(n);
}
if (n !== null) {
res = '' + n;
while (res.length < this.length) {
res = '0' + res;
}
}
}
while (res.length < this.length) {
res += this.settings.placeholder;
}
return res;
};
// Section extracts its value from input using following method
MaskSection.prototype.extract = function (maskValue, // Input mask value
sectionPos, // Section value's first char index
selStart, selLength) {
if (selStart === void 0) { selStart = 0; }
if (selLength === void 0) { selLength = 0; }
var res = new MaskValue();
var len = this.length;
var i2 = sectionPos + len;
// Variable length brings trouble...
if (this.length < this.maxLength) {
// ...so here a small block should be placed to identify relative position compared to delimiter or end of line,
// but less than MaxLength
var i = sectionPos;
while (i < maskValue.length && i < (sectionPos + this.maxLength)) {
if (this.delimiter !== '' && maskValue[i] === this.delimiter[0]) {
break;
}
i++;
}
i2 = i;
len = i2 - sectionPos;
}
// Delimiter might not be present...
var delimiterStart = i2;
var delimiterEnd = delimiterStart;
if (this.delimiter !== '') {
while (delimiterEnd < maskValue.length) {
var c = maskValue[delimiterEnd];
if (c === this.delimiter[delimiterEnd - delimiterStart]) {
delimiterEnd++;
}
else {
break;
}
}
}
if (i2 > maskValue.length) {
i2 = maskValue.length;
}
if (delimiterEnd > maskValue.length) {
delimiterEnd = maskValue.length;
}
var section = maskValue.substring(sectionPos, i2);
if (section.length > this.maxLength) {
throw new Error('Invalid value length: ' + section);
}
var selStart_local = selStart - sectionPos;
res.sectionPos = sectionPos;
res.before = maskValue.substring(0, sectionPos); // До секции
res.section = new MaskSectionValue(section, sectionPos, selStart); // Значение секции
res.delimiter = maskValue.substring(delimiterStart, delimiterEnd); // Разделитель
res.after = maskValue.substring(delimiterEnd); // После секции
res.inSection = selStart_local >= 0 && selStart_local <= res.section.length;
return res;
};
MaskSection.prototype.skip = function (mv, selStart) {
var res = new MaskResult(mv.value(), MaskSectionAction.SKIP, mv.nextSectionPos());
res.selStart = selStart;
return res;
};
MaskSection.prototype.none = function (mv) {
var res = new MaskResult(mv.value(), MaskSectionAction.NONE, mv.nextSectionPos());
return res;
};
// Section processing result - button pressing is applied
MaskSection.prototype.apply = function (mv, newSectionValue, selStart, direction, isLast) {
if (direction === void 0) { direction = 1; }
if (isLast === void 0) { isLast = false; }
var selStart_local = selStart - mv.sectionPos;
// If section is the last and value length is equal to maximum length and carriage is positioned at end of line...
// ... we're correcting the value
if (isLast
&& newSectionValue.length === this.maxLength
&& selStart_local >= newSectionValue.length - 1
&& direction > 0) {
newSectionValue = this.autoCorrectVal(newSectionValue);
mv.delimiter = this.delimiter;
}
// Updating the value
mv.update(newSectionValue, selStart);
// Deciding what's next
if (direction > 0) {
return this.goFwd(mv, selStart, 1, false); // Moving forward
}
else {
if (direction < 0) {
return this.goBack(mv, selStart, 1, false, isLast); // Moving backwards
}
else {
// Stand still
var res = new MaskResult(mv.value(), MaskSectionAction.APPLY, mv.nextSectionPos());
res.selStart = selStart;
res.selLength = this.settings.replaceMode && selStart_local < this.length ? 1 : 0;
return res;
}
}
};
// Section processing result - button with delimiter symbol pressing is applied
MaskSection.prototype.applyDelimiter = function (mv, selStart) {
// autoCorrection is necessary
var sv = this.autoCorrectVal(mv.section.value());
mv.update(sv, selStart);
mv.delimiter = this.delimiter;
// Moving forward
var res = new MaskResult(mv.value(), MaskSectionAction.GO_FWD, mv.nextSectionPos());
res.selStart = mv.nextSectionPos();
return res;
};
// Moving carriage backward
MaskSection.prototype.goBack = function (mv, selStart, selLength, byBackspace, isLast) {
if (byBackspace === void 0) { byBackspace = false; }
if (isLast === void 0) { isLast = false; }
var res = new MaskResult(mv.value(), MaskSectionAction.APPLY, mv.nextSectionPos());
if (selStart === 0 && selLength <= 1) {
// Case when first symbol is selected. Carriage position is set to the beginning of the line
res.selLength = 0;
return res;
}
// For last section with delimiter it doesn't matter if we are standing before or after delimiter
if (isLast && selStart > (mv.sectionPos + this.maxLength) && this.settings.replaceMode) {
selStart = mv.sectionPos + this.maxLength;
}
res.selStart = selStart - 1;
res.selLength = this.settings.replaceMode ? 1 : 0;
var selStart_local = res.selStart - mv.sectionPos;
// If we exceed minimal length...
if (selStart_local >= this.length && selStart_local >= mv.section.length) {
res.selLength = 0;
}
var newSelStart_local = res.selStart - mv.sectionPos;
if (newSelStart_local < 0 && mv.before !== '') {
res.action = byBackspace ? MaskSectionAction.GO_BACK_AND_DELETE : MaskSectionAction.GO_BACK;
}
if (res.selStart < 0) {
res.selStart = 0;
}
if (res.selLength === 0 && selStart === 0) {
res.selLength = 0;
}
return res;
};
// Moving carriage forward
MaskSection.prototype.goFwd = function (mv, selStart, selLength, appendDelimiter) {
if (appendDelimiter === void 0) { appendDelimiter = false; }
var selStart_local = selStart - mv.sectionPos;
var res = new MaskResult(mv.value(), MaskSectionAction.APPLY, mv.nextSectionPos());
// replaceMode, selLength === 0 and something is present
if (this.settings.replaceMode && selLength === 0 && mv.section.currentChar !== '') {
// Carriage stands still and next char is selected
res.selStart = selStart;
res.selLength = 1;
}
else {
// Moving one char forward
if (mv.after !== '' || mv.section.afterChars !== '') {
res.selStart = selStart + 1;
res.selLength = (mv.section.currentChar !== '' && this.settings.replaceMode) ? 1 : 0;
// Section with variable length. If we reached the end of it, then selLength = 0
if (res.selStart - mv.sectionPos >= this.length && mv.section.afterChars === '') {
res.selLength = 0;
}
}
else {
// Nothing's present next, just positoning the carriage in the end of the section
res.selStart = selStart + 1;
res.selLength = 0;
}
}
var newSelStart_local = res.selStart - mv.sectionPos;
// We're out of bounds
if (newSelStart_local > mv.section.length || (newSelStart_local === this.maxLength && mv.after !== '')) {
// Trying to apply autoCorrection
var v = this.autoCorrectVal(mv.section.value());
mv.delimiter = this.delimiter;
// After that carriage's position and next section's position could be different
mv.update(v, newSelStart_local);
res.newValue = mv.value();
res.nextSectionPos = mv.nextSectionPos();
res.selStart = mv.section.length;
// Moving forward to next section
res.action = MaskSectionAction.GO_FWD;
}
return res;
};
MaskSection.prototype.isDigit = function (char) {
return /\d/.test(char); // char.match(/\d/) !== '';
};
// Checking if Button is applicable to section
// We should return:
// - if button pressing has been applied
// - new section value
// - new carriage position
// - new selection length
MaskSection.prototype.applyKey = function (value, // Mask value
keyCode, // Key which has been pressed
keyChar, sectionPos, // Section's first symbol index
selStart, // Current carriage position
selLength, // Number of currently selected chars
acceptDelimiterChars, // If symbols of delimiter are acceptable
isLast // Is last section
) {
if (acceptDelimiterChars === void 0) { acceptDelimiterChars = false; }
if (isLast === void 0) { isLast = false; }
// Parsing the value
var mv = this.extract(value, sectionPos, selStart, selLength);
// Cursor's positioned before the section. Doing nothing
if (selStart < sectionPos) {
return this.none(mv);
}
// Cursor's positioned after the section. Skipping the section
if (!mv.inSection) {
if (value.length !== selStart || !isLast) {
return this.skip(mv, selStart);
}
}
// Relative carriage position compared to current section's beginning
var selStart_local = selStart - sectionPos;
// If carriage is positioned at the end of section and section doesn't have a delimiter
// And next section contains something
// Then we're located not in current section, but in the beginning of the next. Sending a SKIP
if (selStart_local === this.maxLength && this.delimiter === '' && mv.after !== '') {
return this.skip(mv, selStart);
}
if (keyChar !== this.delimiter[0] || !acceptDelimiterChars) {
if (this.isEmptySection() || // Empty section
(selStart === (sectionPos + mv.section.length) && // Our position's the beginning of the section
mv.section.length === this.maxLength && // Section value is complete
keyChar.length === 1)) {
// Adding a delimiter if necessary
// Setting a corrected value and asking to move to a next section
mv.delimiter = this.delimiter;
mv.update(this.autoCorrectVal(mv.section.value()), selStart);
return this.skip(mv, mv.nextSectionPos()); // To the beginning of next section
}
}
// Single char
if (keyChar.length === 1) {
var cValue_1 = mv.section.beforeChars;
cValue_1 += keyChar; // Potentially new value
// Section values are limited to a list of possible values
if (this.hasOptions() && cValue_1 !== mv.section.beforeChars) {
var optionFound_1 = null;
this.sectionType.options.some(function (variant) {
if (selStart_local < variant.length && variant.substring(0, cValue_1.length).toLowerCase() === cValue_1.toLowerCase()) {
optionFound_1 = variant;
return true;
}
return false;
});
if (optionFound_1 !== null) {
return this.apply(mv, optionFound_1, selStart, 1, isLast);
}
}
// Regular expression
if (this.hasRegExp()) {
// New value will be:
var nv = mv.section.value(keyChar);
if (this.sectionType.regExp.test(nv)) {
// And we can accept it
return this.apply(mv, nv, selStart, 1, isLast);
}
}
// Section is configured with char types
if (!this.hasRegExp() && !this.hasOptions() && !this.isEmptySection()) {
var isOk = false;
if (selStart_local < this.maxLength) {
if (this.sectionType.numeric && this.isDigit(keyChar)) {
isOk = true;
}
}
if (isOk) {
return this.apply(mv, mv.section.value(keyChar), selStart, 1, isLast);
}
}
// Delimiter char is entered
if (this.delimiter !== '' && keyChar === this.delimiter[0] && acceptDelimiterChars) {
// If nothing has been entered then there is no reason to go forward
if (this.removePlaceholders(mv.section.value()) === '' && !this.isEmptySection()) {
return this.apply(mv, mv.section.value(), selStart, 0);
}
return this.applyDelimiter(mv, selStart);
}
}
// Delete key
if (keyCode === Keys.DELETE) {
// Variable length of the section. We've exceeded minimum value
if (selStart_local >= this.length && mv.section.afterChars === '') {
if (mv.after === '') {
mv.delimiter = '';
}
return this.apply(mv, mv.section.beforeChars, selStart, 0);
}
if (mv.after === '' && mv.section.afterChars === '') {
// No more data after the char being deleted
mv.delimiter = '';
return this.apply(mv, mv.section.beforeChars, selStart, 0);
}
else {
return this.apply(mv, mv.section.value(this.settings.placeholder), selStart, 0);
}
}
// Backspace key
if (keyCode === Keys.BACKSPACE) {
if (mv.section.length === 0) {
return this.goBack(mv, selStart, selLength, true, isLast);
}
if (mv.section.beforeChars === '') {
// Nothing to delete in current section
if (mv.before === '') {
// is first
return this.none(mv);
}
// Deleting in the previous section
return this.goBack(mv, selStart, selLength, true, isLast);
}
mv.section.beforeChars = mv.section.beforeChars.substring(0, mv.section.beforeChars.length - 1);
if ((mv.section.beforeChars.length >= this.length && mv.after === '') || (mv.section.currentChar === '' && mv.after === '')) {
// Deleting completely
// Delimiter should be deleted too
mv.delimiter = '';
}
else {
// Replacing with a placeholder
if (mv.section.beforeChars.length < this.length) {
mv.section.beforeChars += this.settings.placeholder;
}
}
return this.apply(mv, mv.section.value(), selStart, -1, isLast);
}
// Moving backward
if (keyCode === Keys.LEFT) {
return this.goBack(mv, selStart, selLength, false, isLast);
}
// Moving forward
if (keyCode === Keys.RIGHT) {
return this.goFwd(mv, selStart, selLength);
}
// Incrementing value of the section upon 'Up' key pressing
if (keyCode === Keys.UP && this.settings.incDecByArrows) {
return this.apply(mv, this.incValue(mv.section.value()), selStart, 0);
}
// Decrementing value of the section upon 'Down' key pressing
if (keyCode === Keys.DOWN && this.settings.incDecByArrows) {
return this.apply(mv, this.decValue(mv.section.value()), selStart, 0);
}
return this.none(mv);
};
// If section is empty then fill with first option of list
MaskSection.prototype.setDefaultVariant = function (value, sectionPos) {
if (!this.settings.defaultOptions) {
return value;
}
if (!this.hasOptions()) {
return value;
}
// Get section value
var mv = this.extract(value, sectionPos, 0, 0);
// Remove placeholderes
var s = this.removePlaceholders(mv.section.value());
// If empty...
if (s === '') {
var applyResult = this.apply(mv, this.sectionType.options[0], 0, 0);
value = applyResult.newValue;
}
return value;
};
// Selcting first symbol of the section
MaskSection.prototype.selectFirst = function (value, sectionPos) {
var mv = this.extract(value, sectionPos, sectionPos, 0);
var res = new MaskResult(mv.value(), MaskSectionAction.APPLY, mv.nextSectionPos());
res.selStart = sectionPos;
res.selLength = this.settings.replaceMode ? 1 : 0;
if (this.isEmptySection()) {
res.selLength = 0;
}
return res;
};
// Selecting last symbol of the section
MaskSection.prototype.selectLast = function (value, sectionPos, forDelete) {
if (forDelete === void 0) { forDelete = false; }
var mv = this.extract(value, sectionPos, sectionPos, 0);
var res = new MaskResult(mv.value(), MaskSectionAction.APPLY, mv.nextSectionPos());
if (!this.settings.replaceMode) {
// We need to be positioned before the last symbol
res.selStart = sectionPos + mv.section.length - 1;
res.selLength = 0;
return res;
}
if ((!forDelete && mv.section.length >= this.length && mv.section.length < this.maxLength) || this.isEmptySection()) {
// We haven't reached maximum length of the section
res.selStart = sectionPos + mv.section.length;
res.selLength = 0;
}
else {
// We've reached maxLength - selecting last symbol
res.selStart = sectionPos + mv.section.length - 1;
res.selLength = 1;
}
return res;
};
// Autocorrection of the value. Returning everything required in order to apply changes to a control
MaskSection.prototype.autoCorrect = function (value, sectionPos, selStart, selLength) {
// Parsing
var mv = this.extract(value, sectionPos, selStart, 0);
var v = mv.section.value();
// Correcting the value
v = this.autoCorrectVal(v);
// Updating the result
mv.update(v, selStart);
var res = new MaskResult(mv.value(), MaskSectionAction.APPLY, mv.nextSectionPos());
res.selStart = res.newValue.length;
res.selLength = 0;
return res;
};
return MaskSection;
}());
export { MaskSection };