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
ad86c40de6
commit
b65673b77a
|
@ -15,7 +15,8 @@
|
||||||
"booleanfield",
|
"booleanfield",
|
||||||
"longformfield",
|
"longformfield",
|
||||||
"datetimefield",
|
"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 LongFormFieldSelect from "./PropertyControls/LongFormFieldSelect.svelte"
|
||||||
import DateTimeFieldSelect from "./PropertyControls/DateTimeFieldSelect.svelte"
|
import DateTimeFieldSelect from "./PropertyControls/DateTimeFieldSelect.svelte"
|
||||||
import AttachmentFieldSelect from "./PropertyControls/AttachmentFieldSelect.svelte"
|
import AttachmentFieldSelect from "./PropertyControls/AttachmentFieldSelect.svelte"
|
||||||
|
import RelationshipFieldSelect from "./PropertyControls/RelationshipFieldSelect.svelte"
|
||||||
|
|
||||||
export let componentDefinition = {}
|
export let componentDefinition = {}
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
|
@ -72,6 +73,7 @@
|
||||||
"field/longform": LongFormFieldSelect,
|
"field/longform": LongFormFieldSelect,
|
||||||
"field/datetime": DateTimeFieldSelect,
|
"field/datetime": DateTimeFieldSelect,
|
||||||
"field/attachment": AttachmentFieldSelect,
|
"field/attachment": AttachmentFieldSelect,
|
||||||
|
"field/relationship": RelationshipFieldSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
const getControl = type => {
|
const getControl = type => {
|
||||||
|
|
|
@ -1284,5 +1284,28 @@
|
||||||
"key": "label"
|
"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/popover/dist/index-vars.css"
|
||||||
import "@spectrum-css/menu/dist/index-vars.css"
|
import "@spectrum-css/menu/dist/index-vars.css"
|
||||||
import SpectrumField from "./SpectrumField.svelte"
|
import SpectrumField from "./SpectrumField.svelte"
|
||||||
|
import Picker from "./Picker.svelte"
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
export let label
|
export let label
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
$: options = fieldSchema?.constraints?.inclusion ?? []
|
$: options = fieldSchema?.constraints?.inclusion ?? []
|
||||||
$: placeholderText = placeholder || "Choose an option"
|
$: placeholderText = placeholder || "Choose an option"
|
||||||
$: isNull = $fieldState?.value == null || $fieldState?.value === ""
|
$: isNull = $fieldState?.value == null || $fieldState?.value === ""
|
||||||
|
$: fieldText = isNull ? placeholderText : $fieldState?.value
|
||||||
|
|
||||||
const selectOption = value => {
|
const selectOption = value => {
|
||||||
fieldApi.setValue(value)
|
fieldApi.setValue(value)
|
||||||
|
@ -26,70 +28,14 @@
|
||||||
|
|
||||||
<SpectrumField {field} {label} bind:fieldState bind:fieldApi bind:fieldSchema>
|
<SpectrumField {field} {label} bind:fieldState bind:fieldApi bind:fieldSchema>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<button
|
<Picker
|
||||||
id={$fieldState.fieldId}
|
bind:open
|
||||||
class="spectrum-Picker"
|
{fieldState}
|
||||||
class:is-invalid={!$fieldState.valid}
|
{fieldText}
|
||||||
class:is-open={open}
|
{options}
|
||||||
aria-haspopup="listbox"
|
isPlaceholder={isNull}
|
||||||
on:click={() => (open = true)}>
|
placeholderOption={placeholderText}
|
||||||
<span class="spectrum-Picker-label" class:is-placeholder={isNull}>
|
isOptionSelected={option => option === $fieldState.value}
|
||||||
{isNull ? placeholderText : $fieldState.value}
|
onSelectOption={selectOption} />
|
||||||
</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>
|
|
||||||
{/if}
|
{/if}
|
||||||
</SpectrumField>
|
</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 longformfield } from "./LongFormField.svelte"
|
||||||
export { default as datetimefield } from "./DateTimeField.svelte"
|
export { default as datetimefield } from "./DateTimeField.svelte"
|
||||||
export { default as attachmentfield } from "./AttachmentField.svelte"
|
export { default as attachmentfield } from "./AttachmentField.svelte"
|
||||||
|
export { default as relationshipfield } from "./RelationshipField.svelte"
|
||||||
|
|
Loading…
Reference in New Issue