Add reltationship field. Add common picker component to dedupe options and relationship fields. Add better animation and position to picker dropdowns
This commit is contained in:
parent
c9bf2700e3
commit
b1c2780a72
|
@ -15,7 +15,8 @@
|
|||
"booleanfield",
|
||||
"longformfield",
|
||||
"datetimefield",
|
||||
"attachmentfield"
|
||||
"attachmentfield",
|
||||
"relationshipfield"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
||||
</script>
|
||||
|
||||
<FormFieldSelect {...$$props} type="link" />
|
|
@ -24,6 +24,7 @@
|
|||
import LongFormFieldSelect from "./PropertyControls/LongFormFieldSelect.svelte"
|
||||
import DateTimeFieldSelect from "./PropertyControls/DateTimeFieldSelect.svelte"
|
||||
import AttachmentFieldSelect from "./PropertyControls/AttachmentFieldSelect.svelte"
|
||||
import RelationshipFieldSelect from "./PropertyControls/RelationshipFieldSelect.svelte"
|
||||
|
||||
export let componentDefinition = {}
|
||||
export let componentInstance = {}
|
||||
|
@ -72,6 +73,7 @@
|
|||
"field/longform": LongFormFieldSelect,
|
||||
"field/datetime": DateTimeFieldSelect,
|
||||
"field/attachment": AttachmentFieldSelect,
|
||||
"field/relationship": RelationshipFieldSelect,
|
||||
}
|
||||
|
||||
const getControl = type => {
|
||||
|
|
|
@ -1284,5 +1284,28 @@
|
|||
"key": "label"
|
||||
}
|
||||
]
|
||||
},
|
||||
"relationshipfield": {
|
||||
"name": "Relationship Picker",
|
||||
"icon": "ri-edit-box-line",
|
||||
"styleable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "field/relationship",
|
||||
"label": "Field",
|
||||
"key": "field"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Label",
|
||||
"key": "label"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Placeholder",
|
||||
"key": "placeholder",
|
||||
"placeholder": "Choose an option"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import "@spectrum-css/popover/dist/index-vars.css"
|
||||
import "@spectrum-css/menu/dist/index-vars.css"
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Picker from "./Picker.svelte"
|
||||
|
||||
export let field
|
||||
export let label
|
||||
|
@ -17,6 +18,7 @@
|
|||
$: options = fieldSchema?.constraints?.inclusion ?? []
|
||||
$: placeholderText = placeholder || "Choose an option"
|
||||
$: isNull = $fieldState?.value == null || $fieldState?.value === ""
|
||||
$: fieldText = isNull ? placeholderText : $fieldState?.value
|
||||
|
||||
const selectOption = value => {
|
||||
fieldApi.setValue(value)
|
||||
|
@ -26,70 +28,14 @@
|
|||
|
||||
<SpectrumField {field} {label} bind:fieldState bind:fieldApi bind:fieldSchema>
|
||||
{#if fieldState}
|
||||
<button
|
||||
id={$fieldState.fieldId}
|
||||
class="spectrum-Picker"
|
||||
class:is-invalid={!$fieldState.valid}
|
||||
class:is-open={open}
|
||||
aria-haspopup="listbox"
|
||||
on:click={() => (open = true)}>
|
||||
<span class="spectrum-Picker-label" class:is-placeholder={isNull}>
|
||||
{isNull ? placeholderText : $fieldState.value}
|
||||
</span>
|
||||
{#if !$fieldState.valid}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
aria-label="Folder">
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true">
|
||||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover"
|
||||
class:is-open={open}
|
||||
style="z-index: 1;">
|
||||
<ul class="spectrum-Menu" role="listbox">
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={isNull}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => selectOption(null)}>
|
||||
<span class="spectrum-Menu-itemLabel">{placeholderText}</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true">
|
||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||
</svg>
|
||||
</li>
|
||||
{#each options as option}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={option === $fieldState.value}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => selectOption(option)}>
|
||||
<span class="spectrum-Menu-itemLabel">{option}</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true">
|
||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||
</svg>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<Picker
|
||||
bind:open
|
||||
{fieldState}
|
||||
{fieldText}
|
||||
{options}
|
||||
isPlaceholder={isNull}
|
||||
placeholderOption={placeholderText}
|
||||
isOptionSelected={option => option === $fieldState.value}
|
||||
onSelectOption={selectOption} />
|
||||
{/if}
|
||||
</SpectrumField>
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<script>
|
||||
import { fly } from "svelte/transition"
|
||||
|
||||
export let fieldState
|
||||
export let fieldText = ""
|
||||
export let isPlaceholder = false
|
||||
export let placeholderOption = null
|
||||
export let options = []
|
||||
export let isOptionSelected = () => false
|
||||
export let onSelectOption = () => {}
|
||||
export let getOptionLabel = option => option
|
||||
export let getOptionValue = option => option
|
||||
export let open = false
|
||||
</script>
|
||||
|
||||
{#if fieldState}
|
||||
<button
|
||||
id={$fieldState.fieldId}
|
||||
class="spectrum-Picker"
|
||||
class:is-invalid={!$fieldState.valid}
|
||||
class:is-open={open}
|
||||
aria-haspopup="listbox"
|
||||
on:click={() => (open = true)}>
|
||||
<span class="spectrum-Picker-label" class:is-placeholder={isPlaceholder}>
|
||||
{fieldText}
|
||||
</span>
|
||||
{#if !$fieldState.valid}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
aria-label="Folder">
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true">
|
||||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||
</svg>
|
||||
</button>
|
||||
{#if open}
|
||||
<div class="overlay" on:mousedown|self={() => (open = false)} />
|
||||
<div
|
||||
transition:fly={{ y: -20, duration: 200 }}
|
||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open">
|
||||
<ul class="spectrum-Menu" role="listbox">
|
||||
{#if placeholderOption}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={isPlaceholder}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => onSelectOption(null)}>
|
||||
<span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true">
|
||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||
</svg>
|
||||
</li>
|
||||
{/if}
|
||||
{#each options as option}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={isOptionSelected(getOptionValue(option))}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => onSelectOption(getOptionValue(option))}>
|
||||
<span
|
||||
class="spectrum-Menu-itemLabel">{getOptionLabel(option)}</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true">
|
||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||
</svg>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 999;
|
||||
}
|
||||
.spectrum-Popover {
|
||||
max-height: 240px;
|
||||
width: var(--spectrum-global-dimension-size-2400);
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,76 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Picker from "./Picker.svelte"
|
||||
|
||||
const { API } = getContext("sdk")
|
||||
|
||||
export let field
|
||||
export let label
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
let fieldSchema
|
||||
|
||||
// Picker state
|
||||
let options = []
|
||||
let tableDefinition
|
||||
|
||||
$: fieldText = `${$fieldState?.value?.length ?? 0} selected rows`
|
||||
$: valueLookupMap = getValueLookupMap($fieldState?.value)
|
||||
$: isOptionSelected = option => valueLookupMap[option] === true
|
||||
$: linkedTableId = fieldSchema?.tableId
|
||||
$: fetchRows(linkedTableId)
|
||||
$: fetchTable(linkedTableId)
|
||||
|
||||
const fetchTable = async id => {
|
||||
if (id != null) {
|
||||
tableDefinition = await API.fetchTableDefinition(id)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchRows = async id => {
|
||||
if (id != null) {
|
||||
options = await API.fetchTableData(id)
|
||||
}
|
||||
}
|
||||
|
||||
const getDisplayName = row => {
|
||||
return row[tableDefinition?.primaryDisplay || "_id"]
|
||||
}
|
||||
|
||||
const getValueLookupMap = value => {
|
||||
let map = {}
|
||||
if (value?.length) {
|
||||
value.forEach(option => {
|
||||
map[option] = true
|
||||
})
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
const toggleOption = option => {
|
||||
if ($fieldState.value.includes(option)) {
|
||||
fieldApi.setValue($fieldState.value.filter(x => x !== option))
|
||||
} else {
|
||||
fieldApi.setValue([...$fieldState.value, option])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SpectrumField
|
||||
{label}
|
||||
{field}
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
bind:fieldSchema
|
||||
defaultValue={[]}>
|
||||
<Picker
|
||||
{fieldState}
|
||||
{fieldText}
|
||||
{options}
|
||||
{isOptionSelected}
|
||||
onSelectOption={toggleOption}
|
||||
getOptionLabel={getDisplayName}
|
||||
getOptionValue={option => option._id} />
|
||||
</SpectrumField>
|
|
@ -7,3 +7,4 @@ export { default as booleanfield } from "./BooleanField.svelte"
|
|||
export { default as longformfield } from "./LongFormField.svelte"
|
||||
export { default as datetimefield } from "./DateTimeField.svelte"
|
||||
export { default as attachmentfield } from "./AttachmentField.svelte"
|
||||
export { default as relationshipfield } from "./RelationshipField.svelte"
|
||||
|
|
Loading…
Reference in New Issue