Merge pull request #2372 from mslourens/autocomplete_component

Autocomplete component
This commit is contained in:
Andrew Kingston 2021-08-18 10:01:47 +01:00 committed by GitHub
commit 7c2b5b6f04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 3 deletions

View File

@ -11,6 +11,7 @@
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
export let readonly = false export let readonly = false
export let autocomplete = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: selectedLookupMap = getSelectedLookupMap(value) $: selectedLookupMap = getSelectedLookupMap(value)
@ -77,6 +78,7 @@
{fieldText} {fieldText}
{options} {options}
isPlaceholder={!value?.length} isPlaceholder={!value?.length}
{autocomplete}
{isOptionSelected} {isOptionSelected}
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}

View File

@ -5,6 +5,7 @@
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import clickOutside from "../../Actions/click_outside" import clickOutside from "../../Actions/click_outside"
import Search from "./Search.svelte"
export let id = null export let id = null
export let disabled = false export let disabled = false
@ -23,16 +24,24 @@
export let readonly = false export let readonly = false
export let quiet = false export let quiet = false
export let autoWidth = false export let autoWidth = false
export let autocomplete = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let searchTerm = null
$: sortedOptions = getSortedOptions(options, getOptionLabel) $: sortedOptions = getSortedOptions(options, getOptionLabel)
$: filteredOptions = getFilteredOptions(
sortedOptions,
searchTerm,
getOptionLabel
)
const onClick = () => { const onClick = () => {
dispatch("click") dispatch("click")
if (readonly) { if (readonly) {
return return
} }
searchTerm = null
open = true open = true
} }
@ -46,6 +55,16 @@
return labelA > labelB ? 1 : -1 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> </script>
<button <button
@ -96,6 +115,14 @@
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open" class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
class:auto-width={autoWidth} class:auto-width={autoWidth}
> >
{#if autocomplete}
<Search
value={searchTerm}
on:change={event => (searchTerm = event.detail)}
{disabled}
placeholder="Search"
/>
{/if}
<ul class="spectrum-Menu" role="listbox"> <ul class="spectrum-Menu" role="listbox">
{#if placeholderOption} {#if placeholderOption}
<li <li
@ -116,8 +143,8 @@
</svg> </svg>
</li> </li>
{/if} {/if}
{#if sortedOptions.length} {#if filteredOptions.length}
{#each sortedOptions as option, idx} {#each filteredOptions as option, idx}
<li <li
class="spectrum-Menu-item" class="spectrum-Menu-item"
class:is-selected={isOptionSelected(getOptionValue(option, idx))} class:is-selected={isOptionSelected(getOptionValue(option, idx))}
@ -188,4 +215,23 @@
padding-top: 5px; padding-top: 5px;
padding-right: 10px; padding-right: 10px;
} }
.spectrum-Popover :global(.spectrum-Search) {
margin-top: -1px;
margin-left: -1px;
width: calc(100% + 2px);
}
.spectrum-Popover :global(.spectrum-Search input) {
height: auto;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding-top: var(--spectrum-global-dimension-size-100);
padding-bottom: var(--spectrum-global-dimension-size-100);
}
.spectrum-Popover :global(.spectrum-Search .spectrum-ClearButton) {
right: 1px;
top: 2px;
}
.spectrum-Popover :global(.spectrum-Search .spectrum-Textfield-icon) {
top: 9px;
}
</style> </style>

View File

@ -95,4 +95,7 @@
.is-disabled { .is-disabled {
pointer-events: none; pointer-events: none;
} }
.spectrum-Search-clearButton {
position: absolute;
}
</style> </style>

View File

@ -14,6 +14,7 @@
export let readonly = false export let readonly = false
export let quiet = false export let quiet = false
export let autoWidth = false export let autoWidth = false
export let autocomplete = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let open = false let open = false
@ -70,6 +71,7 @@
{getOptionValue} {getOptionValue}
{getOptionIcon} {getOptionIcon}
{fieldIcon} {fieldIcon}
{autocomplete}
isPlaceholder={value == null || value === ""} isPlaceholder={value == null || value === ""}
placeholderOption={placeholder} placeholderOption={placeholder}
isOptionSelected={option => option === value} isOptionSelected={option => option === value}

View File

@ -1926,6 +1926,16 @@
"label": "Default value", "label": "Default value",
"key": "defaultValue" "key": "defaultValue"
}, },
{
"type": "boolean",
"label": "Autocomplete",
"key": "autocomplete",
"defaultValue": false,
"dependsOn": {
"setting": "optionsType",
"value": "select"
}
},
{ {
"type": "boolean", "type": "boolean",
"label": "Disabled", "label": "Disabled",
@ -2192,6 +2202,12 @@
"label": "Placeholder", "label": "Placeholder",
"key": "placeholder" "key": "placeholder"
}, },
{
"type": "boolean",
"label": "Autocomplete",
"key": "autocomplete",
"defaultValue": false
},
{ {
"type": "boolean", "type": "boolean",
"label": "Disabled", "label": "Disabled",

View File

@ -14,6 +14,7 @@
export let labelColumn export let labelColumn
export let valueColumn export let valueColumn
export let customOptions export let customOptions
export let autocomplete = false
let fieldState let fieldState
let fieldApi let fieldApi
@ -85,6 +86,7 @@
on:change={e => fieldApi.setValue(e.detail)} on:change={e => fieldApi.setValue(e.detail)}
getOptionLabel={flatOptions ? x => x : x => x.label} getOptionLabel={flatOptions ? x => x : x => x.label}
getOptionValue={flatOptions ? x => x : x => x.value} getOptionValue={flatOptions ? x => x : x => x.value}
{autocomplete}
/> />
{:else if optionsType === "radio"} {:else if optionsType === "radio"}
<CoreRadioGroup <CoreRadioGroup

View File

@ -10,6 +10,7 @@
export let placeholder export let placeholder
export let disabled = false export let disabled = false
export let validation export let validation
export let autocomplete = false
let fieldState let fieldState
let fieldApi let fieldApi
@ -24,6 +25,7 @@
$: fetchTable(linkedTableId) $: fetchTable(linkedTableId)
$: singleValue = flatten($fieldState?.value)?.[0] $: singleValue = flatten($fieldState?.value)?.[0]
$: multiValue = flatten($fieldState?.value) ?? [] $: multiValue = flatten($fieldState?.value) ?? []
$: component = multiselect ? CoreMultiselect : CoreSelect
const fetchTable = async id => { const fetchTable = async id => {
if (id) { if (id) {
@ -74,8 +76,9 @@
> >
{#if fieldState} {#if fieldState}
<svelte:component <svelte:component
this={multiselect ? CoreMultiselect : CoreSelect} this={component}
{options} {options}
{autocomplete}
value={multiselect ? multiValue : singleValue} value={multiselect ? multiValue : singleValue}
on:change={multiselect ? multiHandler : singleHandler} on:change={multiselect ? multiHandler : singleHandler}
id={$fieldState.fieldId} id={$fieldState.fieldId}