diff --git a/lib/index.js b/lib/index.js index 3c7c31f..b8a5dec 100644 --- a/lib/index.js +++ b/lib/index.js @@ -138,6 +138,10 @@ Pattern.prototype.formatValue = function format(value) { var valueBuffer = new Array(this.length) var valueIndex = 0 + if (this.isRevealingMask && value.length === 0) { + return valueBuffer + } + for (var i = 0, l = this.length; i < l; i++) { if (this.isEditableIndex(i)) { if (this.isRevealingMask && @@ -233,40 +237,44 @@ InputMask.prototype.input = function input(char) { var inputIndex = this.selection.start - // If the cursor or selection is prior to the first editable character, make - // sure any input given is applied to it. - if (inputIndex < this.pattern.firstEditableIndex) { - inputIndex = this.pattern.firstEditableIndex + // Find next editable index + var nextEditableIndex = inputIndex + while (!this.pattern.isEditableIndex(nextEditableIndex)) { + if (nextEditableIndex > this.pattern.lastEditableIndex) { + return false + } + nextEditableIndex++ } - // Bail out or add the character to input - if (this.pattern.isEditableIndex(inputIndex)) { - if (!this.pattern.isValidAtIndex(char, inputIndex)) { - return false + if (!this.pattern.isValidAtIndex(char, nextEditableIndex)) { + return false + } + + // Add statics until next editable + while (inputIndex < nextEditableIndex) { + this.value[inputIndex] = this.pattern.pattern[inputIndex] + inputIndex++ + } + + this.value[inputIndex] = this.pattern.transform(char, inputIndex) + + // If this is the last editable index fill with the rest + if (inputIndex === this.pattern.lastEditableIndex) { + while (inputIndex + 1 < this.pattern.length - 1) { + inputIndex++ + this.value[inputIndex] = this.pattern.pattern[inputIndex] } - this.value[inputIndex] = this.pattern.transform(char, inputIndex) } // If multiple characters were selected, blank the remainder out based on the // pattern. - var end = this.selection.end - 1 - while (end > inputIndex) { - if (this.pattern.isEditableIndex(end)) { - this.value[end] = this.placeholderChar - } - end-- + if (inputIndex + 1 < this.selection.end) { + this.remove(inputIndex + 1, this.selection.end - 1) } // Advance the cursor to the next character this.selection.start = this.selection.end = inputIndex + 1 - // Skip over any subsequent static characters - while (this.pattern.length > this.selection.start && - !this.pattern.isEditableIndex(this.selection.start)) { - this.selection.start++ - this.selection.end++ - } - // History if (this._historyIndex != null) { // Took more input after undoing, so blow any subsequent history away @@ -299,26 +307,26 @@ InputMask.prototype.backspace = function backspace() { var selectionBefore = copy(this.selection) var valueBefore = this.getValue() - // No range selected - work on the character preceding the cursor + // No range selected if (this.selection.start === this.selection.end) { - if (this.pattern.isEditableIndex(this.selection.start - 1)) { - this.value[this.selection.start - 1] = this.placeholderChar + var previousEditableIndex = this.selection.start - 1 + + while (!this.pattern.isEditableIndex(previousEditableIndex)) { + if (previousEditableIndex === 0) { + break + } + previousEditableIndex-- } - this.selection.start-- - this.selection.end-- + + this.remove(previousEditableIndex, this.selection.end) + this.selection.start = previousEditableIndex } - // Range selected - delete characters and leave the cursor at the start of the selection else { - var end = this.selection.end - 1 - while (end >= this.selection.start) { - if (this.pattern.isEditableIndex(end)) { - this.value[end] = this.placeholderChar - } - end-- - } - this.selection.end = this.selection.start + this.remove(this.selection.start, this.selection.end - 1) } + this.selection.end = this.selection.start + // History if (this._historyIndex != null) { // Took more input after undoing, so blow any subsequent history away @@ -381,7 +389,7 @@ InputMask.prototype.paste = function paste(input) { if (!valid) { if (this.selection.start > 0) { // XXX This only allows for one static character to be skipped - var patternIndex = this.selection.start - 1 + var patternIndex = this.selection.start if (!this.pattern.isEditableIndex(patternIndex) && input.charAt(i) === this.pattern.pattern[patternIndex]) { continue @@ -395,6 +403,34 @@ InputMask.prototype.paste = function paste(input) { return true } +InputMask.prototype.remove = function remove(start, end) { + if (this.pattern.isRevealingMask) { + this.value.splice(start, end - start) + + var index = start + while (index < this.value.length) { + if (!this.pattern.isEditableIndex(index)) { + this.value.splice(index, 0, this.pattern.pattern[index]) + index++ + } + else if (this.pattern.isValidAtIndex(this.value[index], index)) { + index++ + } + else { + this.value.splice(index, 1) + } + } + } + else { + while (end >= start) { + if (this.pattern.isEditableIndex(end)) { + this.value[end] = this.placeholderChar + } + end-- + } + } +} + // History InputMask.prototype.undo = function undo() { diff --git a/test/index.js b/test/index.js index 34f473e..1c90019 100644 --- a/test/index.js +++ b/test/index.js @@ -143,7 +143,7 @@ test('Escaping placeholder characters', function(t) { }) test('Basic input', function(t) { - t.plan(23) + t.plan(26) var mask = new InputMask({ pattern: '1111 1111 1111 1111' @@ -156,18 +156,21 @@ test('Basic input', function(t) { t.true(mask.input('2'), 'Valid input accepted') t.true(mask.input('3'), 'Valid input accepted') t.true(mask.input('4'), 'Valid input accepted') - t.deepEqual(mask.selection, {start: 5, end: 5}, 'Skipped over blank') + t.deepEqual(mask.selection, {start: 4, end: 4}, 'Keep in position') t.true(mask.input('1'), 'Valid input accepted') + t.deepEqual(mask.selection, {start: 6, end: 6}, 'Skipped over blank after input') t.true(mask.input('2'), 'Valid input accepted') t.true(mask.input('3'), 'Valid input accepted') t.true(mask.input('4'), 'Valid input accepted') - t.deepEqual(mask.selection, {start: 10, end: 10}, 'Skipped over blank') + t.deepEqual(mask.selection, {start: 9, end: 9}, 'Keep in position') t.true(mask.input('1'), 'Valid input accepted') + t.deepEqual(mask.selection, {start: 11, end: 11}, 'Skipped over blank') t.true(mask.input('2'), 'Valid input accepted') t.true(mask.input('3'), 'Valid input accepted') t.true(mask.input('4'), 'Valid input accepted') - t.deepEqual(mask.selection, {start: 15, end: 15}, 'Skipped over blank') + t.deepEqual(mask.selection, {start: 14, end: 14}, 'Keep in position') t.true(mask.input('1'), 'Valid input accepted') + t.deepEqual(mask.selection, {start: 16, end: 16}, 'Skipped over blank') t.true(mask.input('2'), 'Valid input accepted') t.true(mask.input('3'), 'Valid input accepted') t.true(mask.input('4'), 'Valid input accepted') @@ -256,7 +259,7 @@ test('Skipping multiple static characters', function(t) { }) test('Basic backspacing', function(t) { - t.plan(24) + t.plan(21) var mask = new InputMask({ pattern: '1111 1111 1111 1111', @@ -268,22 +271,18 @@ test('Basic backspacing', function(t) { t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') - // Backspacking doesn't automatically skip characters, as we can't tell when - // the user intends to start making input again, so it just steps over static - // parts of the mask when you backspace with the cursor ahead of them. - t.true(mask.backspace(), 'Skipped over blank') + // Backspacking automatically skip characters, and goes to the + // previous valid editable character t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') t.equal(mask.getValue(), '1234 1234 ____ ____', 'Intermediate value') t.deepEqual(mask.selection, {start: 10, end: 10}, 'Cursor remains in front of last deleted character') - t.true(mask.backspace(), 'Skipped over blank') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') - t.true(mask.backspace(), 'Skipped over blank') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted') t.true(mask.backspace(), 'Valid backspace accepted')