MaskedInput
Overview
Section titled “Overview”MaskedInput derives directly from TextBox rather than introducing a lookless multi-part
template: the web contract is a single native <input> wired to one @oninput handler, and the
WPF port mirrors that with a single OnTextChanged override. Every edit, typing, Backspace,
Delete, or paste, funnels through the same pure MaskEngine.Format pipeline, which filters
characters against the mask’s digit/letter/alphanumeric/literal tokens and keeps the caret
stable across the reformat.
xmlns:masked="clr-namespace:Navius.Wpf.Primitives.Controls.MaskedInput;assembly=Navius.Wpf.Primitives"
<masked:NaviusMaskedInput Mask="(000) 000-0000" Value="{Binding Phone, Mode=TwoWay}" PlaceholderChar="_" />Properties
Section titled “Properties”| Property | Type | Default | From | Description |
|---|---|---|---|---|
Mask |
string |
"" |
NaviusMaskedInput |
Mask pattern: 0 = digit, A = letter, * = alphanumeric, any other character is a fixed literal. |
Value |
string? |
null |
NaviusMaskedInput |
Controlled masked value; IS this control’s Text. Two-way bindable by default via a normal binding. |
DefaultValue |
string? |
null |
NaviusMaskedInput |
Uncontrolled initial value, applied once on Loaded and only when no Value is set (normalized through the mask on that first application). |
PlaceholderChar |
char? |
null |
NaviusMaskedInput |
Char shown for empty slots (e.g. _); null renders nothing (a lazy skeleton). Maps to the web contract’s Placeholder (renamed to avoid colliding with the string Placeholder watermark used across Select/Combobox/TagInput). |
Lazy |
bool |
true |
NaviusMaskedInput |
true: trailing fixed tokens appear only once reached. false: eager, always emitted. |
Overwrite |
bool |
false |
NaviusMaskedInput |
Reserved for type-over (replace) mode. Registered but never read, a no-op stub carried forward unchanged from the web source’s own stub. |
Preprocessors |
IReadOnlyList<Func<ElementState, ElementState>>? |
null |
NaviusMaskedInput |
Ordered pure transforms run on the proposed state before masking. |
Postprocessors |
IReadOnlyList<Func<ElementState, ElementState>>? |
null |
NaviusMaskedInput |
Ordered pure transforms run on the masked state after masking (postfix, clamp, …). |
UnmaskedValue |
string |
"" |
NaviusMaskedInput |
Read-only. The raw editable characters (placeholder-filled, no literals) behind the current masked Value. |
Invalid |
bool |
false |
NaviusMaskedInput |
Presentational only (a skin hook, e.g. a destructive border). No validation logic lives here; validation is the consumer’s concern. |
Disabled is not a separate DP; the control uses standard WPF IsEnabled.
Events
Section titled “Events”| Event | Signature | Fires when |
|---|---|---|
UnmaskedValueChanged |
EventHandler<string> |
After every reformat, alongside Value updating, carrying the placeholder-filled unmasked editable characters. |
Value’s own change notification is the standard WPF two-way binding path (ValueProperty with
BindsTwoWayByDefault), not a separate CLR event.
Keyboard interactions
Section titled “Keyboard interactions”No PreviewKeyDown/KeyDown handler exists anywhere in this control, matching the web contract’s
single-event design: typing, Backspace, Delete, and paste all funnel through the same
OnTextChanged override and are processed uniformly by MaskEngine.Format, which filters
non-matching characters and recomputes the caret. The proposed caret is collapsed to the
selection end before masking (a keystroke’s selection is already collapsed there; a programmatic
SelectedText insertion leaves the inserted text selected with End right after it), so the
pipeline never tries to preserve a non-collapsed range across a reformat.
UIA mechanism
Section titled “UIA mechanism”NaviusMaskedInput adds no custom AutomationPeer: as a direct TextBox subclass, it inherits
the stock TextBoxAutomationPeer (AutomationControlType.Edit, ValuePattern), which already
covers the bare-native-input contract (no custom ARIA role exists in the web markup either). The
unit suite exhaustively pins the caret-stability guarantee (mid-string insertion, Backspace over a
fixed literal, mid-string digit deletion, paste with garbage, non-collapsed selection ends mapped
independently, placeholder skeletons, clamping) directly against MaskEngine, plus control-level
tests for the single-input-path wiring.
Web deltas
Section titled “Web deltas”- The web’s async JS-interop bridge (
CreateMaskedSelectionAsync/GetStateAsync/SetStateAsync) and itsOnAfterRenderAsyncpending-caret-reapply dance, needed only to work around Blazor’s async re-render resetting the caret, disappear entirely: WPF’sTextBox.Text/CaretIndex/Selectare synchronous, so the whole reformat-and-reposition happens inside oneOnTextChangedhook. - The web’s
Placeholder(char?) parameter is namedPlaceholderCharon the WPF control, to avoid colliding with the stringPlaceholderwatermark parameter used across Select, Combobox, and TagInput. Behavior is identical; only the name differs. This rename was not recorded in the original WPF implementation notes and was an M6-confirmed documentation gap, now recorded here. Overwriteis carried forward as the same no-op stub the web source itself ships (“accepted for parity, not yet wired to insertion”); implementing real type-over here would diverge from the behavior this port mirrors, so it was deliberately not implemented.Preprocessors/Postprocessorskeep the exactFunc<ElementState, ElementState>delegate contract from the web source;ElementState’s selection was flattened from a tuple toSelectionStart/SelectionEndints (the tuple bought nothing in WPF).DefaultValueprecedence mirrors the web’s “used only on first init” control flow: it seeds the control once onLoaded, only whenValueis unset; afterward onlyValueis honored.- IME composition input arrives through the same
OnTextChangedpath (WPF raises it after composition updates), so no separateTextCompositionManagerhandling was added, matching the web source, which also has no dedicated composition handling. data-invalid/data-disabled(presentational data attributes on the web) map to the plainInvalidbool (consumed by a template trigger) and nativeIsEnabled, not toValidation.HasError, preserving the web source’s “validation is the consumer’s concern” stance.- M6 audit found the codebase’s masking core and control wiring accurate to the doc’s claims,
with one undocumented parameter rename (
PlaceholdertoPlaceholderChar, recorded above) and no other confirmed or plausible disparities.
Captures
Section titled “Captures”The MaskedInput Gallery page rendered at the pinned commit, in each theme.


