2023-01-23 16:38:43 +01:00
|
|
|
<script>
|
|
|
|
import { createEventDispatcher } from "svelte"
|
|
|
|
import FancyField from "./FancyField.svelte"
|
|
|
|
import Icon from "../Icon/Icon.svelte"
|
|
|
|
import FancyFieldLabel from "./FancyFieldLabel.svelte"
|
2023-08-29 15:41:34 +02:00
|
|
|
import StatusLight from "../StatusLight/StatusLight.svelte"
|
|
|
|
import Picker from "../Form/Core/Picker.svelte"
|
2023-01-23 16:38:43 +01:00
|
|
|
|
|
|
|
export let label
|
|
|
|
export let value
|
|
|
|
export let disabled = false
|
|
|
|
export let error = null
|
|
|
|
export let validate = null
|
|
|
|
export let options = []
|
2023-08-29 15:41:34 +02:00
|
|
|
export let isOptionEnabled = () => true
|
2023-01-23 16:38:43 +01:00
|
|
|
export let getOptionLabel = option => extractProperty(option, "label")
|
|
|
|
export let getOptionValue = option => extractProperty(option, "value")
|
2023-08-29 15:41:34 +02:00
|
|
|
export let getOptionSubtitle = option => extractProperty(option, "subtitle")
|
|
|
|
export let getOptionColour = () => null
|
2023-01-23 16:38:43 +01:00
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
|
|
|
|
let open = false
|
|
|
|
let wrapper
|
|
|
|
|
|
|
|
$: placeholder = !value
|
2023-01-25 15:11:33 +01:00
|
|
|
$: selectedLabel = getSelectedLabel(value)
|
2023-08-29 15:41:34 +02:00
|
|
|
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
2023-01-23 16:38:43 +01:00
|
|
|
|
2023-08-29 15:41:34 +02:00
|
|
|
const getFieldAttribute = (getAttribute, 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 ? getAttribute(options[index], index) : null
|
|
|
|
}
|
2023-01-23 16:38:43 +01:00
|
|
|
const extractProperty = (value, property) => {
|
|
|
|
if (value && typeof value === "object") {
|
|
|
|
return value[property]
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
const onChange = newValue => {
|
|
|
|
dispatch("change", newValue)
|
|
|
|
value = newValue
|
|
|
|
if (validate) {
|
|
|
|
error = validate(newValue)
|
|
|
|
}
|
|
|
|
open = false
|
|
|
|
}
|
2023-01-25 15:11:33 +01:00
|
|
|
|
|
|
|
const getSelectedLabel = value => {
|
|
|
|
if (!value || !options?.length) {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
const selectedOption = options.find(x => getOptionValue(x) === value)
|
|
|
|
if (!selectedOption) {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
return getOptionLabel(selectedOption)
|
|
|
|
}
|
2023-01-23 16:38:43 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<FancyField
|
|
|
|
bind:ref={wrapper}
|
|
|
|
{error}
|
|
|
|
{value}
|
|
|
|
{validate}
|
|
|
|
{disabled}
|
|
|
|
clickable
|
|
|
|
on:click={() => (open = true)}
|
|
|
|
>
|
|
|
|
{#if label}
|
|
|
|
<FancyFieldLabel {placeholder}>{label}</FancyFieldLabel>
|
|
|
|
{/if}
|
|
|
|
|
2023-08-29 15:41:34 +02:00
|
|
|
{#if fieldColour}
|
|
|
|
<span class="align">
|
|
|
|
<StatusLight square color={fieldColour} />
|
|
|
|
</span>
|
|
|
|
{/if}
|
|
|
|
|
2023-01-23 16:38:43 +01:00
|
|
|
<div class="value" class:placeholder>
|
2023-01-25 15:11:33 +01:00
|
|
|
{selectedLabel || ""}
|
2023-01-23 16:38:43 +01:00
|
|
|
</div>
|
|
|
|
|
2023-08-29 15:41:34 +02:00
|
|
|
<div class="align">
|
2023-01-23 16:38:43 +01:00
|
|
|
<Icon name="ChevronDown" />
|
|
|
|
</div>
|
|
|
|
</FancyField>
|
|
|
|
|
2023-08-29 15:41:34 +02:00
|
|
|
<Picker
|
|
|
|
customAnchor={wrapper}
|
|
|
|
onlyPopover={true}
|
|
|
|
bind:open
|
|
|
|
{error}
|
|
|
|
{disabled}
|
|
|
|
{options}
|
|
|
|
{getOptionLabel}
|
|
|
|
{getOptionValue}
|
|
|
|
{getOptionSubtitle}
|
|
|
|
{getOptionColour}
|
|
|
|
{isOptionEnabled}
|
|
|
|
isPlaceholder={value == null || value === ""}
|
|
|
|
placeholderOption={placeholder === false ? null : placeholder}
|
|
|
|
onSelectOption={onChange}
|
|
|
|
isOptionSelected={option => option === value}
|
|
|
|
/>
|
2023-01-23 16:38:43 +01:00
|
|
|
|
|
|
|
<style>
|
|
|
|
.value {
|
|
|
|
display: block;
|
|
|
|
flex: 1 1 auto;
|
|
|
|
font-size: 15px;
|
|
|
|
line-height: 17px;
|
|
|
|
color: var(--spectrum-global-color-gray-900);
|
|
|
|
transition: transform 130ms ease-out, opacity 130ms ease-out;
|
|
|
|
opacity: 1;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
overflow: hidden;
|
|
|
|
width: 0;
|
|
|
|
transform: translateY(9px);
|
|
|
|
}
|
2023-08-29 15:41:34 +02:00
|
|
|
|
|
|
|
.align {
|
|
|
|
display: block;
|
|
|
|
font-size: 15px;
|
|
|
|
line-height: 17px;
|
|
|
|
color: var(--spectrum-global-color-gray-900);
|
|
|
|
transition: transform 130ms ease-out, opacity 130ms ease-out;
|
|
|
|
transform: translateY(9px);
|
|
|
|
}
|
2023-01-23 16:38:43 +01:00
|
|
|
.value.placeholder {
|
|
|
|
transform: translateY(0);
|
|
|
|
opacity: 0;
|
|
|
|
pointer-events: none;
|
|
|
|
margin-top: 0;
|
|
|
|
}
|
|
|
|
</style>
|