add autocomplete component and make it optional for optionsField and relationshipField
This commit is contained in:
parent
dd7edd6f6b
commit
e8078662f8
|
@ -0,0 +1,80 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import Picker from "./Picker.svelte"
|
||||
|
||||
export let value = null
|
||||
export let id = null
|
||||
export let selectPlaceholder = "Choose an option"
|
||||
export let autocompletePlaceholder = "Search an option"
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let options = []
|
||||
export let getOptionLabel = option => option
|
||||
export let getOptionValue = option => option
|
||||
export let getOptionIcon = () => null
|
||||
export let readonly = false
|
||||
export let quiet = false
|
||||
export let autoWidth = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let open = false
|
||||
$: fieldText = getFieldText(value, options, selectPlaceholder)
|
||||
$: fieldIcon = getFieldIcon(value, options, selectPlaceholder)
|
||||
|
||||
const getFieldText = (value, options, placeholder) => {
|
||||
// Always use placeholder if no value
|
||||
if (value == null || value === "") {
|
||||
return placeholder || "Choose an option"
|
||||
}
|
||||
|
||||
// Wait for options to load if there is a value but no options
|
||||
if (!options?.length) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Render the label if the selected option is found, otherwise raw value
|
||||
const index = options.findIndex(
|
||||
(option, idx) => getOptionValue(option, idx) === value
|
||||
)
|
||||
return index !== -1 ? getOptionLabel(options[index], index) : value
|
||||
}
|
||||
|
||||
const getFieldIcon = (value, options) => {
|
||||
// Wait for options to load if there is a value but no options
|
||||
if (!options?.length) {
|
||||
return ""
|
||||
}
|
||||
const index = options.findIndex(
|
||||
(option, idx) => getOptionValue(option, idx) === value
|
||||
)
|
||||
return index !== -1 ? getOptionIcon(options[index], index) : null
|
||||
}
|
||||
|
||||
const selectOption = value => {
|
||||
dispatch("change", value)
|
||||
open = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<Picker
|
||||
on:click
|
||||
bind:open
|
||||
{quiet}
|
||||
{id}
|
||||
{error}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{fieldText}
|
||||
{options}
|
||||
{autoWidth}
|
||||
{getOptionLabel}
|
||||
{getOptionValue}
|
||||
{getOptionIcon}
|
||||
{fieldIcon}
|
||||
autocomplete={true}
|
||||
isPlaceholder={value == null || value === ""}
|
||||
{autocompletePlaceholder}
|
||||
placeholderOption={selectPlaceholder}
|
||||
isOptionSelected={option => option === value}
|
||||
onSelectOption={selectOption}
|
||||
/>
|
|
@ -5,6 +5,7 @@
|
|||
import { fly } from "svelte/transition"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import clickOutside from "../../Actions/click_outside"
|
||||
import Search from "./Search.svelte"
|
||||
|
||||
export let id = null
|
||||
export let disabled = false
|
||||
|
@ -13,6 +14,7 @@
|
|||
export let fieldIcon = ""
|
||||
export let isPlaceholder = false
|
||||
export let placeholderOption = null
|
||||
export let autocompletePlaceholder = ""
|
||||
export let options = []
|
||||
export let isOptionSelected = () => false
|
||||
export let onSelectOption = () => {}
|
||||
|
@ -23,10 +25,17 @@
|
|||
export let readonly = false
|
||||
export let quiet = false
|
||||
export let autoWidth = false
|
||||
export let autocomplete = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let searchTerm = null
|
||||
|
||||
$: sortedOptions = getSortedOptions(options, getOptionLabel)
|
||||
$: filteredOptions = getFilteredOptions(
|
||||
sortedOptions,
|
||||
searchTerm,
|
||||
getOptionLabel
|
||||
)
|
||||
|
||||
const onClick = () => {
|
||||
dispatch("click")
|
||||
|
@ -46,6 +55,16 @@
|
|||
return labelA > labelB ? 1 : -1
|
||||
})
|
||||
}
|
||||
|
||||
const getFilteredOptions = (options, term, getLabel) => {
|
||||
if (autocomplete && term) {
|
||||
const lowerCaseTerm = term.toLowerCase()
|
||||
return options.filter(option =>
|
||||
getLabel(option).toLowerCase().includes(lowerCaseTerm)
|
||||
)
|
||||
}
|
||||
return options
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
|
@ -96,6 +115,14 @@
|
|||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||
class:auto-width={autoWidth}
|
||||
>
|
||||
{#if autocomplete}
|
||||
<Search
|
||||
on:change={event => (searchTerm = event.detail)}
|
||||
updateOnChange="true"
|
||||
{disabled}
|
||||
placeholder={autocompletePlaceholder}
|
||||
/>
|
||||
{/if}
|
||||
<ul class="spectrum-Menu" role="listbox">
|
||||
{#if placeholderOption}
|
||||
<li
|
||||
|
@ -116,8 +143,8 @@
|
|||
</svg>
|
||||
</li>
|
||||
{/if}
|
||||
{#if sortedOptions.length}
|
||||
{#each sortedOptions as option, idx}
|
||||
{#if filteredOptions.length}
|
||||
{#each filteredOptions as option, idx}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={isOptionSelected(getOptionValue(option, idx))}
|
||||
|
|
|
@ -95,4 +95,7 @@
|
|||
.is-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
.spectrum-Search-clearButton {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,3 +9,4 @@ export { default as CoreSwitch } from "./Switch.svelte"
|
|||
export { default as CoreSearch } from "./Search.svelte"
|
||||
export { default as CoreDatePicker } from "./DatePicker.svelte"
|
||||
export { default as CoreDropzone } from "./Dropzone.svelte"
|
||||
export { default as CoreAutocomplete } from "./Autocomplete.svelte"
|
||||
|
|
|
@ -1914,6 +1914,10 @@
|
|||
"label": "Select",
|
||||
"value": "select"
|
||||
},
|
||||
{
|
||||
"label": "Autocomplete",
|
||||
"value": "autocomplete"
|
||||
},
|
||||
{
|
||||
"label": "Radio buttons",
|
||||
"value": "radio"
|
||||
|
@ -2135,6 +2139,12 @@
|
|||
"label": "Placeholder",
|
||||
"key": "placeholder"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Autocomplete",
|
||||
"key": "autocomplete",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Disabled",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { CoreSelect, RadioGroup } from "@budibase/bbui"
|
||||
import { CoreAutocomplete, CoreSelect, RadioGroup } from "@budibase/bbui"
|
||||
import Field from "./Field.svelte"
|
||||
|
||||
export let field
|
||||
|
@ -37,6 +37,16 @@
|
|||
{placeholder}
|
||||
on:change={e => fieldApi.setValue(e.detail)}
|
||||
/>
|
||||
{:else if optionsType === "autocomplete"}
|
||||
<CoreAutocomplete
|
||||
value={$fieldState.value}
|
||||
id={$fieldState.fieldId}
|
||||
disabled={$fieldState.disabled}
|
||||
error={$fieldState.error}
|
||||
options={fieldSchema?.constraints?.inclusion ?? []}
|
||||
{placeholder}
|
||||
on:change={e => fieldApi.setValue(e.detail)}
|
||||
/>
|
||||
{:else if optionsType === "radio"}
|
||||
<RadioGroup
|
||||
value={$fieldState.value}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { CoreSelect, CoreMultiselect } from "@budibase/bbui"
|
||||
import { CoreAutocomplete, CoreSelect, CoreMultiselect } from "@budibase/bbui"
|
||||
import { getContext } from "svelte"
|
||||
import Field from "./Field.svelte"
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
|||
export let placeholder
|
||||
export let disabled = false
|
||||
export let validation
|
||||
export let autocomplete = false
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -24,6 +25,11 @@
|
|||
$: fetchTable(linkedTableId)
|
||||
$: singleValue = flatten($fieldState?.value)?.[0]
|
||||
$: multiValue = flatten($fieldState?.value) ?? []
|
||||
$: component = multiselect
|
||||
? CoreMultiselect
|
||||
: autocomplete
|
||||
? CoreAutocomplete
|
||||
: CoreSelect
|
||||
|
||||
const fetchTable = async id => {
|
||||
if (id) {
|
||||
|
@ -74,7 +80,7 @@
|
|||
>
|
||||
{#if fieldState}
|
||||
<svelte:component
|
||||
this={multiselect ? CoreMultiselect : CoreSelect}
|
||||
this={component}
|
||||
{options}
|
||||
value={multiselect ? multiValue : singleValue}
|
||||
on:change={multiselect ? multiHandler : singleHandler}
|
||||
|
|
Loading…
Reference in New Issue