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(
setting => setting.key === "field" && setting.type.startsWith("field/")
)
if (fieldSetting && component.field) {
if (fieldSetting) {
const type = fieldSetting.type.split("field/")[1]
if (type) {
schema[component.field] = { type }
const key = component.field || component._instanceName
if (type && key) {
schema[key] = { type }
}
}
component._children?.forEach(child => {

View File

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

View File

@ -1,7 +1,10 @@
<script>
import Placeholder from "../Placeholder.svelte"
import { getContext, onDestroy } from "svelte"
import { writable } from "svelte/store"
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 field
@ -20,26 +23,39 @@
const formContext = getContext("form")
const formStepContext = getContext("form-step")
const fieldGroupContext = getContext("field-group")
const { styleable, builderStore } = getContext("sdk")
const { styleable, builderStore, Provider } = getContext("sdk")
const component = getContext("component")
// Register field with form
const formApi = formContext?.formApi
const labelPos = fieldGroupContext?.labelPosition || "above"
let formField
let touched = false
let labelNode
$: formStep = formStepContext ? $formStepContext || 1 : 1
$: formField = formApi?.registerField(
field,
// Memoize values required to register the field to avoid loops
const formStep = formStepContext || writable(1)
const fieldInfo = memo({
field: field || $component.name,
type,
defaultValue,
disabled,
readonly,
validation,
formStep
)
formStep: $formStep || 1,
})
$: fieldInfo.set({
field: field || $component.name,
type,
defaultValue,
disabled,
readonly,
validation,
formStep: $formStep || 1,
})
$: registerField($fieldInfo)
$: schemaType =
fieldSchema?.type !== "formula" && fieldSchema?.type !== "bigint"
? fieldSchema?.type
@ -58,6 +74,18 @@
// Determine label class from position
$: 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 => {
if (touched) {
builderStore.actions.updateProp("label", e.target.textContent)
@ -71,52 +99,65 @@
})
</script>
<div
class="spectrum-Form-item"
class:span-2={span === 2}
class:span-3={span === 3}
class:span-6={span === 6 || !span}
use:styleable={$component.styles}
class:above={labelPos === "above"}
>
{#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}`}
<Provider data={{ value: fieldState?.value }}>
{#if !formContext}
<InnerForm
{disabled}
{readonly}
currentStep={writable(1)}
provideContext={false}
>
{label || " "}
</label>
{/key}
<div class="spectrum-Form-itemField">
{#if !formContext}
<Placeholder text="Form components need to be wrapped in a form" />
{:else 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>
<svelte:self {...$$props} bind:fieldState bind:fieldApi bind:fieldSchema>
<slot />
</svelte:self>
</InnerForm>
{:else}
<div
class="spectrum-Form-item"
class:span-2={span === 2}
class:span-3={span === 3}
class:span-6={span === 6 || !span}
use:styleable={$component.styles}
class:above={labelPos === "above"}
>
{#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 || " "}
</label>
{/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>
:global(.form-block .spectrum-Form-item.span-2) {

View File

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

View File

@ -14,6 +14,10 @@
export let disableSchemaValidation = 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
// persist what step we're on
export let currentStep
@ -442,8 +446,14 @@
]
</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}>
<slot />
</div>
</Provider>
{/if}