playbook_ui 16.1.0.pre.alpha.play264213817 → 16.1.0.pre.alpha.play273614090
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/Components/RegularTableView.tsx +12 -2
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +33 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.jsx +71 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.md +4 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_card/docs/_card_light.html.erb +3 -35
- data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
- data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
- data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
- data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +14 -5
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_default.md +1 -0
- data/app/pb_kits/playbook/pb_dialog/_dialog.scss +8 -6
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +6 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +83 -13
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_rails.md +3 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_react.md +3 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.html.erb +52 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.jsx +72 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.md +5 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height.jsx +33 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_rails.html.erb +20 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_rails.md +8 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_constrain_height_react.md +8 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.html.erb +1 -1
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.html.erb +9 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.jsx +33 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.md +3 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +6 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +12 -6
- data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +15 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +94 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +5 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +15 -10
- data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
- data/app/pb_kits/playbook/pb_dropdown/index.js +191 -83
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +3 -0
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +18 -1
- data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
- data/app/pb_kits/playbook/pb_filter/Filter/SortMenu.tsx +1 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_default.html.erb +2 -2
- data/app/pb_kits/playbook/pb_filter/docs/_filter_default.jsx +16 -9
- data/app/pb_kits/playbook/pb_filter/filter.rb +2 -2
- data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +6 -2
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +9 -2
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.html.erb +5 -5
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.jsx +4 -4
- data/app/pb_kits/playbook/pb_form_pill/form_pill.rb +4 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
- data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +40 -7
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +1 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.html.erb +7 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.jsx +24 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +2 -0
- data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +30 -1
- data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +3 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.html.erb +5 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.jsx +14 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.rb +3 -0
- data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.test.js +34 -3
- data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +51 -16
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +28 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +1 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_table/index.ts +29 -27
- data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +10 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.html.erb +3 -3
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.jsx +3 -0
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.md +1 -0
- data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +25 -9
- data/app/pb_kits/playbook/pb_textarea/textarea.rb +7 -1
- data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +97 -11
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +5 -2
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.html.erb +6 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.jsx +16 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_time_picker/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +3 -0
- data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +47 -1
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +24 -1
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +412 -324
- data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
- data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +4 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_truncated_text.html.erb +1 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_truncated_text.jsx +1 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
- data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +7 -1
- data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
- data/dist/chunks/_typeahead-BKSzddAX.js +1 -0
- data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
- data/dist/chunks/lib-BwX82vim.js +29 -0
- data/dist/chunks/vendor.js +3 -3
- data/dist/menu.yml +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/forms/builder/form_field_builder.rb +1 -1
- data/lib/playbook/forms/builder/phone_number_field.rb +9 -0
- data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
- data/lib/playbook/forms/builder.rb +2 -2
- data/lib/playbook/truncate.rb +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +42 -6
- data/dist/chunks/_typeahead-B9a6ZsEP.js +0 -1
- data/dist/chunks/lib-DD34ZrWL.js +0 -29
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import Select from
|
|
3
|
-
import AsyncSelect from
|
|
4
|
-
import CreateableSelect from
|
|
5
|
-
import AsyncCreateableSelect from
|
|
6
|
-
import {
|
|
1
|
+
import React, {useState, useEffect, forwardRef, useRef} from "react"
|
|
2
|
+
import Select from "react-select"
|
|
3
|
+
import AsyncSelect from "react-select/async"
|
|
4
|
+
import CreateableSelect from "react-select/creatable"
|
|
5
|
+
import AsyncCreateableSelect from "react-select/async-creatable"
|
|
6
|
+
import {get, isString, uniqueId} from "../utilities/object"
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import classnames from
|
|
8
|
+
import {globalProps, GlobalProps} from "../utilities/globalProps"
|
|
9
|
+
import classnames from "classnames"
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
Control,
|
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
|
|
22
22
|
import * as kitComponents from "./components"
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
24
|
+
import {noop, buildDataProps, buildHtmlProps} from "../utilities/props"
|
|
25
|
+
import {GenericObject, Noop} from "../types"
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* @typedef {object} Props
|
|
@@ -33,365 +33,450 @@ import { GenericObject, Noop } from '../types'
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
type TypeaheadProps = {
|
|
36
|
-
async?: boolean
|
|
37
|
-
className?: string
|
|
38
|
-
components?: GenericObject
|
|
39
|
-
createable?: boolean
|
|
40
|
-
dark?: boolean
|
|
41
|
-
data?: {
|
|
42
|
-
disabled?: boolean
|
|
43
|
-
error?: string
|
|
44
|
-
htmlOptions?: {[key: string]: string | number | boolean | (() => void)}
|
|
45
|
-
id?: string
|
|
46
|
-
inputDisplay?: "pills" | "none"
|
|
47
|
-
label?: string
|
|
48
|
-
loadOptions?: string | Noop
|
|
49
|
-
getOptionLabel?: string | (() => string)
|
|
50
|
-
getOptionValue?: string | (() => string)
|
|
51
|
-
name?: string
|
|
52
|
-
options?: Array<{
|
|
53
|
-
marginBottom?: "none" | "xxs" | "xs" | "sm" | "md" | "lg" | "xl"
|
|
54
|
-
pillColor?:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
36
|
+
async?: boolean
|
|
37
|
+
className?: string
|
|
38
|
+
components?: GenericObject
|
|
39
|
+
createable?: boolean
|
|
40
|
+
dark?: boolean
|
|
41
|
+
data?: {[key: string]: string}
|
|
42
|
+
disabled?: boolean
|
|
43
|
+
error?: string
|
|
44
|
+
htmlOptions?: {[key: string]: string | number | boolean | (() => void)}
|
|
45
|
+
id?: string
|
|
46
|
+
inputDisplay?: "pills" | "none"
|
|
47
|
+
label?: string
|
|
48
|
+
loadOptions?: string | Noop
|
|
49
|
+
getOptionLabel?: string | (() => string)
|
|
50
|
+
getOptionValue?: string | (() => string)
|
|
51
|
+
name?: string
|
|
52
|
+
options?: Array<{label: string; value?: string}>
|
|
53
|
+
marginBottom?: "none" | "xxs" | "xs" | "sm" | "md" | "lg" | "xl"
|
|
54
|
+
pillColor?:
|
|
55
|
+
| "primary"
|
|
56
|
+
| "neutral"
|
|
57
|
+
| "success"
|
|
58
|
+
| "warning"
|
|
59
|
+
| "error"
|
|
60
|
+
| "info"
|
|
61
|
+
| "data_1"
|
|
62
|
+
| "data_2"
|
|
63
|
+
| "data_3"
|
|
64
|
+
| "data_4"
|
|
65
|
+
| "data_5"
|
|
66
|
+
| "data_6"
|
|
67
|
+
| "data_7"
|
|
68
|
+
| "data_8"
|
|
69
|
+
| "windows"
|
|
70
|
+
| "siding"
|
|
71
|
+
| "roofing"
|
|
72
|
+
| "doors"
|
|
73
|
+
| "gutters"
|
|
74
|
+
| "solar"
|
|
75
|
+
| "insulation"
|
|
76
|
+
| "accessories"
|
|
77
|
+
onChange?: any
|
|
78
|
+
optionsByContext?: Record<string, Array<{label: string; value?: string}>>
|
|
79
|
+
required?: boolean
|
|
80
|
+
requiredIndicator?: boolean
|
|
81
|
+
validation?: {message: string}
|
|
82
|
+
searchContextSelector?: string
|
|
83
|
+
clearOnContextChange?: boolean
|
|
84
|
+
preserveSearchInput?: boolean
|
|
62
85
|
} & GlobalProps
|
|
63
86
|
|
|
64
87
|
export type SelectValueType = {
|
|
65
|
-
label
|
|
88
|
+
label?: string,
|
|
66
89
|
value: string,
|
|
90
|
+
name?: string,
|
|
67
91
|
imageUrl?: string,
|
|
68
92
|
pillColor?: string,
|
|
69
93
|
}
|
|
70
94
|
|
|
71
95
|
type TagOnChangeValues = {
|
|
72
|
-
action?: string
|
|
73
|
-
option?: SelectValueType
|
|
74
|
-
removedValue?: string
|
|
96
|
+
action?: string
|
|
97
|
+
option?: SelectValueType
|
|
98
|
+
removedValue?: string
|
|
75
99
|
}
|
|
76
100
|
|
|
77
101
|
/**
|
|
78
102
|
* @constant {React.ReactComponent} Typeahead
|
|
79
103
|
* @param {TypeaheadProps} props - props as described at https://react-select.com/props
|
|
80
104
|
*/
|
|
81
|
-
const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
105
|
+
const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(
|
|
106
|
+
({
|
|
107
|
+
async,
|
|
108
|
+
className,
|
|
109
|
+
components = {},
|
|
110
|
+
createable,
|
|
111
|
+
error = "",
|
|
112
|
+
data = {},
|
|
113
|
+
disabled = false,
|
|
114
|
+
getOptionLabel,
|
|
115
|
+
getOptionValue,
|
|
116
|
+
htmlOptions = {},
|
|
117
|
+
id,
|
|
118
|
+
inputDisplay = "pills",
|
|
119
|
+
name,
|
|
120
|
+
label,
|
|
121
|
+
loadOptions = noop,
|
|
122
|
+
marginBottom = "sm",
|
|
123
|
+
pillColor,
|
|
124
|
+
onChange,
|
|
125
|
+
optionsByContext = {},
|
|
126
|
+
searchContextSelector,
|
|
127
|
+
required = false,
|
|
128
|
+
requiredIndicator = false,
|
|
129
|
+
validation,
|
|
130
|
+
clearOnContextChange = false,
|
|
131
|
+
preserveSearchInput = false, // Default to false to maintain backward compatibility
|
|
132
|
+
...props
|
|
133
|
+
}: TypeaheadProps) => {
|
|
134
|
+
// State to manage the input value when preserveSearchInput is true
|
|
135
|
+
const [inputValue, setInputValue] = useState("")
|
|
136
|
+
// State to track if form has been submitted to control validation display for react rendered rails kit
|
|
137
|
+
const [formSubmitted, setFormSubmitted] = useState(false)
|
|
138
|
+
// State to track if user has made a selection (to disable defaultValue focus behavior)
|
|
139
|
+
const [hasUserSelected, setHasUserSelected] = useState(false)
|
|
140
|
+
|
|
141
|
+
// If preserveSearchInput is true, we need to control the input value
|
|
142
|
+
const handleInputChange = preserveSearchInput
|
|
143
|
+
? (newValue: string, actionMeta: {action: string}) => {
|
|
144
|
+
// Only update the input value for certain actions
|
|
145
|
+
if (actionMeta.action === "input-change") {
|
|
146
|
+
setInputValue(newValue)
|
|
147
|
+
} else if (actionMeta.action === "menu-close" && !props.value) {
|
|
148
|
+
// Don't clear the input when the menu closes without a selection
|
|
149
|
+
// unless the component is controlled and has a value
|
|
150
|
+
} else if (actionMeta.action === "set-value") {
|
|
151
|
+
// When an option is selected, clear the input
|
|
152
|
+
setInputValue("")
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// If the original onInputChange was provided, call it too
|
|
156
|
+
if (props.onInputChange) {
|
|
157
|
+
return props.onInputChange(newValue, actionMeta)
|
|
158
|
+
}
|
|
159
|
+
return newValue
|
|
160
|
+
}
|
|
161
|
+
: props.onInputChange
|
|
162
|
+
|
|
163
|
+
// Handle blur events if we're preserving input
|
|
164
|
+
const handleBlur = preserveSearchInput
|
|
165
|
+
? (event: React.FocusEvent<HTMLInputElement>) => {
|
|
166
|
+
// Do not clear input on blur - the value is preserved in our state
|
|
167
|
+
if (props.onBlur) {
|
|
168
|
+
props.onBlur(event)
|
|
169
|
+
}
|
|
126
170
|
}
|
|
171
|
+
: props.onBlur
|
|
172
|
+
|
|
173
|
+
// Create a ref to access React Select instance
|
|
174
|
+
const selectRef = useRef<any>(null)
|
|
127
175
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
176
|
+
// Helper function to flatten grouped options if custom groups are used
|
|
177
|
+
const flattenOptions = (options: any[]): any[] => {
|
|
178
|
+
if (!options) return []
|
|
179
|
+
|
|
180
|
+
return options.reduce((acc, option) => {
|
|
181
|
+
if (option.options && Array.isArray(option.options)) {
|
|
182
|
+
return [...acc, ...option.options]
|
|
131
183
|
}
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
184
|
+
return [...acc, option]
|
|
185
|
+
}, [])
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Configure focus on selected option using React Select's API
|
|
189
|
+
const handleMenuOpen = () => {
|
|
190
|
+
setTimeout(() => {
|
|
191
|
+
let currentValue = props.value || props.defaultValue
|
|
192
|
+
|
|
193
|
+
// Handle react rendered rails version which passes arrays even for single selects
|
|
194
|
+
if (Array.isArray(currentValue) && currentValue.length > 0) {
|
|
195
|
+
currentValue = currentValue[0]
|
|
142
196
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// Handle scrolling so selected option is visible
|
|
194
|
-
setTimeout(() => {
|
|
195
|
-
if (selectRef.current && selectRef.current.menuListRef) {
|
|
196
|
-
const menuElement = selectRef.current.menuListRef
|
|
197
|
-
// Find the focused option using React Select's class
|
|
198
|
-
const focusedElement = menuElement.querySelector('.typeahead-kit-select__option--is-focused')
|
|
199
|
-
|
|
200
|
-
if (focusedElement) {
|
|
201
|
-
const optionTop = focusedElement.offsetTop
|
|
202
|
-
const optionHeight = focusedElement.offsetHeight
|
|
203
|
-
const menuHeight = menuElement.clientHeight
|
|
204
|
-
|
|
205
|
-
// Set the menu's scrollTop to position the selected option in the middle
|
|
206
|
-
const scrollToMiddle = optionTop - (menuHeight / 2) + (optionHeight / 2)
|
|
207
|
-
menuElement.scrollTop = Math.max(0, scrollToMiddle)
|
|
197
|
+
|
|
198
|
+
// Only apply custom focus if user has NOT made a selection yet
|
|
199
|
+
if (
|
|
200
|
+
currentValue &&
|
|
201
|
+
selectRef.current &&
|
|
202
|
+
!hasUserSelected &&
|
|
203
|
+
!props.isMulti
|
|
204
|
+
) {
|
|
205
|
+
const options = props.options
|
|
206
|
+
if (options) {
|
|
207
|
+
// Flatten grouped options to find the matching option and find matching option
|
|
208
|
+
const flatOptions = flattenOptions(options)
|
|
209
|
+
|
|
210
|
+
const targetOption = flatOptions.find((option: any) => {
|
|
211
|
+
const optionValue = props.getOptionValue
|
|
212
|
+
? props.getOptionValue(option)
|
|
213
|
+
: option.value
|
|
214
|
+
const currentOptionValue = props.getOptionValue
|
|
215
|
+
? props.getOptionValue(currentValue)
|
|
216
|
+
: currentValue.value
|
|
217
|
+
return optionValue === currentOptionValue
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
if (targetOption) {
|
|
221
|
+
// Use React Select's internal state to set focused option
|
|
222
|
+
if (selectRef.current && selectRef.current.setState) {
|
|
223
|
+
selectRef.current.setState({
|
|
224
|
+
focusedOption: targetOption,
|
|
225
|
+
focusedValue: null,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// Handle scrolling so selected option is visible
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
if (selectRef.current && selectRef.current.menuListRef) {
|
|
231
|
+
const menuElement = selectRef.current.menuListRef
|
|
232
|
+
// Find the focused option using React Select's class
|
|
233
|
+
const focusedElement = menuElement.querySelector(
|
|
234
|
+
".typeahead-kit-select__option--is-focused",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if (focusedElement) {
|
|
238
|
+
const optionTop = focusedElement.offsetTop
|
|
239
|
+
const optionHeight = focusedElement.offsetHeight
|
|
240
|
+
const menuHeight = menuElement.clientHeight
|
|
241
|
+
|
|
242
|
+
// Set the menu's scrollTop to position the selected option in the middle
|
|
243
|
+
const scrollToMiddle =
|
|
244
|
+
optionTop - menuHeight / 2 + optionHeight / 2
|
|
245
|
+
menuElement.scrollTop = Math.max(0, scrollToMiddle)
|
|
246
|
+
}
|
|
208
247
|
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
248
|
+
}, 20)
|
|
249
|
+
}
|
|
211
250
|
}
|
|
212
251
|
}
|
|
213
252
|
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
props.onMenuOpen()
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const selectProps = {
|
|
224
|
-
cacheOptions: true,
|
|
225
|
-
required,
|
|
226
|
-
components: {
|
|
227
|
-
Control,
|
|
228
|
-
ClearIndicator,
|
|
229
|
-
IndicatorsContainer,
|
|
230
|
-
IndicatorSeparator: null as null,
|
|
231
|
-
MenuList,
|
|
232
|
-
MultiValue,
|
|
233
|
-
Option,
|
|
234
|
-
Placeholder,
|
|
235
|
-
ValueContainer,
|
|
236
|
-
...components
|
|
237
|
-
},
|
|
238
|
-
loadOptions: isString(loadOptions) ? get(window, loadOptions) : loadOptions,
|
|
239
|
-
getOptionLabel: isString(getOptionLabel) ? get(window, getOptionLabel) : getOptionLabel,
|
|
240
|
-
getOptionValue: isString(getOptionValue) ? get(window, getOptionValue) : getOptionValue,
|
|
241
|
-
defaultOptions: true,
|
|
242
|
-
id: id || uniqueId(),
|
|
243
|
-
inputDisplay: inputDisplay,
|
|
244
|
-
inline: false,
|
|
245
|
-
isClearable: true,
|
|
246
|
-
isSearchable: true,
|
|
247
|
-
name,
|
|
248
|
-
multiKit: '',
|
|
249
|
-
onCreateOption: null as null,
|
|
250
|
-
plusIcon: false,
|
|
251
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
252
|
-
onMultiValueClick: (_option: SelectValueType): any => undefined,
|
|
253
|
-
pillColor: pillColor,
|
|
254
|
-
...(preserveSearchInput ? { inputValue } : {}),
|
|
255
|
-
onInputChange: handleInputChange,
|
|
256
|
-
onBlur: handleBlur,
|
|
257
|
-
onMenuOpen: handleMenuOpen,
|
|
258
|
-
...props,
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const [contextValue, setContextValue] = useState("")
|
|
262
|
-
|
|
263
|
-
// Add listener for form validation to track when validation should be shown (needed for react rendered rails kit)
|
|
264
|
-
useEffect(() => {
|
|
265
|
-
const handleInvalid = (event: Event) => {
|
|
266
|
-
const target = event.target as HTMLInputElement
|
|
267
|
-
const typeaheadContainer = target.closest('[data-pb-react-component="Typeahead"]')
|
|
268
|
-
|
|
269
|
-
if (typeaheadContainer) {
|
|
270
|
-
// Check if this invalid event is specifically for our typeahead by comparing names so we do not have to require ids
|
|
271
|
-
const invalidInputName = target.name || target.getAttribute('name')
|
|
272
|
-
if (invalidInputName === name) {
|
|
273
|
-
setFormSubmitted(true)
|
|
274
|
-
}
|
|
253
|
+
}, 0)
|
|
254
|
+
|
|
255
|
+
// Call original onMenuOpen if provided
|
|
256
|
+
if (props.onMenuOpen) {
|
|
257
|
+
props.onMenuOpen()
|
|
275
258
|
}
|
|
276
259
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
260
|
+
|
|
261
|
+
const selectProps = {
|
|
262
|
+
cacheOptions: true,
|
|
263
|
+
components: {
|
|
264
|
+
Control,
|
|
265
|
+
ClearIndicator,
|
|
266
|
+
IndicatorsContainer,
|
|
267
|
+
IndicatorSeparator: null as null,
|
|
268
|
+
MenuList,
|
|
269
|
+
MultiValue,
|
|
270
|
+
Option,
|
|
271
|
+
Placeholder,
|
|
272
|
+
ValueContainer,
|
|
273
|
+
...components,
|
|
274
|
+
},
|
|
275
|
+
loadOptions: isString(loadOptions)
|
|
276
|
+
? get(window, loadOptions)
|
|
277
|
+
: loadOptions,
|
|
278
|
+
getOptionLabel: isString(getOptionLabel)
|
|
279
|
+
? get(window, getOptionLabel)
|
|
280
|
+
: getOptionLabel,
|
|
281
|
+
getOptionValue: isString(getOptionValue)
|
|
282
|
+
? get(window, getOptionValue)
|
|
283
|
+
: getOptionValue,
|
|
284
|
+
defaultOptions: true,
|
|
285
|
+
id: id || uniqueId(),
|
|
286
|
+
inputDisplay: inputDisplay,
|
|
287
|
+
inline: false,
|
|
288
|
+
isClearable: true,
|
|
289
|
+
isSearchable: true,
|
|
290
|
+
label: label,
|
|
291
|
+
name,
|
|
292
|
+
multiKit: "",
|
|
293
|
+
onCreateOption: null as null,
|
|
294
|
+
plusIcon: false,
|
|
295
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
296
|
+
onMultiValueClick: (_option: SelectValueType): any => undefined,
|
|
297
|
+
pillColor: pillColor,
|
|
298
|
+
...(preserveSearchInput ? {inputValue} : {}),
|
|
299
|
+
onInputChange: handleInputChange,
|
|
300
|
+
onBlur: handleBlur,
|
|
301
|
+
onMenuOpen: handleMenuOpen,
|
|
302
|
+
required,
|
|
303
|
+
requiredIndicator: requiredIndicator,
|
|
304
|
+
...props,
|
|
281
305
|
}
|
|
282
|
-
}, [name])
|
|
283
306
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
307
|
+
const [contextValue, setContextValue] = useState("")
|
|
308
|
+
|
|
309
|
+
// Add listener for form validation to track when validation should be shown (needed for react rendered rails kit)
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
const handleInvalid = (event: Event) => {
|
|
312
|
+
const target = event.target as HTMLInputElement
|
|
313
|
+
const typeaheadContainer = target.closest(
|
|
314
|
+
'[data-pb-react-component="Typeahead"]',
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
if (typeaheadContainer) {
|
|
318
|
+
// Check if this invalid event is specifically for our typeahead by comparing names so we do not have to require ids
|
|
319
|
+
const invalidInputName = target.name || target.getAttribute("name")
|
|
320
|
+
if (invalidInputName === name) {
|
|
321
|
+
setFormSubmitted(true)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
289
324
|
}
|
|
290
|
-
|
|
325
|
+
document.addEventListener("invalid", handleInvalid, true)
|
|
291
326
|
|
|
292
|
-
|
|
327
|
+
return () => {
|
|
328
|
+
document.removeEventListener("invalid", handleInvalid, true)
|
|
329
|
+
}
|
|
330
|
+
}, [name])
|
|
293
331
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
useEffect(() => {
|
|
300
|
-
if (searchContextSelector) {
|
|
301
|
-
const searchContextElement = document.getElementById(searchContextSelector)
|
|
302
|
-
|
|
303
|
-
setContextValue((searchContextElement as HTMLInputElement)?.value)
|
|
304
|
-
const handleContextChange = (e: Event) => {
|
|
305
|
-
const target = e.target as HTMLInputElement;
|
|
306
|
-
setContextValue(target.value);
|
|
307
|
-
if (clearOnContextChange) {
|
|
308
|
-
document.dispatchEvent(new CustomEvent(`pb-typeahead-kit-${selectProps.id}:clear`))
|
|
309
|
-
if (preserveSearchInput) {
|
|
310
|
-
setInputValue('')
|
|
311
|
-
}
|
|
332
|
+
// Add listener for clearing
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
const handleClear = () => {
|
|
335
|
+
if (preserveSearchInput) {
|
|
336
|
+
setInputValue("")
|
|
312
337
|
}
|
|
313
338
|
}
|
|
314
339
|
|
|
315
|
-
|
|
340
|
+
document.addEventListener(
|
|
341
|
+
`pb-typeahead-kit-${selectProps.id}:clear`,
|
|
342
|
+
handleClear,
|
|
343
|
+
)
|
|
316
344
|
|
|
317
345
|
return () => {
|
|
318
|
-
|
|
346
|
+
document.removeEventListener(
|
|
347
|
+
`pb-typeahead-kit-${selectProps.id}:clear`,
|
|
348
|
+
handleClear,
|
|
349
|
+
)
|
|
319
350
|
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
351
|
+
}, [selectProps.id, preserveSearchInput])
|
|
352
|
+
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
if (searchContextSelector) {
|
|
355
|
+
const searchContextElement = document.getElementById(
|
|
356
|
+
searchContextSelector,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
setContextValue((searchContextElement as HTMLInputElement)?.value)
|
|
360
|
+
const handleContextChange = (e: Event) => {
|
|
361
|
+
const target = e.target as HTMLInputElement
|
|
362
|
+
setContextValue(target.value)
|
|
363
|
+
if (clearOnContextChange) {
|
|
364
|
+
document.dispatchEvent(
|
|
365
|
+
new CustomEvent(`pb-typeahead-kit-${selectProps.id}:clear`),
|
|
366
|
+
)
|
|
367
|
+
if (preserveSearchInput) {
|
|
368
|
+
setInputValue("")
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (searchContextElement)
|
|
374
|
+
searchContextElement.addEventListener("change", handleContextChange)
|
|
375
|
+
|
|
376
|
+
return () => {
|
|
377
|
+
if (searchContextElement)
|
|
378
|
+
searchContextElement.removeEventListener(
|
|
379
|
+
"change",
|
|
380
|
+
handleContextChange,
|
|
381
|
+
)
|
|
382
|
+
}
|
|
341
383
|
}
|
|
384
|
+
}, [
|
|
385
|
+
searchContextSelector,
|
|
386
|
+
clearOnContextChange,
|
|
387
|
+
selectProps.id,
|
|
388
|
+
preserveSearchInput,
|
|
389
|
+
])
|
|
390
|
+
|
|
391
|
+
const contextArray = optionsByContext[contextValue]
|
|
392
|
+
if (Array.isArray(contextArray) && contextArray.length > 0) {
|
|
393
|
+
selectProps.options = contextArray
|
|
342
394
|
}
|
|
343
395
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
396
|
+
const Tag = createable
|
|
397
|
+
? async
|
|
398
|
+
? AsyncCreateableSelect
|
|
399
|
+
: CreateableSelect
|
|
400
|
+
: async
|
|
401
|
+
? AsyncSelect
|
|
402
|
+
: Select
|
|
403
|
+
|
|
404
|
+
const handleOnChange = (
|
|
405
|
+
_data: SelectValueType,
|
|
406
|
+
{action, option, removedValue}: TagOnChangeValues,
|
|
407
|
+
) => {
|
|
408
|
+
if (onChange) {
|
|
409
|
+
const isReactHookForm = onChange.toString().includes("target")
|
|
410
|
+
if (isReactHookForm) {
|
|
411
|
+
onChange({target: {name, value: _data}})
|
|
412
|
+
} else {
|
|
413
|
+
onChange(_data)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
350
416
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
417
|
+
// Reset form submitted state when a selection is made (this is all for react rendered rails kit)
|
|
418
|
+
if (action === "select-option") {
|
|
419
|
+
setFormSubmitted(false)
|
|
420
|
+
// Mark that user has made a selection to disable default value focus behavior
|
|
421
|
+
setHasUserSelected(true)
|
|
422
|
+
}
|
|
355
423
|
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
424
|
+
// If a value is selected and we're preserving input on blur, clear the input
|
|
425
|
+
if (action === "select-option" && preserveSearchInput) {
|
|
426
|
+
setInputValue("")
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (action === "select-option") {
|
|
430
|
+
if (selectProps.onMultiValueClick && option)
|
|
431
|
+
selectProps.onMultiValueClick(option)
|
|
432
|
+
const multiValueClearEvent = new CustomEvent(
|
|
433
|
+
`pb-typeahead-kit-${selectProps.id}-result-option-select`,
|
|
434
|
+
{detail: option ? option : _data},
|
|
435
|
+
)
|
|
436
|
+
document.dispatchEvent(multiValueClearEvent)
|
|
437
|
+
}
|
|
438
|
+
if (action === "remove-value" || action === "pop-value") {
|
|
439
|
+
const multiValueRemoveEvent = new CustomEvent(
|
|
440
|
+
`pb-typeahead-kit-${selectProps.id}-result-option-remove`,
|
|
441
|
+
{detail: removedValue},
|
|
442
|
+
)
|
|
443
|
+
document.dispatchEvent(multiValueRemoveEvent)
|
|
444
|
+
}
|
|
445
|
+
if (action === "clear") {
|
|
446
|
+
const multiValueClearEvent = new CustomEvent(
|
|
447
|
+
`pb-typeahead-kit-${selectProps.id}-result-clear`,
|
|
448
|
+
)
|
|
449
|
+
document.dispatchEvent(multiValueClearEvent)
|
|
450
|
+
// If preserving input on blur, also clear input on explicit clear
|
|
451
|
+
if (preserveSearchInput) {
|
|
452
|
+
setInputValue("")
|
|
453
|
+
}
|
|
371
454
|
}
|
|
372
455
|
}
|
|
373
|
-
}
|
|
374
456
|
|
|
375
|
-
|
|
376
|
-
|
|
457
|
+
const filteredProps: TypeaheadProps = {...props}
|
|
458
|
+
delete filteredProps.truncate
|
|
377
459
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
460
|
+
const dataProps = buildDataProps(data)
|
|
461
|
+
const htmlProps = buildHtmlProps(htmlOptions)
|
|
462
|
+
const classes = classnames(
|
|
463
|
+
"pb_typeahead_kit react-select",
|
|
464
|
+
`mb_${marginBottom}`,
|
|
465
|
+
globalProps(filteredProps),
|
|
466
|
+
className
|
|
467
|
+
)
|
|
386
468
|
|
|
387
|
-
|
|
469
|
+
const inlineClass = selectProps.inline ? "inline" : null
|
|
388
470
|
|
|
389
|
-
|
|
390
|
-
formSubmitted
|
|
471
|
+
const shouldShowValidationError = required && formSubmitted
|
|
391
472
|
|
|
392
|
-
|
|
473
|
+
const errorDisplay =
|
|
474
|
+
error ||
|
|
475
|
+
(shouldShowValidationError
|
|
476
|
+
? validation?.message || "Please fill out this field."
|
|
477
|
+
: "")
|
|
393
478
|
|
|
394
|
-
|
|
479
|
+
return (
|
|
395
480
|
<div
|
|
396
481
|
{...dataProps}
|
|
397
482
|
{...htmlProps}
|
|
@@ -406,12 +491,15 @@ const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(({
|
|
|
406
491
|
{...selectProps}
|
|
407
492
|
/>
|
|
408
493
|
</div>
|
|
409
|
-
|
|
410
|
-
}
|
|
494
|
+
)
|
|
495
|
+
},
|
|
496
|
+
)
|
|
411
497
|
|
|
412
498
|
Object.keys(kitComponents).forEach((k) => {
|
|
413
|
-
(Typeahead as GenericObject)[k] = (
|
|
499
|
+
(Typeahead as GenericObject)[k] = (
|
|
500
|
+
kitComponents as {[key: string]: unknown}
|
|
501
|
+
)[k]
|
|
414
502
|
})
|
|
415
503
|
|
|
416
|
-
Typeahead.displayName =
|
|
504
|
+
Typeahead.displayName = "Typeahead"
|
|
417
505
|
export default Typeahead
|