Merge pull request #14999 from Budibase/cheeks-lab-day-fields

Allow standalone field components without forms
This commit is contained in:
Andrew Kingston 2024-11-26 13:06:18 +00:00 committed by GitHub
commit 86d14d688d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 277 additions and 114 deletions

View File

@ -1141,10 +1141,11 @@ export const buildFormSchema = (component, asset) => {
const fieldSetting = settings.find( const fieldSetting = settings.find(
setting => setting.key === "field" && setting.type.startsWith("field/") setting => setting.key === "field" && setting.type.startsWith("field/")
) )
if (fieldSetting && component.field) { if (fieldSetting) {
const type = fieldSetting.type.split("field/")[1] const type = fieldSetting.type.split("field/")[1]
if (type) { const key = component.field || component._instanceName
schema[component.field] = { type } if (type && key) {
schema[key] = { type }
} }
} }
component._children?.forEach(child => { component._children?.forEach(child => {

View File

@ -3096,7 +3096,6 @@
"name": "Text Field", "name": "Text Field",
"icon": "Text", "icon": "Text",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -3106,8 +3105,7 @@
{ {
"type": "field/string", "type": "field/string",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -3226,13 +3224,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "string"
}
]
}
}, },
"numberfield": { "numberfield": {
"name": "Number Field", "name": "Number Field",
"icon": "123", "icon": "123",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -3242,8 +3249,7 @@
{ {
"type": "field/number", "type": "field/number",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -3328,13 +3334,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "number"
}
]
}
}, },
"bigintfield": { "bigintfield": {
"name": "BigInt Field", "name": "BigInt Field",
"icon": "TagBold", "icon": "TagBold",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -3344,8 +3359,7 @@
{ {
"type": "field/bigint", "type": "field/bigint",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -3414,13 +3428,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "number"
}
]
}
}, },
"passwordfield": { "passwordfield": {
"name": "Password Field", "name": "Password Field",
"icon": "LockClosed", "icon": "LockClosed",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -3430,8 +3453,7 @@
{ {
"type": "field/string", "type": "field/string",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -3500,13 +3522,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "string"
}
]
}
}, },
"optionsfield": { "optionsfield": {
"name": "Options Picker", "name": "Options Picker",
"icon": "Menu", "icon": "Menu",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -3516,8 +3547,7 @@
{ {
"type": "field/options", "type": "field/options",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -3714,13 +3744,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "string"
}
]
}
}, },
"multifieldselect": { "multifieldselect": {
"name": "Multi-select Picker", "name": "Multi-select Picker",
"icon": "ViewList", "icon": "ViewList",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -3730,8 +3769,7 @@
{ {
"type": "field/array", "type": "field/array",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -3922,13 +3960,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "array"
}
]
}
}, },
"booleanfield": { "booleanfield": {
"name": "Checkbox", "name": "Checkbox",
"icon": "SelectBox", "icon": "SelectBox",
"editable": true, "editable": true,
"requiredAncestors": ["form"],
"size": { "size": {
"width": 400, "width": 400,
"height": 60 "height": 60
@ -3937,8 +3984,7 @@
{ {
"type": "field/boolean", "type": "field/boolean",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -4047,13 +4093,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "boolean"
}
]
}
}, },
"longformfield": { "longformfield": {
"name": "Long Form Field", "name": "Long Form Field",
"icon": "TextAlignLeft", "icon": "TextAlignLeft",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -4063,8 +4118,7 @@
{ {
"type": "field/longform", "type": "field/longform",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -4171,13 +4225,22 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "string"
}
]
}
}, },
"datetimefield": { "datetimefield": {
"name": "Date Picker", "name": "Date Picker",
"icon": "Date", "icon": "Date",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -4187,8 +4250,7 @@
{ {
"type": "field/datetime", "type": "field/datetime",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -4291,7 +4353,17 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "datetime"
}
]
}
}, },
"codescanner": { "codescanner": {
"name": "Barcode/QR Scanner", "name": "Barcode/QR Scanner",
@ -4305,8 +4377,7 @@
{ {
"type": "field/barcodeqr", "type": "field/barcodeqr",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -4451,7 +4522,17 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "string"
}
]
}
}, },
"signaturesinglefield": { "signaturesinglefield": {
"name": "Signature", "name": "Signature",
@ -4924,7 +5005,6 @@
"icon": "Brackets", "icon": "Brackets",
"styles": ["size"], "styles": ["size"],
"editable": true, "editable": true,
"requiredAncestors": ["form"],
"size": { "size": {
"width": 400, "width": 400,
"height": 100 "height": 100
@ -4933,8 +5013,7 @@
{ {
"type": "field/json", "type": "field/json",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -5014,7 +5093,17 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "string"
}
]
}
}, },
"s3upload": { "s3upload": {
"name": "S3 File Upload", "name": "S3 File Upload",
@ -5029,8 +5118,7 @@
{ {
"type": "field/s3", "type": "field/s3",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -5075,7 +5163,17 @@
"label": "Validation", "label": "Validation",
"key": "validation" "key": "validation"
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "array"
}
]
}
}, },
"dataprovider": { "dataprovider": {
"name": "Data Provider", "name": "Data Provider",
@ -7643,7 +7741,6 @@
"name": "User List Field", "name": "User List Field",
"icon": "UserGroup", "icon": "UserGroup",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -7653,8 +7750,7 @@
{ {
"type": "field/bb_reference", "type": "field/bb_reference",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -7744,14 +7840,23 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "array"
}
]
}
}, },
"bbreferencesinglefield": { "bbreferencesinglefield": {
"devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels", "devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels",
"name": "User Field", "name": "User Field",
"icon": "User", "icon": "User",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"],
"editable": true, "editable": true,
"size": { "size": {
"width": 400, "width": 400,
@ -7761,8 +7866,7 @@
{ {
"type": "field/bb_reference_single", "type": "field/bb_reference_single",
"label": "Field", "label": "Field",
"key": "field", "key": "field"
"required": true
}, },
{ {
"type": "text", "type": "text",
@ -7852,6 +7956,16 @@
} }
] ]
} }
] ],
"context": {
"type": "static",
"values": [
{
"label": "Value",
"key": "value",
"type": "string"
}
]
}
} }
} }

View File

@ -1,7 +1,10 @@
<script> <script>
import Placeholder from "../Placeholder.svelte"
import { getContext, onDestroy } from "svelte" import { getContext, onDestroy } from "svelte"
import { writable } from "svelte/store"
import { Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
import { memo } from "@budibase/frontend-core"
import Placeholder from "../Placeholder.svelte"
import InnerForm from "./InnerForm.svelte"
export let label export let label
export let field export let field
@ -20,26 +23,39 @@
const formContext = getContext("form") const formContext = getContext("form")
const formStepContext = getContext("form-step") const formStepContext = getContext("form-step")
const fieldGroupContext = getContext("field-group") const fieldGroupContext = getContext("field-group")
const { styleable, builderStore } = getContext("sdk") const { styleable, builderStore, Provider } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
// Register field with form // Register field with form
const formApi = formContext?.formApi const formApi = formContext?.formApi
const labelPos = fieldGroupContext?.labelPosition || "above" const labelPos = fieldGroupContext?.labelPosition || "above"
let formField
let touched = false let touched = false
let labelNode let labelNode
$: formStep = formStepContext ? $formStepContext || 1 : 1 // Memoize values required to register the field to avoid loops
$: formField = formApi?.registerField( const formStep = formStepContext || writable(1)
field, const fieldInfo = memo({
field: field || $component.name,
type, type,
defaultValue, defaultValue,
disabled, disabled,
readonly, readonly,
validation, validation,
formStep formStep: $formStep || 1,
) })
$: fieldInfo.set({
field: field || $component.name,
type,
defaultValue,
disabled,
readonly,
validation,
formStep: $formStep || 1,
})
$: registerField($fieldInfo)
$: schemaType = $: schemaType =
fieldSchema?.type !== "formula" && fieldSchema?.type !== "bigint" fieldSchema?.type !== "formula" && fieldSchema?.type !== "bigint"
? fieldSchema?.type ? fieldSchema?.type
@ -58,6 +74,18 @@
// Determine label class from position // Determine label class from position
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}` $: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
const registerField = info => {
formField = formApi?.registerField(
info.field,
info.type,
info.defaultValue,
info.disabled,
info.readonly,
info.validation,
info.formStep
)
}
const updateLabel = e => { const updateLabel = e => {
if (touched) { if (touched) {
builderStore.actions.updateProp("label", e.target.textContent) builderStore.actions.updateProp("label", e.target.textContent)
@ -71,52 +99,65 @@
}) })
</script> </script>
<div <Provider data={{ value: fieldState?.value }}>
class="spectrum-Form-item" {#if !formContext}
class:span-2={span === 2} <InnerForm
class:span-3={span === 3} {disabled}
class:span-6={span === 6 || !span} {readonly}
use:styleable={$component.styles} currentStep={writable(1)}
class:above={labelPos === "above"} provideContext={false}
>
{#key $component.editing}
<label
bind:this={labelNode}
contenteditable={$component.editing}
on:blur={$component.editing ? updateLabel : null}
on:input={() => (touched = true)}
class:hidden={!label}
class:readonly
for={fieldState?.fieldId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
> >
{label || " "} <svelte:self {...$$props} bind:fieldState bind:fieldApi bind:fieldSchema>
</label> <slot />
{/key} </svelte:self>
<div class="spectrum-Form-itemField"> </InnerForm>
{#if !formContext} {:else}
<Placeholder text="Form components need to be wrapped in a form" /> <div
{:else if !fieldState} class="spectrum-Form-item"
<Placeholder /> class:span-2={span === 2}
{:else if schemaType && schemaType !== type && !["options", "longform"].includes(type)} class:span-3={span === 3}
<Placeholder class:span-6={span === 6 || !span}
text="This Field setting is the wrong data type for this component" use:styleable={$component.styles}
/> class:above={labelPos === "above"}
{:else} >
<slot /> {#key $component.editing}
{#if fieldState.error} <label
<div class="error"> bind:this={labelNode}
<Icon name="Alert" /> contenteditable={$component.editing}
<span>{fieldState.error}</span> on:blur={$component.editing ? updateLabel : null}
</div> on:input={() => (touched = true)}
{:else if helpText} class:hidden={!label}
<div class="helpText"> class:readonly
<Icon name="HelpOutline" /> <span>{helpText}</span> for={fieldState?.fieldId}
</div> class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
{/if} >
{/if} {label || " "}
</div> </label>
</div> {/key}
<div class="spectrum-Form-itemField">
{#if !fieldState}
<Placeholder />
{:else if schemaType && schemaType !== type && !["options", "longform"].includes(type)}
<Placeholder
text="This Field setting is the wrong data type for this component"
/>
{:else}
<slot />
{#if fieldState.error}
<div class="error">
<Icon name="Alert" />
<span>{fieldState.error}</span>
</div>
{:else if helpText}
<div class="helpText">
<Icon name="HelpOutline" /> <span>{helpText}</span>
</div>
{/if}
{/if}
</div>
</div>
{/if}
</Provider>
<style> <style>
:global(.form-block .spectrum-Form-item.span-2) { :global(.form-block .spectrum-Form-item.span-2) {

View File

@ -5,7 +5,6 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
export let dataSource export let dataSource
export let theme
export let size export let size
export let disabled = false export let disabled = false
export let readonly = false export let readonly = false
@ -113,11 +112,9 @@
{#key resetKey} {#key resetKey}
<InnerForm <InnerForm
{dataSource} {dataSource}
{theme}
{size} {size}
{disabled} {disabled}
{readonly} {readonly}
{actionType}
{schema} {schema}
{definition} {definition}
{initialValues} {initialValues}

View File

@ -14,6 +14,10 @@
export let disableSchemaValidation = false export let disableSchemaValidation = false
export let editAutoColumns = false export let editAutoColumns = false
// For internal use only, to disable context when being used with standalone
// fields
export let provideContext = true
// We export this store so that when we remount the inner form we can still // We export this store so that when we remount the inner form we can still
// persist what step we're on // persist what step we're on
export let currentStep export let currentStep
@ -442,8 +446,14 @@
] ]
</script> </script>
<Provider {actions} data={dataContext}> {#if provideContext}
<Provider {actions} data={dataContext}>
<div use:styleable={$component.styles} class={size}>
<slot />
</div>
</Provider>
{:else}
<div use:styleable={$component.styles} class={size}> <div use:styleable={$component.styles} class={size}>
<slot /> <slot />
</div> </div>
</Provider> {/if}