Files
2022-04-07 14:11:14 +02:00

432 lines
12 KiB
JavaScript

import { _ as _createClass, a as _classCallCheck, b as _objectWithoutProperties } from '../_rollupPluginBabelHelpers-b054ecd2.js';
import { objectIncludes, DIRECTION } from '../core/utils.js';
import ActionDetails from '../core/action-details.js';
import '../masked/date.js';
import createMask, { maskedClass } from '../masked/factory.js';
import MaskElement from './mask-element.js';
import HTMLMaskElement from './html-mask-element.js';
import HTMLContenteditableMaskElement from './html-contenteditable-mask-element.js';
import IMask from '../core/holder.js';
import '../core/change-details.js';
import '../masked/pattern.js';
import '../masked/base.js';
import '../core/continuous-tail-details.js';
import '../masked/pattern/input-definition.js';
import '../masked/pattern/fixed-definition.js';
import '../masked/pattern/chunk-tail-details.js';
import '../masked/pattern/cursor.js';
import '../masked/regexp.js';
import '../masked/range.js';
var _excluded = ["mask"];
/** Listens to element events and controls changes between element and {@link Masked} */
var InputMask = /*#__PURE__*/function () {
/**
View element
@readonly
*/
/**
Internal {@link Masked} model
@readonly
*/
/**
@param {MaskElement|HTMLInputElement|HTMLTextAreaElement} el
@param {Object} opts
*/
function InputMask(el, opts) {
_classCallCheck(this, InputMask);
this.el = el instanceof MaskElement ? el : el.isContentEditable && el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA' ? new HTMLContenteditableMaskElement(el) : new HTMLMaskElement(el);
this.masked = createMask(opts);
this._listeners = {};
this._value = '';
this._unmaskedValue = '';
this._saveSelection = this._saveSelection.bind(this);
this._onInput = this._onInput.bind(this);
this._onChange = this._onChange.bind(this);
this._onDrop = this._onDrop.bind(this);
this._onFocus = this._onFocus.bind(this);
this._onClick = this._onClick.bind(this);
this.alignCursor = this.alignCursor.bind(this);
this.alignCursorFriendly = this.alignCursorFriendly.bind(this);
this._bindEvents(); // refresh
this.updateValue();
this._onChange();
}
/** Read or update mask */
_createClass(InputMask, [{
key: "mask",
get: function get() {
return this.masked.mask;
},
set: function set(mask) {
if (this.maskEquals(mask)) return; // $FlowFixMe No ideas ... after update
if (!(mask instanceof IMask.Masked) && this.masked.constructor === maskedClass(mask)) {
this.masked.updateOptions({
mask: mask
});
return;
}
var masked = createMask({
mask: mask
});
masked.unmaskedValue = this.masked.unmaskedValue;
this.masked = masked;
}
/** Raw value */
}, {
key: "maskEquals",
value: function maskEquals(mask) {
var _this$masked;
return mask == null || ((_this$masked = this.masked) === null || _this$masked === void 0 ? void 0 : _this$masked.maskEquals(mask));
}
}, {
key: "value",
get: function get() {
return this._value;
},
set: function set(str) {
this.masked.value = str;
this.updateControl();
this.alignCursor();
}
/** Unmasked value */
}, {
key: "unmaskedValue",
get: function get() {
return this._unmaskedValue;
},
set: function set(str) {
this.masked.unmaskedValue = str;
this.updateControl();
this.alignCursor();
}
/** Typed unmasked value */
}, {
key: "typedValue",
get: function get() {
return this.masked.typedValue;
},
set: function set(val) {
this.masked.typedValue = val;
this.updateControl();
this.alignCursor();
}
/**
Starts listening to element events
@protected
*/
}, {
key: "_bindEvents",
value: function _bindEvents() {
this.el.bindEvents({
selectionChange: this._saveSelection,
input: this._onInput,
drop: this._onDrop,
click: this._onClick,
focus: this._onFocus,
commit: this._onChange
});
}
/**
Stops listening to element events
@protected
*/
}, {
key: "_unbindEvents",
value: function _unbindEvents() {
if (this.el) this.el.unbindEvents();
}
/**
Fires custom event
@protected
*/
}, {
key: "_fireEvent",
value: function _fireEvent(ev) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var listeners = this._listeners[ev];
if (!listeners) return;
listeners.forEach(function (l) {
return l.apply(void 0, args);
});
}
/**
Current selection start
@readonly
*/
}, {
key: "selectionStart",
get: function get() {
return this._cursorChanging ? this._changingCursorPos : this.el.selectionStart;
}
/** Current cursor position */
}, {
key: "cursorPos",
get: function get() {
return this._cursorChanging ? this._changingCursorPos : this.el.selectionEnd;
},
set: function set(pos) {
if (!this.el || !this.el.isActive) return;
this.el.select(pos, pos);
this._saveSelection();
}
/**
Stores current selection
@protected
*/
}, {
key: "_saveSelection",
value: function
/* ev */
_saveSelection() {
if (this.value !== this.el.value) {
console.warn('Element value was changed outside of mask. Syncronize mask using `mask.updateValue()` to work properly.'); // eslint-disable-line no-console
}
this._selection = {
start: this.selectionStart,
end: this.cursorPos
};
}
/** Syncronizes model value from view */
}, {
key: "updateValue",
value: function updateValue() {
this.masked.value = this.el.value;
this._value = this.masked.value;
}
/** Syncronizes view from model value, fires change events */
}, {
key: "updateControl",
value: function updateControl() {
var newUnmaskedValue = this.masked.unmaskedValue;
var newValue = this.masked.value;
var isChanged = this.unmaskedValue !== newUnmaskedValue || this.value !== newValue;
this._unmaskedValue = newUnmaskedValue;
this._value = newValue;
if (this.el.value !== newValue) this.el.value = newValue;
if (isChanged) this._fireChangeEvents();
}
/** Updates options with deep equal check, recreates @{link Masked} model if mask type changes */
}, {
key: "updateOptions",
value: function updateOptions(opts) {
var mask = opts.mask,
restOpts = _objectWithoutProperties(opts, _excluded);
var updateMask = !this.maskEquals(mask);
var updateOpts = !objectIncludes(this.masked, restOpts);
if (updateMask) this.mask = mask;
if (updateOpts) this.masked.updateOptions(restOpts);
if (updateMask || updateOpts) this.updateControl();
}
/** Updates cursor */
}, {
key: "updateCursor",
value: function updateCursor(cursorPos) {
if (cursorPos == null) return;
this.cursorPos = cursorPos; // also queue change cursor for mobile browsers
this._delayUpdateCursor(cursorPos);
}
/**
Delays cursor update to support mobile browsers
@private
*/
}, {
key: "_delayUpdateCursor",
value: function _delayUpdateCursor(cursorPos) {
var _this = this;
this._abortUpdateCursor();
this._changingCursorPos = cursorPos;
this._cursorChanging = setTimeout(function () {
if (!_this.el) return; // if was destroyed
_this.cursorPos = _this._changingCursorPos;
_this._abortUpdateCursor();
}, 10);
}
/**
Fires custom events
@protected
*/
}, {
key: "_fireChangeEvents",
value: function _fireChangeEvents() {
this._fireEvent('accept', this._inputEvent);
if (this.masked.isComplete) this._fireEvent('complete', this._inputEvent);
}
/**
Aborts delayed cursor update
@private
*/
}, {
key: "_abortUpdateCursor",
value: function _abortUpdateCursor() {
if (this._cursorChanging) {
clearTimeout(this._cursorChanging);
delete this._cursorChanging;
}
}
/** Aligns cursor to nearest available position */
}, {
key: "alignCursor",
value: function alignCursor() {
this.cursorPos = this.masked.nearestInputPos(this.masked.nearestInputPos(this.cursorPos, DIRECTION.LEFT));
}
/** Aligns cursor only if selection is empty */
}, {
key: "alignCursorFriendly",
value: function alignCursorFriendly() {
if (this.selectionStart !== this.cursorPos) return; // skip if range is selected
this.alignCursor();
}
/** Adds listener on custom event */
}, {
key: "on",
value: function on(ev, handler) {
if (!this._listeners[ev]) this._listeners[ev] = [];
this._listeners[ev].push(handler);
return this;
}
/** Removes custom event listener */
}, {
key: "off",
value: function off(ev, handler) {
if (!this._listeners[ev]) return this;
if (!handler) {
delete this._listeners[ev];
return this;
}
var hIndex = this._listeners[ev].indexOf(handler);
if (hIndex >= 0) this._listeners[ev].splice(hIndex, 1);
return this;
}
/** Handles view input event */
}, {
key: "_onInput",
value: function _onInput(e) {
this._inputEvent = e;
this._abortUpdateCursor(); // fix strange IE behavior
if (!this._selection) return this.updateValue();
var details = new ActionDetails( // new state
this.el.value, this.cursorPos, // old state
this.value, this._selection);
var oldRawValue = this.masked.rawInputValue;
var offset = this.masked.splice(details.startChangePos, details.removed.length, details.inserted, details.removeDirection).offset; // force align in remove direction only if no input chars were removed
// otherwise we still need to align with NONE (to get out from fixed symbols for instance)
var removeDirection = oldRawValue === this.masked.rawInputValue ? details.removeDirection : DIRECTION.NONE;
var cursorPos = this.masked.nearestInputPos(details.startChangePos + offset, removeDirection);
if (removeDirection !== DIRECTION.NONE) cursorPos = this.masked.nearestInputPos(cursorPos, DIRECTION.NONE);
this.updateControl();
this.updateCursor(cursorPos);
delete this._inputEvent;
}
/** Handles view change event and commits model value */
}, {
key: "_onChange",
value: function _onChange() {
if (this.value !== this.el.value) {
this.updateValue();
}
this.masked.doCommit();
this.updateControl();
this._saveSelection();
}
/** Handles view drop event, prevents by default */
}, {
key: "_onDrop",
value: function _onDrop(ev) {
ev.preventDefault();
ev.stopPropagation();
}
/** Restore last selection on focus */
}, {
key: "_onFocus",
value: function _onFocus(ev) {
this.alignCursorFriendly();
}
/** Restore last selection on focus */
}, {
key: "_onClick",
value: function _onClick(ev) {
this.alignCursorFriendly();
}
/** Unbind view events and removes element reference */
}, {
key: "destroy",
value: function destroy() {
this._unbindEvents(); // $FlowFixMe why not do so?
this._listeners.length = 0; // $FlowFixMe
delete this.el;
}
}]);
return InputMask;
}();
IMask.InputMask = InputMask;
export { InputMask as default };