playbook_ui 16.2.0.pre.rc.0 → 16.2.0.pre.rc.2

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +12 -2
  3. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +33 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.jsx +71 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.md +4 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  8. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
  9. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
  10. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
  11. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
  12. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
  13. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
  14. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
  15. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  16. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  17. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +14 -5
  18. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_default.md +1 -0
  19. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +46 -11
  20. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  21. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  22. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  23. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +10 -4
  24. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +9 -0
  25. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +7 -2
  26. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  27. data/app/pb_kits/playbook/pb_dropdown/index.js +125 -73
  28. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +16 -0
  29. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  30. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +6 -3
  31. data/app/pb_kits/playbook/pb_form/pb_form_validation.js +9 -2
  32. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  33. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
  34. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  35. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  36. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  37. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  38. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
  39. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +51 -16
  40. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
  41. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
  42. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
  43. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +28 -0
  44. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
  45. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +1 -0
  46. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
  47. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
  48. data/app/pb_kits/playbook/pb_table/index.ts +29 -27
  49. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  50. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +10 -0
  51. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.html.erb +3 -3
  52. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.jsx +3 -0
  53. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_default.md +1 -0
  54. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +25 -9
  55. data/app/pb_kits/playbook/pb_textarea/textarea.rb +7 -1
  56. data/app/pb_kits/playbook/pb_time_picker/_time_picker.tsx +97 -11
  57. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_on_handler.jsx +5 -2
  58. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.html.erb +6 -0
  59. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.jsx +16 -0
  60. data/app/pb_kits/playbook/pb_time_picker/docs/_time_picker_required_indicator.md +3 -0
  61. data/app/pb_kits/playbook/pb_time_picker/docs/example.yml +2 -0
  62. data/app/pb_kits/playbook/pb_time_picker/docs/index.js +1 -0
  63. data/app/pb_kits/playbook/pb_time_picker/time_picker.rb +3 -0
  64. data/app/pb_kits/playbook/pb_time_picker/time_picker.test.jsx +47 -1
  65. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +410 -323
  66. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  67. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  68. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  69. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  70. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  71. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  72. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  73. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
  74. data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
  75. data/dist/chunks/_typeahead-BKSzddAX.js +1 -0
  76. data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
  77. data/dist/chunks/lib-BwX82vim.js +29 -0
  78. data/dist/chunks/vendor.js +3 -3
  79. data/dist/menu.yml +2 -2
  80. data/dist/playbook-rails-react-bindings.js +1 -1
  81. data/dist/playbook-rails.js +1 -1
  82. data/dist/playbook.css +1 -1
  83. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  84. data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
  85. data/lib/playbook/forms/builder.rb +2 -2
  86. data/lib/playbook/version.rb +1 -1
  87. metadata +24 -6
  88. data/dist/chunks/_typeahead-Bfy-4mll.js +0 -1
  89. data/dist/chunks/lib-DD34ZrWL.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,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?: { [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,
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
- 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('')
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
- // If the original onInputChange was provided, call it too
130
- if (props.onInputChange) {
131
- return props.onInputChange(newValue, actionMeta)
180
+ return options.reduce((acc, option) => {
181
+ if (option.options && Array.isArray(option.options)) {
182
+ return [...acc, ...option.options]
132
183
  }
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)
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
- : 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)
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
- }, 20)
248
+ }, 20)
249
+ }
212
250
  }
213
251
  }
214
252
  }
215
- }
216
- }, 0)
217
-
218
- // Call original onMenuOpen if provided
219
- if (props.onMenuOpen) {
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
- document.addEventListener('invalid', handleInvalid, true)
279
-
280
- return () => {
281
- document.removeEventListener('invalid', handleInvalid, true)
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
- // Add listener for clearing
286
- useEffect(() => {
287
- const handleClear = () => {
288
- if (preserveSearchInput) {
289
- setInputValue('')
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
- document.addEventListener(`pb-typeahead-kit-${selectProps.id}:clear`, handleClear)
327
+ return () => {
328
+ document.removeEventListener("invalid", handleInvalid, true)
329
+ }
330
+ }, [name])
294
331
 
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('')
312
- }
332
+ // Add listener for clearing
333
+ useEffect(() => {
334
+ const handleClear = () => {
335
+ if (preserveSearchInput) {
336
+ setInputValue("")
313
337
  }
314
338
  }
315
339
 
316
- if (searchContextElement) searchContextElement.addEventListener('change', handleContextChange)
340
+ document.addEventListener(
341
+ `pb-typeahead-kit-${selectProps.id}:clear`,
342
+ handleClear,
343
+ )
317
344
 
318
345
  return () => {
319
- if (searchContextElement) searchContextElement.removeEventListener('change', handleContextChange)
346
+ document.removeEventListener(
347
+ `pb-typeahead-kit-${selectProps.id}:clear`,
348
+ handleClear,
349
+ )
320
350
  }
321
- }
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)
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
- // 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
- }
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
- // If a value is selected and we're preserving input on blur, clear the input
353
- if (action === 'select-option' && preserveSearchInput) {
354
- setInputValue('')
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
- 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('')
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
- const filteredProps: TypeaheadProps = {...props}
377
- delete filteredProps.truncate
457
+ const filteredProps: TypeaheadProps = {...props}
458
+ delete filteredProps.truncate
378
459
 
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
- )
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
- const inlineClass = selectProps.inline ? 'inline' : null
469
+ const inlineClass = selectProps.inline ? "inline" : null
389
470
 
390
- const shouldShowValidationError = required &&
391
- formSubmitted
471
+ const shouldShowValidationError = required && formSubmitted
392
472
 
393
- const errorDisplay = error || (shouldShowValidationError ? validation?.message || "Please fill out this field." : "")
473
+ const errorDisplay =
474
+ error ||
475
+ (shouldShowValidationError
476
+ ? validation?.message || "Please fill out this field."
477
+ : "")
394
478
 
395
- return (
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] = (kitComponents as {[key: string]: unknown})[k]
499
+ (Typeahead as GenericObject)[k] = (
500
+ kitComponents as {[key: string]: unknown}
501
+ )[k]
415
502
  })
416
503
 
417
- Typeahead.displayName = 'Typeahead'
504
+ Typeahead.displayName = "Typeahead"
418
505
  export default Typeahead