Add more functionality to core SpectrumField component to simplify other form components

This commit is contained in:
Andrew Kingston 2021-01-28 08:47:44 +00:00
parent b8fb2ba43c
commit 8c90edf462
5 changed files with 102 additions and 92 deletions

View File

@ -22,7 +22,7 @@
$: options = getOptions(schema, type)
const getOptions = (schema, fieldType) => {
let entries = Object.entries(schema)
let entries = Object.entries(schema ?? {})
if (fieldType) {
entries = entries.filter(entry => entry[1].type === fieldType)
}

View File

@ -8,7 +8,7 @@
export let theme
export let size
const { styleable, API } = getContext("sdk")
const { styleable, API, setBindableValue } = getContext("sdk")
const component = getContext("component")
let loaded = false
@ -24,7 +24,7 @@
// Form API contains functions to control the form
const formApi = {
registerField: field => {
registerField: (field, componentId) => {
if (!field) {
return
}
@ -38,11 +38,8 @@
fieldMap[field] = {
fieldState: makeFieldState(field),
fieldApi: makeFieldApi(field, validate),
fieldApi: makeFieldApi(field, componentId, validate),
fieldSchema: schema?.[field] ?? {},
fieldId: `${Math.random()
.toString(32)
.substr(2)}/${field}`,
}
fieldMap = fieldMap
return fieldMap[field]
@ -53,9 +50,11 @@
setContext("form", { formApi, formState })
// Creates an API for a specific field
const makeFieldApi = (field, validate) => {
const makeFieldApi = (field, componentId, validate) => {
return {
setValue: value => {
console.log("setting " + componentId + " to " + value)
setBindableValue(value, componentId)
const { fieldState } = fieldMap[field]
fieldState.update(state => {
state.value = value
@ -72,6 +71,9 @@
const makeFieldState = field => {
return writable({
field,
fieldId: `${Math.random()
.toString(32)
.substr(2)}/${field}`,
value: null,
error: null,
valid: true,

View File

@ -2,17 +2,15 @@
import "@spectrum-css/picker/dist/index-vars.css"
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { getContext } from "svelte"
import SpectrumField from "./SpectrumField.svelte"
export let field
export let label
export let placeholder
// Register this field with its form
const { formApi } = getContext("form") ?? {}
const formField = formApi?.registerField(field) ?? {}
const { fieldApi, fieldState, fieldSchema } = formField
let fieldState
let fieldApi
let fieldSchema
// Picker state
let open = false
@ -20,68 +18,52 @@
$: placeholderText = placeholder || "Choose an option"
$: isNull = $fieldState?.value == null || $fieldState?.value === ""
// Update value on blur only
const selectOption = value => {
fieldApi.setValue(value)
open = false
}
</script>
<SpectrumField {field} {label}>
<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}>
<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>
<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-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
aria-hidden="true"
aria-label="Folder">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
</li>
{#each options as option}
{/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={option === $fieldState.value}
class:is-selected={isNull}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => selectOption(option)}>
<span class="spectrum-Menu-itemLabel">{option}</span>
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"
@ -89,7 +71,24 @@
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/each}
</ul>
</div>
{#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}
</SpectrumField>

View File

@ -5,21 +5,32 @@
export let label
export let field
export let fieldState
export let fieldApi
export let fieldSchema
// Get contexts
const formContext = getContext("form")
const fieldGroupContext = getContext("fieldGroup")
const { styleable } = getContext("sdk")
const component = getContext("component")
// Register field with form
const { formApi } = formContext || {}
const labelPosition = fieldGroupContext?.labelPosition || "above"
const formField = formApi?.registerField(field) ?? {}
const { fieldId, fieldState } = formField
const formField = formApi?.registerField(field, $component.id)
// Expose field properties to parent component
fieldState = formField?.fieldState
fieldApi = formField?.fieldApi
fieldSchema = formField?.fieldSchema
// Extract label position from field group context
$: labelPositionClass =
labelPosition === "above" ? "" : `spectrum-FieldLabel--${labelPosition}`
</script>
{#if !fieldId}
{#if !fieldState}
<Placeholder>Add the Field setting to start using your component</Placeholder>
{:else if !formContext}
<Placeholder>Form components need to be wrapped in a Form</Placeholder>
@ -28,7 +39,7 @@
<div class="spectrum-Form-item" use:styleable={$component.styles}>
{#if label}
<label
for={fieldId}
for={$fieldState.fieldId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}>
{label}
</label>

View File

@ -1,6 +1,5 @@
<script>
import "@spectrum-css/textfield/dist/index-vars.css"
import { getContext } from "svelte"
import SpectrumField from "./SpectrumField.svelte"
export let field
@ -8,33 +7,32 @@
export let placeholder
export let type = "text"
// Register this field with its form
const { formApi } = getContext("form") ?? {}
const formField = formApi?.registerField(field) ?? {}
const { fieldApi, fieldState } = formField
let fieldState
let fieldApi
// Update value on blur only
const onBlur = event => {
fieldApi.setValue(event.target.value)
}
</script>
<SpectrumField {label} {field}>
<div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
{#if !$fieldState.valid}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<input
id={$fieldState.fieldId}
value={$fieldState.value || ''}
placeholder={placeholder || ''}
on:blur={onBlur}
{type}
class="spectrum-Textfield-input" />
</div>
<SpectrumField {label} {field} bind:fieldState bind:fieldApi>
{#if fieldState}
<div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
{#if !$fieldState.valid}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<input
id={$fieldState.fieldId}
value={$fieldState.value || ''}
placeholder={placeholder || ''}
on:blur={onBlur}
{type}
class="spectrum-Textfield-input" />
</div>
{/if}
</SpectrumField>