playbook_ui 14.15.0.pre.rc.3 → 14.15.0
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 +127 -0
- data/app/pb_kits/playbook/pb_advanced_table/Components/TableActionBar.tsx +55 -0
- data/app/pb_kits/playbook/pb_advanced_table/Components/TablePagination.tsx +33 -0
- data/app/pb_kits/playbook/pb_advanced_table/Components/VirtualizedTableView.tsx +275 -0
- data/app/pb_kits/playbook/pb_advanced_table/Context/AdvancedTableContext.tsx +143 -3
- data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableActions.ts +66 -0
- data/app/pb_kits/playbook/pb_advanced_table/Hooks/useTableState.ts +195 -0
- data/app/pb_kits/playbook/pb_advanced_table/SubKits/TableBody.tsx +45 -99
- data/app/pb_kits/playbook/pb_advanced_table/Utilities/CellRendererUtils.tsx +73 -0
- data/app/pb_kits/playbook/pb_advanced_table/Utilities/RowUtils.ts +52 -0
- data/app/pb_kits/playbook/pb_advanced_table/Utilities/TableContainerStyles.ts +80 -0
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +123 -7
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +153 -299
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_infinite_scroll.jsx +50 -0
- data/app/pb_kits/playbook/pb_advanced_table/docs/advanced_table_mock_data_infinite_scroll.json +152002 -0
- data/app/pb_kits/playbook/pb_card/_card.tsx +2 -1
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +4 -1
- data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +2 -0
- data/app/pb_kits/playbook/pb_date_picker/index.ts +38 -0
- data/app/pb_kits/playbook/pb_drawer/_drawer.scss +19 -3
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_borders.jsx +3 -3
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_breakpoints.jsx +20 -37
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.jsx +6 -6
- data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.jsx +1 -0
- data/app/pb_kits/playbook/pb_drawer/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.tsx +5 -4
- data/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx +2 -2
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +1 -1
- data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +9 -2
- data/app/pb_kits/playbook/pb_form_pill/_form_pill.tsx +4 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.html.erb +40 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.jsx +50 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_wrapped.md +3 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_form_pill/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_form_pill/form_pill.rb +7 -1
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +13 -3
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.html.erb +72 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_disabled.jsx +91 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +2 -1
- data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +6 -0
- data/app/pb_kits/playbook/pb_overlay/_overlay.scss +13 -0
- data/app/pb_kits/playbook/pb_overlay/_overlay.tsx +7 -1
- data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar.html.erb +11 -0
- data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar.jsx +37 -0
- data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar_rails.md +1 -0
- data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar_react.md +1 -0
- data/app/pb_kits/playbook/pb_overlay/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_overlay/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_overlay/overlay.rb +7 -1
- data/app/pb_kits/playbook/pb_overlay/overlay.test.jsx +12 -0
- data/app/pb_kits/playbook/pb_popover/_popover.tsx +1 -1
- data/app/pb_kits/playbook/pb_radio/_radio.tsx +85 -74
- data/app/pb_kits/playbook/pb_radio/docs/_radio_react_hook.jsx +60 -0
- data/app/pb_kits/playbook/pb_radio/docs/_radio_react_hook.md +1 -0
- data/app/pb_kits/playbook/pb_radio/docs/example.yml +2 -1
- data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_radio/radio.test.js +16 -0
- data/app/pb_kits/playbook/pb_select/docs/_select_react_hook.jsx +58 -0
- data/app/pb_kits/playbook/pb_select/docs/_select_react_hook.md +1 -0
- data/app/pb_kits/playbook/pb_select/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_select/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_select/select.html.erb +3 -5
- data/app/pb_kits/playbook/pb_selectable_card/selectable_card.html.erb +1 -5
- data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.html.erb +1 -4
- data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.html.erb +1 -5
- data/app/pb_kits/playbook/pb_timeline/_timeline.scss +2 -2
- data/app/pb_kits/playbook/pb_title/_title.scss +32 -0
- data/app/pb_kits/playbook/pb_title/_title.tsx +10 -1
- data/app/pb_kits/playbook/pb_title/docs/_title_default.html.erb +1 -2
- data/app/pb_kits/playbook/pb_title/docs/_title_default.jsx +1 -1
- data/app/pb_kits/playbook/pb_title/docs/_title_display_size.html.erb +7 -0
- data/app/pb_kits/playbook/pb_title/docs/_title_display_size.jsx +54 -0
- data/app/pb_kits/playbook/pb_title/docs/_title_display_size.md +1 -0
- data/app/pb_kits/playbook/pb_title/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_title/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_title/title.rb +10 -1
- data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +25 -0
- data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_sizing.jsx +69 -0
- data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_sizing.md +3 -0
- data/app/pb_kits/playbook/pb_tooltip/docs/example.yml +1 -1
- data/app/pb_kits/playbook/pb_tooltip/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +36 -2
- data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +5 -1
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options.html.erb +45 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options.md +5 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options_pure_rails.html.erb +33 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_dynamic_options_pure_rails.md +3 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_typeahead/index.ts +61 -8
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +17 -2
- data/app/pb_kits/playbook/utilities/object.test.js +99 -0
- data/app/pb_kits/playbook/utilities/object.ts +29 -1
- data/dist/chunks/_typeahead-BhfaW1J9.js +36 -0
- data/dist/chunks/_weekday_stacked-CKRIELiF.js +45 -0
- data/dist/chunks/lazysizes-DHz07jlL.js +1 -0
- data/dist/chunks/{lib-Fr78pbpF.js → lib-5OzNgeeu.js} +2 -2
- data/dist/chunks/{pb_form_validation-CN51bfsD.js → pb_form_validation-DGhKbZtO.js} +1 -1
- data/dist/chunks/vendor.js +1 -1
- data/dist/playbook-doc.js +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +42 -7
- data/dist/chunks/_typeahead-BGTMEmj7.js +0 -36
- data/dist/chunks/_weekday_stacked-De2TduOp.js +0 -45
- data/dist/chunks/lazysizes-B7xYodB-.js +0 -1
@@ -1,9 +1,10 @@
|
|
1
|
-
import React from 'react'
|
1
|
+
import React, { useState, useEffect} from 'react'
|
2
2
|
import Select from 'react-select'
|
3
3
|
import AsyncSelect from 'react-select/async'
|
4
4
|
import CreateableSelect from 'react-select/creatable'
|
5
5
|
import AsyncCreateableSelect from 'react-select/async-creatable'
|
6
|
-
import { get, isString, uniqueId } from '
|
6
|
+
import { get, isString, uniqueId } from '../utilities/object'
|
7
|
+
|
7
8
|
import { globalProps, GlobalProps } from '../utilities/globalProps'
|
8
9
|
import classnames from 'classnames'
|
9
10
|
|
@@ -45,8 +46,12 @@ type TypeaheadProps = {
|
|
45
46
|
getOptionLabel?: string | (() => string),
|
46
47
|
getOptionValue?: string | (() => string),
|
47
48
|
name?: string,
|
49
|
+
options?: Array<{ label: string; value?: string }>,
|
48
50
|
marginBottom?: "none" | "xxs" | "xs" | "sm" | "md" | "lg" | "xl",
|
49
51
|
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",
|
52
|
+
optionsByContext?: Record<string, Array<{ label: string; value?: string }>>
|
53
|
+
searchContextSelector?: string,
|
54
|
+
clearOnContextChange?: boolean,
|
50
55
|
} & GlobalProps
|
51
56
|
|
52
57
|
export type SelectValueType = {
|
@@ -81,6 +86,9 @@ const Typeahead = ({
|
|
81
86
|
loadOptions = noop,
|
82
87
|
marginBottom = "sm",
|
83
88
|
pillColor,
|
89
|
+
optionsByContext = {},
|
90
|
+
searchContextSelector,
|
91
|
+
clearOnContextChange = false,
|
84
92
|
...props
|
85
93
|
}: TypeaheadProps) => {
|
86
94
|
const selectProps = {
|
@@ -115,6 +123,32 @@ const Typeahead = ({
|
|
115
123
|
...props,
|
116
124
|
}
|
117
125
|
|
126
|
+
const [contextValue, setContextValue] = useState("")
|
127
|
+
|
128
|
+
useEffect(() => {
|
129
|
+
if (searchContextSelector) {
|
130
|
+
const searchContextElement = document.getElementById(searchContextSelector)
|
131
|
+
|
132
|
+
setContextValue((searchContextElement as HTMLInputElement)?.value)
|
133
|
+
const handleContextChange = (e: Event) => {
|
134
|
+
const target = e.target as HTMLInputElement;
|
135
|
+
setContextValue(target.value);
|
136
|
+
if (clearOnContextChange) document.dispatchEvent(new CustomEvent(`pb-typeahead-kit-${selectProps.id}:clear`))
|
137
|
+
}
|
138
|
+
|
139
|
+
if (searchContextElement) searchContextElement.addEventListener('change', handleContextChange)
|
140
|
+
|
141
|
+
return () => {
|
142
|
+
if (searchContextElement) searchContextElement.removeEventListener('change', handleContextChange)
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}, [searchContextSelector])
|
146
|
+
|
147
|
+
const contextArray = optionsByContext[contextValue]
|
148
|
+
if (Array.isArray(contextArray) && contextArray.length > 0) {
|
149
|
+
selectProps.options = contextArray
|
150
|
+
}
|
151
|
+
|
118
152
|
const Tag = (
|
119
153
|
createable
|
120
154
|
? (async ? AsyncCreateableSelect : CreateableSelect)
|
@@ -8,15 +8,17 @@ import { SelectValueType } from '../_typeahead'
|
|
8
8
|
type Props = {
|
9
9
|
data: SelectValueType,
|
10
10
|
multiValueTemplate: any,
|
11
|
+
wrapped?: boolean,
|
11
12
|
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",
|
12
13
|
removeProps: any,
|
13
14
|
selectProps: any,
|
14
15
|
}
|
15
16
|
|
17
|
+
|
16
18
|
const MultiValue = (props: Props) => {
|
17
19
|
const { removeProps } = props
|
18
20
|
const { imageUrl, label } = props.data
|
19
|
-
const { dark, multiKit, pillColor, truncate } = props.selectProps
|
21
|
+
const { dark, multiKit, pillColor, truncate, wrapped } = props.selectProps
|
20
22
|
|
21
23
|
const formPillProps = {
|
22
24
|
marginRight: 'xs',
|
@@ -52,6 +54,7 @@ const MultiValue = (props: Props) => {
|
|
52
54
|
size={multiKit === 'smallPill' ? 'small' : ''}
|
53
55
|
text=''
|
54
56
|
truncate={truncate}
|
57
|
+
wrapped={wrapped}
|
55
58
|
{...props}
|
56
59
|
/>
|
57
60
|
}
|
@@ -66,6 +69,7 @@ const MultiValue = (props: Props) => {
|
|
66
69
|
size={multiKit === 'smallPill' ? 'small' : ''}
|
67
70
|
text={label}
|
68
71
|
truncate={truncate}
|
72
|
+
wrapped={wrapped}
|
69
73
|
{...props}
|
70
74
|
/>
|
71
75
|
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<%= pb_rails("select", props: {
|
2
|
+
id:"color_context_2",
|
3
|
+
label: "Choose a Color",
|
4
|
+
name: "color_name",
|
5
|
+
options: [
|
6
|
+
{ value: "red", value_text: "Red" },
|
7
|
+
{ value: "blue", value_text: "Blue" },
|
8
|
+
{ value: "green", value_text: "Green" }
|
9
|
+
],
|
10
|
+
}) %>
|
11
|
+
|
12
|
+
<%= pb_rails("typeahead", props: {
|
13
|
+
label: "Pick a Shade",
|
14
|
+
is_multi: false,
|
15
|
+
search_context_selector: "color_context_2",
|
16
|
+
options_by_context: {
|
17
|
+
"red": [
|
18
|
+
{ label: "Scarlet", value: "scarlet" },
|
19
|
+
{ label: "Mahogany", value: "mahogany" },
|
20
|
+
{ label: "Crimson", value: "crimson" }
|
21
|
+
],
|
22
|
+
"blue": [
|
23
|
+
{ label: "Sky Blue", value: "sky" },
|
24
|
+
{ label: "Cerulean", value: "cerulean" },
|
25
|
+
{ label: "Navy", value: "navy" }
|
26
|
+
],
|
27
|
+
"green": [
|
28
|
+
{ label: "Emerald", value: "emerald" },
|
29
|
+
{ label: "Mint", value: "mint" },
|
30
|
+
{ label: "Olive", value: "olive" }
|
31
|
+
]
|
32
|
+
},
|
33
|
+
id: "typeahead-dynamic-options",
|
34
|
+
}) %>
|
35
|
+
|
36
|
+
|
37
|
+
<%= javascript_tag defer: "defer" do %>
|
38
|
+
document.addEventListener("pb-typeahead-kit-typeahead-dynamic-options-result-option-select", function(event) {
|
39
|
+
console.log('Single Option selected')
|
40
|
+
console.dir(event.detail)
|
41
|
+
})
|
42
|
+
document.addEventListener("pb-typeahead-kit-typeahead-dynamic-options-result-clear", function() {
|
43
|
+
console.log('All options cleared')
|
44
|
+
})
|
45
|
+
<% end %>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
You can also set up a typeahead to render options dynamically based on input from a select. To achieve this:
|
2
|
+
- The typeahead must have a unique `id`
|
3
|
+
- Use the `search_context_selector` prop on the typeahead. The value here must match the id of the select so the Typeahead knows where to read the current "context" from.
|
4
|
+
- Use `options_by_context` to pass in a hash whose keys match the possible values of your “context” select. Each key maps to an array of { label, value } objects. The typeahead automatically displays only the subset of options matching the current context.
|
5
|
+
- Additionally, the optional `clear_on_context_change` prop controls whether the typeahead clears or not when a change happens in the linked select. This prop is set to true by default so that whenever a selection is made in the select, the Typeahead automatically clears its current input/selection.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<%= pb_rails("select", props: {
|
2
|
+
id:"color_context",
|
3
|
+
label: "Choose a Color",
|
4
|
+
name: "color_name_2",
|
5
|
+
options: [
|
6
|
+
{ value: "red", value_text: "Red" },
|
7
|
+
{ value: "blue", value_text: "Blue" },
|
8
|
+
{ value: "green", value_text: "Green" }
|
9
|
+
],
|
10
|
+
}) %>
|
11
|
+
|
12
|
+
<%= pb_rails("typeahead", props: {
|
13
|
+
label: "Pick a Shade",
|
14
|
+
search_context_selector: "color_context",
|
15
|
+
options_by_context: {
|
16
|
+
"red": [
|
17
|
+
{ label: "Scarlet", value: "scarlet" },
|
18
|
+
{ label: "Mahogany", value: "mahogany" },
|
19
|
+
{ label: "Crimson", value: "crimson" }
|
20
|
+
],
|
21
|
+
"blue": [
|
22
|
+
{ label: "Sky Blue", value: "sky" },
|
23
|
+
{ label: "Cerulean", value: "cerulean" },
|
24
|
+
{ label: "Navy", value: "navy" }
|
25
|
+
],
|
26
|
+
"green": [
|
27
|
+
{ label: "Emerald", value: "emerald" },
|
28
|
+
{ label: "Mint", value: "mint" },
|
29
|
+
{ label: "Olive", value: "olive" }
|
30
|
+
]
|
31
|
+
},
|
32
|
+
search_term_minimum_length: 0,
|
33
|
+
}) %>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
The dynamic rendering of options for the typeahead can also be achieved with a pure Rails implementation (not react rendered). For this implementation, use all the props as above with the following additions:
|
2
|
+
|
3
|
+
- `search_term_minimum_length`: this sets the minimum input in the typeahead needed to display the dropdown. This is set to 3 by default. Set it to 0 for the dropdown to always display when the typeahead is interacted with.
|
@@ -12,6 +12,8 @@ examples:
|
|
12
12
|
- typeahead_margin_bottom: Margin Bottom
|
13
13
|
- typeahead_with_pills_color: With Pills (Custom Color)
|
14
14
|
- typeahead_truncated_text: Truncated Text
|
15
|
+
- typeahead_dynamic_options: Dynamic Options
|
16
|
+
- typeahead_dynamic_options_pure_rails: Dynamic Options (Pure Rails)
|
15
17
|
|
16
18
|
react:
|
17
19
|
- typeahead_default: Default
|
@@ -20,6 +20,34 @@ export default class PbTypeahead extends PbEnhancedElement {
|
|
20
20
|
this.searchInput.addEventListener('focus', () => this.debouncedSearch())
|
21
21
|
this.searchInput.addEventListener('input', () => this.debouncedSearch())
|
22
22
|
this.resultsElement.addEventListener('click', (event: MouseEvent) => this.optionSelected(event))
|
23
|
+
|
24
|
+
if (this.clearOnContextChange && this.searchContextElement) {
|
25
|
+
this.searchContextElement.addEventListener('change', () => {
|
26
|
+
this.searchInputClear()
|
27
|
+
this.resultsCacheClear()
|
28
|
+
this.clearResults()
|
29
|
+
})
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
get optionsByContext() {
|
34
|
+
return (this.element as HTMLElement).dataset.pbTypeaheadKitOptionsByContext
|
35
|
+
? JSON.parse((this.element as HTMLElement).dataset.pbTypeaheadKitOptionsByContext)
|
36
|
+
: null
|
37
|
+
}
|
38
|
+
|
39
|
+
get searchContextElement() {
|
40
|
+
const selector = (this.element as HTMLElement).dataset.pbTypeaheadKitSearchContextSelector
|
41
|
+
if (!selector) return null
|
42
|
+
|
43
|
+
const found = this.element.parentNode?.querySelector(`#${selector}`)
|
44
|
+
|| this.element.closest(selector)
|
45
|
+
|
46
|
+
return found || null
|
47
|
+
}
|
48
|
+
|
49
|
+
get clearOnContextChange() {
|
50
|
+
return (this.element as HTMLElement).dataset.pbTypeaheadKitClearOnContextChange === 'true'
|
23
51
|
}
|
24
52
|
|
25
53
|
handleKeydown(event: KeyboardEvent) {
|
@@ -40,14 +68,32 @@ export default class PbTypeahead extends PbEnhancedElement {
|
|
40
68
|
|
41
69
|
const searchTerm = this.searchTerm
|
42
70
|
const searchContext = this.searchContext
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
71
|
+
|
72
|
+
if (this.optionsByContext && Object.keys(this.optionsByContext).length > 0) {
|
73
|
+
const contextArray = this.optionsByContext[searchContext] || []
|
74
|
+
|
75
|
+
const filteredResults = contextArray.filter((obj: { label: string, value: string }) => {
|
76
|
+
return obj.label
|
77
|
+
&& obj.label.toLowerCase().includes(searchTerm.toLowerCase())
|
78
|
+
})
|
79
|
+
|
80
|
+
const optionFragments = filteredResults.map((obj: { label: string, value: string }) => {
|
81
|
+
const singleOption = document.createDocumentFragment()
|
82
|
+
singleOption.appendChild(document.createTextNode(obj.label))
|
83
|
+
return singleOption
|
84
|
+
})
|
85
|
+
|
86
|
+
this.resultsCacheUpdate(searchTerm, searchContext, optionFragments)
|
87
|
+
} else {
|
88
|
+
const search = {
|
89
|
+
searchingFor: searchTerm,
|
90
|
+
searchingContext: searchContext,
|
91
|
+
setResults: (results: Array<DocumentFragment>) => {
|
92
|
+
this.resultsCacheUpdate(searchTerm, searchContext, results)
|
93
|
+
},
|
94
|
+
}
|
95
|
+
this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-search', { bubbles: true, detail: search }))
|
49
96
|
}
|
50
|
-
this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-search', { bubbles: true, detail: search }))
|
51
97
|
}
|
52
98
|
|
53
99
|
resultsCacheUpdate(searchTerm: string, searchContext: string, results: Array<DocumentFragment>) {
|
@@ -87,11 +133,15 @@ export default class PbTypeahead extends PbEnhancedElement {
|
|
87
133
|
const resultOption = (event.target as Element).closest('[data-result-option-item]')
|
88
134
|
if (!resultOption) return
|
89
135
|
|
136
|
+
const selectedText = resultOption.textContent.trim()
|
137
|
+
|
90
138
|
this._validSelection = true
|
91
139
|
this.removeValidationError()
|
92
140
|
|
141
|
+
if (this.searchContextElement) this.searchInput.value = selectedText
|
142
|
+
|
93
143
|
this.resultsCacheClear()
|
94
|
-
this.searchInputClear()
|
144
|
+
if (!this.searchContextElement) this.searchInputClear()
|
95
145
|
this.clearResults()
|
96
146
|
|
97
147
|
this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-result-option-selected', { bubbles: true, detail: { selected: resultOption, typeahead: this } }))
|
@@ -173,6 +223,9 @@ export default class PbTypeahead extends PbEnhancedElement {
|
|
173
223
|
this.element.parentNode.querySelector(selector) ||
|
174
224
|
this.element.closest(selector)
|
175
225
|
) as HTMLInputElement).value
|
226
|
+
else if (this.searchContextElement) {
|
227
|
+
return (this.searchContextElement as HTMLInputElement).value
|
228
|
+
}
|
176
229
|
|
177
230
|
return null
|
178
231
|
}
|
@@ -44,6 +44,14 @@ module Playbook
|
|
44
44
|
default: false
|
45
45
|
prop :validation, type: Playbook::Props::HashProp,
|
46
46
|
default: {}
|
47
|
+
prop :wrapped, type: Playbook::Props::Boolean,
|
48
|
+
default: false
|
49
|
+
prop :clear_on_context_change, type: Playbook::Props::Boolean,
|
50
|
+
default: true
|
51
|
+
prop :options_by_context, type: Playbook::Props::HashProp,
|
52
|
+
default: {}
|
53
|
+
prop :search_context_selector, type: Playbook::Props::String,
|
54
|
+
default: nil
|
47
55
|
|
48
56
|
def classname
|
49
57
|
default_margin_bottom = margin_bottom.present? ? "" : " mb_sm"
|
@@ -58,12 +66,15 @@ module Playbook
|
|
58
66
|
Hash(values[:data]).merge(
|
59
67
|
pb_typeahead_kit: true,
|
60
68
|
pb_typeahead_kit_search_term_minimum_length: search_term_minimum_length,
|
61
|
-
pb_typeahead_kit_search_debounce_timeout: search_debounce_timeout
|
69
|
+
pb_typeahead_kit_search_debounce_timeout: search_debounce_timeout,
|
70
|
+
pb_typeahead_kit_clear_on_context_change: clear_on_context_change,
|
71
|
+
pb_typeahead_kit_search_context_selector: search_context_selector,
|
72
|
+
pb_typeahead_kit_options_by_context: options_by_context.to_json
|
62
73
|
)
|
63
74
|
end
|
64
75
|
|
65
76
|
def is_react?
|
66
|
-
pills || !is_multi
|
77
|
+
pills || !is_multi || wrapped
|
67
78
|
end
|
68
79
|
|
69
80
|
def typeahead_react_options
|
@@ -85,6 +96,10 @@ module Playbook
|
|
85
96
|
placeholder: placeholder,
|
86
97
|
plusIcon: plus_icon,
|
87
98
|
truncate: truncate,
|
99
|
+
wrapped: wrapped,
|
100
|
+
searchContextSelector: search_context_selector,
|
101
|
+
optionsByContext: options_by_context,
|
102
|
+
clearOnContextChange: clear_on_context_change,
|
88
103
|
}
|
89
104
|
|
90
105
|
base_options[:getOptionLabel] = get_option_label if get_option_label.present?
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import { isEmpty, get, isString, uniqueId, omitBy } from './object';
|
2
|
+
|
3
|
+
describe('Lodash functions', () => {
|
4
|
+
describe('isEmpty', () => {
|
5
|
+
test('returns true for empty objects', () => {
|
6
|
+
expect(isEmpty({})).toBe(true);
|
7
|
+
});
|
8
|
+
test('returns true for empty arrays', () => {
|
9
|
+
expect(isEmpty([])).toBe(true);
|
10
|
+
});
|
11
|
+
test('returns false for non-empty objects', () => {
|
12
|
+
expect(isEmpty({ a: 1 })).toBe(false);
|
13
|
+
});
|
14
|
+
test('returns false for non-empty arrays', () => {
|
15
|
+
expect(isEmpty([1])).toBe(false);
|
16
|
+
});
|
17
|
+
test('returns true for null and undefined', () => {
|
18
|
+
expect(isEmpty(null)).toBe(true);
|
19
|
+
expect(isEmpty(undefined)).toBe(true);
|
20
|
+
});
|
21
|
+
test('returns false for non-object, non-array values', () => {
|
22
|
+
expect(isEmpty("hello")).toBe(false);
|
23
|
+
expect(isEmpty(123)).toBe(false);
|
24
|
+
});
|
25
|
+
});
|
26
|
+
|
27
|
+
describe('get', () => {
|
28
|
+
const obj = { a: { b: { c: 42 } }, arr: [{ x: 1 }, { y: 2 }] };
|
29
|
+
|
30
|
+
test('retrieves nested properties by dot path', () => {
|
31
|
+
expect(get(obj, 'a.b.c')).toBe(42);
|
32
|
+
});
|
33
|
+
test('returns default value if path does not exist', () => {
|
34
|
+
expect(get(obj, 'a.b.d', 'default')).toBe('default');
|
35
|
+
});
|
36
|
+
test('retrieves array values using bracket notation', () => {
|
37
|
+
expect(get(obj, 'arr[1].y')).toBe(2);
|
38
|
+
});
|
39
|
+
test('returns default for non-existent paths', () => {
|
40
|
+
expect(get(obj, 'non.existent.path', null)).toBe(null);
|
41
|
+
});
|
42
|
+
});
|
43
|
+
|
44
|
+
describe('isString', () => {
|
45
|
+
test('returns true for string literal', () => {
|
46
|
+
expect(isString('test')).toBe(true);
|
47
|
+
});
|
48
|
+
test('returns true for String object', () => {
|
49
|
+
expect(isString(new String('test'))).toBe(true);
|
50
|
+
});
|
51
|
+
test('returns false for non-string values', () => {
|
52
|
+
expect(isString(123)).toBe(false);
|
53
|
+
expect(isString(null)).toBe(false);
|
54
|
+
expect(isString({ a: 1 })).toBe(false);
|
55
|
+
expect(isString([ 'a','b','c' ])).toBe(false);
|
56
|
+
});
|
57
|
+
});
|
58
|
+
|
59
|
+
describe('uniqueId', () => {
|
60
|
+
test('generates unique ids without prefix', () => {
|
61
|
+
const id1 = uniqueId();
|
62
|
+
const id2 = uniqueId();
|
63
|
+
expect(id1).not.toBe(id2);
|
64
|
+
expect(Number(id1)).toBeLessThan(Number(id2));
|
65
|
+
});
|
66
|
+
test('generates unique ids with prefix', () => {
|
67
|
+
const id1 = uniqueId('id_');
|
68
|
+
const id2 = uniqueId('id_');
|
69
|
+
const id3 = uniqueId('id_');
|
70
|
+
expect(id1).not.toBe(id2);
|
71
|
+
expect(id1).not.toBe(id3);
|
72
|
+
expect(id2).not.toBe(id3);
|
73
|
+
expect(id1.startsWith('id_')).toBe(true);
|
74
|
+
expect(id2.startsWith('id_')).toBe(true);
|
75
|
+
expect(id3.startsWith('id_')).toBe(true);
|
76
|
+
});
|
77
|
+
});
|
78
|
+
|
79
|
+
describe('omitBy', () => {
|
80
|
+
test('omits keys for which predicate returns true', () => {
|
81
|
+
const obj = { a: 1, b: 2, c: 3 };
|
82
|
+
const isEven = value => value % 2 === 0;
|
83
|
+
const noEvenValues = omitBy(obj, isEven);
|
84
|
+
expect(noEvenValues).toEqual({ a: 1, c: 3 });
|
85
|
+
});
|
86
|
+
test('returns empty object for null input', () => {
|
87
|
+
expect(omitBy(null, () => true)).toEqual({});
|
88
|
+
});
|
89
|
+
test('returns empty object for non-object input', () => {
|
90
|
+
expect(omitBy("string", () => true)).toEqual({});
|
91
|
+
});
|
92
|
+
test('returns original object if predicate returns false for all keys', () => {
|
93
|
+
const obj = { a: 1, b: 2 };
|
94
|
+
const isBiggerThanFive = value => value >= 4;
|
95
|
+
const objWithSmallValues = omitBy(obj, isBiggerThanFive);
|
96
|
+
expect(objWithSmallValues).toEqual(obj);
|
97
|
+
});
|
98
|
+
});
|
99
|
+
});
|
@@ -1,3 +1,31 @@
|
|
1
1
|
/* 🛠️ Any commonly used lodash functions can be added here. 🤙 */
|
2
2
|
|
3
|
-
export const isEmpty = (obj:
|
3
|
+
export const isEmpty = (obj: any) => [Object, Array].includes((obj || {}).constructor) && !Object.entries((obj || {})).length;
|
4
|
+
|
5
|
+
export const get = <T, R = any>(obj: T, path: string, defaultValue?: R): R | any => {
|
6
|
+
const travel = (regexp: RegExp): any =>
|
7
|
+
String.prototype.split
|
8
|
+
.call(path, regexp)
|
9
|
+
.filter(Boolean)
|
10
|
+
.reduce((res: any, key: string) => (res !== null && res !== undefined ? res[key] : res), obj)
|
11
|
+
const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/)
|
12
|
+
return result === undefined || result === obj ? defaultValue : result
|
13
|
+
}
|
14
|
+
|
15
|
+
export const isString = (str: unknown): str is string =>
|
16
|
+
str != null && typeof (str as any).valueOf() === "string"
|
17
|
+
|
18
|
+
export const uniqueId: (prefix?: string) => string = (() => {
|
19
|
+
let counter = 0
|
20
|
+
return (prefix = '') => `${prefix}${++counter}`
|
21
|
+
})()
|
22
|
+
|
23
|
+
export const omitBy = (obj: Record<string, any>, predicate: (value: any, key: string) => boolean) => {
|
24
|
+
if (obj === null || typeof obj !== 'object') return {}
|
25
|
+
return Object.keys(obj).reduce((result: Record<string, any>, key: string) => {
|
26
|
+
if (!predicate(obj[key], key)) {
|
27
|
+
result[key] = obj[key];
|
28
|
+
}
|
29
|
+
return result;
|
30
|
+
}, {})
|
31
|
+
}
|