playbook_ui 15.1.0.pre.alpha.PLAY2468phonenuminputvalidation10993 → 15.1.0.pre.alpha.PLAY251810942

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f282ba3da61911cdfdc409d4cf8272cd1a89f0110a381e78c7af5faf80da9faf
4
- data.tar.gz: 5c04ca68a269de660b5fae7f0ef9886e8b8d3545d065f6f91cdf45deba513c83
3
+ metadata.gz: ea9f5c8acfb10e8fb3d40fea187b82854b1fb068361853c9b032f91fca4685ad
4
+ data.tar.gz: ff8472f9f4055f5b24cb17583c02c37fa5cc022f37bf7d36869cb4d2dc06feaf
5
5
  SHA512:
6
- metadata.gz: 3869b47c55c537d86d46e33849016d975bcb8184d7f8c2529b68a556087c349a7e6438bfdb07e5c1e8ecd8e99daae22251919181714d8520b3df7e91cff67c48
7
- data.tar.gz: 55243c0bbbafd08287ac9cb8524bfca5a76730222e3d459be8c9cfaf8afdf752eae4ed5ab787ce94c3d5b82d28e6caee59028b71dc5feb88b9f73df247ee8d08
6
+ metadata.gz: a7a3df82c8d6bb097dc840c452b3658a4751f3ecb1efde791db7043328cca7a1450adb4d56b0b1d52e13ebd7e75163573105a2910bd01faeca49c05937086f83
7
+ data.tar.gz: 869e0b7510f081c28a277b0f77f1c96e2f63dccfc5955913140a00952741545f79e728370a234ded1cedba4fde080bbd396650e357a37b24ede37ac55428bfff
@@ -10,9 +10,15 @@
10
10
  @import "./scss_partials/dropdown_animation";
11
11
  @import "dropdown_mixin";
12
12
 
13
- [class*="pb_dropdown"] {
13
+ .pb_dropdown_default,
14
+ .pb_dropdown_subtle,
15
+ .pb_dropdown_default_separators_hidden,
16
+ .pb_dropdown_subtle_separators_hidden {
14
17
  .dropdown_wrapper {
15
- [class*="dropdown_trigger_wrapper"] {
18
+ .dropdown_trigger_wrapper,
19
+ .dropdown_trigger_wrapper_focus,
20
+ .dropdown_trigger_wrapper_focus_select_only,
21
+ .dropdown_trigger_wrapper_select_only {
16
22
  @include pb_body;
17
23
  border: 1px solid $border_light;
18
24
  background-color: $white;
@@ -48,11 +54,11 @@
48
54
  transition: box-shadow 0.15s ease-in-out;
49
55
  }
50
56
 
51
- &[class*="_select_only"] {
57
+ &.dropdown_trigger_wrapper_select_only {
52
58
  box-shadow: inset 0 -11px 20px rgba($primary, 0.05);
53
59
  }
54
60
 
55
- &[class*="_focus"] {
61
+ &.dropdown_trigger_wrapper_focus {
56
62
  box-shadow: 0px 0px 0 1px $primary !important;
57
63
  transition: box-shadow 0.1s ease-in-out;
58
64
  }
@@ -69,7 +75,12 @@
69
75
  z-index: $z_1;
70
76
  width: 100%;
71
77
 
72
- [class*="pb_dropdown_option"] {
78
+ .pb_dropdown_option,
79
+ .pb_dropdown_option_list,
80
+ .pb_dropdown_option_selected,
81
+ .pb_dropdown_option_focused,
82
+ .pb_dropdown_option_list_focused,
83
+ .pb_dropdown_option_selected_focused {
73
84
  padding-left: $space_sm;
74
85
  padding-right: $space_sm;
75
86
  padding-top: $space_xs;
@@ -79,11 +90,14 @@
79
90
  background-color: $bg_light;
80
91
  }
81
92
 
82
- &[class*="_focused"] {
93
+ &.pb_dropdown_option_focused,
94
+ &.pb_dropdown_option_list_focused,
95
+ &.pb_dropdown_option_selected_focused {
83
96
  background-color: $bg_light;
84
97
  }
85
98
 
86
- &[class*="_list"] {
99
+ &.pb_dropdown_option_list,
100
+ &.pb_dropdown_option_list_focused {
87
101
  border-bottom: 1px solid $border_light;
88
102
 
89
103
  &:hover, &:focus {
@@ -92,7 +106,8 @@
92
106
  }
93
107
  }
94
108
  }
95
- &[class*="selected"] {
109
+ &.pb_dropdown_option_selected,
110
+ &.pb_dropdown_option_selected_focused {
96
111
  background-color: $primary;
97
112
  color: $white;
98
113
  [class^="pb_body"],
@@ -141,41 +156,66 @@
141
156
  }
142
157
 
143
158
  &.error {
144
- [class*=pb_body_kit] {
159
+ .pb_body_kit_negative {
145
160
  margin-top: $space_xs / 2;
146
161
  }
147
162
 
148
- [class*="dropdown_trigger_wrapper"] {
163
+ .dropdown_trigger_wrapper,
164
+ .dropdown_trigger_wrapper_focus,
165
+ .dropdown_trigger_wrapper_focus_select_only,
166
+ .dropdown_trigger_wrapper_select_only {
149
167
  border-color: $error;
150
168
  box-shadow: none !important;
151
169
  }
152
170
  }
153
171
  }
154
172
 
155
- &[class*="separators_hidden"] {
173
+ &.pb_dropdown_default_separators_hidden,
174
+ &.pb_dropdown_subtle_separators_hidden {
156
175
  .dropdown_wrapper {
157
176
  .pb_dropdown_container {
158
177
 
159
- [class*="pb_dropdown_option"] {
178
+ .pb_dropdown_option,
179
+ .pb_dropdown_option_list,
180
+ .pb_dropdown_option_selected,
181
+ .pb_dropdown_option_focused,
182
+ .pb_dropdown_option_list_focused,
183
+ .pb_dropdown_option_selected_focused {
160
184
  border: none;
161
185
  }
162
186
  }
163
187
  }
164
188
  }
165
189
 
166
- &[class*="subtle"] {
190
+ &.pb_dropdown_subtle,
191
+ &.pb_dropdown_subtle_separators_hidden {
167
192
  .dropdown_wrapper {
168
193
  .pb_dropdown_container {
169
194
 
170
- [class*="pb_dropdown_option"]:first-child {
195
+ .pb_dropdown_option:first-child,
196
+ .pb_dropdown_option_list:first-child,
197
+ .pb_dropdown_option_selected:first-child,
198
+ .pb_dropdown_option_focused:first-child,
199
+ .pb_dropdown_option_list_focused:first-child,
200
+ .pb_dropdown_option_selected_focused:first-child {
171
201
  margin-top: $space_xs;
172
202
  }
173
203
 
174
- [class*="pb_dropdown_option"]:last-child {
204
+ .pb_dropdown_option:last-child,
205
+ .pb_dropdown_option_list:last-child,
206
+ .pb_dropdown_option_selected:last-child,
207
+ .pb_dropdown_option_focused:last-child,
208
+ .pb_dropdown_option_list_focused:last-child,
209
+ .pb_dropdown_option_selected_focused:last-child {
175
210
  margin-bottom: $space_xs;
176
211
  }
177
212
 
178
- [class*="pb_dropdown_option"] {
213
+ .pb_dropdown_option,
214
+ .pb_dropdown_option_list,
215
+ .pb_dropdown_option_selected,
216
+ .pb_dropdown_option_focused,
217
+ .pb_dropdown_option_list_focused,
218
+ .pb_dropdown_option_selected_focused {
179
219
  margin: $space_xs;
180
220
  padding: $space_xs;
181
221
  border-radius: $border_radius_md;
@@ -193,24 +233,44 @@
193
233
  }
194
234
  }
195
235
 
196
- [class*="pb_dropdown_option"]:last-child::after {
236
+ .pb_dropdown_option:last-child::after,
237
+ .pb_dropdown_option_list:last-child::after,
238
+ .pb_dropdown_option_selected:last-child::after,
239
+ .pb_dropdown_option_focused:last-child::after,
240
+ .pb_dropdown_option_list_focused:last-child::after,
241
+ .pb_dropdown_option_selected_focused:last-child::after {
197
242
  display: none;
198
243
  }
199
244
  }
200
245
  }
201
246
 
202
- &[class*="separators_hidden"] {
247
+ &.pb_dropdown_subtle_separators_hidden {
203
248
  .dropdown_wrapper {
204
249
  .pb_dropdown_container {
205
- [class*="pb_dropdown_option"]:first-child {
250
+ .pb_dropdown_option:first-child,
251
+ .pb_dropdown_option_list:first-child,
252
+ .pb_dropdown_option_selected:first-child,
253
+ .pb_dropdown_option_focused:first-child,
254
+ .pb_dropdown_option_list_focused:first-child,
255
+ .pb_dropdown_option_selected_focused:first-child {
206
256
  margin-top: $space_xs;
207
257
  }
208
258
 
209
- [class*="pb_dropdown_option"]:last-child {
259
+ .pb_dropdown_option:last-child,
260
+ .pb_dropdown_option_list:last-child,
261
+ .pb_dropdown_option_selected:last-child,
262
+ .pb_dropdown_option_focused:last-child,
263
+ .pb_dropdown_option_list_focused:last-child,
264
+ .pb_dropdown_option_selected_focused:last-child {
210
265
  margin-bottom: $space_xs;
211
266
  }
212
267
 
213
- [class*="pb_dropdown_option"] {
268
+ .pb_dropdown_option,
269
+ .pb_dropdown_option_list,
270
+ .pb_dropdown_option_selected,
271
+ .pb_dropdown_option_focused,
272
+ .pb_dropdown_option_list_focused,
273
+ .pb_dropdown_option_selected_focused {
214
274
  padding: $space_xxs $space_xs;
215
275
  margin: $space_xxs $space_xs;
216
276
 
@@ -218,7 +278,8 @@
218
278
  height: 0px;
219
279
  }
220
280
 
221
- &[class*="selected"] {
281
+ &.pb_dropdown_option_selected,
282
+ &.pb_dropdown_option_selected_focused {
222
283
  border-bottom: none;
223
284
  }
224
285
  }
@@ -229,7 +290,10 @@
229
290
 
230
291
  &.dark {
231
292
  .dropdown_wrapper {
232
- [class*="dropdown_trigger_wrapper"] {
293
+ .dropdown_trigger_wrapper,
294
+ .dropdown_trigger_wrapper_focus,
295
+ .dropdown_trigger_wrapper_focus_select_only,
296
+ .dropdown_trigger_wrapper_select_only {
233
297
  @include pb_body_light_dark;
234
298
  background-color: rgba($white, 0.1) !important;
235
299
  background: none;
@@ -244,7 +308,7 @@
244
308
  [class^="pb_body"] {
245
309
  color: $white;
246
310
  }
247
- &[class*="_select_only"] {
311
+ &.dropdown_trigger_wrapper_select_only {
248
312
  box-shadow: inset 0 -11px 20px rgba($white, 0.05);
249
313
  }
250
314
  .dropdown_input {
@@ -254,7 +318,10 @@
254
318
  }
255
319
 
256
320
  &.error {
257
- [class*="dropdown_trigger_wrapper"] {
321
+ .dropdown_trigger_wrapper,
322
+ .dropdown_trigger_wrapper_focus,
323
+ .dropdown_trigger_wrapper_focus_select_only,
324
+ .dropdown_trigger_wrapper_select_only {
258
325
  border-color: $error_dark;
259
326
  }
260
327
  }
@@ -268,19 +335,28 @@
268
335
  color: $white !important;
269
336
  }
270
337
 
271
- [class*="pb_dropdown_option"] {
338
+ .pb_dropdown_option,
339
+ .pb_dropdown_option_list,
340
+ .pb_dropdown_option_selected,
341
+ .pb_dropdown_option_focused,
342
+ .pb_dropdown_option_list_focused,
343
+ .pb_dropdown_option_selected_focused {
272
344
  &:hover {
273
345
  background-color: $hover_dark;
274
346
  }
275
347
 
276
- &[class*="_focused"] {
348
+ &.pb_dropdown_option_focused,
349
+ &.pb_dropdown_option_list_focused,
350
+ &.pb_dropdown_option_selected_focused {
277
351
  background-color: $hover_dark;
278
352
  }
279
353
 
280
- &[class*="_list"] {
354
+ &.pb_dropdown_option_list,
355
+ &.pb_dropdown_option_list_focused {
281
356
  border-color: rgba($white, 0.15);
282
357
  }
283
- &[class*="selected"] {
358
+ &.pb_dropdown_option_selected,
359
+ &.pb_dropdown_option_selected_focused {
284
360
  background-color: $primary;
285
361
  border-bottom: rgba($white, 0.15);
286
362
  }
@@ -90,7 +90,7 @@
90
90
  <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %>
91
91
  <%= form.typeahead :example_typeahead_validation, props: { data: { typeahead_example2: true, user: {} }, label: true, placeholder: "Search for a user", required: true, validation: { message: "Please select a user." } } %>
92
92
  <%= form.text_field :example_text_field_validation, props: { label: true, required: true } %>
93
- <%= form.phone_number_field :example_phone_number_field_validation, props: { label: "Example phone field", hidden_inputs: true, required: true } %>
93
+ <%= form.phone_number_field :example_phone_number_field_validation, props: { label: "Example phone field", hidden_inputs: true } %>
94
94
  <%= form.email_field :example_email_field_validation, props: { label: true, required: true } %>
95
95
  <%= form.number_field :example_number_field_validation, props: { label: true, required: true } %>
96
96
  <%= form.search_field :example_project_number_validation, props: { label: true, required: true, validation: { pattern: "[0-9]{2}-[0-9]{5}", message: "Please enter a valid project number (example: 33-12345)." } } %>
@@ -2,13 +2,12 @@ import PbEnhancedElement from '../pb_enhanced_element'
2
2
  import { debounce } from '../utilities/object'
3
3
 
4
4
  // Kit selectors
5
- const KIT_SELECTOR = '[class^="pb_"][class*="_kit"]'
6
- const ERROR_MESSAGE_SELECTOR = '.pb_body_kit_negative'
5
+ const KIT_SELECTOR = '[class^="pb_"][class*="_kit"]'
6
+ const ERROR_MESSAGE_SELECTOR = '.pb_body_kit_negative'
7
7
 
8
8
  // Validation selectors
9
- const FORM_SELECTOR = 'form[data-pb-form-validation="true"]'
10
- const REQUIRED_FIELDS_SELECTOR = 'input[required],textarea[required],select[required]'
11
- const PHONE_NUMBER_VALIDATION_ERROR_SELECTOR = '[data-pb-phone-validation-error="true"]'
9
+ const FORM_SELECTOR = 'form[data-pb-form-validation="true"]'
10
+ const REQUIRED_FIELDS_SELECTOR = 'input[required],textarea[required],select[required]'
12
11
 
13
12
  const FIELD_EVENTS = [
14
13
  'change',
@@ -23,24 +22,12 @@ class PbFormValidation extends PbEnhancedElement {
23
22
 
24
23
  connect() {
25
24
  this.formValidationFields.forEach((field) => {
26
- // Skip phone number inputs - they handle their own validation
27
- const isPhoneNumberInput = field.closest('.pb_phone_number_input')
28
- if (isPhoneNumberInput) return
29
-
30
25
  FIELD_EVENTS.forEach((e) => {
31
26
  field.addEventListener(e, debounce((event) => {
32
27
  this.validateFormField(event)
33
28
  }, 250), false)
34
29
  })
35
30
  })
36
-
37
- // Add event listener to check for phone number validation errors
38
- this.element.addEventListener('submit', (event) => {
39
- if (this.hasPhoneNumberValidationErrors()) {
40
- event.preventDefault()
41
- return false
42
- }
43
- })
44
31
  }
45
32
 
46
33
  validateFormField(event) {
@@ -58,25 +45,20 @@ class PbFormValidation extends PbEnhancedElement {
58
45
 
59
46
  showValidationMessage(target) {
60
47
  const { parentElement } = target
61
- const kitElement = parentElement.closest(KIT_SELECTOR)
62
-
63
- // Check if this is a phone number input
64
- const isPhoneNumberInput = kitElement && kitElement.classList.contains('pb_phone_number_input')
65
48
 
66
49
  // ensure clean error message state
67
50
  this.clearError(target)
68
- kitElement.classList.add('error')
51
+ parentElement.closest(KIT_SELECTOR).classList.add('error')
69
52
 
70
- // Only add error message if it's NOT a phone number input
71
- if (!isPhoneNumberInput) {
72
- // set the error message element
73
- const errorMessageContainer = this.errorMessageContainer
74
- if (target.dataset.message) target.setCustomValidity(target.dataset.message)
75
- errorMessageContainer.innerHTML = target.validationMessage
53
+ // set the error message element
54
+ const errorMessageContainer = this.errorMessageContainer
76
55
 
77
- // add the error message element to the dom tree
78
- parentElement.appendChild(errorMessageContainer)
79
- }
56
+ if (target.dataset.message) target.setCustomValidity(target.dataset.message)
57
+
58
+ errorMessageContainer.innerHTML = target.validationMessage
59
+
60
+ // add the error message element to the dom tree
61
+ parentElement.appendChild(errorMessageContainer)
80
62
  }
81
63
 
82
64
  clearError(target) {
@@ -86,12 +68,6 @@ class PbFormValidation extends PbEnhancedElement {
86
68
  if (errorMessageContainer) errorMessageContainer.remove()
87
69
  }
88
70
 
89
- // Check if there are phone number input errors
90
- hasPhoneNumberValidationErrors() {
91
- const phoneNumberErrors = this.element.querySelectorAll(PHONE_NUMBER_VALIDATION_ERROR_SELECTOR)
92
- return phoneNumberErrors.length > 0
93
- }
94
-
95
71
  get errorMessageContainer() {
96
72
  const errorContainer = document.createElement('div')
97
73
  const kitClassName = ERROR_MESSAGE_SELECTOR.replace(/\./, '')
@@ -110,43 +110,38 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
110
110
 
111
111
  const inputRef = useRef<HTMLInputElement | null>(null)
112
112
  const itiRef = useRef<any>(null);
113
- const wrapperRef = useRef<HTMLDivElement | null>(null);
114
113
  const [inputValue, setInputValue] = useState(value)
115
- const [error, setError] = useState("")
114
+ const [error, setError] = useState(props.error)
116
115
  const [dropDownIsOpen, setDropDownIsOpen] = useState(false)
117
116
  const [selectedData, setSelectedData] = useState()
118
117
  const [hasTyped, setHasTyped] = useState(false)
119
- const [formSubmitted, setFormSubmitted] = useState(false)
120
-
121
- // Function to update validation state on the wrapper element
122
- // Only applies when input is required
123
- const updateValidationState = (hasError: boolean) => {
124
- if (wrapperRef.current && required) {
125
- if (hasError) {
126
- wrapperRef.current.setAttribute('data-pb-phone-validation-error', 'true')
127
- } else {
128
- wrapperRef.current.removeAttribute('data-pb-phone-validation-error')
129
- }
130
- }
131
- }
132
-
133
- // Determine which error to display
134
- // Show internal errors on blur (hasTyped) or on form submission (formSubmitted)
135
- const shouldShowInternalError = (hasTyped || formSubmitted) && required && error
136
- const displayError = props.error || (shouldShowInternalError ? error : "")
137
118
 
138
119
  useEffect(() => {
139
- const hasError = error.length > 0
140
- if (hasError) {
120
+ if ((error ?? '').length > 0) {
141
121
  onValidate(false)
142
122
  } else {
143
123
  onValidate(true)
144
124
  }
145
-
146
- // Update validation state whenever error changes
147
- updateValidationState(hasError)
148
125
  }, [error, onValidate])
149
126
 
127
+ /*
128
+ useImperativeHandle exposes the kit's input element to a parent component via a ref.
129
+ See the Playbook docs for use cases.
130
+ Read: https://react.dev/reference/react/useImperativeHandle
131
+ */
132
+ useImperativeHandle(ref, () => {
133
+ return {
134
+ clearField() {
135
+ setInputValue("")
136
+ setError("")
137
+ setHasTyped(false)
138
+ },
139
+ inputNode() {
140
+ return inputRef.current
141
+ }
142
+ }
143
+ })
144
+
150
145
  const unformatNumber = (formattedNumber: any) => {
151
146
  return formattedNumber.replace(/\D/g, "")
152
147
  }
@@ -169,13 +164,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
169
164
 
170
165
  const validateTooShortNumber = (itiInit: any) => {
171
166
  if (!itiInit) return
172
-
173
- // If field is empty, don't show "too short" error
174
- if (!inputValue || inputValue.trim() === '') {
175
- setError('')
176
- return false
177
- }
178
-
179
167
  if (itiInit.getValidationError() === ValidationError.TooShort) {
180
168
  return showFormattedError('too short')
181
169
  } else {
@@ -218,33 +206,16 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
218
206
 
219
207
  const validateRepeatCountryCode = (itiInit: any) => {
220
208
  if (!itiInit) return
221
- const countryDialCode = itiRef.current.getSelectedCountryData().dialCode;
209
+ const countryDialCode = itiInit.getSelectedCountryData().dialCode;
222
210
  if (unformatNumber(inputValue).startsWith(countryDialCode)) {
223
211
  return showFormattedError('repeat country code')
224
212
  }
225
213
  }
226
214
 
227
- // Validation for required empty fields
228
- const validateRequiredField = () => {
229
- if (required && (!inputValue || inputValue.trim() === '')) {
230
- setError('Missing phone number')
231
- return true
232
- }
233
- return false
234
- }
235
215
 
236
216
  const validateErrors = () => {
237
- // If field is empty, only show required field error if applicable
238
- if (!inputValue || inputValue.trim() === '') {
239
- if (validateRequiredField()) return
240
- // Clear any existing errors if field is empty and not required
241
- if (!required) {
242
- setError('')
243
- }
244
- return
245
- }
217
+ if (!hasTyped && !error) return
246
218
 
247
- // Run validation checks
248
219
  if (itiRef.current) isValid(itiRef.current.isValidNumber())
249
220
  if (validateOnlyNumbers(itiRef.current)) return
250
221
  if (validateTooLongNumber(itiRef.current)) return
@@ -254,121 +225,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
254
225
  if (validateRepeatCountryCode(itiRef.current)) return
255
226
  }
256
227
 
257
- // Add listener for form validation to track when validation should be shown
258
- useEffect(() => {
259
- const handleInvalid = (event: Event) => {
260
- const target = event.target as HTMLInputElement
261
- const phoneNumberContainer = target.closest('.pb_phone_number_input')
262
-
263
- if (phoneNumberContainer && phoneNumberContainer === wrapperRef.current) {
264
- const invalidInputName = target.name || target.getAttribute('name')
265
- if (invalidInputName === name) {
266
- setFormSubmitted(true)
267
- // Trigger validation when form is submitted
268
- validateErrors()
269
- }
270
- }
271
- }
272
-
273
- document.addEventListener('invalid', handleInvalid, true)
274
-
275
- return () => {
276
- document.removeEventListener('invalid', handleInvalid, true)
277
- }
278
- }, [name, inputValue])
279
-
280
- /*
281
- useImperativeHandle exposes the kit's input element to a parent component via a ref.
282
- See the Playbook docs for use cases.
283
- Read: https://react.dev/reference/react/useImperativeHandle
284
- */
285
- useImperativeHandle(ref, () => {
286
- return {
287
- clearField() {
288
- setInputValue("")
289
- setError("")
290
- setHasTyped(false)
291
- setFormSubmitted(false)
292
- // Only clear validation state if field was required
293
- if (required) {
294
- updateValidationState(false)
295
- }
296
- },
297
- inputNode() {
298
- return inputRef.current
299
- },
300
- // Expose validation method for React Hook Form
301
- validate() {
302
- // Run validation and return error message or true
303
- const isEmpty = !inputValue || inputValue.trim() === ''
304
-
305
- if (required && isEmpty) {
306
- setError('Missing phone number')
307
- setFormSubmitted(true)
308
- return 'Missing phone number'
309
- }
310
-
311
- if (isEmpty) {
312
- setError('')
313
- return true
314
- }
315
-
316
- if (!itiRef.current) {
317
- return true
318
- }
319
-
320
- // Check for repeat country code first
321
- const countryDialCode = itiRef.current.getSelectedCountryData().dialCode;
322
- if (unformatNumber(inputValue).startsWith(countryDialCode)) {
323
- const countryName = itiRef.current.getSelectedCountryData().name
324
- const errorMessage = `Invalid ${countryName} phone number (repeat country code)`
325
- setError(errorMessage)
326
- setFormSubmitted(true)
327
- setHasTyped(true)
328
- return errorMessage
329
- }
330
-
331
- // Check if it only contains valid characters
332
- if (!containOnlyNumbers(inputValue)) {
333
- const countryName = itiRef.current.getSelectedCountryData().name
334
- const errorMessage = `Invalid ${countryName} phone number (enter numbers only)`
335
- setError(errorMessage)
336
- setFormSubmitted(true)
337
- setHasTyped(true)
338
- return errorMessage
339
- }
340
-
341
- // Check if valid number
342
- if (!itiRef.current.isValidNumber()) {
343
- const countryName = itiRef.current.getSelectedCountryData().name
344
- const validationError = itiRef.current.getValidationError()
345
- let errorMessage = ''
346
-
347
- if (validationError === ValidationError.TooShort) {
348
- errorMessage = `Invalid ${countryName} phone number (too short)`
349
- } else if (validationError === ValidationError.TooLong) {
350
- errorMessage = `Invalid ${countryName} phone number (too long)`
351
- } else if (validationError === ValidationError.MissingAreaCode) {
352
- errorMessage = `Invalid ${countryName} phone number (missing area code)`
353
- } else {
354
- errorMessage = `Invalid ${countryName} phone number`
355
- }
356
-
357
- // Set the error state so the validation attribute gets added
358
- setError(errorMessage)
359
- setFormSubmitted(true)
360
- setHasTyped(true)
361
-
362
- return errorMessage
363
- }
364
-
365
- // Clear error if valid
366
- setError('')
367
- return true
368
- }
369
- }
370
- })
371
-
372
228
  const getCurrentSelectedData = (itiInit: any, inputValue: string) => {
373
229
  return { ...itiInit.getSelectedCountryData(), number: inputValue }
374
230
  }
@@ -376,12 +232,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
376
232
  const handleOnChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
377
233
  if (!hasTyped) setHasTyped(true)
378
234
  setInputValue(evt.target.value)
379
-
380
- // Reset form submitted state when user types
381
- if (formSubmitted) {
382
- setFormSubmitted(false)
383
- }
384
-
385
235
  let phoneNumberData
386
236
  if (formatAsYouType) {
387
237
  const formattedPhoneNumberData = getCurrentSelectedData(itiRef.current, evt.target.value)
@@ -390,28 +240,8 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
390
240
  phoneNumberData = getCurrentSelectedData(itiRef.current, evt.target.value)
391
241
  }
392
242
  setSelectedData(phoneNumberData)
393
-
394
- // Check if this is React Hook Form by checking if onChange expects target format
395
- const isReactHookForm = onChange.toString().includes("target")
396
- if (isReactHookForm) {
397
- // For React Hook Form, pass the event with modified target value
398
- onChange({
399
- ...evt,
400
- target: {
401
- ...evt.target,
402
- name,
403
- value: phoneNumberData
404
- }
405
- } as any)
406
- } else {
407
- onChange(phoneNumberData)
408
- }
409
-
243
+ onChange(phoneNumberData)
410
244
  isValid(itiRef.current.isValidNumber())
411
-
412
- // Trigger validation after onChange for React Hook Form
413
- // This ensures validation state is up-to-date
414
- setTimeout(() => validateErrors(), 0)
415
245
  }
416
246
 
417
247
  // Separating Concerns as React Docs Recommend
@@ -470,7 +300,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
470
300
  dark,
471
301
  "data-phone-number": JSON.stringify(selectedData),
472
302
  disabled,
473
- error: displayError,
303
+ error,
474
304
  type: 'tel',
475
305
  id,
476
306
  label,
@@ -480,10 +310,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
480
310
  value: inputValue
481
311
  }
482
312
 
483
- let wrapperProps: Record<string, unknown> = {
484
- className: classes,
485
- ref: wrapperRef
486
- }
313
+ let wrapperProps: Record<string, unknown> = { className: classes }
487
314
 
488
315
  if (!isEmpty(aria)) textInputProps = {...textInputProps, ...ariaProps}
489
316
  if (!isEmpty(data)) wrapperProps = {...wrapperProps, ...dataProps}