0
# As-You-Type Formatting
1
2
Interactive phone number formatting for user interfaces, providing real-time formatting as users type phone numbers. This capability enables better user experience in forms and input fields.
3
4
## Capabilities
5
6
### AsYouTypeFormatter Class
7
8
The main class for providing real-time phone number formatting as users enter digits.
9
10
```python { .api }
11
class AsYouTypeFormatter:
12
"""
13
A formatter that formats phone numbers as they are being typed.
14
15
Provides real-time formatting feedback for user interfaces,
16
automatically applying appropriate formatting patterns based
17
on the digits entered so far.
18
"""
19
20
def __init__(self, region_code: str):
21
"""
22
Initialize formatter for a specific region.
23
24
Parameters:
25
- region_code: Two-letter region code for formatting context
26
"""
27
28
def input_digit(self, next_char: str, remember_position: bool = False) -> str:
29
"""
30
Input a single digit and get the formatted result.
31
32
Parameters:
33
- next_char: Single character ('0'-'9', '+', etc.) to add
34
- remember_position: Whether to remember position for cursor tracking
35
36
Returns:
37
Formatted phone number string with the new character incorporated
38
"""
39
40
def clear(self) -> None:
41
"""
42
Clear the formatter state and prepare for new input.
43
44
Clears internal state so the formatter can be reused.
45
"""
46
47
def get_remembered_position(self) -> int:
48
"""
49
Get the position in formatted output of a remembered character.
50
51
Returns position of character that was input with remember_position=True.
52
53
Returns:
54
Position index in the formatted string
55
"""
56
```
57
58
## Usage Examples
59
60
### Basic As-You-Type Formatting
61
62
```python
63
import phonenumbers
64
65
# Create formatter for US numbers
66
formatter = phonenumbers.AsYouTypeFormatter("US")
67
68
# Simulate user typing a phone number
69
digits = "6502532222"
70
print("As user types:")
71
72
for digit in digits:
73
result = formatter.input_digit(digit)
74
print(f" Input '{digit}' -> '{result}'")
75
76
# Output will show progressive formatting:
77
# Input '6' -> '6'
78
# Input '5' -> '65'
79
# Input '0' -> '650'
80
# Input '2' -> '650-2'
81
# Input '5' -> '650-25'
82
# Input '3' -> '650-253'
83
# Input '2' -> '650-2532'
84
# Input '2' -> '(650) 253-22'
85
# Input '2' -> '(650) 253-222'
86
# Input '2' -> '(650) 253-2222'
87
```
88
89
### International Number Formatting
90
91
```python
92
import phonenumbers
93
94
# Format international number as user types
95
formatter = phonenumbers.AsYouTypeFormatter("US")
96
97
# User starts with international prefix
98
international_digits = "+442083661177"
99
print("International number formatting:")
100
101
for digit in international_digits:
102
result = formatter.input_digit(digit)
103
print(f" '{digit}' -> '{result}'")
104
105
# Shows progression from local to international formatting
106
```
107
108
### Interactive Phone Input Implementation
109
110
```python
111
import phonenumbers
112
113
class PhoneInputHandler:
114
"""Example implementation of interactive phone number input."""
115
116
def __init__(self, default_region="US"):
117
self.formatter = phonenumbers.AsYouTypeFormatter(default_region)
118
self.current_value = ""
119
120
def handle_digit_input(self, digit):
121
"""Handle single digit input from user."""
122
if digit.isdigit() or digit in "+":
123
formatted = self.formatter.input_digit(digit)
124
self.current_value = formatted
125
return formatted
126
return self.current_value
127
128
def handle_backspace(self):
129
"""Handle backspace/delete key."""
130
if self.current_value:
131
# Clear formatter and re-input all but last character
132
self.formatter.clear()
133
input_chars = [c for c in self.current_value if c.isdigit() or c == '+']
134
if input_chars:
135
input_chars.pop() # Remove last character
136
137
self.current_value = ""
138
for char in input_chars:
139
self.current_value = self.formatter.input_digit(char)
140
return self.current_value
141
return ""
142
143
def clear_input(self):
144
"""Clear all input."""
145
self.formatter.clear()
146
self.current_value = ""
147
return ""
148
149
def get_parsed_number(self):
150
"""Get parsed PhoneNumber object if input is valid."""
151
try:
152
# Remove formatting for parsing
153
clean_number = ''.join(filter(str.isdigit, self.current_value))
154
if self.current_value.startswith('+'):
155
clean_number = '+' + clean_number
156
157
return phonenumbers.parse(clean_number)
158
except phonenumbers.NumberParseException:
159
return None
160
161
def is_valid_number(self):
162
"""Check if current input represents a valid number."""
163
parsed = self.get_parsed_number()
164
if parsed:
165
return phonenumbers.is_valid_number(parsed)
166
return False
167
168
# Example usage
169
phone_input = PhoneInputHandler("US")
170
171
# Simulate user input
172
test_input = "6502532222"
173
print("Simulating phone input:")
174
175
for digit in test_input:
176
formatted = phone_input.handle_digit_input(digit)
177
is_valid = phone_input.is_valid_number()
178
print(f"Input: '{digit}' -> Display: '{formatted}' (Valid: {is_valid})")
179
180
# Test backspace
181
print("\nTesting backspace:")
182
for _ in range(3):
183
formatted = phone_input.handle_backspace()
184
is_valid = phone_input.is_valid_number()
185
print(f"After backspace: '{formatted}' (Valid: {is_valid})")
186
```
187
188
### Multi-Region Support
189
190
```python
191
import phonenumbers
192
193
class MultiRegionPhoneFormatter:
194
"""Phone formatter that can switch regions based on input."""
195
196
def __init__(self, default_region="US"):
197
self.default_region = default_region
198
self.current_region = default_region
199
self.formatter = phonenumbers.AsYouTypeFormatter(default_region)
200
self.input_buffer = ""
201
202
def detect_region_from_input(self, input_so_far):
203
"""Attempt to detect region from country code in input."""
204
if input_so_far.startswith('+'):
205
# Try to parse and determine region
206
try:
207
# Extract potential country code
208
digits_only = ''.join(filter(str.isdigit, input_so_far))
209
if len(digits_only) >= 1:
210
# Try different country code lengths
211
for cc_length in [1, 2, 3]:
212
if len(digits_only) >= cc_length:
213
potential_cc = int(digits_only[:cc_length])
214
region = phonenumbers.region_code_for_country_code(potential_cc)
215
if region and region != "ZZ":
216
return region
217
except (ValueError, TypeError):
218
pass
219
return self.default_region
220
221
def input_digit(self, digit):
222
"""Input digit with automatic region detection."""
223
self.input_buffer += digit
224
225
# Check if we need to switch regions
226
detected_region = self.detect_region_from_input(self.input_buffer)
227
228
if detected_region != self.current_region:
229
# Switch to new region and restart formatting
230
self.current_region = detected_region
231
self.formatter = phonenumbers.AsYouTypeFormatter(detected_region)
232
233
# Re-input all digits with new formatter
234
result = ""
235
for d in self.input_buffer:
236
result = self.formatter.input_digit(d)
237
return result
238
else:
239
return self.formatter.input_digit(digit)
240
241
def clear(self):
242
"""Clear formatter and reset to default region."""
243
self.input_buffer = ""
244
self.current_region = self.default_region
245
self.formatter = phonenumbers.AsYouTypeFormatter(self.default_region)
246
self.formatter.clear()
247
return ""
248
249
# Example usage
250
multi_formatter = MultiRegionPhoneFormatter("US")
251
252
# Test with US number
253
us_number = "6502532222"
254
print("US number formatting:")
255
for digit in us_number:
256
result = multi_formatter.input_digit(digit)
257
print(f" '{digit}' -> '{result}' (Region: {multi_formatter.current_region})")
258
259
print()
260
multi_formatter.clear()
261
262
# Test with UK number (starts with +44)
263
uk_number = "+442083661177"
264
print("UK number formatting:")
265
for digit in uk_number:
266
result = multi_formatter.input_digit(digit)
267
print(f" '{digit}' -> '{result}' (Region: {multi_formatter.current_region})")
268
```
269
270
### Form Validation Integration
271
272
```python
273
import phonenumbers
274
275
class PhoneNumberField:
276
"""Phone number field with real-time validation and formatting."""
277
278
def __init__(self, region="US", required=True):
279
self.region = region
280
self.required = required
281
self.formatter = phonenumbers.AsYouTypeFormatter(region)
282
self.raw_value = ""
283
self.formatted_value = ""
284
self.is_valid = False
285
self.validation_message = ""
286
287
def set_value(self, value):
288
"""Set the field value and update formatting."""
289
self.raw_value = value
290
self.formatter.clear()
291
292
# Apply formatting character by character
293
self.formatted_value = ""
294
for char in value:
295
if char.isdigit() or char == '+':
296
self.formatted_value = self.formatter.input_digit(char)
297
298
# Validate the result
299
self._validate()
300
301
return {
302
'formatted': self.formatted_value,
303
'is_valid': self.is_valid,
304
'message': self.validation_message
305
}
306
307
def _validate(self):
308
"""Internal validation logic."""
309
if not self.raw_value and self.required:
310
self.is_valid = False
311
self.validation_message = "Phone number is required"
312
return
313
314
if not self.raw_value:
315
self.is_valid = True
316
self.validation_message = ""
317
return
318
319
try:
320
parsed = phonenumbers.parse(self.raw_value, self.region)
321
322
if phonenumbers.is_valid_number(parsed):
323
self.is_valid = True
324
self.validation_message = ""
325
elif phonenumbers.is_possible_number(parsed):
326
self.is_valid = False
327
self.validation_message = "Phone number format is not valid"
328
else:
329
reason = phonenumbers.is_possible_number_with_reason(parsed)
330
if reason == phonenumbers.ValidationResult.TOO_SHORT:
331
self.validation_message = "Phone number is too short"
332
elif reason == phonenumbers.ValidationResult.TOO_LONG:
333
self.validation_message = "Phone number is too long"
334
else:
335
self.validation_message = "Invalid phone number"
336
self.is_valid = False
337
338
except phonenumbers.NumberParseException as e:
339
self.is_valid = False
340
if e.error_type == phonenumbers.NumberParseException.INVALID_COUNTRY_CODE:
341
self.validation_message = "Invalid country code"
342
elif e.error_type == phonenumbers.NumberParseException.NOT_A_NUMBER:
343
self.validation_message = "Not a valid phone number"
344
else:
345
self.validation_message = "Invalid phone number format"
346
347
# Example usage
348
phone_field = PhoneNumberField("US", required=True)
349
350
# Test various inputs
351
test_inputs = [
352
"", # Empty (required field)
353
"650", # Too short
354
"6502532222", # Valid US number
355
"+442083661177", # Valid UK number
356
"invalid", # Not a number
357
"65025322221234567", # Too long
358
]
359
360
for test_input in test_inputs:
361
result = phone_field.set_value(test_input)
362
print(f"Input: '{test_input}'")
363
print(f" Formatted: '{result['formatted']}'")
364
print(f" Valid: {result['is_valid']}")
365
print(f" Message: '{result['message']}'")
366
print()
367
```
368
369
## Integration Patterns
370
371
### Web Framework Integration
372
373
```python
374
import phonenumbers
375
376
def create_phone_input_widget(name, region="US", placeholder=None):
377
"""Create HTML for phone input with JavaScript formatting."""
378
379
placeholder_text = placeholder or f"Enter {region} phone number"
380
381
html = f'''
382
<div class="phone-input-container">
383
<input type="tel"
384
name="{name}"
385
id="{name}"
386
placeholder="{placeholder_text}"
387
data-region="{region}"
388
class="phone-input">
389
<div class="validation-message" id="{name}-validation"></div>
390
</div>
391
392
<script>
393
// JavaScript integration would use phonenumbers library
394
// or server-side formatting via AJAX calls
395
document.getElementById('{name}').addEventListener('input', function(e) {{
396
// Format input in real-time
397
// Validate and show feedback
398
}});
399
</script>
400
'''
401
402
return html
403
404
# Server-side formatting endpoint
405
def format_phone_api(request):
406
"""API endpoint for real-time phone formatting."""
407
digit = request.GET.get('digit', '')
408
region = request.GET.get('region', 'US')
409
current_state = request.GET.get('state', '')
410
411
# In a real implementation, you'd maintain formatter state
412
# in session or pass it back and forth
413
414
formatter = phonenumbers.AsYouTypeFormatter(region)
415
416
# Restore previous state
417
for d in current_state:
418
if d.isdigit() or d == '+':
419
formatter.input_digit(d)
420
421
# Add new digit
422
if digit:
423
result = formatter.input_digit(digit)
424
return {'formatted': result, 'state': current_state + digit}
425
426
return {'formatted': current_state, 'state': current_state}
427
```