You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add native masks to Input so applications can format card numbers, phone numbers and mixed character codes while users type, with caret and selection handling built into the component. For example, mask="0000 0000 0000 0000" displays 1234567890123456 as 1234 5678 9012 3456 without an additional runtime dependency.
Related to ant-design/ant-design#54442. This provides the native foundation; exposing it through Ant Design requires a separate integration after release.
API
mask?: InputMask: a string pattern, a readonly array of literal strings and single-character regular expressions, or a pure function receiving { value, selection } and returning a pattern. Dynamic masks receive the proposed input text and its selection offsets; selection is null for initial or externally updated values.
maskDefinitions?: Readonly<Record<string, RegExp | null>>: extends or overrides the default string tokens. 0 accepts ASCII digits, X accepts Latin letters, and * accepts alphanumeric characters. A null definition makes a token literal; backslash escapes tokens. Array strings remain literals. Stateful g and y regex flags are ignored without mutating caller-provided expressions.
maskPlaceholder?: string | null: unfilled positions are hidden when omitted, undefined, null or ''. An explicit character repeats across empty positions; a longer string supplies placeholders at the corresponding pattern positions, with _ used beyond its length. Placeholder characters are reserved for empty positions, and conflicting rules produce a development warning.
Exports InputMaskState, InputMaskPattern, InputMask, and InputMaskDefinitions.
Supports raw or formatted controlled and uncontrolled values, inline dynamic masks and definition updates.
Handles insertion, deletion around separators, range replacement, paste, autofill and caret restoration, including shadow roots. Copied partial values retain their characters when pasted into the same mask without placeholders. Typed characters are validated against the next position; edits that cannot be displayed unambiguously without placeholders are ignored.
Defers formatting during IME composition and avoids duplicate change events when composition ends.
Preserves the React event contract: target.value and currentTarget.value contain formatted text, including separators and visible placeholders. Clearing emits ''. No generic formatter/parser or automatic unmasked-value API is added.
Integrates with allowClear, refs, focus, disabled/read-only state, maxLength and character counting. Placeholder-only values hide the clear button through a new hiddenClear prop on BaseInput: since feat: unify allowClear configuration #208, allowClear.disabled removes the affix wrapper, which would remount the input and lose focus. Explicitly disabled clearing keeps the upstream behavior. maxLength limits displayed pattern length; count.exceedFormatter receives formatted text and its result is formatted again.
Applies to text, search, tel, url and password inputs. Mask props are excluded from DOM attributes.
Adds 99 mask tests, a demo covering static and dynamic patterns, and English and Chinese API documentation.
No actionable comments were generated in the recent review.
Recent review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 00bb1d7f-2876-436c-ab8b-9f272316abaf
Commits
Reviewing files that changed from the base of the PR and between e5cb0b2 and 1ba1cc4.
Files selected for processing: 4
src/Input.tsx
src/hooks/useMask.ts
src/utils/maskUtil.ts
tests/mask.test.tsx
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
This change adds opt-in input masking to Input, which only takes effect when mask props are set. The summaries show the reported input-loss cases fixed and covered by regression tests, and I found no remaining merge-blocking risk.
Pre-merge checks: 5
Check name
Status
Explanation
Description Check
Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring coverage is inconclusive: files from earlier reviews still need to be rechecked within the check's analysis limits. Not blocking.
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
Commit to this branch
Create a new PR
🧪 Generate unit tests (beta)
Create a new PR
Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Warning
Some tools did not complete. Review the errors below.
ESLint
If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
ESLint install timed out. The project may have too many dependencies for the sandbox.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Hi @zombieJ, what do you think about adding native input masking to rc-input, as proposed in ant-design/ant-design#54442? I'd appreciate your feedback on the approach and API in this PR.
❌ Patch coverage is 97.33925% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.82%. Comparing base (39f4ae6) to head (e5cb0b2).
❌ Please fix the input-loss cases below before merging. I reproduced all three against e5cb0b2 using additional React Testing Library/user-event tests. The existing 244 tests and 22 snapshots pass, as does the TypeScript check.
Main changes
This adds native input masks with custom character rules, placeholders and dynamic patterns, together with caret/selection handling, IME support, counting and clear-button integration.
<Inputmask={({ value })=>value.length>4 ? '00-000' : ''}/>
Type 12345 into the empty input. Expected: 12-345. Actual: 5. The onChange values are 1, 12, 123, 1234, then 5, so the loss also reaches application state. The same transition can be reproduced through composition events.
The previous token list is empty, so editMask cannot recover the existing 1234 from the previous slots and processes only the inserted 5. Please handle the transition from an empty pattern by formatting the complete proposed value and mapping its caret.
With <Input mask="100" /> (literal 1, followed by two digit slots), type 12. Expected: 112. Actual: 12; the first typed 1 never fills a slot.
getPrefixLength(change.inserted, tokens) runs for typed text as well as complete-value parsing. It consumes the first keystroke as the literal prefix, even though the input already displays that prefix. Please distinguish typed slot input from parsing a value that may include its prefix.
Render <Input mask="0X0" defaultValue="1A2" /> without placeholders.
Place the caret at offset 0 and press Delete. The value becomes A2.
Select offsets [0, 1] (the A) and type B.
Expected: B2. Actual: 2.
The hidden leading digit slot and the visible letter slot share the same display offset. findIndex selects the hidden digit slot, rejects B, and removes the selected A. Please map range replacements to the actual selected character slots, accounting for hidden empty positions.
🟡 Non-blocking recommendations
None beyond adding regression coverage for these cases alongside the fixes.
- Format the whole proposed value, mapping its caret, when a dynamic mask
leaves an empty pattern or a mask is enabled during a composition.
- Match the literal prefix only when reading a value, not when typing.
- When typing over a selection, skip a hidden empty position that rejects
the character.
- Ignore results that would be read back differently, including
count.exceedFormatter results, restoring the previous value or an empty
one.
- Remove unreachable composition and fallback code and cover the remaining
branches.
@zombieJ thanks for the review! The three cases are fixed in 1ba1cc4, each with a regression test:
When a dynamic mask leaves an empty pattern, the whole proposed value is now formatted and the caret is mapped, so typing 12345 gives 12-345. The same applies when the switch happens at the end of a composition.
The literal prefix is only matched when reading a value (paste, autofill), not when typing, so typing 12 with mask="100" gives 112.
When typing over a selection, a hidden empty position that rejects the character is skipped, so replacing A with B in A2 (0X0) gives B2.
While working on these, I also made results that would be read back differently (for example when a literal could be taken as a digit) restore the previous value instead of emitting something else, and added tests for the lines Codecov reported as uncovered.
The reason will be displayed to describe this comment to others. Learn more.
I looked through antd’s existing APIs and found two approaches to formatting: formatter callbacks for transforming values, as in InputNumber and Input.OTP, and string templates, as in DatePicker and the other Picker components. For consistency with the existing APIs, I think we could consolidate these under format:
Also, InputNumber’s built-in decimal formatting already handles caret positioning. Could we extract the caret calculations into a shared function and reuse it in both components?
The reason will be displayed to describe this comment to others. Learn more.
Thanks for looking into the existing APIs, consolidating everything under format makes sense to me. Before reworking the PR, I'd like to check a few details:
Should format.mask accept only strings? The current mask also accepts arrays of literals and regular expressions, and functions that choose the pattern from the value. For example, card numbers use 0000 000000 00000 for Amex and 0000 0000 0000 0000 for the others. There are also custom token rules and placeholders for empty positions. I could keep them in the same object:
format?: {mask?: InputMask;// string, array of literals and RegExp, or a function returning one
definitions?: InputMaskDefinitions;
placeholder?: string;
formatter?: (value: string)=>string;};
Or I could drop arrays and dynamic masks and keep only strings, as in your example. Which do you prefer?
Can mask and formatter be used together? If so, I'd apply formatter first and the mask afterwards, so that, for example, formatter: (value) => value.toUpperCase() works with mask: 'XX-00'.
For the caret, I'd move the useCursor logic from rc-input-number to @rc-component/util and use it for formatter, since it works with any transformation. For the mask, I'd keep the current calculation: the shared logic only compares the text before and after the caret, so it can't track editable positions. For example, pressing Backspace right after the separator in 12-34 gives 13-4: the mask places the caret after 1, while the shared logic would move it to the end. Does that work for you? If so, I'll open the @rc-component/util PR first and update this one afterwards.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add native masks to
Inputso applications can format card numbers, phone numbers and mixed character codes while users type, with caret and selection handling built into the component. For example,mask="0000 0000 0000 0000"displays1234567890123456as1234 5678 9012 3456without an additional runtime dependency.Related to ant-design/ant-design#54442. This provides the native foundation; exposing it through Ant Design requires a separate integration after release.
API
mask?: InputMask: a string pattern, a readonly array of literal strings and single-character regular expressions, or a pure function receiving{ value, selection }and returning a pattern. Dynamic masks receive the proposed input text and its selection offsets; selection isnullfor initial or externally updated values.maskDefinitions?: Readonly<Record<string, RegExp | null>>: extends or overrides the default string tokens.0accepts ASCII digits,Xaccepts Latin letters, and*accepts alphanumeric characters. Anulldefinition makes a token literal; backslash escapes tokens. Array strings remain literals. Statefulgandyregex flags are ignored without mutating caller-provided expressions.maskPlaceholder?: string | null: unfilled positions are hidden when omitted,undefined,nullor''. An explicit character repeats across empty positions; a longer string supplies placeholders at the corresponding pattern positions, with_used beyond its length. Placeholder characters are reserved for empty positions, and conflicting rules produce a development warning.Exports
InputMaskState,InputMaskPattern,InputMask, andInputMaskDefinitions.Behavior and coverage
target.valueandcurrentTarget.valuecontain formatted text, including separators and visible placeholders. Clearing emits''. No generic formatter/parser or automatic unmasked-value API is added.allowClear, refs, focus, disabled/read-only state,maxLengthand character counting. Placeholder-only values hide the clear button through a newhiddenClearprop onBaseInput: since feat: unify allowClear configuration #208,allowClear.disabledremoves the affix wrapper, which would remount the input and lose focus. Explicitly disabled clearing keeps the upstream behavior.maxLengthlimits displayed pattern length;count.exceedFormatterreceives formatted text and its result is formatted again.text,search,tel,urlandpasswordinputs. Mask props are excluded from DOM attributes.Summary by CodeRabbit