playbook_ui 16.1.0.pre.alpha.play276813969 → 16.1.0.pre.alpha.testingrailsfix14159
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_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_form/docs/_form_with_required_indicator.html.erb +6 -3
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +9 -2
- data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
- 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.tsx +410 -323
- data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
- 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/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 +3 -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/lib/playbook/forms/builder/form_field_builder.rb +1 -1
- data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
- data/lib/playbook/forms/builder.rb +2 -2
- data/lib/playbook/version.rb +1 -1
- metadata +23 -6
- data/dist/chunks/_typeahead-C4YsbA48.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,32 +33,55 @@ 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 = {
|
|
@@ -70,329 +93,390 @@ export type SelectValueType = {
|
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
type TagOnChangeValues = {
|
|
73
|
-
action?: string
|
|
74
|
-
option?: SelectValueType
|
|
75
|
-
removedValue?: string
|
|
96
|
+
action?: string
|
|
97
|
+
option?: SelectValueType
|
|
98
|
+
removedValue?: string
|
|
76
99
|
}
|
|
77
100
|
|
|
78
101
|
/**
|
|
79
102
|
* @constant {React.ReactComponent} Typeahead
|
|
80
103
|
* @param {TypeaheadProps} props - props as described at https://react-select.com/props
|
|
81
104
|
*/
|
|
82
|
-
const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(
|
|
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
|
-
|
|
126
|
-
|
|
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
|
|
127
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
|
+
}
|
|
170
|
+
}
|
|
171
|
+
: props.onBlur
|
|
172
|
+
|
|
173
|
+
// Create a ref to access React Select instance
|
|
174
|
+
const selectRef = useRef<any>(null)
|
|
175
|
+
|
|
176
|
+
// Helper function to flatten grouped options if custom groups are used
|
|
177
|
+
const flattenOptions = (options: any[]): any[] => {
|
|
178
|
+
if (!options) return []
|
|
128
179
|
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
return
|
|
180
|
+
return options.reduce((acc, option) => {
|
|
181
|
+
if (option.options && Array.isArray(option.options)) {
|
|
182
|
+
return [...acc, ...option.options]
|
|
132
183
|
}
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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]
|
|
143
196
|
}
|
|
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
|
-
|
|
194
|
-
// Handle scrolling so selected option is visible
|
|
195
|
-
setTimeout(() => {
|
|
196
|
-
if (selectRef.current && selectRef.current.menuListRef) {
|
|
197
|
-
const menuElement = selectRef.current.menuListRef
|
|
198
|
-
// Find the focused option using React Select's class
|
|
199
|
-
const focusedElement = menuElement.querySelector('.typeahead-kit-select__option--is-focused')
|
|
200
|
-
|
|
201
|
-
if (focusedElement) {
|
|
202
|
-
const optionTop = focusedElement.offsetTop
|
|
203
|
-
const optionHeight = focusedElement.offsetHeight
|
|
204
|
-
const menuHeight = menuElement.clientHeight
|
|
205
|
-
|
|
206
|
-
// Set the menu's scrollTop to position the selected option in the middle
|
|
207
|
-
const scrollToMiddle = optionTop - (menuHeight / 2) + (optionHeight / 2)
|
|
208
|
-
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
|
+
}
|
|
209
247
|
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
248
|
+
}, 20)
|
|
249
|
+
}
|
|
212
250
|
}
|
|
213
251
|
}
|
|
214
252
|
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
props.onMenuOpen()
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const selectProps = {
|
|
225
|
-
cacheOptions: true,
|
|
226
|
-
required,
|
|
227
|
-
components: {
|
|
228
|
-
Control,
|
|
229
|
-
ClearIndicator,
|
|
230
|
-
IndicatorsContainer,
|
|
231
|
-
IndicatorSeparator: null as null,
|
|
232
|
-
MenuList,
|
|
233
|
-
MultiValue,
|
|
234
|
-
Option,
|
|
235
|
-
Placeholder,
|
|
236
|
-
ValueContainer,
|
|
237
|
-
...components
|
|
238
|
-
},
|
|
239
|
-
loadOptions: isString(loadOptions) ? get(window, loadOptions) : loadOptions,
|
|
240
|
-
getOptionLabel: isString(getOptionLabel) ? get(window, getOptionLabel) : getOptionLabel,
|
|
241
|
-
getOptionValue: isString(getOptionValue) ? get(window, getOptionValue) : getOptionValue,
|
|
242
|
-
defaultOptions: true,
|
|
243
|
-
id: id || uniqueId(),
|
|
244
|
-
inputDisplay: inputDisplay,
|
|
245
|
-
inline: false,
|
|
246
|
-
isClearable: true,
|
|
247
|
-
isSearchable: true,
|
|
248
|
-
name,
|
|
249
|
-
multiKit: '',
|
|
250
|
-
onCreateOption: null as null,
|
|
251
|
-
plusIcon: false,
|
|
252
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
253
|
-
onMultiValueClick: (_option: SelectValueType): any => undefined,
|
|
254
|
-
pillColor: pillColor,
|
|
255
|
-
...(preserveSearchInput ? { inputValue } : {}),
|
|
256
|
-
onInputChange: handleInputChange,
|
|
257
|
-
onBlur: handleBlur,
|
|
258
|
-
onMenuOpen: handleMenuOpen,
|
|
259
|
-
...props,
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const [contextValue, setContextValue] = useState("")
|
|
263
|
-
|
|
264
|
-
// Add listener for form validation to track when validation should be shown (needed for react rendered rails kit)
|
|
265
|
-
useEffect(() => {
|
|
266
|
-
const handleInvalid = (event: Event) => {
|
|
267
|
-
const target = event.target as HTMLInputElement
|
|
268
|
-
const typeaheadContainer = target.closest('[data-pb-react-component="Typeahead"]')
|
|
269
|
-
|
|
270
|
-
if (typeaheadContainer) {
|
|
271
|
-
// Check if this invalid event is specifically for our typeahead by comparing names so we do not have to require ids
|
|
272
|
-
const invalidInputName = target.name || target.getAttribute('name')
|
|
273
|
-
if (invalidInputName === name) {
|
|
274
|
-
setFormSubmitted(true)
|
|
275
|
-
}
|
|
253
|
+
}, 0)
|
|
254
|
+
|
|
255
|
+
// Call original onMenuOpen if provided
|
|
256
|
+
if (props.onMenuOpen) {
|
|
257
|
+
props.onMenuOpen()
|
|
276
258
|
}
|
|
277
259
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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,
|
|
282
305
|
}
|
|
283
|
-
}, [name])
|
|
284
306
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
+
}
|
|
290
324
|
}
|
|
291
|
-
|
|
325
|
+
document.addEventListener("invalid", handleInvalid, true)
|
|
292
326
|
|
|
293
|
-
|
|
327
|
+
return () => {
|
|
328
|
+
document.removeEventListener("invalid", handleInvalid, true)
|
|
329
|
+
}
|
|
330
|
+
}, [name])
|
|
294
331
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
useEffect(() => {
|
|
301
|
-
if (searchContextSelector) {
|
|
302
|
-
const searchContextElement = document.getElementById(searchContextSelector)
|
|
303
|
-
|
|
304
|
-
setContextValue((searchContextElement as HTMLInputElement)?.value)
|
|
305
|
-
const handleContextChange = (e: Event) => {
|
|
306
|
-
const target = e.target as HTMLInputElement;
|
|
307
|
-
setContextValue(target.value);
|
|
308
|
-
if (clearOnContextChange) {
|
|
309
|
-
document.dispatchEvent(new CustomEvent(`pb-typeahead-kit-${selectProps.id}:clear`))
|
|
310
|
-
if (preserveSearchInput) {
|
|
311
|
-
setInputValue('')
|
|
312
|
-
}
|
|
332
|
+
// Add listener for clearing
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
const handleClear = () => {
|
|
335
|
+
if (preserveSearchInput) {
|
|
336
|
+
setInputValue("")
|
|
313
337
|
}
|
|
314
338
|
}
|
|
315
339
|
|
|
316
|
-
|
|
340
|
+
document.addEventListener(
|
|
341
|
+
`pb-typeahead-kit-${selectProps.id}:clear`,
|
|
342
|
+
handleClear,
|
|
343
|
+
)
|
|
317
344
|
|
|
318
345
|
return () => {
|
|
319
|
-
|
|
346
|
+
document.removeEventListener(
|
|
347
|
+
`pb-typeahead-kit-${selectProps.id}:clear`,
|
|
348
|
+
handleClear,
|
|
349
|
+
)
|
|
320
350
|
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
}
|
|
342
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
|
|
343
394
|
}
|
|
344
395
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
+
}
|
|
351
416
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
}
|
|
356
423
|
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
+
}
|
|
372
454
|
}
|
|
373
455
|
}
|
|
374
|
-
}
|
|
375
456
|
|
|
376
|
-
|
|
377
|
-
|
|
457
|
+
const filteredProps: TypeaheadProps = {...props}
|
|
458
|
+
delete filteredProps.truncate
|
|
378
459
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
+
)
|
|
387
468
|
|
|
388
|
-
|
|
469
|
+
const inlineClass = selectProps.inline ? "inline" : null
|
|
389
470
|
|
|
390
|
-
|
|
391
|
-
formSubmitted
|
|
471
|
+
const shouldShowValidationError = required && formSubmitted
|
|
392
472
|
|
|
393
|
-
|
|
473
|
+
const errorDisplay =
|
|
474
|
+
error ||
|
|
475
|
+
(shouldShowValidationError
|
|
476
|
+
? validation?.message || "Please fill out this field."
|
|
477
|
+
: "")
|
|
394
478
|
|
|
395
|
-
|
|
479
|
+
return (
|
|
396
480
|
<div
|
|
397
481
|
{...dataProps}
|
|
398
482
|
{...htmlProps}
|
|
@@ -407,12 +491,15 @@ const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(({
|
|
|
407
491
|
{...selectProps}
|
|
408
492
|
/>
|
|
409
493
|
</div>
|
|
410
|
-
|
|
411
|
-
}
|
|
494
|
+
)
|
|
495
|
+
},
|
|
496
|
+
)
|
|
412
497
|
|
|
413
498
|
Object.keys(kitComponents).forEach((k) => {
|
|
414
|
-
(Typeahead as GenericObject)[k] = (
|
|
499
|
+
(Typeahead as GenericObject)[k] = (
|
|
500
|
+
kitComponents as {[key: string]: unknown}
|
|
501
|
+
)[k]
|
|
415
502
|
})
|
|
416
503
|
|
|
417
|
-
Typeahead.displayName =
|
|
504
|
+
Typeahead.displayName = "Typeahead"
|
|
418
505
|
export default Typeahead
|