Skip to content

Commit

Permalink
Fix deleting from the middle of dates (ampproject#20656)
Browse files Browse the repository at this point in the history
Includes backspace, delete, cut and highlight replace.
  • Loading branch information
cvializ authored and Noran Azmy committed Mar 22, 2019
1 parent b14bf65 commit d3f2756
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 13 deletions.
4 changes: 2 additions & 2 deletions third_party/inputmask/bundle.js

Large diffs are not rendered by default.

92 changes: 82 additions & 10 deletions third_party/inputmask/inputmask.date.extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export function factory(Inputmask) {
}
return result;
},
onKeyDown: function(e, buffer, caretPos, opts) {
onKeyDown: function(e, buffer, caretPos, opts, initial, previousPos) {
var input = this;
if (e.ctrlKey && e.keyCode === Inputmask.keyCode.RIGHT) {
var today = new Date(), match, date = "";
Expand All @@ -253,21 +253,77 @@ export function factory(Inputmask) {
$(input).trigger("setvalue");
}

const {begin, end} = previousPos;
// If the user presses backspace in the middle of the input
// value, stop masking until the field is cleared.
if (e.keyCode === Inputmask.keyCode.BACKSPACE) {
if (e.keyCode === Inputmask.keyCode.BACKSPACE ||
e.keyCode === Inputmask.keyCode.DELETE ||
((e.metaKey || e.ctrlKey) && e.keyCode === 'X'.charCodeAt(0))) {
const length = input.value.length;
const cursorIsInMiddle = input.selectionStart != length &&
input.selectionEnd != length;
if (length > 0 && cursorIsInMiddle) {
const cachedController = input.inputmask;
input.inputmask.remove();
input.addEventListener('input', function onclear() {
if (input.value.length == 0) {
input.removeEventListener('input', onclear);
cachedController.mask(input);
}
});
disableMaskingUntilClear(input);

// The library tries to be smart about moving the cursor
// and autofilling values, but here we want to just
// delete the text that was selected like a normal
// input.
// So we take the original selection and the origina
// value before the library auto-changed them,
// and apply the edits ourselves here.
const difference = end - begin;
if (e.keyCode === Inputmask.keyCode.BACKSPACE) {
const selectionBegin =
difference == 0 ? begin - 1 : begin;
input.value =
initial.slice(0, selectionBegin) +
initial.slice(end);
input.setSelectionRange(
selectionBegin, selectionBegin);
} else if (e.keyCode === Inputmask.keyCode.DELETE) { // delete
const selectionEnd =
difference == 0 ? end + 1 : end;
input.value =
initial.slice(0, begin) +
initial.slice(selectionEnd);
input.setSelectionRange(begin, begin);
}
}
return;
}

// If the user enters text with a selection and causes delete...
if (previousPos.begin != previousPos.end) {
// ... via paste
if ((e.metaKey || e.ctrlKey) && e.keyCode === 'V'.charCodeAt(0)) {
disableMaskingUntilClear(input);
return;
}

// Don't handle CTRL+C/CMD+C or CTRL or CMD alone
if (e.metaKey || e.ctrlKey) {
return;
}

// ... via typing a char
const fromCharCode = String.fromCharCode(e.keyCode);
const char = e.shiftKey ?
fromCharCode.toLocaleUpperCase() :
fromCharCode.toLocaleLowerCase();
if (!/\s/.test(char)) {
disableMaskingUntilClear(input);
// The library still sets the value after the keydown
// handler returns, so we need to asynchronously
// set the value here.
setTimeout(() => {
input.value =
initial.slice(0, begin) +
char +
initial.slice(end);
input.setSelectionRange(
begin + 1, begin + 1);
}, 0);
}
}
},
Expand All @@ -283,5 +339,21 @@ export function factory(Inputmask) {
shiftPositions: false
}
});

/**
* When the user clears the input, reapply the
* inputmask behavior.
* @param {!Element} input
*/
function disableMaskingUntilClear(input) {
const cachedController = input.inputmask;
input.inputmask.remove();
input.addEventListener('input', function onclear() {
if (input.value.length == 0) {
input.removeEventListener('input', onclear);
cachedController.mask(input);
}
});
}
return Inputmask;
}
4 changes: 3 additions & 1 deletion third_party/inputmask/inputmask.js
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,8 @@ export function factory($, window, document, undefined) {
var EventHandlers = {
keydownEvent: function(e) {
var input = this, $input = $(input), k = e.keyCode, pos = caret(input);
const originalPos = {begin: input.selectionStart, end: input.selectionEnd};
const initial = input.value;
if (k === Inputmask.keyCode.BACKSPACE || k === Inputmask.keyCode.DELETE || iphone && k === Inputmask.keyCode.BACKSPACE_SAFARI || (e.ctrlKey || e.metaKey) && k === Inputmask.keyCode.X && !isInputEventSupported("cut")) {
e.preventDefault();

Expand Down Expand Up @@ -1745,7 +1747,7 @@ export function factory($, window, document, undefined) {
caret(input, pos.begin, pos.end);
}
}
opts.onKeyDown.call(this, e, getBuffer(), caret(input).begin, opts);
opts.onKeyDown.call(this, e, getBuffer(), caret(input).begin, opts, initial, originalPos);
ignorable = $.inArray(k, opts.ignorables) !== -1;
},
keypressEvent: function(e, checkval, writeOut, strict, ndx) {
Expand Down
144 changes: 144 additions & 0 deletions third_party/inputmask/patches/0014-delete-middle-date.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
diff --git a/third_party/inputmask/inputmask.date.extensions.js b/third_party/inputmask/inputmask.date.extensions.js
index ea5f730fb..fb45ed966 100755
--- a/third_party/inputmask/inputmask.date.extensions.js
+++ b/third_party/inputmask/inputmask.date.extensions.js
@@ -234,7 +234,7 @@ export function factory(Inputmask) {
}
return result;
},
- onKeyDown: function(e, buffer, caretPos, opts) {
+ onKeyDown: function(e, buffer, caretPos, opts, initial, previousPos) {
var input = this;
if (e.ctrlKey && e.keyCode === Inputmask.keyCode.RIGHT) {
var today = new Date(), match, date = "";
@@ -253,21 +253,77 @@ export function factory(Inputmask) {
$(input).trigger("setvalue");
}

+ const {begin, end} = previousPos;
// If the user presses backspace in the middle of the input
// value, stop masking until the field is cleared.
- if (e.keyCode === Inputmask.keyCode.BACKSPACE) {
+ if (e.keyCode === Inputmask.keyCode.BACKSPACE ||
+ e.keyCode === Inputmask.keyCode.DELETE ||
+ ((e.metaKey || e.ctrlKey) && e.keyCode === 'X'.charCodeAt(0))) {
const length = input.value.length;
const cursorIsInMiddle = input.selectionStart != length &&
input.selectionEnd != length;
if (length > 0 && cursorIsInMiddle) {
- const cachedController = input.inputmask;
- input.inputmask.remove();
- input.addEventListener('input', function onclear() {
- if (input.value.length == 0) {
- input.removeEventListener('input', onclear);
- cachedController.mask(input);
- }
- });
+ disableMaskingUntilClear(input);
+
+ // The library tries to be smart about moving the cursor
+ // and autofilling values, but here we want to just
+ // delete the text that was selected like a normal
+ // input.
+ // So we take the original selection and the origina
+ // value before the library auto-changed them,
+ // and apply the edits ourselves here.
+ const difference = end - begin;
+ if (e.keyCode === Inputmask.keyCode.BACKSPACE) {
+ const selectionBegin =
+ difference == 0 ? begin - 1 : begin;
+ input.value =
+ initial.slice(0, selectionBegin) +
+ initial.slice(end);
+ input.setSelectionRange(
+ selectionBegin, selectionBegin);
+ } else if (e.keyCode === Inputmask.keyCode.DELETE) { // delete
+ const selectionEnd =
+ difference == 0 ? end + 1 : end;
+ input.value =
+ initial.slice(0, begin) +
+ initial.slice(selectionEnd);
+ input.setSelectionRange(begin, begin);
+ }
+ }
+ return;
+ }
+
+ // If the user enters text with a selection and causes delete...
+ if (previousPos.begin != previousPos.end) {
+ // ... via paste
+ if ((e.metaKey || e.ctrlKey) && e.keyCode === 'V'.charCodeAt(0)) {
+ disableMaskingUntilClear(input);
+ return;
+ }
+
+ // Don't handle CTRL+C/CMD+C or CTRL or CMD alone
+ if (e.metaKey || e.ctrlKey) {
+ return;
+ }
+
+ // ... via typing a char
+ const fromCharCode = String.fromCharCode(e.keyCode);
+ const char = e.shiftKey ?
+ fromCharCode.toLocaleUpperCase() :
+ fromCharCode.toLocaleLowerCase();
+ if (!/\s/.test(char)) {
+ disableMaskingUntilClear(input);
+ // The library still sets the value after the keydown
+ // handler returns, so we need to asynchronously
+ // set the value here.
+ setTimeout(() => {
+ input.value =
+ initial.slice(0, begin) +
+ char +
+ initial.slice(end);
+ input.setSelectionRange(
+ begin + 1, begin + 1);
+ }, 0);
}
}
},
@@ -283,5 +339,21 @@ export function factory(Inputmask) {
shiftPositions: false
}
});
+
+ /**
+ * When the user clears the input, reapply the
+ * inputmask behavior.
+ * @param {!Element} input
+ */
+ function disableMaskingUntilClear(input) {
+ const cachedController = input.inputmask;
+ input.inputmask.remove();
+ input.addEventListener('input', function onclear() {
+ if (input.value.length == 0) {
+ input.removeEventListener('input', onclear);
+ cachedController.mask(input);
+ }
+ });
+ }
return Inputmask;
}
diff --git a/third_party/inputmask/inputmask.js b/third_party/inputmask/inputmask.js
index 6ab796f7e..7539512c5 100644
--- a/third_party/inputmask/inputmask.js
+++ b/third_party/inputmask/inputmask.js
@@ -1699,6 +1699,8 @@ export function factory($, window, document, undefined) {
var EventHandlers = {
keydownEvent: function(e) {
var input = this, $input = $(input), k = e.keyCode, pos = caret(input);
+ const originalPos = {begin: input.selectionStart, end: input.selectionEnd};
+ const initial = input.value;
if (k === Inputmask.keyCode.BACKSPACE || k === Inputmask.keyCode.DELETE || iphone && k === Inputmask.keyCode.BACKSPACE_SAFARI || (e.ctrlKey || e.metaKey) && k === Inputmask.keyCode.X && !isInputEventSupported("cut")) {
e.preventDefault();

@@ -1745,7 +1747,7 @@ export function factory($, window, document, undefined) {
caret(input, pos.begin, pos.end);
}
}
- opts.onKeyDown.call(this, e, getBuffer(), caret(input).begin, opts);
+ opts.onKeyDown.call(this, e, getBuffer(), caret(input).begin, opts, initial, originalPos);
ignorable = $.inArray(k, opts.ignorables) !== -1;
},
keypressEvent: function(e, checkval, writeOut, strict, ndx) {

0 comments on commit d3f2756

Please sign in to comment.