react-masked-field
Version:
A masked field component built in React
326 lines (319 loc) • 12.3 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var tslib = require('tslib');
var React = _interopDefault(require('react'));
var PropTypes = _interopDefault(require('prop-types'));
function getSelection(node) {
return {
start: node.selectionStart || 0,
end: node.selectionEnd || 0
};
}
function setSelection(node, start, end) {
node.setSelectionRange(start, end);
}
/**
* Copyright (c) 2015 ZenPayroll
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var DEFAULT_TRANSLATIONS = {
9: /\d/,
a: /[A-Za-z]/,
'*': /[A-Za-z0-9]/
};
var BLANK_CHAR = '_';
var AlwaysMaskedField = /** @class */ (function (_super) {
tslib.__extends(AlwaysMaskedField, _super);
function AlwaysMaskedField(props) {
var _this = _super.call(this, props) || this;
_this.buffer = [];
_this.firstNonMaskIdx = -1;
_this.cursorPos = -1;
_this.input = null;
_this.handleFocus = function (e) {
setTimeout(function () { return _this.setSelection(_this.cursorPos); }, 0);
var onFocus = _this.props.onFocus;
if (onFocus) {
onFocus(e);
}
_this.setState({ value: _this.bufferString() });
};
_this.handleBlur = function (e) {
if (_this.isBufferEmpty()) {
_this.setValue('');
}
var onBlur = _this.props.onBlur;
if (onBlur) {
onBlur(e);
}
};
_this.handleKeyDown = function (e) {
if (e.key === 'Backspace' || e.key === 'Delete') {
var _a = _this.getSelection(), start = _a.start, end = _a.end;
if (start === end) {
start = e.key === 'Delete' ? _this.nextNonMaskIdx(start - 1) : _this.prevNonMaskIdx(start);
end = _this.nextNonMaskIdx(start);
}
var newVal = void 0;
var pattern = _this.getPattern(start);
if (pattern && pattern.test(_this.buffer[end])) {
var value = _this.state.value;
newVal = _this.maskedValue(value.substring(end), start);
}
else {
_this.resetBuffer(start, end);
newVal = _this.bufferString();
}
_this.setValue(newVal);
_this.cursorPos = Math.max(start, _this.firstNonMaskIdx);
e.preventDefault();
}
var onKeyDown = _this.props.onKeyDown;
if (onKeyDown) {
onKeyDown(e);
}
};
_this.handleChange = function (e) {
var value = _this.maskedValue(e.target.value);
_this.setValue(value);
_this.callOnComplete(value);
};
_this.buffer = _this.initialBuffer();
_this.cursorPos = _this.firstNonMaskIdx;
var propsValue = _this.getPropsValue();
_this.state = {
// TODO: Any way we can do this in one pass?
value: propsValue ? _this.maskedValue(propsValue) : ''
};
return _this;
}
AlwaysMaskedField.prototype.componentDidMount = function () {
var propsValue = this.getPropsValue();
var value = this.state.value;
if (typeof propsValue === 'string' && value !== propsValue) {
this.callOnChange(value);
}
};
AlwaysMaskedField.prototype.componentDidUpdate = function (_a) {
var prevValue = _a.value;
if (this.cursorPos !== -1) {
this.setSelection(this.cursorPos);
}
var value = this.props.value;
if (value && value !== prevValue && value !== this.bufferString()) {
var maskedValue = this.maskedValue(value);
this.setValue(maskedValue);
this.callOnComplete(maskedValue);
}
};
AlwaysMaskedField.prototype.getSelection = function () {
if (this.input) {
return getSelection(this.input);
}
var cursorPos = (this.getPropsValue() || '').length;
return { start: cursorPos, end: cursorPos };
};
AlwaysMaskedField.prototype.getPropsValue = function () {
var _a = this.props, valueLink = _a.valueLink, value = _a.value;
if (valueLink) {
return valueLink.value;
}
return value;
};
AlwaysMaskedField.prototype.getPattern = function (idx) {
var _a = this.props, mask = _a.mask, translations = _a.translations;
var maskChar = mask[idx];
var pattern = translations ? translations[maskChar] : null;
return pattern || DEFAULT_TRANSLATIONS[maskChar];
};
AlwaysMaskedField.prototype.setSelection = function (start, end) {
if (end === void 0) { end = start; }
if (this.input && this.input === document.activeElement) {
setSelection(this.input, start, end);
}
};
AlwaysMaskedField.prototype.setValue = function (newVal) {
var value = this.state.value;
if (newVal !== value) {
this.callOnChange(newVal);
}
this.setState({ value: newVal });
};
AlwaysMaskedField.prototype.resetBuffer = function (start, end) {
for (var i = start; i < end; i += 1) {
if (this.getPattern(i)) {
this.buffer[i] = BLANK_CHAR;
}
}
};
AlwaysMaskedField.prototype.initialBuffer = function () {
var buffer = [];
var mask = this.props.mask;
for (var idx = 0; idx < mask.length; idx += 1) {
if (this.getPattern(idx)) {
if (this.firstNonMaskIdx === -1) {
this.firstNonMaskIdx = idx;
}
buffer.push('_');
}
else {
buffer.push(mask[idx]);
}
}
return buffer;
};
AlwaysMaskedField.prototype.bufferString = function () {
return this.buffer.join('');
};
AlwaysMaskedField.prototype.isBufferEmpty = function () {
var _this = this;
return this.buffer.every(function (char, idx) { return !_this.getPattern(idx) || char === BLANK_CHAR; });
};
AlwaysMaskedField.prototype.isBufferFull = function () {
var _this = this;
return this.buffer.every(function (char, idx) { return !_this.getPattern(idx) || char !== BLANK_CHAR; });
};
AlwaysMaskedField.prototype.nextNonMaskIdx = function (idx) {
var next = idx + 1;
var mask = this.props.mask;
for (; next < mask.length; next += 1) {
if (this.getPattern(next)) {
break;
}
}
return next;
};
AlwaysMaskedField.prototype.prevNonMaskIdx = function (idx) {
var prev = idx - 1;
for (; prev >= 0; prev -= 1) {
if (this.getPattern(prev)) {
break;
}
}
return prev;
};
AlwaysMaskedField.prototype.callOnChange = function (value) {
var _a = this.props, id = _a.id, name = _a.name, valueLink = _a.valueLink, onChange = _a.onChange;
if (valueLink) {
valueLink.requestChange(value);
}
else if (onChange) {
onChange({ target: { id: id, name: name, value: value } });
}
};
AlwaysMaskedField.prototype.callOnComplete = function (value) {
var onComplete = this.props.onComplete;
if (onComplete && this.isBufferFull()) {
onComplete(value);
}
};
AlwaysMaskedField.prototype.maskedValue = function (value, start) {
if (start === void 0) { start = 0; }
this.cursorPos = this.getSelection().start;
var originalCursorPos = this.cursorPos;
var mask = this.props.mask;
for (var bufferIdx = start, valueIdx = 0; bufferIdx < mask.length; bufferIdx += 1) {
var pattern = this.getPattern(bufferIdx);
if (pattern) {
var lastPatternIdx = bufferIdx;
this.buffer[bufferIdx] = BLANK_CHAR;
while (valueIdx < value.length && bufferIdx < mask.length) {
var c = value[valueIdx];
valueIdx += 1;
if (c === this.buffer[bufferIdx]) {
bufferIdx += 1;
}
else if (pattern.test(c)) {
while (this.buffer[bufferIdx] !== '_' && bufferIdx < this.buffer.length) {
bufferIdx += 1;
}
if (this.buffer[bufferIdx] !== undefined) {
this.buffer[bufferIdx] = c;
}
break;
}
else if (this.cursorPos > lastPatternIdx) {
this.cursorPos -= 1;
}
}
if (valueIdx >= value.length) {
this.resetBuffer(lastPatternIdx + 1, mask.length);
break;
}
}
else if (this.buffer[bufferIdx] === value[valueIdx]) {
if (valueIdx === originalCursorPos) {
this.cursorPos += 1;
}
valueIdx += 1;
}
else if (valueIdx <= originalCursorPos) {
this.cursorPos += 1;
}
}
return this.bufferString();
};
AlwaysMaskedField.prototype.render = function () {
var _this = this;
var _a = this.props, mask = _a.mask, translations = _a.translations, onComplete = _a.onComplete, valueLink = _a.valueLink, placeholder = _a.placeholder, inputRef = _a.inputRef, props = tslib.__rest(_a, ["mask", "translations", "onComplete", "valueLink", "placeholder", "inputRef"]);
var value = this.state.value;
return (React.createElement("input", tslib.__assign({ ref: function (c) {
_this.input = c;
if (inputRef) {
inputRef(c);
}
} }, props, { onChange: this.handleChange, onKeyDown: this.handleKeyDown, onFocus: this.handleFocus, onBlur: this.handleBlur, value: value, placeholder: placeholder || this.initialBuffer().join(''), type: "text" })));
};
AlwaysMaskedField.propTypes = {
mask: PropTypes.string,
translations: PropTypes.objectOf(PropTypes.instanceOf(RegExp)),
value: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onKeyDown: PropTypes.func,
onComplete: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
valueLink: PropTypes.shape({
value: PropTypes.string.isRequired,
requestChange: PropTypes.func.isRequired
})
};
AlwaysMaskedField.defaultProps = {
mask: undefined,
translations: undefined,
value: undefined,
placeholder: undefined,
onChange: undefined,
onKeyDown: undefined,
onComplete: undefined,
onFocus: undefined,
onBlur: undefined,
valueLink: undefined
};
return AlwaysMaskedField;
}(React.Component));
/**
* Copyright (c) 2015 ZenPayroll
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var MaskedField = function (_a) {
var mask = _a.mask, props = tslib.__rest(_a, ["mask"]);
if (mask) {
return React.createElement(AlwaysMaskedField, tslib.__assign({ mask: mask }, props));
}
var translations = props.translations, onComplete = props.onComplete, valueLink = props.valueLink, inputRef = props.inputRef, inputProps = tslib.__rest(props, ["translations", "onComplete", "valueLink", "inputRef"]);
return React.createElement("input", tslib.__assign({}, inputProps, { type: "text" }));
};
MaskedField.propTypes = {
mask: PropTypes.string
};
MaskedField.defaultProps = {
mask: undefined
};
module.exports = MaskedField;
//# sourceMappingURL=index.js.map