import { _ as _createClass, a as _classCallCheck, b as _objectWithoutProperties } from '../_rollupPluginBabelHelpers-a0b34764.js'; import { objectIncludes, DIRECTION } from '../core/utils.js'; import ActionDetails from '../core/action-details.js'; import MaskedDate from '../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 '../masked/pattern.js'; import '../core/change-details.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/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; 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) { return mask == null || mask === this.masked.mask || mask === Date && this.masked instanceof MaskedDate; } }, { 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 _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.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); 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 };