refactor(VOtpInput): single overlay input#22803
refactor(VOtpInput): single overlay input#22803Yanis-Riani wants to merge 16 commits intovuetifyjs:devfrom
Conversation
|
I have a question about the local translation, since one aria label has been touch did i need to clear all the translation ? |
|
yes to both
additionally
btw. I am not sure if it counts as breaking or not.. would be a shame to push it to v5 |
|
Ok thank i will fix that tomorrow, i can do the French translation since im a native and for the doc i will see if i could updated it properly, i will probably need some feedback about it. |
|
I am curious if we could make it all backward compatible. This would mean we need to preserve the original "otp" key in translations and add a new one "otpInput" or sth. |
d7d3ac3 to
5351f5c
Compare
I’m confused about what the use case would be for keeping the old key. isn't just dead code ? the only part of the component that needs an aria-label is the hidden input, we can add is an aria-description, but the old pattern with index no longer makes much sense here. |
|
Also, I figured out that once the input is selected, it intercepts the hover CSS of VField. The only workaround I found to this is to add an hover prop to VField and add |
Replace the N separate <input> elements with a single hidden <input> overlaying all visual slots, inspired by the input-otp library. - Single real input improves a11y, native autofill (autocomplete="one-time-code"), and mobile keyboard support - Forced 1-char range selection ([i, i+1]) drives the active-slot highlight via onSelectionChange; mirrors stored in renderSelectionStart/End refs - Visual slots are now <div> + <span> inside VField (no more input elements) - Fake caret rendered on active empty slot with CSS blink animation - deleteContentForward (Delete key) intercepted in beforeinput to keep selection at slot i instead of drifting backward - Modifier + Backspace/Delete intercepted in keydown (before browser converts to deleteContentBackward due to forced range selection) - Android IME fallback: word/line delete inputTypes handled in beforeinput - renderSelectionStart/End updated directly after setSelectionRange to avoid missed selectionchange on empty inputs in Chromium - Tests adapted: single input selector, active slot via .v-field--focused, clickSlot helper for selection-based slot targeting
- Add VOtpField, VOtpGroup, VOtpSeparator sub-components for custom layouts - Add merged prop to VOtpInput and VOtpGroup for merged-border style - Propagate merged/divider from root via provide/inject context - Auto-layout uses sub-components internally - VOtpGroup uses display:contents (non-merged) for equal field sizing - VOtpGroup uses flex:N (merged, N=field count) for proportional sizing - Merge mode collapses adjacent borders via margin-inline-start:-1px - Restore focused field border via z-index and border-inline-start-width
Add focusAt(index) to context for programmatic slot selection. VOtpField emits data-otp-index + onClick for pre-focus slot clicks. Input uses onMousedown + elementsFromPoint for post-focus detection. Pointer-events toggle (none/all via :focus-within) enables native hover before focus and right-click paste after focus. Fix hover borders in merged groups with z-index layering. Clamp focusAt index to input.value.length for empty slot clicks.
Remove individual field box-shadows in merged groups to prevent overlapping artifacts. Re-apply elevation on the group container via :has() for solo variants. Add overlay on focused solo/plain fields. Adapt tests to pointer-events toggle and add coverage for click-to-select, empty slot redirect, merged layout and custom sub-component layouts.
5351f5c to
27c8eec
Compare
|
The doc is updated, and I made some style changes when |
Description
resolves #22659
resolves #18427
Refactors VOtpInput to use a single hidden overlay input instead of individual inputs per slot. This architecture fixes copy/paste, RTL support, and improves screen reader support. The component is fully backward-compatible slots can be customized via the default slot using new sub-components.
This component can be manipulated to other used case then an OTP such as a PIN code, Bank codes (BSB, IBAN, ...) and maybe other but it may need changes on the pattern side to check the whole input and not each character
New Sub Components
index(Number) - Index of slotmerged(Boolean | null) default =null- Merge childVOtpField, fallback to root componentmergeddefault- to create custom separator (replace text)Root Component
pattern(’numeric’ | ‘alpha’ | ‘alphanumeric’ | RegExp) default =undefined- filter accepted character, fallback totype=numberif it’s the case then pattern ='numeric'for backward-compatibility reasonmerged(Boolean) default =false- define VOtpGroup default behavior, when merge no divider can be displayeddefault- to custom slots layout with sub components:VOtpField,VOtpGroup,VOtpSeparatordivider- Slot for custom divider content, Receives{ index }with the divider positionMarkup: