Add more functionality to core SpectrumField component to simplify other form components
This commit is contained in:
parent
734e341ebb
commit
32ad2b6a08
|
@ -22,7 +22,7 @@
|
||||||
$: options = getOptions(schema, type)
|
$: options = getOptions(schema, type)
|
||||||
|
|
||||||
const getOptions = (schema, fieldType) => {
|
const getOptions = (schema, fieldType) => {
|
||||||
let entries = Object.entries(schema)
|
let entries = Object.entries(schema ?? {})
|
||||||
if (fieldType) {
|
if (fieldType) {
|
||||||
entries = entries.filter(entry => entry[1].type === fieldType)
|
entries = entries.filter(entry => entry[1].type === fieldType)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
export let theme
|
export let theme
|
||||||
export let size
|
export let size
|
||||||
|
|
||||||
const { styleable, API } = getContext("sdk")
|
const { styleable, API, setBindableValue } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
// Form API contains functions to control the form
|
// Form API contains functions to control the form
|
||||||
const formApi = {
|
const formApi = {
|
||||||
registerField: field => {
|
registerField: (field, componentId) => {
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,8 @@
|
||||||
|
|
||||||
fieldMap[field] = {
|
fieldMap[field] = {
|
||||||
fieldState: makeFieldState(field),
|
fieldState: makeFieldState(field),
|
||||||
fieldApi: makeFieldApi(field, validate),
|
fieldApi: makeFieldApi(field, componentId, validate),
|
||||||
fieldSchema: schema?.[field] ?? {},
|
fieldSchema: schema?.[field] ?? {},
|
||||||
fieldId: `${Math.random()
|
|
||||||
.toString(32)
|
|
||||||
.substr(2)}/${field}`,
|
|
||||||
}
|
}
|
||||||
fieldMap = fieldMap
|
fieldMap = fieldMap
|
||||||
return fieldMap[field]
|
return fieldMap[field]
|
||||||
|
@ -53,9 +50,11 @@
|
||||||
setContext("form", { formApi, formState })
|
setContext("form", { formApi, formState })
|
||||||
|
|
||||||
// Creates an API for a specific field
|
// Creates an API for a specific field
|
||||||
const makeFieldApi = (field, validate) => {
|
const makeFieldApi = (field, componentId, validate) => {
|
||||||
return {
|
return {
|
||||||
setValue: value => {
|
setValue: value => {
|
||||||
|
console.log("setting " + componentId + " to " + value)
|
||||||
|
setBindableValue(value, componentId)
|
||||||
const { fieldState } = fieldMap[field]
|
const { fieldState } = fieldMap[field]
|
||||||
fieldState.update(state => {
|
fieldState.update(state => {
|
||||||
state.value = value
|
state.value = value
|
||||||
|
@ -72,6 +71,9 @@
|
||||||
const makeFieldState = field => {
|
const makeFieldState = field => {
|
||||||
return writable({
|
return writable({
|
||||||
field,
|
field,
|
||||||
|
fieldId: `${Math.random()
|
||||||
|
.toString(32)
|
||||||
|
.substr(2)}/${field}`,
|
||||||
value: null,
|
value: null,
|
||||||
error: null,
|
error: null,
|
||||||
valid: true,
|
valid: true,
|
||||||
|
|
|
@ -2,17 +2,15 @@
|
||||||
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 { getContext } from "svelte"
|
|
||||||
import SpectrumField from "./SpectrumField.svelte"
|
import SpectrumField from "./SpectrumField.svelte"
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
export let label
|
export let label
|
||||||
export let placeholder
|
export let placeholder
|
||||||
|
|
||||||
// Register this field with its form
|
let fieldState
|
||||||
const { formApi } = getContext("form") ?? {}
|
let fieldApi
|
||||||
const formField = formApi?.registerField(field) ?? {}
|
let fieldSchema
|
||||||
const { fieldApi, fieldState, fieldSchema } = formField
|
|
||||||
|
|
||||||
// Picker state
|
// Picker state
|
||||||
let open = false
|
let open = false
|
||||||
|
@ -20,68 +18,52 @@
|
||||||
$: placeholderText = placeholder || "Choose an option"
|
$: placeholderText = placeholder || "Choose an option"
|
||||||
$: isNull = $fieldState?.value == null || $fieldState?.value === ""
|
$: isNull = $fieldState?.value == null || $fieldState?.value === ""
|
||||||
|
|
||||||
// Update value on blur only
|
|
||||||
const selectOption = value => {
|
const selectOption = value => {
|
||||||
fieldApi.setValue(value)
|
fieldApi.setValue(value)
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SpectrumField {field} {label}>
|
<SpectrumField {field} {label} bind:fieldState bind:fieldApi bind:fieldSchema>
|
||||||
<button
|
{#if fieldState}
|
||||||
id={$fieldState.fieldId}
|
<button
|
||||||
class="spectrum-Picker"
|
id={$fieldState.fieldId}
|
||||||
class:is-invalid={!$fieldState.valid}
|
class="spectrum-Picker"
|
||||||
class:is-open={open}
|
class:is-invalid={!$fieldState.valid}
|
||||||
aria-haspopup="listbox"
|
class:is-open={open}
|
||||||
on:click={() => (open = true)}>
|
aria-haspopup="listbox"
|
||||||
<span class="spectrum-Picker-label" class:is-placeholder={isNull}>
|
on:click={() => (open = true)}>
|
||||||
{isNull ? placeholderText : $fieldState.value}
|
<span class="spectrum-Picker-label" class:is-placeholder={isNull}>
|
||||||
</span>
|
{isNull ? placeholderText : $fieldState.value}
|
||||||
{#if !$fieldState.valid}
|
</span>
|
||||||
<svg
|
{#if !$fieldState.valid}
|
||||||
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}>
|
|
||||||
<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
|
<svg
|
||||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
aria-hidden="true">
|
aria-hidden="true"
|
||||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
aria-label="Folder">
|
||||||
|
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||||
</svg>
|
</svg>
|
||||||
</li>
|
{/if}
|
||||||
{#each options as option}
|
<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}>
|
||||||
|
<ul class="spectrum-Menu" role="listbox">
|
||||||
<li
|
<li
|
||||||
class="spectrum-Menu-item"
|
class="spectrum-Menu-item"
|
||||||
class:is-selected={option === $fieldState.value}
|
class:is-selected={isNull}
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected="true"
|
aria-selected="true"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:click={() => selectOption(option)}>
|
on:click={() => selectOption(null)}>
|
||||||
<span class="spectrum-Menu-itemLabel">{option}</span>
|
<span class="spectrum-Menu-itemLabel">{placeholderText}</span>
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
|
@ -89,7 +71,24 @@
|
||||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
</svg>
|
</svg>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{#each options as option}
|
||||||
</ul>
|
<li
|
||||||
</div>
|
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}
|
||||||
</SpectrumField>
|
</SpectrumField>
|
||||||
|
|
|
@ -5,21 +5,32 @@
|
||||||
|
|
||||||
export let label
|
export let label
|
||||||
export let field
|
export let field
|
||||||
|
export let fieldState
|
||||||
|
export let fieldApi
|
||||||
|
export let fieldSchema
|
||||||
|
|
||||||
|
// Get contexts
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
const fieldGroupContext = getContext("fieldGroup")
|
const fieldGroupContext = getContext("fieldGroup")
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
|
// Register field with form
|
||||||
const { formApi } = formContext || {}
|
const { formApi } = formContext || {}
|
||||||
const labelPosition = fieldGroupContext?.labelPosition || "above"
|
const labelPosition = fieldGroupContext?.labelPosition || "above"
|
||||||
const formField = formApi?.registerField(field) ?? {}
|
const formField = formApi?.registerField(field, $component.id)
|
||||||
const { fieldId, fieldState } = formField
|
|
||||||
|
|
||||||
|
// Expose field properties to parent component
|
||||||
|
fieldState = formField?.fieldState
|
||||||
|
fieldApi = formField?.fieldApi
|
||||||
|
fieldSchema = formField?.fieldSchema
|
||||||
|
|
||||||
|
// Extract label position from field group context
|
||||||
$: labelPositionClass =
|
$: labelPositionClass =
|
||||||
labelPosition === "above" ? "" : `spectrum-FieldLabel--${labelPosition}`
|
labelPosition === "above" ? "" : `spectrum-FieldLabel--${labelPosition}`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !fieldId}
|
{#if !fieldState}
|
||||||
<Placeholder>Add the Field setting to start using your component</Placeholder>
|
<Placeholder>Add the Field setting to start using your component</Placeholder>
|
||||||
{:else if !formContext}
|
{:else if !formContext}
|
||||||
<Placeholder>Form components need to be wrapped in a Form</Placeholder>
|
<Placeholder>Form components need to be wrapped in a Form</Placeholder>
|
||||||
|
@ -28,7 +39,7 @@
|
||||||
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
||||||
{#if label}
|
{#if label}
|
||||||
<label
|
<label
|
||||||
for={fieldId}
|
for={$fieldState.fieldId}
|
||||||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}>
|
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
import { getContext } from "svelte"
|
|
||||||
import SpectrumField from "./SpectrumField.svelte"
|
import SpectrumField from "./SpectrumField.svelte"
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
|
@ -8,33 +7,32 @@
|
||||||
export let placeholder
|
export let placeholder
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
|
|
||||||
// Register this field with its form
|
let fieldState
|
||||||
const { formApi } = getContext("form") ?? {}
|
let fieldApi
|
||||||
const formField = formApi?.registerField(field) ?? {}
|
|
||||||
const { fieldApi, fieldState } = formField
|
|
||||||
|
|
||||||
// Update value on blur only
|
|
||||||
const onBlur = event => {
|
const onBlur = event => {
|
||||||
fieldApi.setValue(event.target.value)
|
fieldApi.setValue(event.target.value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SpectrumField {label} {field}>
|
<SpectrumField {label} {field} bind:fieldState bind:fieldApi>
|
||||||
<div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
|
{#if fieldState}
|
||||||
{#if !$fieldState.valid}
|
<div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
|
||||||
<svg
|
{#if !$fieldState.valid}
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
<svg
|
||||||
focusable="false"
|
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
||||||
aria-hidden="true">
|
focusable="false"
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
aria-hidden="true">
|
||||||
</svg>
|
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||||
{/if}
|
</svg>
|
||||||
<input
|
{/if}
|
||||||
id={$fieldState.fieldId}
|
<input
|
||||||
value={$fieldState.value || ''}
|
id={$fieldState.fieldId}
|
||||||
placeholder={placeholder || ''}
|
value={$fieldState.value || ''}
|
||||||
on:blur={onBlur}
|
placeholder={placeholder || ''}
|
||||||
{type}
|
on:blur={onBlur}
|
||||||
class="spectrum-Textfield-input" />
|
{type}
|
||||||
</div>
|
class="spectrum-Textfield-input" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</SpectrumField>
|
</SpectrumField>
|
||||||
|
|
Loading…
Reference in New Issue