From dfc168033f9bf8feb59e1157d656e53765226440 Mon Sep 17 00:00:00 2001 From: BugFix Date: Tue, 16 Jun 2026 08:24:44 +0000 Subject: [PATCH] fix(inputmask): prevent digit shift across section boundaries on delete When using InputMask with a segmented pattern like '99.99.9999', pressing Backspace or Delete inside one section would shift digits from subsequent sections into the current one, causing values like '10.04.2026' to bleed across the '.' separators. Root cause: shiftL() iterated with seekNext() which skips over fixed mask characters (null entries in the tests array). This allowed the left-shift loop to pull digits across separator boundaries. Fix: Added a countFixedChars() helper that counts null (fixed) positions in a range. Before each shift step, we compare the number of fixed characters encountered on the destination side (begin..i) vs. the source side (end+1..j). If they differ, the shift has crossed a section boundary and we break early. The fix is applied to both the InputMask directive and the InputMask standalone component classes. Fixes #19629 --- packages/primeng/src/inputmask/inputmask.ts | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/primeng/src/inputmask/inputmask.ts b/packages/primeng/src/inputmask/inputmask.ts index 94fff6ea702..d74ff39060c 100755 --- a/packages/primeng/src/inputmask/inputmask.ts +++ b/packages/primeng/src/inputmask/inputmask.ts @@ -498,6 +498,18 @@ export class InputMaskDirective extends BaseComponent { return pos; } + /** + * Count the number of fixed (non-editable) mask characters in the range [from, to). + * Fixed characters are those where tests[k] is null (e.g. separators like '.' or '/'). + */ + private countFixedChars(from: number, to: number): number { + let count = 0; + for (let k = from; k < to; k++) { + if (!this.tests[k]) count++; + } + return count; + } + shiftL(begin: number, end: number) { let i, j; @@ -507,6 +519,12 @@ export class InputMaskDirective extends BaseComponent { for (i = begin, j = this.seekNext(end); i < (this.len as number); i++) { if (this.tests[i]) { + // Stop shifting if j has crossed more fixed-mask separators than i has. + // This prevents digits from bleeding across section boundaries (e.g. 99.99.9999). + if (this.countFixedChars(begin, i + 1) !== this.countFixedChars(end + 1, j + 1)) { + break; + } + if (j < (this.len as number) && this.tests[i].test(this.buffer[j])) { this.buffer[i] = this.buffer[j]; this.buffer[j] = this.getPlaceholder(j); @@ -1070,6 +1088,18 @@ export class InputMask extends BaseInput { return pos; } + /** + * Count the number of fixed (non-editable) mask characters in the range [from, to). + * Fixed characters are those where tests[k] is null (e.g. separators like '.' or '/'). + */ + private countFixedChars(from: number, to: number): number { + let count = 0; + for (let k = from; k < to; k++) { + if (!this.tests[k]) count++; + } + return count; + } + shiftL(begin: number, end: number) { let i, j; @@ -1079,6 +1109,12 @@ export class InputMask extends BaseInput { for (i = begin, j = this.seekNext(end); i < (this.len as number); i++) { if (this.tests[i]) { + // Stop shifting if j has crossed more fixed-mask separators than i has. + // This prevents digits from bleeding across section boundaries (e.g. 99.99.9999). + if (this.countFixedChars(begin, i + 1) !== this.countFixedChars(end + 1, j + 1)) { + break; + } + if (j < (this.len as number) && this.tests[i].test(this.buffer[j])) { this.buffer[i] = this.buffer[j]; this.buffer[j] = this.getPlaceholder(j);