SMOODEV-2153: Phone formatting + validation on pre-chat form (libphonenumber-js)#17
Merged
Merged
Conversation
…enumber-js, US)
The pre-chat form's Phone field had no client-side formatting or validation —
visitors typed free-form numbers and only the backend's E.164 normalization
(smooai #2138) caught problems. This adds the frontend half so the field is
self-explaining and the value we send is already canonical when it parses.
- As-you-type formatting via AsYouType('US'). To avoid the standard AsYouType
caret caveat (full-string reformat jumps the caret to the end), we only
rewrite the value when the caret is at the end (the append-a-digit case) and
never on a deletion — backspacing formatting characters works naturally.
- Inline, themed validity hint driven by isValidPhoneNumber(value,'US'). Empty
stays neutral (the field is optional unless requirePhone).
- On submit: block + hint when requirePhone and the number is invalid; allow
submit when optional. Send canonical E.164 (parsePhoneNumber.number) when it
parses, else the raw value (the backend re-parses + normalizes/nulls).
- Autofill preserved: type="tel", autocomplete="tel", and the implicit <label>
are unchanged, and we reformat/validate on `change` too so a browser-autofilled
value is handled.
- Standalone IIFE bundle inlines libphonenumber-js/min (added to
deps.alwaysBundle) — without it the IIFE left a bare `libphonenumber_js_min`
external reference that would ReferenceError in a plain <script> embed.
Bundle-size impact (standalone IIFE chat-widget.global.js): 140 kB → 438 kB raw
(39 kB → 92 kB gzip) from the /min metadata. The recommended loader embed
lazy-loads this past the host's LCP. ESM index.js (lib externalized) is unchanged.
Version bumped to 0.8.0 (minor — new validation behavior) + changeset added.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01AxL4LdeChNivUCZM6No2NJ
🦋 Changeset detectedLatest commit: ff5c7ad The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Problem
The pre-chat form's Phone field had no client-side formatting or validation. Visitors typed free-form numbers and only the backend's authoritative E.164 normalization (smooai #2138, the backend half of SMOODEV-2153) caught problems. The field gave no feedback and the value sent was whatever the user typed.
Solution (frontend half of SMOODEV-2153)
Wire
libphonenumber-js(US default region) into the pre-chat phone input — kept in the widget's hand-rolled, dependency-light idiom (no React).AsYouType('US')(e.g.2133734253→(213) 373-4253).isValidPhoneNumber(value, 'US')— a subtle, themed valid/invalid state on the field + a small hint span. Empty stays neutral (the field is optional unlessrequirePhone).requirePhoneand the number is invalid; allow submit when optional. Send canonical E.164 (parsePhoneNumber(value, 'US').number) when it parses, else fall back to the raw value — the backend re-parses and normalizes/nulls either way.type="tel",autocomplete="tel", and the implicit<label>are unchanged; the value is also reformatted/validated onchangeso a browser-autofilled number is handled.Caret / UX compromise (as-you-type)
AsYouTypereformats the entire string, which on a mid-string edit moves the caret to the end (the well-known caveat). To avoid fighting the user, the value is only rewritten when the caret is at the end (the common append-a-digit case) and never on a deletion — so backspacing the formatting characters works naturally. The trade-off: editing in the middle of an already-formatted number does not live-reformat (it re-validates onchange/submit). This is the standard pragmatic compromise for vanilla inputs without a masking library.Bundle-size impact
libphonenumber-js/min(smaller metadata set, sufficient for AsYouType + isValidPhoneNumber + parsePhoneNumber).chat-widget.global.js(standalone IIFE)index.js(ESM, lib externalized)The IIFE jump is the inlined
/mincountry metadata. The recommended loader embed lazy-loadschat-widget.global.jspast the host's LCP, so this stays off the critical render path. The ESM build keeps the lib external so bundler hosts dedupe it.Verification
pnpm typecheck✅pnpm test✅ — 101 passed (9 new phone tests: as-you-type US formatting, isValid good vs garbage, neutral-when-empty, no-reformat-while-deleting, autofillchangereformat, E.164 on submit, requirePhone blocks invalid, optional forwards raw)pnpm build✅ — IIFE/global builds clean, noMISSING_GLOBAL_NAME, smoke-evaluated without ReferenceErrorVersion
Bumped to 0.8.0 (minor — new validation behavior) + changeset added.
Rollout
Do not merge yet — the main session coordinates the FE+BE rollout + the smoo.ai embed bump. Backend half is up as smooai #2138.
🤖 Generated with Claude Code
https://claude.ai/code/session_01AxL4LdeChNivUCZM6No2NJ