playbook_ui 15.1.0.pre.alpha.PLAY2468phonenuminputvalidation10993 → 15.1.0.pre.alpha.alphaforaudiences151011000
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 +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +0 -2
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.scss +4 -0
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_stacked_alert.html.erb +16 -16
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_stacked_alert.jsx +2 -1
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_status.html.erb +31 -31
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_status.jsx +4 -3
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +1 -1
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +13 -37
- data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +25 -198
- data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +14 -6
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_default.html.erb +8 -4
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_default.jsx +5 -0
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_default.md +1 -0
- data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +3 -1
- data/app/pb_kits/playbook/pb_text_input/text_input.rb +6 -0
- data/dist/chunks/{_line_graph-C3zTTfo0.js → _line_graph-BnK1i7QI.js} +1 -1
- data/dist/chunks/{_typeahead-CfKPIYmd.js → _typeahead-pbS3fEzb.js} +1 -1
- data/dist/chunks/{_weekday_stacked-CUTwEQ-P.js → _weekday_stacked-OOFiMFSs.js} +2 -2
- data/dist/chunks/pb_form_validation-CleM960_.js +1 -0
- data/dist/chunks/vendor.js +1 -1
- data/dist/playbook-doc.js +2 -2
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +6 -5
- data/dist/chunks/pb_form_validation-D1VURgVg.js +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ada0b8869b8f7e86b4f9fec2216ae14a2525539440e67b22af9856df81c5261
|
|
4
|
+
data.tar.gz: 3bdbee334dc6ed0c7d937ccb4fe9855e9987cab7b5747e47fe51fb760a573b0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87f028d5f2cba6bcf56e2ea6054e48a67179ca1192e45710b19096607102981a442aa5ed5e653d5e70b25d180cda4b4c7d008d3a1a1956983d8cd5dd13a2ca03
|
|
7
|
+
data.tar.gz: 58335f67e22a6055a4b631f211b99f6801f68ea9cb2f9bc13ba6dc7145caa067a626d4f57e03e8981b2420f81e8964e93f09ca9ebbe9c84a46579e6f892bd8f7
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
<%= pb_rails("button", props: { text: "Delete Status", data: {"open-dialog": "dialog-stacked-delete"}, margin_right: "md" }) %>
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
<%= pb_rails("dialog", props: {
|
|
7
|
-
id:"dialog-stacked-default",
|
|
6
|
+
<%= pb_rails("dialog", props: {
|
|
7
|
+
id:"dialog-stacked-default",
|
|
8
8
|
status: "default",
|
|
9
|
-
size: "sm",
|
|
10
|
-
title: "Are you sure?",
|
|
11
|
-
text: "Text explaining why there is an alert",
|
|
9
|
+
size: "sm",
|
|
10
|
+
title: "Are you sure?",
|
|
11
|
+
text: "Text explaining why there is an alert",
|
|
12
12
|
}) do %>
|
|
13
13
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
14
14
|
<%= pb_rails("flex", props: { orientation: "column", padding_x:"md", padding: "sm" }) do %>
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
<% end %>
|
|
19
19
|
<% end %>
|
|
20
20
|
|
|
21
|
-
<%= pb_rails("dialog", props: {
|
|
22
|
-
id:"dialog-stacked-caution",
|
|
21
|
+
<%= pb_rails("dialog", props: {
|
|
22
|
+
id:"dialog-stacked-caution",
|
|
23
23
|
status: "caution",
|
|
24
|
-
size: "sm",
|
|
25
|
-
title: "Are you sure?",
|
|
26
|
-
text: "This is the action you will be taking",
|
|
24
|
+
size: "sm",
|
|
25
|
+
title: "Are you sure?",
|
|
26
|
+
text: "This is the action you will be taking",
|
|
27
27
|
}) do %>
|
|
28
28
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
29
29
|
<%= pb_rails("flex", props: { orientation: "column", padding_x:"md", padding: "sm" }) do %>
|
|
@@ -33,16 +33,16 @@
|
|
|
33
33
|
<% end %>
|
|
34
34
|
<% end %>
|
|
35
35
|
|
|
36
|
-
<%= pb_rails("dialog", props: {
|
|
37
|
-
id:"dialog-stacked-delete",
|
|
36
|
+
<%= pb_rails("dialog", props: {
|
|
37
|
+
id:"dialog-stacked-delete",
|
|
38
38
|
status: "delete",
|
|
39
|
-
size: "sm",
|
|
40
|
-
title: "Delete",
|
|
41
|
-
text: "You are about to delete ...",
|
|
39
|
+
size: "sm",
|
|
40
|
+
title: "Delete",
|
|
41
|
+
text: "You are about to delete ...",
|
|
42
42
|
}) do %>
|
|
43
43
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
44
44
|
<%= pb_rails("flex", props: { orientation: "column", padding_x:"md", padding: "sm" }) do %>
|
|
45
|
-
<%= pb_rails("button", props: { text: "Yes, Action", full_width: true }) %>
|
|
45
|
+
<%= pb_rails("button", props: { text: "Yes, Action", variant: "danger", full_width: true }) %>
|
|
46
46
|
<%= pb_rails("button", props: { text: "No, Cancel", variant: "secondary", full_width: true, margin_top: "sm", data: {"close-dialog": "dialog-stacked-delete" } }) %>
|
|
47
47
|
<% end %>
|
|
48
48
|
<% end %>
|
|
@@ -51,7 +51,7 @@ const DialogStackedAlert = () => {
|
|
|
51
51
|
return (
|
|
52
52
|
<div>
|
|
53
53
|
<Flex
|
|
54
|
-
rowGap="xs"
|
|
54
|
+
rowGap="xs"
|
|
55
55
|
wrap
|
|
56
56
|
>
|
|
57
57
|
<Button
|
|
@@ -93,6 +93,7 @@ const DialogStackedAlert = () => {
|
|
|
93
93
|
<Button
|
|
94
94
|
fullWidth
|
|
95
95
|
onClick={dialog.toggle}
|
|
96
|
+
variant= {dialog.status == "delete" ? "danger" : "primary"}
|
|
96
97
|
>
|
|
97
98
|
{dialog.buttonOneText}
|
|
98
99
|
</Button>
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
<%= pb_rails("button", props: { text: "Success Status", data: {"open-dialog": "dialog-status-success"}, margin_right: "md" }) %>
|
|
9
9
|
<% end %>
|
|
10
10
|
|
|
11
|
-
<%= pb_rails("dialog", props: {
|
|
12
|
-
id:"dialog-status-default",
|
|
11
|
+
<%= pb_rails("dialog", props: {
|
|
12
|
+
id:"dialog-status-default",
|
|
13
13
|
status: "default",
|
|
14
|
-
size: "status_size",
|
|
15
|
-
title: "Are you sure?",
|
|
16
|
-
text: "Text explaining why there is an alert",
|
|
14
|
+
size: "status_size",
|
|
15
|
+
title: "Are you sure?",
|
|
16
|
+
text: "Text explaining why there is an alert",
|
|
17
17
|
}) do %>
|
|
18
18
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
19
19
|
<%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
<% end %>
|
|
24
24
|
<% end %>
|
|
25
25
|
|
|
26
|
-
<%= pb_rails("dialog", props: {
|
|
27
|
-
id:"dialog-status-info",
|
|
26
|
+
<%= pb_rails("dialog", props: {
|
|
27
|
+
id:"dialog-status-info",
|
|
28
28
|
status: "info",
|
|
29
|
-
size: "status_size",
|
|
30
|
-
title: "Information",
|
|
31
|
-
text: "Text explaining why there is an alert",
|
|
29
|
+
size: "status_size",
|
|
30
|
+
title: "Information",
|
|
31
|
+
text: "Text explaining why there is an alert",
|
|
32
32
|
}) do %>
|
|
33
33
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
34
34
|
<%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
<% end %>
|
|
38
38
|
<% end %>
|
|
39
39
|
|
|
40
|
-
<%= pb_rails("dialog", props: {
|
|
41
|
-
id:"dialog-status-caution",
|
|
40
|
+
<%= pb_rails("dialog", props: {
|
|
41
|
+
id:"dialog-status-caution",
|
|
42
42
|
status: "caution",
|
|
43
|
-
size: "status_size",
|
|
44
|
-
title: "Are you Sure?",
|
|
45
|
-
text: "This is the action you will be taking",
|
|
43
|
+
size: "status_size",
|
|
44
|
+
title: "Are you Sure?",
|
|
45
|
+
text: "This is the action you will be taking",
|
|
46
46
|
}) do %>
|
|
47
47
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
48
48
|
<%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
|
|
@@ -52,27 +52,27 @@
|
|
|
52
52
|
<% end %>
|
|
53
53
|
<% end %>
|
|
54
54
|
|
|
55
|
-
<%= pb_rails("dialog", props: {
|
|
56
|
-
id:"dialog-status-delete",
|
|
55
|
+
<%= pb_rails("dialog", props: {
|
|
56
|
+
id:"dialog-status-delete",
|
|
57
57
|
status: "delete",
|
|
58
|
-
size: "status_size",
|
|
59
|
-
title: "Delete",
|
|
60
|
-
text: "You are about to delete ...",
|
|
58
|
+
size: "status_size",
|
|
59
|
+
title: "Delete",
|
|
60
|
+
text: "You are about to delete ...",
|
|
61
61
|
}) do %>
|
|
62
62
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
63
63
|
<%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
|
|
64
|
-
<%= pb_rails("button", props: { text: "Yes, Delete" }) %>
|
|
64
|
+
<%= pb_rails("button", props: { text: "Yes, Delete", variant: "danger" }) %>
|
|
65
65
|
<%= pb_rails("button", props: { text: "No, Cancel", variant: "secondary", data: {"close-dialog": "dialog-status-delete" } }) %>
|
|
66
66
|
<% end %>
|
|
67
67
|
<% end %>
|
|
68
68
|
<% end %>
|
|
69
69
|
|
|
70
|
-
<%= pb_rails("dialog", props: {
|
|
71
|
-
id:"dialog-status-error",
|
|
70
|
+
<%= pb_rails("dialog", props: {
|
|
71
|
+
id:"dialog-status-error",
|
|
72
72
|
status: "error",
|
|
73
|
-
size: "status_size",
|
|
74
|
-
title: "Error Message",
|
|
75
|
-
text: "Text explaining the error",
|
|
73
|
+
size: "status_size",
|
|
74
|
+
title: "Error Message",
|
|
75
|
+
text: "Text explaining the error",
|
|
76
76
|
}) do %>
|
|
77
77
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
78
78
|
<%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
|
|
@@ -81,12 +81,12 @@
|
|
|
81
81
|
<% end %>
|
|
82
82
|
<% end %>
|
|
83
83
|
|
|
84
|
-
<%= pb_rails("dialog", props: {
|
|
85
|
-
id:"dialog-status-success",
|
|
84
|
+
<%= pb_rails("dialog", props: {
|
|
85
|
+
id:"dialog-status-success",
|
|
86
86
|
status: "success",
|
|
87
|
-
size: "status_size",
|
|
88
|
-
title: "Success!",
|
|
89
|
-
text: "Text explaining what is successful",
|
|
87
|
+
size: "status_size",
|
|
88
|
+
title: "Success!",
|
|
89
|
+
text: "Text explaining what is successful",
|
|
90
90
|
}) do %>
|
|
91
91
|
<%= pb_rails("dialog/dialog_footer") do %>
|
|
92
92
|
<%= pb_rails("flex", props: { spacing:"between", padding_x:"md", padding_bottom:"md", padding: "sm" }) do %>
|
|
@@ -83,8 +83,8 @@ const DialogStatus = () => {
|
|
|
83
83
|
|
|
84
84
|
return (
|
|
85
85
|
<div>
|
|
86
|
-
<Flex
|
|
87
|
-
rowGap="xs"
|
|
86
|
+
<Flex
|
|
87
|
+
rowGap="xs"
|
|
88
88
|
wrap
|
|
89
89
|
>
|
|
90
90
|
<Button
|
|
@@ -117,7 +117,7 @@ const DialogStatus = () => {
|
|
|
117
117
|
>
|
|
118
118
|
{"Success Status"}
|
|
119
119
|
</Button>
|
|
120
|
-
<Button
|
|
120
|
+
<Button
|
|
121
121
|
marginRight="md"
|
|
122
122
|
onClick={toggleErrorAlert}
|
|
123
123
|
>
|
|
@@ -152,6 +152,7 @@ const DialogStatus = () => {
|
|
|
152
152
|
<Button
|
|
153
153
|
onClick={dialog.toggle}
|
|
154
154
|
paddingRight="xl"
|
|
155
|
+
variant={dialog.status == "delete" ? "danger" : "primary"}
|
|
155
156
|
>
|
|
156
157
|
{dialog.buttonOneText}
|
|
157
158
|
</Button>
|
|
@@ -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
|
|
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
|
|
6
|
-
const ERROR_MESSAGE_SELECTOR
|
|
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
|
|
10
|
-
const REQUIRED_FIELDS_SELECTOR
|
|
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
|
-
|
|
51
|
+
parentElement.closest(KIT_SELECTOR).classList.add('error')
|
|
69
52
|
|
|
70
|
-
//
|
|
71
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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}
|