playbook_ui 16.2.0.pre.rc.2 → 16.2.0.pre.rc.3

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
  3. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +0 -17
  4. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +1 -10
  5. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +0 -2
  6. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +0 -2
  7. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +0 -1
  8. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +11 -46
  9. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +3 -6
  10. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +0 -1
  11. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +1 -3
  12. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +4 -10
  13. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +0 -9
  14. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +2 -7
  15. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +0 -4
  16. data/app/pb_kits/playbook/pb_dropdown/index.js +73 -125
  17. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +0 -16
  18. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +0 -6
  19. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +3 -5
  20. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +0 -7
  21. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +549 -638
  22. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  23. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +7 -4
  24. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  25. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  26. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +323 -410
  27. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +0 -2
  28. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +0 -2
  29. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +21 -22
  30. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +2 -3
  31. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +1 -3
  32. data/dist/chunks/{_pb_line_graph-DuJNCf7N.js → _pb_line_graph-CG2X7d4a.js} +1 -1
  33. data/dist/chunks/_typeahead-DjDiMPdY.js +1 -0
  34. data/dist/chunks/{globalProps-Bc-FVsRt.js → globalProps-B_OY_vR9.js} +1 -1
  35. data/dist/chunks/lib-9vEH4omL.js +29 -0
  36. data/dist/chunks/vendor.js +2 -2
  37. data/dist/playbook-rails-react-bindings.js +1 -1
  38. data/dist/playbook-rails.js +1 -1
  39. data/dist/playbook.css +1 -1
  40. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  41. data/lib/playbook/forms/builder/typeahead_field.rb +1 -15
  42. data/lib/playbook/forms/builder.rb +2 -2
  43. data/lib/playbook/version.rb +1 -1
  44. metadata +5 -12
  45. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +0 -6
  46. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +0 -17
  47. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +0 -3
  48. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +0 -3
  49. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +0 -16
  50. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +0 -23
  51. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +0 -3
  52. data/dist/chunks/_typeahead-BKSzddAX.js +0 -1
  53. data/dist/chunks/lib-BwX82vim.js +0 -29
@@ -1,12 +1,12 @@
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"
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 {globalProps, GlobalProps} from "../utilities/globalProps"
9
- import classnames from "classnames"
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 {noop, buildDataProps, buildHtmlProps} from "../utilities/props"
25
- import {GenericObject, Noop} from "../types"
24
+ import { noop, buildDataProps, buildHtmlProps } from '../utilities/props'
25
+ import { GenericObject, Noop } from '../types'
26
26
 
27
27
  /**
28
28
  * @typedef {object} Props
@@ -33,55 +33,32 @@ 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?: {[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
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?: "primary" | "neutral" | "success" | "warning" | "error" | "info" | "data_1" | "data_2" | "data_3" | "data_4" | "data_5" | "data_6" | "data_7" | "data_8" | "windows" | "siding" | "roofing" | "doors" | "gutters" | "solar" | "insulation" | "accessories",
55
+ onChange?: any,
56
+ optionsByContext?: Record<string, Array<{ label: string; value?: string }>>
57
+ required?: boolean,
58
+ validation?: { message: string },
59
+ searchContextSelector?: string,
60
+ clearOnContextChange?: boolean,
61
+ preserveSearchInput?: boolean,
85
62
  } & GlobalProps
86
63
 
87
64
  export type SelectValueType = {
@@ -93,390 +70,329 @@ export type SelectValueType = {
93
70
  }
94
71
 
95
72
  type TagOnChangeValues = {
96
- action?: string
97
- option?: SelectValueType
98
- removedValue?: string
73
+ action?: string,
74
+ option?: SelectValueType,
75
+ removedValue?: string,
99
76
  }
100
77
 
101
78
  /**
102
79
  * @constant {React.ReactComponent} Typeahead
103
80
  * @param {TypeaheadProps} props - props as described at https://react-select.com/props
104
81
  */
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
82
+ const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(({
83
+ async,
84
+ className,
85
+ components = {},
86
+ createable,
87
+ error = "",
88
+ data = {},
89
+ disabled = false,
90
+ getOptionLabel,
91
+ getOptionValue,
92
+ htmlOptions = {},
93
+ id,
94
+ inputDisplay = "pills",
95
+ name,
96
+ loadOptions = noop,
97
+ marginBottom = "sm",
98
+ pillColor,
99
+ onChange,
100
+ optionsByContext = {},
101
+ searchContextSelector,
102
+ required = false,
103
+ validation,
104
+ clearOnContextChange = false,
105
+ preserveSearchInput = false, // Default to false to maintain backward compatibility
106
+ ...props
107
+ }: TypeaheadProps) => {
108
+ // State to manage the input value when preserveSearchInput is true
109
+ const [inputValue, setInputValue] = useState("")
110
+ // State to track if form has been submitted to control validation display for react rendered rails kit
111
+ const [formSubmitted, setFormSubmitted] = useState(false)
112
+ // State to track if user has made a selection (to disable defaultValue focus behavior)
113
+ const [hasUserSelected, setHasUserSelected] = useState(false)
114
+
115
+ // If preserveSearchInput is true, we need to control the input value
116
+ const handleInputChange = preserveSearchInput
117
+ ? (newValue: string, actionMeta: {action: string}) => {
118
+ // Only update the input value for certain actions
119
+ if (actionMeta.action === 'input-change') {
120
+ setInputValue(newValue)
121
+ } else if (actionMeta.action === 'menu-close' && !props.value) {
122
+ // Don't clear the input when the menu closes without a selection
123
+ // unless the component is controlled and has a value
124
+ } else if (actionMeta.action === 'set-value') {
125
+ // When an option is selected, clear the input
126
+ setInputValue('')
160
127
  }
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 []
179
128
 
180
- return options.reduce((acc, option) => {
181
- if (option.options && Array.isArray(option.options)) {
182
- return [...acc, ...option.options]
129
+ // If the original onInputChange was provided, call it too
130
+ if (props.onInputChange) {
131
+ return props.onInputChange(newValue, actionMeta)
183
132
  }
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]
133
+ return newValue
134
+ }
135
+ : props.onInputChange
136
+
137
+ // Handle blur events if we're preserving input
138
+ const handleBlur = preserveSearchInput
139
+ ? (event: React.FocusEvent<HTMLInputElement>) => {
140
+ // Do not clear input on blur - the value is preserved in our state
141
+ if (props.onBlur) {
142
+ props.onBlur(event)
196
143
  }
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
- }
144
+ }
145
+ : props.onBlur
146
+
147
+ // Create a ref to access React Select instance
148
+ const selectRef = useRef<any>(null)
149
+
150
+ // Helper function to flatten grouped options if custom groups are used
151
+ const flattenOptions = (options: any[]): any[] => {
152
+ if (!options) return []
153
+
154
+ return options.reduce((acc, option) => {
155
+ if (option.options && Array.isArray(option.options)) {
156
+ return [...acc, ...option.options]
157
+ }
158
+ return [...acc, option]
159
+ }, [])
160
+ }
161
+
162
+ // Configure focus on selected option using React Select's API
163
+ const handleMenuOpen = () => {
164
+ setTimeout(() => {
165
+ let currentValue = props.value || props.defaultValue
166
+
167
+ // Handle react rendered rails version which passes arrays even for single selects
168
+ if (Array.isArray(currentValue) && currentValue.length > 0) {
169
+ currentValue = currentValue[0]
170
+ }
171
+
172
+ // Only apply custom focus if user has NOT made a selection yet
173
+ if (currentValue && selectRef.current && !hasUserSelected && !props.isMulti) {
174
+
175
+ const options = props.options
176
+ if (options) {
177
+ // Flatten grouped options to find the matching option and find matching option
178
+ const flatOptions = flattenOptions(options)
179
+
180
+ const targetOption = flatOptions.find((option: any) => {
181
+ const optionValue = props.getOptionValue ? props.getOptionValue(option) : option.value
182
+ const currentOptionValue = props.getOptionValue ? props.getOptionValue(currentValue) : currentValue.value
183
+ return optionValue === currentOptionValue
184
+ })
185
+
186
+ if (targetOption) {
187
+ // Use React Select's internal state to set focused option
188
+ if (selectRef.current && selectRef.current.setState) {
189
+ selectRef.current.setState({
190
+ focusedOption: targetOption,
191
+ focusedValue: null
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)
247
209
  }
248
- }, 20)
249
- }
210
+ }
211
+ }, 20)
250
212
  }
251
213
  }
252
214
  }
253
- }, 0)
254
-
255
- // Call original onMenuOpen if provided
256
- if (props.onMenuOpen) {
257
- props.onMenuOpen()
258
215
  }
216
+ }, 0)
217
+
218
+ // Call original onMenuOpen if provided
219
+ if (props.onMenuOpen) {
220
+ props.onMenuOpen()
259
221
  }
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,
305
- }
306
-
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
- }
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)
323
275
  }
324
276
  }
325
- document.addEventListener("invalid", handleInvalid, true)
326
-
327
- return () => {
328
- document.removeEventListener("invalid", handleInvalid, true)
329
- }
330
- }, [name])
277
+ }
278
+ document.addEventListener('invalid', handleInvalid, true)
279
+
280
+ return () => {
281
+ document.removeEventListener('invalid', handleInvalid, true)
282
+ }
283
+ }, [name])
331
284
 
332
- // Add listener for clearing
333
- useEffect(() => {
334
- const handleClear = () => {
335
- if (preserveSearchInput) {
336
- setInputValue("")
337
- }
285
+ // Add listener for clearing
286
+ useEffect(() => {
287
+ const handleClear = () => {
288
+ if (preserveSearchInput) {
289
+ setInputValue('')
338
290
  }
291
+ }
339
292
 
340
- document.addEventListener(
341
- `pb-typeahead-kit-${selectProps.id}:clear`,
342
- handleClear,
343
- )
293
+ document.addEventListener(`pb-typeahead-kit-${selectProps.id}:clear`, handleClear)
344
294
 
345
- return () => {
346
- document.removeEventListener(
347
- `pb-typeahead-kit-${selectProps.id}:clear`,
348
- handleClear,
349
- )
350
- }
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
- }
295
+ return () => {
296
+ document.removeEventListener(`pb-typeahead-kit-${selectProps.id}:clear`, handleClear)
297
+ }
298
+ }, [selectProps.id, preserveSearchInput])
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('')
370
312
  }
371
313
  }
314
+ }
372
315
 
373
- if (searchContextElement)
374
- searchContextElement.addEventListener("change", handleContextChange)
316
+ if (searchContextElement) searchContextElement.addEventListener('change', handleContextChange)
375
317
 
376
- return () => {
377
- if (searchContextElement)
378
- searchContextElement.removeEventListener(
379
- "change",
380
- handleContextChange,
381
- )
382
- }
318
+ return () => {
319
+ if (searchContextElement) searchContextElement.removeEventListener('change', handleContextChange)
383
320
  }
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
394
321
  }
395
-
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
- }
322
+ }, [searchContextSelector, clearOnContextChange, selectProps.id, preserveSearchInput])
323
+
324
+ const contextArray = optionsByContext[contextValue]
325
+ if (Array.isArray(contextArray) && contextArray.length > 0) {
326
+ selectProps.options = contextArray
327
+ }
328
+
329
+ const Tag = (
330
+ createable
331
+ ? (async ? AsyncCreateableSelect : CreateableSelect)
332
+ : (async ? AsyncSelect : Select)
333
+ )
334
+
335
+ const handleOnChange = (_data: SelectValueType, { action, option, removedValue }: TagOnChangeValues) => {
336
+ if (onChange) {
337
+ const isReactHookForm = onChange.toString().includes("target")
338
+ if (isReactHookForm) {
339
+ onChange({ target: { name, value: _data } })
340
+ } else {
341
+ onChange(_data)
415
342
  }
343
+ }
416
344
 
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
- }
345
+ // Reset form submitted state when a selection is made (this is all for react rendered rails kit)
346
+ if (action === 'select-option') {
347
+ setFormSubmitted(false)
348
+ // Mark that user has made a selection to disable default value focus behavior
349
+ setHasUserSelected(true)
350
+ }
423
351
 
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
- }
352
+ // If a value is selected and we're preserving input on blur, clear the input
353
+ if (action === 'select-option' && preserveSearchInput) {
354
+ setInputValue('')
355
+ }
428
356
 
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
- }
357
+ if (action === 'select-option') {
358
+ if (selectProps.onMultiValueClick && option) selectProps.onMultiValueClick(option)
359
+ const multiValueClearEvent = new CustomEvent(`pb-typeahead-kit-${selectProps.id}-result-option-select`, { detail: option ? option : _data })
360
+ document.dispatchEvent(multiValueClearEvent)
361
+ }
362
+ if (action === 'remove-value' || action === 'pop-value') {
363
+ const multiValueRemoveEvent = new CustomEvent(`pb-typeahead-kit-${selectProps.id}-result-option-remove`, { detail: removedValue })
364
+ document.dispatchEvent(multiValueRemoveEvent)
365
+ }
366
+ if (action === 'clear') {
367
+ const multiValueClearEvent = new CustomEvent(`pb-typeahead-kit-${selectProps.id}-result-clear`)
368
+ document.dispatchEvent(multiValueClearEvent)
369
+ // If preserving input on blur, also clear input on explicit clear
370
+ if (preserveSearchInput) {
371
+ setInputValue('')
454
372
  }
455
373
  }
374
+ }
456
375
 
457
- const filteredProps: TypeaheadProps = {...props}
458
- delete filteredProps.truncate
376
+ const filteredProps: TypeaheadProps = {...props}
377
+ delete filteredProps.truncate
459
378
 
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
- )
379
+ const dataProps = buildDataProps(data)
380
+ const htmlProps = buildHtmlProps(htmlOptions)
381
+ const classes = classnames(
382
+ 'pb_typeahead_kit react-select',
383
+ `mb_${marginBottom}`,
384
+ globalProps(filteredProps),
385
+ className
386
+ )
468
387
 
469
- const inlineClass = selectProps.inline ? "inline" : null
388
+ const inlineClass = selectProps.inline ? 'inline' : null
470
389
 
471
- const shouldShowValidationError = required && formSubmitted
390
+ const shouldShowValidationError = required &&
391
+ formSubmitted
472
392
 
473
- const errorDisplay =
474
- error ||
475
- (shouldShowValidationError
476
- ? validation?.message || "Please fill out this field."
477
- : "")
393
+ const errorDisplay = error || (shouldShowValidationError ? validation?.message || "Please fill out this field." : "")
478
394
 
479
- return (
395
+ return (
480
396
  <div
481
397
  {...dataProps}
482
398
  {...htmlProps}
@@ -491,15 +407,12 @@ const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(
491
407
  {...selectProps}
492
408
  />
493
409
  </div>
494
- )
495
- },
496
- )
410
+ )
411
+ })
497
412
 
498
413
  Object.keys(kitComponents).forEach((k) => {
499
- (Typeahead as GenericObject)[k] = (
500
- kitComponents as {[key: string]: unknown}
501
- )[k]
414
+ (Typeahead as GenericObject)[k] = (kitComponents as {[key: string]: unknown})[k]
502
415
  })
503
416
 
504
- Typeahead.displayName = "Typeahead"
417
+ Typeahead.displayName = 'Typeahead'
505
418
  export default Typeahead