playbook_ui 15.1.0.pre.alpha.PLAY2468phonenuminputvalidation11082 → 15.1.0.pre.alpha.PLAY2468phonenuminputvalidation11111

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: 2e1e7a4a49bf159f82731b4678231fc261993cde3556f8c8c90f181196d7d86d
4
- data.tar.gz: c3ecec58f186c3ce30dfa663b80d87f71c5185d9df25963dc3ea328a210c9648
3
+ metadata.gz: 06ac01cc54efddaffa50486aecfb448a580bce3b816f85fdd201fa5ba05478da
4
+ data.tar.gz: '084380bb710d19465a2948e53c3b49e2ea833f0ab9565ba95838eb34075373e1'
5
5
  SHA512:
6
- metadata.gz: af7cb078be3a0615e1f3035fe9f03b3f94994b12436a790905cc86bd38749665c8214160a77cd6599103b6b50b28f68b7b25a08598ffce3b2f4fedfe8df31edb
7
- data.tar.gz: fabf56e0e6d70ce671f875fa5265ad12743e6bdf05d6b37d92e19b3cdc6b80b38d9e69e24e939517deb43582bea0a1d0d2a9c20e0e7572244f3cfa3abf360ffe
6
+ metadata.gz: 676249db4be37cd46c943a4cb0cf7dccd3f1f2bc54ba0f8f67e39dd0fb544aecc63353fdcf9e6e52bc185a3b0034607cc72679e1b1c6b831f7c1ab1d5597e074
7
+ data.tar.gz: 169b3fab71971b434ba4703e65229407e8fdada7c142312ee7236c87886bce64cef2fe91c1b648207cd804b4d12eb86c3e63a1f118f326b68e80b152951571b8
@@ -21,6 +21,15 @@
21
21
  ]
22
22
  %>
23
23
 
24
+ <%
25
+ example_typeahead_options = [
26
+ { label: 'Orange', value: '#FFA500' },
27
+ { label: 'Red', value: '#FF0000' },
28
+ { label: 'Green', value: '#00FF00' },
29
+ { label: 'Blue', value: '#0000FF' },
30
+ ]
31
+ %>
32
+
24
33
  <% treeData = [{
25
34
  label: "Power Home Remodeling",
26
35
  value: "Power Home Remodeling",
@@ -89,6 +98,8 @@
89
98
 
90
99
  <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %>
91
100
  <%= 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." } } %>
101
+ <%= form.typeahead :example_typeahead_validation_react, props: { options: example_typeahead_options, pills: true, label: "Example Typeahead (React Rendered)", placeholder: "Search for a user", required: true, validation: { message: "Please select a color." } } %>
102
+ <%= form.typeahead :example_typeahead_validation_react_2, props: { options: example_typeahead_options, pills: true, label: "Example Typeahead 2 (React Rendered)", placeholder: "Search for a user", required: true } %>
92
103
  <%= form.text_field :example_text_field_validation, props: { label: true, required: true } %>
93
104
  <%= form.phone_number_field :example_phone_number_field_validation, props: { label: "Example phone field", hidden_inputs: true, required: true } %>
94
105
  <%= form.email_field :example_email_field_validation, props: { label: true, required: true } %>
@@ -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(/\./, '')
@@ -3,7 +3,7 @@ import React from 'react'
3
3
  import classnames from 'classnames'
4
4
 
5
5
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
6
- import { globalProps } from '../utilities/globalProps'
6
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
7
7
 
8
8
  import Icon from '../pb_icon/_icon'
9
9
 
@@ -26,7 +26,7 @@ type IconCircleProps = {
26
26
  | "orange"
27
27
  | "green"
28
28
  | "lighter",
29
- }
29
+ } & GlobalProps
30
30
 
31
31
  const IconCircle = (props: IconCircleProps) => {
32
32
  const {
@@ -3,51 +3,45 @@
3
3
  @import "../tokens/spacing";
4
4
  @import "../pb_icon_circle/icon_circle";
5
5
 
6
- [class^=pb_icon_stat_value_kit]{
6
+ .pb_icon_stat_value_kit_horizontal,
7
+ .pb_icon_stat_value_kit_vertical
8
+ {
7
9
  display: flex;
8
10
  align-items: baseline;
9
11
 
10
- &[class*=_vertical] {
12
+ &.pb_icon_stat_value_kit_vertical {
11
13
  flex-direction: column;
12
14
 
13
- &[class*=_center] {
15
+ &.text_align_center {
14
16
  align-items: center;
15
17
 
16
- [class^=pb_title],
17
- [class^=pb_body],
18
- [class^=pb_caption] {
18
+ .pb_title_kit,
19
+ .pb_body_kit,
20
+ .pb_caption_kit_md {
19
21
  text-align: center;
20
22
  }
21
23
  }
22
24
 
23
- &[class*=_right] {
25
+ &.text_align_right {
24
26
  align-items: flex-end;
25
27
 
26
- [class^=pb_title],
27
- [class^=pb_body],
28
- [class^=pb_caption] {
28
+ .pb_title_kit,
29
+ .pb_body_kit,
30
+ .pb_caption_kit_md {
29
31
  text-align: right;
30
32
  }
31
33
  }
32
-
33
- [class^=pb_icon_circle] {
34
- margin-bottom: $space-xs;
35
- }
36
34
  }
37
35
 
38
- &[class*=_horizontal] {
36
+ &.pb_icon_stat_value_kit_horizontal {
39
37
  align-items: center;
40
38
 
41
- &[class*=_center] {
39
+ &.text_align_center {
42
40
  justify-content: center;
43
41
  }
44
42
 
45
- &[class*=_right] {
43
+ &.text_align_right {
46
44
  justify-content: flex-end;
47
45
  }
48
-
49
- [class^=pb_icon_circle] {
50
- margin-right: $space-sm;
51
- }
52
46
  }
53
47
  }
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import classnames from 'classnames'
3
3
 
4
4
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
- import { globalProps } from '../utilities/globalProps'
5
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
6
6
 
7
7
  import Body from '../pb_body/_body'
8
8
  import Caption from '../pb_caption/_caption'
@@ -33,8 +33,7 @@ type IconStatValueProps = {
33
33
  | "yellow"
34
34
  | "orange"
35
35
  | "green"
36
- | "lighter",
37
- }
36
+ } & GlobalProps
38
37
 
39
38
  const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
40
39
  const {
@@ -50,13 +49,13 @@ const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
50
49
  text = '',
51
50
  unit = '',
52
51
  value = 0,
53
- variant = 'lighter',
52
+ variant = 'default',
54
53
  } = props
55
54
  const ariaProps = buildAriaProps(aria)
56
55
  const dataProps = buildDataProps(data)
57
56
  const htmlProps = buildHtmlProps(htmlOptions)
58
57
  const classes = classnames(
59
- buildCss('pb_icon_stat_value_kit', orientation, size, variant), globalProps(props),
58
+ buildCss('pb_icon_stat_value_kit', orientation), globalProps(props),
60
59
  className
61
60
  )
62
61
  const titleSize = function(size: "sm" | "md" | "lg") {
@@ -101,6 +100,8 @@ const IconStatValue = (props: IconStatValueProps): React.ReactElement => {
101
100
  <IconCircle
102
101
  dark={dark}
103
102
  icon={icon}
103
+ marginBottom={orientation == 'vertical' ? 'xs' : undefined}
104
+ marginRight={orientation == 'horizontal' ? 'sm' : undefined}
104
105
  size={size}
105
106
  variant={variant}
106
107
  />
@@ -3,6 +3,8 @@
3
3
  <%= pb_rails("icon_circle", props: {
4
4
  dark: object.dark,
5
5
  icon: object.icon,
6
+ margin_right: object.icon_margin_right,
7
+ margin_bottom: object.icon_margin_bottom,
6
8
  size: object.size,
7
9
  variant: object.variant }) %>
8
10
 
@@ -9,8 +9,8 @@ module Playbook
9
9
  values: %w[sm md lg],
10
10
  default: "sm"
11
11
  prop :variant, type: Playbook::Props::Enum,
12
- values: %w[default royal blue purple teal red yellow green orange lighter],
13
- default: "lighter"
12
+ values: %w[default royal blue purple teal red yellow green orange],
13
+ default: "default"
14
14
 
15
15
  prop :orientation, type: Playbook::Props::Enum,
16
16
  values: %w[vertical horizontal],
@@ -25,7 +25,7 @@ module Playbook
25
25
  prop :value
26
26
 
27
27
  def classname
28
- generate_classname("pb_icon_stat_value_kit", orientation, size, variant)
28
+ generate_classname("pb_icon_stat_value_kit", orientation)
29
29
  end
30
30
 
31
31
  def value_string
@@ -41,6 +41,14 @@ module Playbook
41
41
  3
42
42
  end
43
43
  end
44
+
45
+ def icon_margin_right
46
+ orientation === "horizontal" && "sm"
47
+ end
48
+
49
+ def icon_margin_bottom
50
+ orientation === "vertical" && "xs"
51
+ end
44
52
  end
45
53
  end
46
54
  end
@@ -18,7 +18,7 @@ describe("IconStatValue Kit", () => {
18
18
  )
19
19
 
20
20
  const kit = screen.getByTestId(testId)
21
- expect(kit).toHaveClass("pb_icon_stat_value_kit_horizontal_sm_lighter")
21
+ expect(kit).toHaveClass("pb_icon_stat_value_kit_horizontal")
22
22
  })
23
23
 
24
24
  test("renders icon", () => {
@@ -99,9 +99,10 @@ describe("IconStatValue Kit", () => {
99
99
  value={64.18}
100
100
  />
101
101
  )
102
-
102
+ const size = sizeProp === "sm" ? "3" : sizeProp === "md" ? "2" : "1"
103
103
  const kit = screen.getByTestId(testId)
104
- expect(kit).toHaveClass(`pb_icon_stat_value_kit_horizontal_${sizeProp}_lighter`)
104
+ const title = kit.querySelector(".pb_title_kit")
105
+ expect(title).toHaveClass(`pb_title_${size}`)
105
106
 
106
107
  cleanup()
107
108
  })
@@ -115,8 +116,7 @@ describe("IconStatValue Kit", () => {
115
116
  "teal",
116
117
  "red",
117
118
  "yellow",
118
- "green",
119
- "lighter"].forEach(
119
+ "green"].forEach(
120
120
  (colorProp) => {
121
121
  render(
122
122
  <IconStatValue
@@ -128,9 +128,10 @@ describe("IconStatValue Kit", () => {
128
128
  variant={colorProp}
129
129
  />
130
130
  )
131
-
131
+
132
132
  const kit = screen.getByTestId(testId)
133
- expect(kit).toHaveClass(`pb_icon_stat_value_kit_horizontal_sm_${colorProp}`)
133
+ const iconCircle = kit.querySelector(`.pb_icon_circle_kit_size_sm_${colorProp}`)
134
+ expect(iconCircle).toBeInTheDocument()
134
135
 
135
136
  cleanup()
136
137
  })
@@ -149,7 +150,7 @@ describe("IconStatValue Kit", () => {
149
150
  )
150
151
 
151
152
  const kit = screen.getByTestId(testId)
152
- expect(kit).toHaveClass("pb_icon_stat_value_kit_vertical_sm_lighter")
153
+ expect(kit).toHaveClass("pb_icon_stat_value_kit_vertical")
153
154
  })
154
155
 
155
156
  })
@@ -110,54 +110,18 @@ 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
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
- const [hasStartedValidating, setHasStartedValidating] = useState(false)
121
118
 
122
- // Only sync initial error from props, not continuous updates
123
- // Once validation starts, internal validation takes over
124
119
  useEffect(() => {
125
- if (props.error && !hasStartedValidating) {
126
- setError(props.error)
127
- // If there's an initial error from props, mark as submitted so it shows
128
- if (props.error) {
129
- setFormSubmitted(true)
130
- }
131
- }
132
- }, [props.error, hasStartedValidating])
133
-
134
- // Function to update validation state on the wrapper element
135
- // Only applies when input is required
136
- const updateValidationState = (hasError: boolean) => {
137
- if (wrapperRef.current && required) {
138
- if (hasError) {
139
- wrapperRef.current.setAttribute('data-pb-phone-validation-error', 'true')
140
- } else {
141
- wrapperRef.current.removeAttribute('data-pb-phone-validation-error')
142
- }
143
- }
144
- }
145
-
146
- // Determine which error to display
147
- // Show internal errors on blur (hasTyped) or on form submission (formSubmitted)
148
- const shouldShowInternalError = (hasTyped || formSubmitted) && required && error
149
- const displayError = shouldShowInternalError ? error : ""
150
-
151
- useEffect(() => {
152
- const hasError = error.length > 0
153
- if (hasError) {
120
+ if ((error ?? '').length > 0) {
154
121
  onValidate(false)
155
122
  } else {
156
123
  onValidate(true)
157
124
  }
158
-
159
- // Update validation state whenever error changes
160
- updateValidationState(hasError)
161
125
  }, [error, onValidate])
162
126
 
163
127
  const unformatNumber = (formattedNumber: any) => {
@@ -208,7 +172,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
208
172
  }
209
173
 
210
174
  const validateUnhandledError = (itiInit: any) => {
211
- if (!required || !itiInit) return
175
+ if (!itiInit) return
212
176
  if (itiInit.getValidationError() === ValidationError.SomethingWentWrong) {
213
177
  if (inputValue.length === 1) {
214
178
  return showFormattedError('too short')
@@ -237,9 +201,8 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
237
201
  }
238
202
  }
239
203
 
240
- // Validation for required empty fields
241
204
  const validateRequiredField = () => {
242
- if (required && (!inputValue || inputValue.trim() === '')) {
205
+ if (!inputValue || inputValue.trim() === '') {
243
206
  setError('Missing phone number')
244
207
  return true
245
208
  }
@@ -247,22 +210,14 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
247
210
  }
248
211
 
249
212
  const validateErrors = () => {
250
- // Signal validation has started, so prop errors won't override internal validation
251
- if (!hasStartedValidating) {
252
- setHasStartedValidating(true)
253
- }
254
-
255
- // If field is empty, only show required field error if applicable
213
+ // If field is empty, show error message
256
214
  if (!inputValue || inputValue.trim() === '') {
257
215
  if (validateRequiredField()) return
258
- // Clear any existing errors if field is empty and not required
259
- if (!required) {
260
- setError('')
261
- }
262
216
  return
263
217
  }
264
218
 
265
- // Run validation checks
219
+ if (!hasTyped && !error) return
220
+
266
221
  if (itiRef.current) isValid(itiRef.current.isValidNumber())
267
222
  if (validateOnlyNumbers(itiRef.current)) return
268
223
  if (validateTooLongNumber(itiRef.current)) return
@@ -272,29 +227,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
272
227
  if (validateRepeatCountryCode(itiRef.current)) return
273
228
  }
274
229
 
275
- // Add listener for form validation to track when validation should be shown
276
- useEffect(() => {
277
- const handleInvalid = (event: Event) => {
278
- const target = event.target as HTMLInputElement
279
- const phoneNumberContainer = target.closest('.pb_phone_number_input')
280
-
281
- if (phoneNumberContainer && phoneNumberContainer === wrapperRef.current) {
282
- const invalidInputName = target.name || target.getAttribute('name')
283
- if (invalidInputName === name) {
284
- setFormSubmitted(true)
285
- // Trigger validation when form is submitted
286
- validateErrors()
287
- }
288
- }
289
- }
290
-
291
- document.addEventListener('invalid', handleInvalid, true)
292
-
293
- return () => {
294
- document.removeEventListener('invalid', handleInvalid, true)
295
- }
296
- }, [name, inputValue])
297
-
298
230
  /*
299
231
  useImperativeHandle exposes the kit's input element to a parent component via a ref.
300
232
  See the Playbook docs for use cases.
@@ -306,12 +238,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
306
238
  setInputValue("")
307
239
  setError("")
308
240
  setHasTyped(false)
309
- setFormSubmitted(false)
310
- setHasStartedValidating(false)
311
- // Only clear validation state if field was required
312
- if (required) {
313
- updateValidationState(false)
314
- }
315
241
  },
316
242
  inputNode() {
317
243
  return inputRef.current
@@ -321,15 +247,13 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
321
247
  // Run validation and return error message or true
322
248
  const isEmpty = !inputValue || inputValue.trim() === ''
323
249
 
324
- if (required && isEmpty) {
325
- setError('Missing phone number')
326
- setFormSubmitted(true)
327
- return 'Missing phone number'
328
- }
329
-
330
250
  if (isEmpty) {
331
- setError('')
332
- return true
251
+ // Show missing phone number error
252
+ const errorMessage = 'Missing phone number'
253
+ setError(errorMessage)
254
+ setHasTyped(true)
255
+ // Only return error for React Hook Form if field is required
256
+ return required ? errorMessage : true
333
257
  }
334
258
 
335
259
  if (!itiRef.current) {
@@ -342,7 +266,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
342
266
  const countryName = itiRef.current.getSelectedCountryData().name
343
267
  const errorMessage = `Invalid ${countryName} phone number (repeat country code)`
344
268
  setError(errorMessage)
345
- setFormSubmitted(true)
346
269
  setHasTyped(true)
347
270
  return errorMessage
348
271
  }
@@ -352,7 +275,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
352
275
  const countryName = itiRef.current.getSelectedCountryData().name
353
276
  const errorMessage = `Invalid ${countryName} phone number (enter numbers only)`
354
277
  setError(errorMessage)
355
- setFormSubmitted(true)
356
278
  setHasTyped(true)
357
279
  return errorMessage
358
280
  }
@@ -373,9 +295,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
373
295
  errorMessage = `Invalid ${countryName} phone number`
374
296
  }
375
297
 
376
- // Set the error state so the validation attribute gets added
377
298
  setError(errorMessage)
378
- setFormSubmitted(true)
379
299
  setHasTyped(true)
380
300
 
381
301
  return errorMessage
@@ -395,12 +315,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
395
315
  const handleOnChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
396
316
  if (!hasTyped) setHasTyped(true)
397
317
  setInputValue(evt.target.value)
398
-
399
- // Reset form submitted state when user types
400
- if (formSubmitted) {
401
- setFormSubmitted(false)
402
- }
403
-
404
318
  let phoneNumberData
405
319
  if (formatAsYouType) {
406
320
  const formattedPhoneNumberData = getCurrentSelectedData(itiRef.current, evt.target.value)
@@ -427,10 +341,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
427
341
  }
428
342
 
429
343
  isValid(itiRef.current.isValidNumber())
430
-
431
- // Trigger validation after onChange for React Hook Form
432
- // This ensures validation state is up-to-date
433
- setTimeout(() => validateErrors(), 0)
434
344
  }
435
345
 
436
346
  // Separating Concerns as React Docs Recommend
@@ -489,7 +399,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
489
399
  dark,
490
400
  "data-phone-number": JSON.stringify(selectedData),
491
401
  disabled,
492
- error: displayError,
402
+ error: hasTyped ? error : props.error,
493
403
  type: 'tel',
494
404
  id,
495
405
  label,
@@ -499,10 +409,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
499
409
  value: inputValue
500
410
  }
501
411
 
502
- let wrapperProps: Record<string, unknown> = {
503
- className: classes,
504
- ref: wrapperRef
505
- }
412
+ let wrapperProps: Record<string, unknown> = { className: classes }
506
413
 
507
414
  if (!isEmpty(aria)) textInputProps = {...textInputProps, ...ariaProps}
508
415
  if (!isEmpty(data)) wrapperProps = {...wrapperProps, ...dataProps}