Merge branch 'develop' into budi-6922-the-number-0-cannot-be-displayed-on-input-number-fields-when
This commit is contained in:
commit
7056ad7c44
|
@ -0,0 +1,21 @@
|
||||||
|
name: close-featurebranch
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: passeidireto/trigger-external-workflow-action@main
|
||||||
|
env:
|
||||||
|
PAYLOAD_BRANCH: ${{ github.head_ref }}
|
||||||
|
PAYLOAD_PR_NUMBER: ${{ github.ref }}
|
||||||
|
with:
|
||||||
|
repository: budibase/budibase-deploys
|
||||||
|
event: featurebranch-qa-close
|
||||||
|
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.10.12-alpha.13",
|
"version": "2.10.12-alpha.16",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
export let autocomplete = false
|
export let autocomplete = false
|
||||||
export let sort = false
|
export let sort = false
|
||||||
export let autoWidth = false
|
export let autoWidth = false
|
||||||
export let fetchTerm = null
|
export let searchTerm = null
|
||||||
export let useFetch = false
|
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
export let customPopoverOffsetBelow
|
export let customPopoverOffsetBelow
|
||||||
export let customPopoverMaxHeight
|
export let customPopoverMaxHeight
|
||||||
export let open = false
|
export let open = false
|
||||||
|
export let loading
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Picker
|
<Picker
|
||||||
|
on:loadMore
|
||||||
{id}
|
{id}
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -90,9 +91,8 @@
|
||||||
{options}
|
{options}
|
||||||
isPlaceholder={!arrayValue.length}
|
isPlaceholder={!arrayValue.length}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
bind:fetchTerm
|
bind:searchTerm
|
||||||
bind:open
|
bind:open
|
||||||
{useFetch}
|
|
||||||
{isOptionSelected}
|
{isOptionSelected}
|
||||||
{getOptionLabel}
|
{getOptionLabel}
|
||||||
{getOptionValue}
|
{getOptionValue}
|
||||||
|
@ -102,4 +102,5 @@
|
||||||
{customPopoverHeight}
|
{customPopoverHeight}
|
||||||
{customPopoverOffsetBelow}
|
{customPopoverOffsetBelow}
|
||||||
{customPopoverMaxHeight}
|
{customPopoverMaxHeight}
|
||||||
|
{loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import "@spectrum-css/picker/dist/index-vars.css"
|
import "@spectrum-css/picker/dist/index-vars.css"
|
||||||
import "@spectrum-css/popover/dist/index-vars.css"
|
import "@spectrum-css/popover/dist/index-vars.css"
|
||||||
import "@spectrum-css/menu/dist/index-vars.css"
|
import "@spectrum-css/menu/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
import clickOutside from "../../Actions/click_outside"
|
import clickOutside from "../../Actions/click_outside"
|
||||||
import Search from "./Search.svelte"
|
import Search from "./Search.svelte"
|
||||||
import Icon from "../../Icon/Icon.svelte"
|
import Icon from "../../Icon/Icon.svelte"
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
import Popover from "../../Popover/Popover.svelte"
|
import Popover from "../../Popover/Popover.svelte"
|
||||||
import Tags from "../../Tags/Tags.svelte"
|
import Tags from "../../Tags/Tags.svelte"
|
||||||
import Tag from "../../Tags/Tag.svelte"
|
import Tag from "../../Tags/Tag.svelte"
|
||||||
|
import ProgressCircle from "../../ProgressCircle/ProgressCircle.svelte"
|
||||||
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
@ -35,19 +36,20 @@
|
||||||
export let autoWidth = false
|
export let autoWidth = false
|
||||||
export let autocomplete = false
|
export let autocomplete = false
|
||||||
export let sort = false
|
export let sort = false
|
||||||
export let fetchTerm = null
|
export let searchTerm = null
|
||||||
export let useFetch = false
|
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
export let customPopoverOffsetBelow
|
export let customPopoverOffsetBelow
|
||||||
export let customPopoverMaxHeight
|
export let customPopoverMaxHeight
|
||||||
export let align = "left"
|
export let align = "left"
|
||||||
export let footer = null
|
export let footer = null
|
||||||
export let customAnchor = null
|
export let customAnchor = null
|
||||||
|
export let loading
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let searchTerm = null
|
|
||||||
let button
|
let button
|
||||||
let popover
|
let popover
|
||||||
|
let component
|
||||||
|
|
||||||
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
||||||
$: filteredOptions = getFilteredOptions(
|
$: filteredOptions = getFilteredOptions(
|
||||||
|
@ -82,7 +84,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFilteredOptions = (options, term, getLabel) => {
|
const getFilteredOptions = (options, term, getLabel) => {
|
||||||
if (autocomplete && term && !fetchTerm) {
|
if (autocomplete && term) {
|
||||||
const lowerCaseTerm = term.toLowerCase()
|
const lowerCaseTerm = term.toLowerCase()
|
||||||
return options.filter(option => {
|
return options.filter(option => {
|
||||||
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
|
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
|
||||||
|
@ -90,6 +92,20 @@
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onScroll = e => {
|
||||||
|
const scrollPxThreshold = 100
|
||||||
|
const scrollPositionFromBottom =
|
||||||
|
e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop
|
||||||
|
if (scrollPositionFromBottom < scrollPxThreshold) {
|
||||||
|
dispatch("loadMore")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: component?.addEventListener("scroll", onScroll)
|
||||||
|
onDestroy(() => {
|
||||||
|
component?.removeEventListener("scroll", null)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -163,14 +179,13 @@
|
||||||
>
|
>
|
||||||
{#if autocomplete}
|
{#if autocomplete}
|
||||||
<Search
|
<Search
|
||||||
value={useFetch ? fetchTerm : searchTerm}
|
value={searchTerm}
|
||||||
on:change={event =>
|
on:change={event => (searchTerm = event.detail)}
|
||||||
useFetch ? (fetchTerm = event.detail) : (searchTerm = event.detail)}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<ul class="spectrum-Menu" role="listbox">
|
<ul class="spectrum-Menu" role="listbox" bind:this={component}>
|
||||||
{#if placeholderOption}
|
{#if placeholderOption}
|
||||||
<li
|
<li
|
||||||
class="spectrum-Menu-item placeholder"
|
class="spectrum-Menu-item placeholder"
|
||||||
|
@ -248,6 +263,12 @@
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="loading" class:loading--withAutocomplete={autocomplete}>
|
||||||
|
<ProgressCircle size="S" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if footer}
|
{#if footer}
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
{footer}
|
{footer}
|
||||||
|
@ -323,18 +344,19 @@
|
||||||
/* Search styles inside popover */
|
/* Search styles inside popover */
|
||||||
.popover-content :global(.spectrum-Search) {
|
.popover-content :global(.spectrum-Search) {
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
margin-left: -1px;
|
width: 100%;
|
||||||
width: calc(100% + 2px);
|
|
||||||
}
|
}
|
||||||
.popover-content :global(.spectrum-Search input) {
|
.popover-content :global(.spectrum-Search input) {
|
||||||
height: auto;
|
height: auto;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
padding-top: var(--spectrum-global-dimension-size-100);
|
padding-top: var(--spectrum-global-dimension-size-100);
|
||||||
padding-bottom: var(--spectrum-global-dimension-size-100);
|
padding-bottom: var(--spectrum-global-dimension-size-100);
|
||||||
}
|
}
|
||||||
.popover-content :global(.spectrum-Search .spectrum-ClearButton) {
|
.popover-content :global(.spectrum-Search .spectrum-ClearButton) {
|
||||||
right: 1px;
|
right: 2px;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
.popover-content :global(.spectrum-Search .spectrum-Textfield-icon) {
|
.popover-content :global(.spectrum-Search .spectrum-Textfield-icon) {
|
||||||
|
@ -359,4 +381,14 @@
|
||||||
.option-tag :global(.spectrum-Tags-item > .spectrum-Icon) {
|
.option-tag :global(.spectrum-Tags-item > .spectrum-Icon) {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
position: fixed;
|
||||||
|
justify-content: center;
|
||||||
|
right: var(--spacing-s);
|
||||||
|
top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.loading--withAutocomplete {
|
||||||
|
top: calc(34px + var(--spacing-m));
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
export let tag = null
|
export let tag = null
|
||||||
export let customPopoverOffsetBelow
|
export let customPopoverOffsetBelow
|
||||||
export let customPopoverMaxHeight
|
export let customPopoverMaxHeight
|
||||||
|
export let searchTerm = null
|
||||||
|
export let loading
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -65,6 +67,8 @@
|
||||||
<Picker
|
<Picker
|
||||||
on:click
|
on:click
|
||||||
bind:open
|
bind:open
|
||||||
|
bind:searchTerm
|
||||||
|
on:loadMore
|
||||||
{quiet}
|
{quiet}
|
||||||
{id}
|
{id}
|
||||||
{error}
|
{error}
|
||||||
|
@ -92,4 +96,5 @@
|
||||||
placeholderOption={placeholder === false ? null : placeholder}
|
placeholderOption={placeholder === false ? null : placeholder}
|
||||||
isOptionSelected={option => option === value}
|
isOptionSelected={option => option === value}
|
||||||
onSelectOption={selectOption}
|
onSelectOption={selectOption}
|
||||||
|
{loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
export let sort = false
|
export let sort = false
|
||||||
export let autoWidth = false
|
export let autoWidth = false
|
||||||
export let autocomplete = false
|
export let autocomplete = false
|
||||||
export let fetchTerm = null
|
export let searchTerm = null
|
||||||
export let useFetch = false
|
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -41,8 +40,7 @@
|
||||||
{autoWidth}
|
{autoWidth}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{customPopoverHeight}
|
{customPopoverHeight}
|
||||||
bind:fetchTerm
|
bind:searchTerm
|
||||||
{useFetch}
|
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:click
|
on:click
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -500,7 +500,7 @@
|
||||||
<DatePicker bind:value={editableColumn.constraints.datetime.latest} />
|
<DatePicker bind:value={editableColumn.constraints.datetime.latest} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
|
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER" && !editableColumn.dateOnly}
|
||||||
<div>
|
<div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Label>Time zones</Label>
|
<Label>Time zones</Label>
|
||||||
|
@ -520,6 +520,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<Toggle bind:value={editableColumn.dateOnly} text="Date only" />
|
||||||
{:else if editableColumn.type === "number" && !editableColumn.autocolumn}
|
{:else if editableColumn.type === "number" && !editableColumn.autocolumn}
|
||||||
<div class="split-label">
|
<div class="split-label">
|
||||||
<div class="label-length">
|
<div class="label-length">
|
||||||
|
|
|
@ -86,8 +86,16 @@
|
||||||
$: userPage = $userPageInfo.page
|
$: userPage = $userPageInfo.page
|
||||||
$: logsPage = $logsPageInfo.page
|
$: logsPage = $logsPageInfo.page
|
||||||
|
|
||||||
|
let usersObj = {}
|
||||||
|
$: usersObj = {
|
||||||
|
...usersObj,
|
||||||
|
...$users.data?.reduce((accumulator, user) => {
|
||||||
|
accumulator[user._id] = user
|
||||||
|
return accumulator
|
||||||
|
}, {}),
|
||||||
|
}
|
||||||
$: sortedUsers = sort(
|
$: sortedUsers = sort(
|
||||||
enrich($users.data || [], selectedUsers, "_id"),
|
enrich(Object.values(usersObj), selectedUsers, "_id"),
|
||||||
"email"
|
"email"
|
||||||
)
|
)
|
||||||
$: sortedEvents = sort(
|
$: sortedEvents = sort(
|
||||||
|
@ -256,8 +264,7 @@
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<Multiselect
|
<Multiselect
|
||||||
bind:fetchTerm={userSearchTerm}
|
bind:searchTerm={userSearchTerm}
|
||||||
useFetch
|
|
||||||
placeholder="All users"
|
placeholder="All users"
|
||||||
label="Users"
|
label="Users"
|
||||||
autocomplete
|
autocomplete
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { CoreSelect, CoreMultiselect } from "@budibase/bbui"
|
||||||
CoreSelect,
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
CoreMultiselect,
|
|
||||||
Input,
|
|
||||||
ProgressCircle,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { fetchData, Utils } from "@budibase/frontend-core"
|
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
|
@ -26,16 +21,8 @@
|
||||||
let fieldApi
|
let fieldApi
|
||||||
let fieldSchema
|
let fieldSchema
|
||||||
let tableDefinition
|
let tableDefinition
|
||||||
let primaryDisplay
|
let searchTerm
|
||||||
let options
|
let open
|
||||||
let selectedOptions = []
|
|
||||||
let isOpen = false
|
|
||||||
let hasFilter
|
|
||||||
|
|
||||||
let searchResults
|
|
||||||
let searchString
|
|
||||||
let searching = false
|
|
||||||
let lastSearchId
|
|
||||||
|
|
||||||
$: multiselect = fieldSchema?.relationshipType !== "one-to-many"
|
$: multiselect = fieldSchema?.relationshipType !== "one-to-many"
|
||||||
$: linkedTableId = fieldSchema?.tableId
|
$: linkedTableId = fieldSchema?.tableId
|
||||||
|
@ -50,54 +37,74 @@
|
||||||
limit: 100,
|
limit: 100,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
$: hasFilter = !!filter?.filter(f => !!f.field)?.length
|
|
||||||
$: fetch.update({ filter })
|
|
||||||
$: {
|
|
||||||
options = searchResults ? searchResults : $fetch.rows
|
|
||||||
const nonMatchingOptions = selectedOptions.filter(
|
|
||||||
option => !options.map(opt => opt._id).includes(option._id)
|
|
||||||
)
|
|
||||||
// Append initially selected options if there is no filter
|
|
||||||
// and hasn't already been appended
|
|
||||||
if (!hasFilter) {
|
|
||||||
options = [...options, ...nonMatchingOptions]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$: tableDefinition = $fetch.definition
|
$: tableDefinition = $fetch.definition
|
||||||
$: primaryDisplay = tableDefinition?.primaryDisplay || "_id"
|
$: selectedValue = multiselect
|
||||||
$: singleValue = flatten(fieldState?.value)?.[0]
|
? flatten(fieldState?.value) ?? []
|
||||||
$: multiValue = flatten(fieldState?.value) ?? []
|
: flatten(fieldState?.value)?.[0]
|
||||||
$: component = multiselect ? CoreMultiselect : CoreSelect
|
$: component = multiselect ? CoreMultiselect : CoreSelect
|
||||||
$: expandedDefaultValue = expand(defaultValue)
|
$: expandedDefaultValue = expand(defaultValue)
|
||||||
$: debouncedSearch(searchString)
|
$: primaryDisplay = tableDefinition?.primaryDisplay
|
||||||
|
|
||||||
|
let optionsObj = {}
|
||||||
|
let initialValuesProcessed
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (searching) {
|
if (!initialValuesProcessed && primaryDisplay) {
|
||||||
isOpen = true
|
// Persist the initial values as options, allowing them to be present in the dropdown,
|
||||||
|
// even if they are not in the inital fetch results
|
||||||
|
initialValuesProcessed = true
|
||||||
|
optionsObj = fieldState?.value?.reduce((accumulator, value) => {
|
||||||
|
accumulator[value._id] = {
|
||||||
|
_id: value._id,
|
||||||
|
[primaryDisplay]: value.primaryDisplay,
|
||||||
|
}
|
||||||
|
return accumulator
|
||||||
|
}, optionsObj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the initially selected values
|
$: enrichedOptions = enrichOptions(optionsObj, $fetch.rows)
|
||||||
// as they may not be within the first 100 records
|
const enrichOptions = (optionsObj, fetchResults) => {
|
||||||
|
const result = (fetchResults || [])?.reduce((accumulator, row) => {
|
||||||
|
if (!accumulator[row._id]) {
|
||||||
|
accumulator[row._id] = row
|
||||||
|
}
|
||||||
|
return accumulator
|
||||||
|
}, optionsObj)
|
||||||
|
|
||||||
|
return Object.values(result)
|
||||||
|
}
|
||||||
$: {
|
$: {
|
||||||
if (
|
// We don't want to reorder while the dropdown is open, to avoid UX jumps
|
||||||
primaryDisplay !== "_id" &&
|
if (!open) {
|
||||||
fieldState?.value?.length &&
|
enrichedOptions = enrichedOptions.sort((a, b) => {
|
||||||
!selectedOptions?.length
|
const selectedValues = flatten(fieldState?.value) || []
|
||||||
) {
|
|
||||||
API.searchTable({
|
const aIsSelected = selectedValues.find(v => v === a._id)
|
||||||
paginate: false,
|
const bIsSelected = selectedValues.find(v => v === b._id)
|
||||||
tableId: linkedTableId,
|
if (aIsSelected && !bIsSelected) {
|
||||||
limit: 100,
|
return -1
|
||||||
query: {
|
} else if (!aIsSelected && bIsSelected) {
|
||||||
oneOf: {
|
return 1
|
||||||
[`1:${primaryDisplay}`]: fieldState?.value?.map(
|
}
|
||||||
value => value.primaryDisplay
|
|
||||||
),
|
return a[primaryDisplay] > b[primaryDisplay]
|
||||||
},
|
})
|
||||||
},
|
}
|
||||||
}).then(response => {
|
}
|
||||||
const value = multiselect ? multiValue : singleValue
|
|
||||||
selectedOptions = response.rows.filter(row => value.includes(row._id))
|
$: fetchRows(searchTerm, primaryDisplay)
|
||||||
|
|
||||||
|
const fetchRows = (searchTerm, primaryDisplay) => {
|
||||||
|
const allRowsFetched =
|
||||||
|
$fetch.loaded &&
|
||||||
|
!Object.keys($fetch.query?.string || {}).length &&
|
||||||
|
!$fetch.hasNextPage
|
||||||
|
// Don't request until we have the primary display
|
||||||
|
if (!allRowsFetched && primaryDisplay) {
|
||||||
|
fetch.update({
|
||||||
|
query: { string: { [primaryDisplay]: searchTerm } },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +120,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDisplayName = row => {
|
const getDisplayName = row => {
|
||||||
return row?.[tableDefinition?.primaryDisplay || "_id"] || "-"
|
return row?.[primaryDisplay] || "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
const singleHandler = e => {
|
const singleHandler = e => {
|
||||||
|
@ -136,66 +143,16 @@
|
||||||
|
|
||||||
const handleChange = value => {
|
const handleChange = value => {
|
||||||
const changed = fieldApi.setValue(value)
|
const changed = fieldApi.setValue(value)
|
||||||
selectedOptions = value.map(val => ({
|
|
||||||
_id: val,
|
|
||||||
[primaryDisplay]: options.find(option => option._id === val)[
|
|
||||||
primaryDisplay
|
|
||||||
],
|
|
||||||
}))
|
|
||||||
if (onChange && changed) {
|
if (onChange && changed) {
|
||||||
onChange({ value })
|
onChange({ value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for rows based on the search string
|
const loadMore = () => {
|
||||||
const search = async searchString => {
|
if (!$fetch.loading) {
|
||||||
// Reset state if this search is invalid
|
fetch.nextPage()
|
||||||
if (!linkedTableId || !searchString) {
|
|
||||||
searchResults = null
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a filter exists, then do a client side search
|
|
||||||
if (hasFilter) {
|
|
||||||
searchResults = $fetch.rows.filter(option =>
|
|
||||||
option[primaryDisplay].startsWith(searchString)
|
|
||||||
)
|
|
||||||
isOpen = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for results, using IDs to track invocations and ensure we're
|
|
||||||
// handling the latest update
|
|
||||||
lastSearchId = Math.random()
|
|
||||||
searching = true
|
|
||||||
const thisSearchId = lastSearchId
|
|
||||||
const results = await API.searchTable({
|
|
||||||
paginate: false,
|
|
||||||
tableId: linkedTableId,
|
|
||||||
limit: 100,
|
|
||||||
query: {
|
|
||||||
string: {
|
|
||||||
[`1:${primaryDisplay}`]: searchString || "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
searching = false
|
|
||||||
|
|
||||||
// In case searching takes longer than our debounced update, abandon these
|
|
||||||
// results
|
|
||||||
if (thisSearchId !== lastSearchId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process results
|
|
||||||
searchResults = results.rows?.map(row => ({
|
|
||||||
...row,
|
|
||||||
primaryDisplay: row[primaryDisplay],
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounced version of searching
|
|
||||||
const debouncedSearch = Utils.debounce(search, 250)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
@ -210,63 +167,23 @@
|
||||||
bind:fieldSchema
|
bind:fieldSchema
|
||||||
>
|
>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<div class={autocomplete ? "field-with-search" : ""}>
|
<svelte:component
|
||||||
<svelte:component
|
this={component}
|
||||||
this={component}
|
options={enrichedOptions}
|
||||||
bind:open={isOpen}
|
{autocomplete}
|
||||||
{options}
|
value={selectedValue}
|
||||||
autocomplete={false}
|
on:change={multiselect ? multiHandler : singleHandler}
|
||||||
value={multiselect ? multiValue : singleValue}
|
on:loadMore={loadMore}
|
||||||
on:change={multiselect ? multiHandler : singleHandler}
|
id={fieldState.fieldId}
|
||||||
id={fieldState.fieldId}
|
disabled={fieldState.disabled}
|
||||||
disabled={fieldState.disabled}
|
error={fieldState.error}
|
||||||
error={fieldState.error}
|
getOptionLabel={getDisplayName}
|
||||||
getOptionLabel={getDisplayName}
|
getOptionValue={option => option._id}
|
||||||
getOptionValue={option => option._id}
|
{placeholder}
|
||||||
{placeholder}
|
bind:searchTerm
|
||||||
customPopoverOffsetBelow={autocomplete ? 32 : null}
|
loading={$fetch.loading}
|
||||||
customPopoverMaxHeight={autocomplete ? 240 : null}
|
bind:open
|
||||||
sort={true}
|
customPopoverMaxHeight={400}
|
||||||
/>
|
/>
|
||||||
{#if autocomplete}
|
|
||||||
<div class="search">
|
|
||||||
<Input
|
|
||||||
autofocus
|
|
||||||
quiet
|
|
||||||
type="text"
|
|
||||||
bind:value={searchString}
|
|
||||||
placeholder={primaryDisplay ? `Search by ${primaryDisplay}` : null}
|
|
||||||
/>
|
|
||||||
{#if searching}
|
|
||||||
<div>
|
|
||||||
<ProgressCircle size="S" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<style>
|
|
||||||
.search {
|
|
||||||
flex: 0 0 calc(var(--default-row-height) - 1px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin: 4px var(--cell-padding);
|
|
||||||
width: calc(100% - 2 * var(--cell-padding));
|
|
||||||
}
|
|
||||||
.search :global(.spectrum-Textfield) {
|
|
||||||
min-width: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.search :global(.spectrum-Textfield-input) {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.search :global(.spectrum-Form-item) {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.field-with-search {
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
Loading…
Reference in New Issue