Add validation drawer and simplify HOC's for different field types
This commit is contained in:
parent
60d7a1af76
commit
b7af4ed65f
|
@ -48,6 +48,9 @@
|
||||||
padding-top: var(--spacing-l);
|
padding-top: var(--spacing-l);
|
||||||
padding-bottom: var(--spacing-l);
|
padding-bottom: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
.gap-XXS {
|
||||||
|
grid-gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
.gap-XS {
|
.gap-XS {
|
||||||
grid-gap: var(--spacing-s);
|
grid-gap: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
placeholder: setting.placeholder,
|
placeholder: setting.placeholder,
|
||||||
}}
|
}}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
{componentDefinition}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="attachment" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="boolean" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="datetime" />
|
|
|
@ -23,6 +23,7 @@
|
||||||
const getOptions = (schema, fieldType) => {
|
const getOptions = (schema, fieldType) => {
|
||||||
let entries = Object.entries(schema ?? {})
|
let entries = Object.entries(schema ?? {})
|
||||||
if (fieldType) {
|
if (fieldType) {
|
||||||
|
fieldType = fieldType.split("/")[1]
|
||||||
entries = entries.filter(entry => entry[1].type === fieldType)
|
entries = entries.filter(entry => entry[1].type === fieldType)
|
||||||
}
|
}
|
||||||
return entries.map(entry => entry[0])
|
return entries.map(entry => entry[0])
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="longform" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="number" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="options" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="link" />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormFieldSelect {...$$props} on:change type="string" />
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
DrawerContent,
|
||||||
|
Layout,
|
||||||
|
Select,
|
||||||
|
Heading,
|
||||||
|
Body,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { currentAsset, selectedComponent } from "builderStore"
|
||||||
|
import { findClosestMatchingComponent } from "builderStore/storeUtils"
|
||||||
|
import { getSchemaForDatasource } from "builderStore/dataBinding"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
|
export let rules = []
|
||||||
|
export let bindings = []
|
||||||
|
export let type
|
||||||
|
|
||||||
|
const Constraints = {
|
||||||
|
Required: {
|
||||||
|
label: "Required",
|
||||||
|
value: "required",
|
||||||
|
},
|
||||||
|
MaxLength: {
|
||||||
|
label: "Max length",
|
||||||
|
value: "maxLength",
|
||||||
|
},
|
||||||
|
MaxValue: {
|
||||||
|
label: "Max value",
|
||||||
|
value: "maxValue",
|
||||||
|
},
|
||||||
|
MinValue: {
|
||||||
|
label: "Min value",
|
||||||
|
value: "minValue",
|
||||||
|
},
|
||||||
|
Equal: {
|
||||||
|
label: "Must equal",
|
||||||
|
value: "equal",
|
||||||
|
},
|
||||||
|
NotEqual: {
|
||||||
|
label: "Must not equal",
|
||||||
|
value: "notEqual",
|
||||||
|
},
|
||||||
|
Regex: {
|
||||||
|
label: "Must match regex",
|
||||||
|
value: "regex",
|
||||||
|
},
|
||||||
|
NotRegex: {
|
||||||
|
label: "Must not match regex",
|
||||||
|
value: "notRegex",
|
||||||
|
},
|
||||||
|
Contains: {
|
||||||
|
label: "Must contain row ID",
|
||||||
|
value: "contains",
|
||||||
|
},
|
||||||
|
NotContains: {
|
||||||
|
label: "Must not contain row ID",
|
||||||
|
value: "notContains",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const ConstraintMap = {
|
||||||
|
["string"]: [
|
||||||
|
Constraints.Required,
|
||||||
|
Constraints.MaxLength,
|
||||||
|
Constraints.Equal,
|
||||||
|
Constraints.NotEqual,
|
||||||
|
Constraints.Regex,
|
||||||
|
Constraints.NotRegex,
|
||||||
|
],
|
||||||
|
["number"]: [
|
||||||
|
Constraints.Required,
|
||||||
|
Constraints.MaxValue,
|
||||||
|
Constraints.MinValue,
|
||||||
|
Constraints.Equal,
|
||||||
|
Constraints.NotEqual,
|
||||||
|
],
|
||||||
|
["boolean"]: [
|
||||||
|
Constraints.Required,
|
||||||
|
Constraints.Equal,
|
||||||
|
Constraints.NotEqual,
|
||||||
|
],
|
||||||
|
["datetime"]: [
|
||||||
|
Constraints.Required,
|
||||||
|
Constraints.MaxValue,
|
||||||
|
Constraints.MinValue,
|
||||||
|
Constraints.Equal,
|
||||||
|
Constraints.NotEqual,
|
||||||
|
],
|
||||||
|
["attachment"]: [Constraints.Required],
|
||||||
|
["link"]: [
|
||||||
|
Constraints.Required,
|
||||||
|
Constraints.Equal,
|
||||||
|
Constraints.NotEqual,
|
||||||
|
Constraints.Contains,
|
||||||
|
Constraints.NotContains,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
|
||||||
|
$: field = $selectedComponent?.field
|
||||||
|
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
|
||||||
|
$: constraintOptions = getConstraintsForType(type)
|
||||||
|
|
||||||
|
const getConstraintsForType = type => {
|
||||||
|
return ConstraintMap[type?.split("/")[1] || "string"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDataSourceSchema = (asset, component) => {
|
||||||
|
if (!asset || !component) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const formParent = findClosestMatchingComponent(
|
||||||
|
asset.props,
|
||||||
|
component._id,
|
||||||
|
component => component._component.endsWith("/form")
|
||||||
|
)
|
||||||
|
return getSchemaForDatasource(asset, formParent?.dataSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseRulesFromSchema = (field, dataSourceSchema) => {
|
||||||
|
if (!field || !dataSourceSchema) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const fieldSchema = dataSourceSchema.schema?.[field]
|
||||||
|
const constraints = fieldSchema?.constraints
|
||||||
|
if (!constraints) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let rules = []
|
||||||
|
|
||||||
|
// Required constraint
|
||||||
|
if (
|
||||||
|
field === dataSourceSchema?.table?.primaryDisplay ||
|
||||||
|
constraints.presence?.allowEmpty === false
|
||||||
|
) {
|
||||||
|
rules.push({
|
||||||
|
constraint: "required",
|
||||||
|
error: "Required field",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// String length constraint
|
||||||
|
if (exists(constraints.length?.maximum)) {
|
||||||
|
const length = constraints.length.maximum
|
||||||
|
rules.push({
|
||||||
|
constraint: "maxLength",
|
||||||
|
constraintValue: length,
|
||||||
|
error: `Maximum ${length} characters`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min / max number constraint
|
||||||
|
if (exists(constraints.numericality?.greaterThanOrEqualTo)) {
|
||||||
|
const min = constraints.numericality.greaterThanOrEqualTo
|
||||||
|
rules.push({
|
||||||
|
constraint: "minValue",
|
||||||
|
constraintValue: min,
|
||||||
|
error: `Minimum value is ${min}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (exists(constraints.numericality?.lessThanOrEqualTo)) {
|
||||||
|
const max = constraints.numericality.lessThanOrEqualTo
|
||||||
|
rules.push({
|
||||||
|
constraint: "maxValue",
|
||||||
|
constraintValue: max,
|
||||||
|
error: `Maximum value is ${max}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists = value => {
|
||||||
|
return value != null && value !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRule = () => {
|
||||||
|
rules = [...(rules || []), {}]
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeRule = id => {
|
||||||
|
rules = rules.filter(link => link.id !== id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DrawerContent>
|
||||||
|
<div class="container">
|
||||||
|
<Layout noPadding gap="M">
|
||||||
|
<Layout noPadding gap={schemaRules?.length ? "S" : "XS"}>
|
||||||
|
<Heading size="XS">Schema validation rules</Heading>
|
||||||
|
{#if schemaRules?.length}
|
||||||
|
<div class="links">
|
||||||
|
{#each schemaRules as rule}
|
||||||
|
<div class="rule schema">
|
||||||
|
<Select
|
||||||
|
placeholder="Constraint"
|
||||||
|
value={rule.constraint}
|
||||||
|
options={constraintOptions}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<DrawerBindableInput
|
||||||
|
placeholder="Constraint value"
|
||||||
|
value={rule.constraintValue}
|
||||||
|
{bindings}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<DrawerBindableInput
|
||||||
|
placeholder="Error message"
|
||||||
|
value={rule.error}
|
||||||
|
{bindings}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Body size="S">
|
||||||
|
There are no built-in validation rules from the schema.
|
||||||
|
</Body>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
<Heading size="XS">Custom validation rules</Heading>
|
||||||
|
{#if rules?.length}
|
||||||
|
<div class="links">
|
||||||
|
{#each rules as rule}
|
||||||
|
<div class="rule">
|
||||||
|
<Select
|
||||||
|
bind:value={rule.constraint}
|
||||||
|
options={constraintOptions}
|
||||||
|
placeholder="Constraint"
|
||||||
|
/>
|
||||||
|
<DrawerBindableInput
|
||||||
|
placeholder="Constraint value"
|
||||||
|
value={rule.constraintValue}
|
||||||
|
{bindings}
|
||||||
|
disabled={rule.constraint === "required"}
|
||||||
|
on:change={e => (rule.constraintValue = e.detail)}
|
||||||
|
/>
|
||||||
|
<DrawerBindableInput
|
||||||
|
placeholder="Error message"
|
||||||
|
value={rule.error}
|
||||||
|
{bindings}
|
||||||
|
on:change={e => (rule.error = e.detail)}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name="Close"
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
on:click={() => removeRule(rule.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="button">
|
||||||
|
<Button secondary icon="Add" on:click={addRule}>Add Rule</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule {
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 20px;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
transition: background-color ease-in-out 130ms;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script>
|
||||||
|
import { Button, ActionButton, Drawer } from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import ValidationDrawer from "./ValidationDrawer.svelte"
|
||||||
|
|
||||||
|
export let value = []
|
||||||
|
export let bindings = []
|
||||||
|
export let componentDefinition
|
||||||
|
export let type
|
||||||
|
|
||||||
|
let drawer
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const save = () => {
|
||||||
|
dispatch("change", value)
|
||||||
|
drawer.hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionButton on:click={drawer.show}>Configure Validation</ActionButton>
|
||||||
|
<Drawer bind:this={drawer} title="Validation Rules">
|
||||||
|
<svelte:fragment slot="description">
|
||||||
|
Configure validation rules for this field.
|
||||||
|
</svelte:fragment>
|
||||||
|
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||||
|
<ValidationDrawer
|
||||||
|
slot="body"
|
||||||
|
bind:rules={value}
|
||||||
|
{type}
|
||||||
|
{bindings}
|
||||||
|
{componentDefinition}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
|
@ -12,14 +12,8 @@ import SectionSelect from "./SectionSelect.svelte"
|
||||||
import NavigationEditor from "./NavigationEditor/NavigationEditor.svelte"
|
import NavigationEditor from "./NavigationEditor/NavigationEditor.svelte"
|
||||||
import FilterEditor from "./FilterEditor/FilterEditor.svelte"
|
import FilterEditor from "./FilterEditor/FilterEditor.svelte"
|
||||||
import URLSelect from "./URLSelect.svelte"
|
import URLSelect from "./URLSelect.svelte"
|
||||||
import StringFieldSelect from "./StringFieldSelect.svelte"
|
import FormFieldSelect from "./FormFieldSelect.svelte"
|
||||||
import NumberFieldSelect from "./NumberFieldSelect.svelte"
|
import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
|
||||||
import OptionsFieldSelect from "./OptionsFieldSelect.svelte"
|
|
||||||
import BooleanFieldSelect from "./BooleanFieldSelect.svelte"
|
|
||||||
import LongFormFieldSelect from "./LongFormFieldSelect.svelte"
|
|
||||||
import DateTimeFieldSelect from "./DateTimeFieldSelect.svelte"
|
|
||||||
import AttachmentFieldSelect from "./AttachmentFieldSelect.svelte"
|
|
||||||
import RelationshipFieldSelect from "./RelationshipFieldSelect.svelte"
|
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: Input,
|
text: Input,
|
||||||
|
@ -39,14 +33,22 @@ const componentMap = {
|
||||||
navigation: NavigationEditor,
|
navigation: NavigationEditor,
|
||||||
filter: FilterEditor,
|
filter: FilterEditor,
|
||||||
url: URLSelect,
|
url: URLSelect,
|
||||||
"field/string": StringFieldSelect,
|
"field/string": FormFieldSelect,
|
||||||
"field/number": NumberFieldSelect,
|
"field/number": FormFieldSelect,
|
||||||
"field/options": OptionsFieldSelect,
|
"field/options": FormFieldSelect,
|
||||||
"field/boolean": BooleanFieldSelect,
|
"field/boolean": FormFieldSelect,
|
||||||
"field/longform": LongFormFieldSelect,
|
"field/longform": FormFieldSelect,
|
||||||
"field/datetime": DateTimeFieldSelect,
|
"field/datetime": FormFieldSelect,
|
||||||
"field/attachment": AttachmentFieldSelect,
|
"field/attachment": FormFieldSelect,
|
||||||
"field/link": RelationshipFieldSelect,
|
"field/link": FormFieldSelect,
|
||||||
|
// Some validation types are the same as others, so not all types are
|
||||||
|
// explicitly listed here. e.g. options uses string validation
|
||||||
|
"validation/string": ValidationEditor,
|
||||||
|
"validation/number": ValidationEditor,
|
||||||
|
"validation/boolean": ValidationEditor,
|
||||||
|
"validation/datetime": ValidationEditor,
|
||||||
|
"validation/attachment": ValidationEditor,
|
||||||
|
"validation/link": ValidationEditor,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getComponentForSettingType = type => {
|
export const getComponentForSettingType = type => {
|
||||||
|
|
|
@ -1758,6 +1758,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/string",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1787,6 +1792,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/number",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1816,6 +1826,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/string",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1862,6 +1877,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/string",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1891,6 +1911,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/boolean",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1921,6 +1946,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/string",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1956,6 +1986,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/datetime",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1980,6 +2015,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/attachment",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2009,6 +2049,11 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/link",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -66,7 +66,7 @@ const presenceConstraint = value => {
|
||||||
} else {
|
} else {
|
||||||
invalid = value == null || value === ""
|
invalid = value == null || value === ""
|
||||||
}
|
}
|
||||||
return invalid ? "Required" : null
|
return invalid ? "Required field" : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const lengthConstraint = maxLength => value => {
|
const lengthConstraint = maxLength => value => {
|
||||||
|
|
Loading…
Reference in New Issue