commit
0c35c2612f
|
@ -1,38 +1,39 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte"
|
||||
import InnerForm from "./InnerForm.svelte"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { writable } from "svelte/store"
|
||||
import type { DataFetchDatasource, Table, TableSchema } from "@budibase/types"
|
||||
|
||||
export let dataSource
|
||||
export let size
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let actionType = "Create"
|
||||
export let initialFormStep = 1
|
||||
export let dataSource: DataFetchDatasource
|
||||
export let size: "Medium" | "Large"
|
||||
export let disabled: boolean = false
|
||||
export let readonly: boolean = false
|
||||
export let actionType: "Create" = "Create"
|
||||
export let initialFormStep: string | number = 1
|
||||
|
||||
// Not exposed as a builder setting. Used internally to disable validation
|
||||
// for fields rendered in things like search blocks.
|
||||
export let disableSchemaValidation = false
|
||||
export let disableSchemaValidation: boolean = false
|
||||
|
||||
// Not exposed as a builder setting. Used internally to allow searching on
|
||||
// auto columns.
|
||||
export let editAutoColumns = false
|
||||
export let editAutoColumns: boolean = false
|
||||
|
||||
const context = getContext("context")
|
||||
const component = getContext("component")
|
||||
const { fetchDatasourceSchema, fetchDatasourceDefinition } = getContext("sdk")
|
||||
|
||||
const getInitialFormStep = () => {
|
||||
const parsedFormStep = parseInt(initialFormStep)
|
||||
const parsedFormStep = parseInt(initialFormStep.toString())
|
||||
if (isNaN(parsedFormStep)) {
|
||||
return 1
|
||||
}
|
||||
return parsedFormStep
|
||||
}
|
||||
|
||||
let definition
|
||||
let schema
|
||||
let definition: Table | undefined
|
||||
let schema: TableSchema | undefined
|
||||
let loaded = false
|
||||
let currentStep = getContext("current-step") || writable(getInitialFormStep())
|
||||
|
||||
|
@ -49,7 +50,12 @@
|
|||
)
|
||||
|
||||
// Returns the closes data context which isn't a built in context
|
||||
const getInitialValues = (type, dataSource, path, context) => {
|
||||
const getInitialValues = (
|
||||
type: string,
|
||||
dataSource: DataFetchDatasource,
|
||||
path: string[],
|
||||
context: Record<string, any>
|
||||
) => {
|
||||
// Only inherit values for update forms
|
||||
if (type !== "Update") {
|
||||
return {}
|
||||
|
@ -82,11 +88,11 @@
|
|||
}
|
||||
|
||||
// Fetches the form schema from this form's dataSource
|
||||
const fetchSchema = async dataSource => {
|
||||
const fetchSchema = async (dataSource: DataFetchDatasource) => {
|
||||
try {
|
||||
definition = await fetchDatasourceDefinition(dataSource)
|
||||
} catch (error) {
|
||||
definition = null
|
||||
definition = undefined
|
||||
}
|
||||
const res = await fetchDatasourceSchema(dataSource)
|
||||
schema = res || {}
|
||||
|
@ -98,7 +104,7 @@
|
|||
// Generates a predictable string that uniquely identifies a schema. We can't
|
||||
// simply stringify the whole schema as there are array fields which have
|
||||
// random order.
|
||||
const generateSchemaKey = schema => {
|
||||
const generateSchemaKey = (schema: TableSchema | undefined) => {
|
||||
if (!schema) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,31 +1,62 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { setContext, getContext } from "svelte"
|
||||
import type { Readable, Writable } from "svelte/store"
|
||||
import { derived, get, writable } from "svelte/store"
|
||||
import { createValidatorFromConstraints } from "./validation"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import type {
|
||||
DataFetchDatasource,
|
||||
FieldSchema,
|
||||
FieldType,
|
||||
Table,
|
||||
TableSchema,
|
||||
UIFieldValidationRule,
|
||||
} from "@budibase/types"
|
||||
|
||||
export let dataSource = undefined
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let initialValues = undefined
|
||||
export let size = undefined
|
||||
export let schema = undefined
|
||||
export let definition = undefined
|
||||
export let disableSchemaValidation = false
|
||||
export let editAutoColumns = false
|
||||
type FieldInfo<T = any> = {
|
||||
name: string
|
||||
step: number
|
||||
type: `${FieldType}`
|
||||
fieldState: {
|
||||
fieldId: string
|
||||
value: T
|
||||
defaultValue: T
|
||||
disabled: boolean
|
||||
readonly: boolean
|
||||
validator: ((_value: T) => string | null) | null
|
||||
error: string | null | undefined
|
||||
lastUpdate: number
|
||||
}
|
||||
fieldApi: {
|
||||
setValue(_value: T): void
|
||||
validate(): boolean
|
||||
reset(): void
|
||||
}
|
||||
fieldSchema: FieldSchema | {}
|
||||
}
|
||||
|
||||
export let dataSource: DataFetchDatasource | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let readonly: boolean = false
|
||||
export let initialValues: Record<string, any> | undefined = undefined
|
||||
export let size: "Medium" | "Large" | undefined = undefined
|
||||
export let schema: TableSchema | undefined = undefined
|
||||
export let definition: Table | undefined = undefined
|
||||
export let disableSchemaValidation: boolean = false
|
||||
export let editAutoColumns: boolean = false
|
||||
|
||||
// For internal use only, to disable context when being used with standalone
|
||||
// fields
|
||||
export let provideContext = true
|
||||
export let provideContext: boolean = 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
|
||||
export let currentStep: Writable<number>
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable, Provider, ActionTypes } = getContext("sdk")
|
||||
|
||||
let fields = []
|
||||
let fields: Writable<FieldInfo>[] = []
|
||||
const formState = writable({
|
||||
values: {},
|
||||
errors: {},
|
||||
|
@ -75,19 +106,24 @@
|
|||
|
||||
// Generates a derived store from an array of fields, comprised of a map of
|
||||
// extracted values from the field array
|
||||
const deriveFieldProperty = (fieldStores, getProp) => {
|
||||
const deriveFieldProperty = (
|
||||
fieldStores: Readable<FieldInfo>[],
|
||||
getProp: (_field: FieldInfo) => any
|
||||
) => {
|
||||
return derived(fieldStores, fieldValues => {
|
||||
const reducer = (map, field) => ({ ...map, [field.name]: getProp(field) })
|
||||
return fieldValues.reduce(reducer, {})
|
||||
return fieldValues.reduce(
|
||||
(map, field) => ({ ...map, [field.name]: getProp(field) }),
|
||||
{}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Derives any enrichments which need to be made so that bindings work for
|
||||
// special data types like attachments. Relationships are currently not
|
||||
// handled as we don't have the primaryDisplay field that is required.
|
||||
const deriveBindingEnrichments = fieldStores => {
|
||||
const deriveBindingEnrichments = (fieldStores: Readable<FieldInfo>[]) => {
|
||||
return derived(fieldStores, fieldValues => {
|
||||
let enrichments = {}
|
||||
const enrichments: Record<string, string> = {}
|
||||
fieldValues.forEach(field => {
|
||||
if (field.type === "attachment") {
|
||||
const value = field.fieldState.value
|
||||
|
@ -104,7 +140,11 @@
|
|||
|
||||
// Derive the overall form value and deeply set all field paths so that we
|
||||
// can support things like JSON fields.
|
||||
const deriveFormValue = (initialValues, values, enrichments) => {
|
||||
const deriveFormValue = (
|
||||
initialValues: Record<string, any> | undefined,
|
||||
values: Record<string, any>,
|
||||
enrichments: Record<string, string>
|
||||
) => {
|
||||
let formValue = Helpers.cloneDeep(initialValues || {})
|
||||
|
||||
// We need to sort the keys to avoid a JSON field overwriting a nested field
|
||||
|
@ -118,7 +158,7 @@
|
|||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.lastUpdate > b.lastUpdate
|
||||
return a.lastUpdate - b.lastUpdate
|
||||
})
|
||||
|
||||
// Merge all values and enrichments into a single value
|
||||
|
@ -132,12 +172,16 @@
|
|||
}
|
||||
|
||||
// Searches the field array for a certain field
|
||||
const getField = name => {
|
||||
return fields.find(field => get(field).name === name)
|
||||
const getField = (name: string) => {
|
||||
return fields.find(field => get(field).name === name)!
|
||||
}
|
||||
|
||||
// Sanitises a value by ensuring it doesn't contain any invalid data
|
||||
const sanitiseValue = (value, schema, type) => {
|
||||
const sanitiseValue = (
|
||||
value: any,
|
||||
schema: FieldSchema | undefined,
|
||||
type: `${FieldType}`
|
||||
) => {
|
||||
// Check arrays - remove any values not present in the field schema and
|
||||
// convert any values supplied to strings
|
||||
if (Array.isArray(value) && type === "array" && schema) {
|
||||
|
@ -149,13 +193,13 @@
|
|||
|
||||
const formApi = {
|
||||
registerField: (
|
||||
field,
|
||||
type,
|
||||
defaultValue = null,
|
||||
fieldDisabled = false,
|
||||
fieldReadOnly = false,
|
||||
validationRules,
|
||||
step = 1
|
||||
field: string,
|
||||
type: FieldType,
|
||||
defaultValue: string | null = null,
|
||||
fieldDisabled: boolean = false,
|
||||
fieldReadOnly: boolean = false,
|
||||
validationRules: UIFieldValidationRule[],
|
||||
step: number = 1
|
||||
) => {
|
||||
if (!field) {
|
||||
return
|
||||
|
@ -200,7 +244,7 @@
|
|||
const isAutoColumn = !!schema?.[field]?.autocolumn
|
||||
|
||||
// Construct field info
|
||||
const fieldInfo = writable({
|
||||
const fieldInfo = writable<FieldInfo>({
|
||||
name: field,
|
||||
type,
|
||||
step: step || 1,
|
||||
|
@ -210,7 +254,8 @@
|
|||
error: initialError,
|
||||
disabled:
|
||||
disabled || fieldDisabled || (isAutoColumn && !editAutoColumns),
|
||||
readonly: readonly || fieldReadOnly || schema?.[field]?.readonly,
|
||||
readonly:
|
||||
readonly || fieldReadOnly || (schema?.[field] as any)?.readonly,
|
||||
defaultValue,
|
||||
validator,
|
||||
lastUpdate: Date.now(),
|
||||
|
@ -254,7 +299,13 @@
|
|||
get(field).fieldApi.reset()
|
||||
})
|
||||
},
|
||||
changeStep: ({ type, number }) => {
|
||||
changeStep: ({
|
||||
type,
|
||||
number,
|
||||
}: {
|
||||
type: "next" | "prev" | "first" | "specific"
|
||||
number: any
|
||||
}) => {
|
||||
if (type === "next") {
|
||||
currentStep.update(step => step + 1)
|
||||
} else if (type === "prev") {
|
||||
|
@ -265,12 +316,12 @@
|
|||
currentStep.set(parseInt(number))
|
||||
}
|
||||
},
|
||||
setStep: step => {
|
||||
setStep: (step: number) => {
|
||||
if (step) {
|
||||
currentStep.set(step)
|
||||
}
|
||||
},
|
||||
setFieldValue: (fieldName, value) => {
|
||||
setFieldValue: (fieldName: string, value: any) => {
|
||||
const field = getField(fieldName)
|
||||
if (!field) {
|
||||
return
|
||||
|
@ -278,7 +329,7 @@
|
|||
const { fieldApi } = get(field)
|
||||
fieldApi.setValue(value)
|
||||
},
|
||||
resetField: fieldName => {
|
||||
resetField: (fieldName: string) => {
|
||||
const field = getField(fieldName)
|
||||
if (!field) {
|
||||
return
|
||||
|
@ -289,9 +340,9 @@
|
|||
}
|
||||
|
||||
// Creates an API for a specific field
|
||||
const makeFieldApi = field => {
|
||||
const makeFieldApi = (field: string) => {
|
||||
// Sets the value for a certain field and invokes validation
|
||||
const setValue = (value, skipCheck = false) => {
|
||||
const setValue = (value: any, skipCheck = false) => {
|
||||
const fieldInfo = getField(field)
|
||||
const { fieldState } = get(fieldInfo)
|
||||
const { validator } = fieldState
|
||||
|
@ -328,36 +379,6 @@
|
|||
})
|
||||
}
|
||||
|
||||
// Updates the validator rules for a certain field
|
||||
const updateValidation = validationRules => {
|
||||
const fieldInfo = getField(field)
|
||||
const { fieldState } = get(fieldInfo)
|
||||
const { value, error } = fieldState
|
||||
|
||||
// Create new validator
|
||||
const schemaConstraints = disableSchemaValidation
|
||||
? null
|
||||
: schema?.[field]?.constraints
|
||||
const validator = createValidatorFromConstraints(
|
||||
schemaConstraints,
|
||||
validationRules,
|
||||
field,
|
||||
definition
|
||||
)
|
||||
|
||||
// Update validator
|
||||
fieldInfo.update(state => {
|
||||
state.fieldState.validator = validator
|
||||
return state
|
||||
})
|
||||
|
||||
// If there is currently an error, run the validator again in case
|
||||
// the error should be cleared by the new validation rules
|
||||
if (error) {
|
||||
setValue(value, true)
|
||||
}
|
||||
}
|
||||
|
||||
// We don't want to actually remove the field state when deregistering, just
|
||||
// remove any errors and validation
|
||||
const deregister = () => {
|
||||
|
@ -370,7 +391,7 @@
|
|||
}
|
||||
|
||||
// Updates the disabled state of a certain field
|
||||
const setDisabled = fieldDisabled => {
|
||||
const setDisabled = (fieldDisabled: boolean) => {
|
||||
const fieldInfo = getField(field)
|
||||
|
||||
// Auto columns are always disabled
|
||||
|
@ -386,7 +407,6 @@
|
|||
return {
|
||||
setValue,
|
||||
reset,
|
||||
updateValidation,
|
||||
setDisabled,
|
||||
deregister,
|
||||
validate: () => {
|
||||
|
@ -412,7 +432,15 @@
|
|||
// register their fields to step 1
|
||||
setContext("form-step", writable(1))
|
||||
|
||||
const handleUpdateFieldValue = ({ type, field, value }) => {
|
||||
const handleUpdateFieldValue = ({
|
||||
type,
|
||||
field,
|
||||
value,
|
||||
}: {
|
||||
type: "set" | "reset"
|
||||
field: string
|
||||
value: any
|
||||
}) => {
|
||||
if (type === "set") {
|
||||
formApi.setFieldValue(field, value)
|
||||
} else {
|
||||
|
@ -420,16 +448,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
const handleScrollToField = ({ field }) => {
|
||||
if (!field.fieldState) {
|
||||
field = get(getField(field))
|
||||
const handleScrollToField = (props: { field: FieldInfo | string }) => {
|
||||
let field
|
||||
if (typeof props.field === "string") {
|
||||
field = get(getField(props.field))
|
||||
} else {
|
||||
field = props.field
|
||||
}
|
||||
const fieldId = field.fieldState.fieldId
|
||||
const fieldElement = document.getElementById(fieldId)
|
||||
if (fieldElement) {
|
||||
fieldElement.focus({ preventScroll: true })
|
||||
}
|
||||
const label = document.querySelector(`label[for="${fieldId}"]`)
|
||||
const label = document.querySelector<HTMLElement>(`label[for="${fieldId}"]`)
|
||||
if (label) {
|
||||
label.style.scrollMargin = "100px"
|
||||
label.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import dayjs from "dayjs"
|
||||
import { FieldTypes } from "../../../constants"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import {
|
||||
FieldConstraints,
|
||||
FieldType,
|
||||
Table,
|
||||
UIFieldValidationRule,
|
||||
} from "@budibase/types"
|
||||
|
||||
/**
|
||||
* Creates a validation function from a combination of schema-level constraints
|
||||
|
@ -12,19 +17,19 @@ import { Helpers } from "@budibase/bbui"
|
|||
* @returns {function} a validator function which accepts test values
|
||||
*/
|
||||
export const createValidatorFromConstraints = (
|
||||
schemaConstraints,
|
||||
customRules,
|
||||
field,
|
||||
definition
|
||||
schemaConstraints: FieldConstraints | null | undefined,
|
||||
customRules: UIFieldValidationRule[],
|
||||
field: string,
|
||||
definition: Table | undefined
|
||||
) => {
|
||||
let rules = []
|
||||
let rules: UIFieldValidationRule[] = []
|
||||
|
||||
// Convert schema constraints into validation rules
|
||||
if (schemaConstraints) {
|
||||
// Required constraint
|
||||
if (
|
||||
field === definition?.primaryDisplay ||
|
||||
schemaConstraints.presence?.allowEmpty === false ||
|
||||
(schemaConstraints.presence as any)?.allowEmpty === false ||
|
||||
schemaConstraints.presence === true
|
||||
) {
|
||||
rules.push({
|
||||
|
@ -106,7 +111,7 @@ export const createValidatorFromConstraints = (
|
|||
rules = rules.concat(customRules || [])
|
||||
|
||||
// Evaluate each constraint
|
||||
return value => {
|
||||
return (value: any) => {
|
||||
for (let rule of rules) {
|
||||
const error = evaluateRule(rule, value)
|
||||
if (error) {
|
||||
|
@ -124,7 +129,7 @@ export const createValidatorFromConstraints = (
|
|||
* @param value the value to validate against
|
||||
* @returns {null|*} an error if validation fails or null if it passes
|
||||
*/
|
||||
const evaluateRule = (rule, value) => {
|
||||
const evaluateRule = (rule: UIFieldValidationRule, value: any) => {
|
||||
if (!rule) {
|
||||
return null
|
||||
}
|
||||
|
@ -150,14 +155,14 @@ const evaluateRule = (rule, value) => {
|
|||
* @param type the type to parse
|
||||
* @returns {boolean|string|*|number|null|array} the parsed value, or null if invalid
|
||||
*/
|
||||
const parseType = (value, type) => {
|
||||
const parseType = (value: any, type: `${FieldType}`) => {
|
||||
// Treat nulls or empty strings as null
|
||||
if (!exists(value) || !type) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Parse as string
|
||||
if (type === FieldTypes.STRING) {
|
||||
if (type === FieldType.STRING) {
|
||||
if (typeof value === "string" || Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
|
@ -168,7 +173,7 @@ const parseType = (value, type) => {
|
|||
}
|
||||
|
||||
// Parse as number
|
||||
if (type === FieldTypes.NUMBER) {
|
||||
if (type === FieldType.NUMBER) {
|
||||
if (isNaN(value)) {
|
||||
return null
|
||||
}
|
||||
|
@ -176,7 +181,7 @@ const parseType = (value, type) => {
|
|||
}
|
||||
|
||||
// Parse as date
|
||||
if (type === FieldTypes.DATETIME) {
|
||||
if (type === FieldType.DATETIME) {
|
||||
if (value instanceof Date) {
|
||||
return value.getTime()
|
||||
}
|
||||
|
@ -185,7 +190,7 @@ const parseType = (value, type) => {
|
|||
}
|
||||
|
||||
// Parse as boolean
|
||||
if (type === FieldTypes.BOOLEAN) {
|
||||
if (type === FieldType.BOOLEAN) {
|
||||
if (typeof value === "string") {
|
||||
return value.toLowerCase() === "true"
|
||||
}
|
||||
|
@ -193,7 +198,7 @@ const parseType = (value, type) => {
|
|||
}
|
||||
|
||||
// Parse attachments, treating no elements as null
|
||||
if (type === FieldTypes.ATTACHMENTS) {
|
||||
if (type === FieldType.ATTACHMENTS) {
|
||||
if (!Array.isArray(value) || !value.length) {
|
||||
return null
|
||||
}
|
||||
|
@ -202,8 +207,8 @@ const parseType = (value, type) => {
|
|||
|
||||
// Parse attachment/signature single, treating no key as null
|
||||
if (
|
||||
type === FieldTypes.ATTACHMENT_SINGLE ||
|
||||
type === FieldTypes.SIGNATURE_SINGLE
|
||||
type === FieldType.ATTACHMENT_SINGLE ||
|
||||
type === FieldType.SIGNATURE_SINGLE
|
||||
) {
|
||||
if (!value?.key) {
|
||||
return null
|
||||
|
@ -212,7 +217,7 @@ const parseType = (value, type) => {
|
|||
}
|
||||
|
||||
// Parse links, treating no elements as null
|
||||
if (type === FieldTypes.LINK) {
|
||||
if (type === FieldType.LINK) {
|
||||
if (!Array.isArray(value) || !value.length) {
|
||||
return null
|
||||
}
|
||||
|
@ -220,7 +225,7 @@ const parseType = (value, type) => {
|
|||
}
|
||||
|
||||
// Parse array, treating no elements as null
|
||||
if (type === FieldTypes.ARRAY) {
|
||||
if (type === FieldType.ARRAY) {
|
||||
if (!Array.isArray(value) || !value.length) {
|
||||
return null
|
||||
}
|
||||
|
@ -229,7 +234,7 @@ const parseType = (value, type) => {
|
|||
|
||||
// For JSON we don't touch the value at all as we want to verify it in its
|
||||
// raw form
|
||||
if (type === FieldTypes.JSON) {
|
||||
if (type === FieldType.JSON) {
|
||||
return value
|
||||
}
|
||||
|
||||
|
@ -238,69 +243,74 @@ const parseType = (value, type) => {
|
|||
}
|
||||
|
||||
// Evaluates a required constraint
|
||||
const requiredHandler = value => {
|
||||
const requiredHandler = (value: any) => {
|
||||
return value != null
|
||||
}
|
||||
|
||||
// Evaluates a min length constraint
|
||||
const minLengthHandler = (value, rule) => {
|
||||
const minLengthHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const limit = parseType(rule.value, "number")
|
||||
return value == null || value.length >= limit
|
||||
}
|
||||
|
||||
// Evaluates a max length constraint
|
||||
const maxLengthHandler = (value, rule) => {
|
||||
const maxLengthHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const limit = parseType(rule.value, "number")
|
||||
return value == null || value.length <= limit
|
||||
}
|
||||
|
||||
// Evaluates a max file size (MB) constraint
|
||||
const maxFileSizeHandler = (value, rule) => {
|
||||
const maxFileSizeHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const limit = parseType(rule.value, "number")
|
||||
const check = attachment => attachment.size / 1000000 > limit
|
||||
const check = (attachment: { size: number }) =>
|
||||
attachment.size / 1000000 > limit
|
||||
return value == null || !(value?.key ? check(value) : value.some(check))
|
||||
}
|
||||
|
||||
// Evaluates a max total upload size (MB) constraint
|
||||
const maxUploadSizeHandler = (value, rule) => {
|
||||
const limit = parseType(rule.value, "number")
|
||||
const maxUploadSizeHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const limit: number = parseType(rule.value, "number")
|
||||
return (
|
||||
value == null ||
|
||||
(value?.key
|
||||
? value.size / 1000000 <= limit
|
||||
: value.reduce((acc, currentItem) => acc + currentItem.size, 0) /
|
||||
: value.reduce(
|
||||
(acc: number, currentItem: { size: number }) =>
|
||||
acc + currentItem.size,
|
||||
0
|
||||
) /
|
||||
1000000 <=
|
||||
limit)
|
||||
)
|
||||
}
|
||||
|
||||
// Evaluates a min value constraint
|
||||
const minValueHandler = (value, rule) => {
|
||||
const minValueHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
// Use same type as the value so that things can be compared
|
||||
const limit = parseType(rule.value, rule.type)
|
||||
return value == null || value >= limit
|
||||
}
|
||||
|
||||
// Evaluates a max value constraint
|
||||
const maxValueHandler = (value, rule) => {
|
||||
const maxValueHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
// Use same type as the value so that things can be compared
|
||||
const limit = parseType(rule.value, rule.type)
|
||||
return value == null || value <= limit
|
||||
}
|
||||
|
||||
// Evaluates an inclusion constraint
|
||||
const inclusionHandler = (value, rule) => {
|
||||
return value == null || rule.value.includes(value)
|
||||
const inclusionHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
return value == null || (rule.value as any).includes(value)
|
||||
}
|
||||
|
||||
// Evaluates an equal constraint
|
||||
const equalHandler = (value, rule) => {
|
||||
const equalHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const ruleValue = parseType(rule.value, rule.type)
|
||||
return value === ruleValue
|
||||
}
|
||||
|
||||
// Evaluates a not equal constraint
|
||||
const notEqualHandler = (value, rule) => {
|
||||
const notEqualHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const ruleValue = parseType(rule.value, rule.type)
|
||||
if (value == null && ruleValue == null) {
|
||||
return true
|
||||
|
@ -309,7 +319,7 @@ const notEqualHandler = (value, rule) => {
|
|||
}
|
||||
|
||||
// Evaluates a regex constraint
|
||||
const regexHandler = (value, rule) => {
|
||||
const regexHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const regex = parseType(rule.value, "string")
|
||||
if (!value) {
|
||||
value = ""
|
||||
|
@ -318,23 +328,23 @@ const regexHandler = (value, rule) => {
|
|||
}
|
||||
|
||||
// Evaluates a not regex constraint
|
||||
const notRegexHandler = (value, rule) => {
|
||||
const notRegexHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
return !regexHandler(value, rule)
|
||||
}
|
||||
|
||||
// Evaluates a contains constraint
|
||||
const containsHandler = (value, rule) => {
|
||||
const containsHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
const expectedValue = parseType(rule.value, "string")
|
||||
return value && value.includes(expectedValue)
|
||||
}
|
||||
|
||||
// Evaluates a not contains constraint
|
||||
const notContainsHandler = (value, rule) => {
|
||||
const notContainsHandler = (value: any, rule: UIFieldValidationRule) => {
|
||||
return !containsHandler(value, rule)
|
||||
}
|
||||
|
||||
// Evaluates a constraint that the value must be a valid json object
|
||||
const jsonHandler = value => {
|
||||
const jsonHandler = (value: any) => {
|
||||
if (typeof value !== "object" || Array.isArray(value)) {
|
||||
return false
|
||||
}
|
||||
|
@ -372,6 +382,6 @@ const handlerMap = {
|
|||
* @param value the value to test
|
||||
* @returns {boolean} whether the value exists or not
|
||||
*/
|
||||
const exists = value => {
|
||||
const exists = <T = any>(value: T | null | undefined): value is T => {
|
||||
return value != null && value !== ""
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { Writable } from "svelte"
|
||||
import { Component, FieldGroupContext, FormContext } from "@/types"
|
||||
import { Readable } from "svelte/store"
|
||||
import { Component, Context, FieldGroupContext, FormContext } from "@/types"
|
||||
import { SDK } from "@/index.ts"
|
||||
|
||||
declare module "svelte" {
|
||||
export function getContext(key: "sdk"): SDK
|
||||
export function getContext(key: "component"): Component
|
||||
export function getContext(key: "context"): Readable<Record<string, any>>
|
||||
export function getContext(key: "current-step"): Writable<number>
|
||||
export function getContext(key: "context"): Context
|
||||
export function getContext(key: "form"): FormContext | undefined
|
||||
export function getContext(key: "form-step"): Writable<number> | undefined
|
||||
export function getContext(key: "field-group"): FieldGroupContext | undefined
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
} from "@/stores"
|
||||
import { get } from "svelte/store"
|
||||
import { initWebsocket } from "@/websocket"
|
||||
import { Readable } from "svelte/store"
|
||||
import {
|
||||
Screen,
|
||||
Theme,
|
||||
|
@ -27,6 +26,8 @@ import {
|
|||
Snippet,
|
||||
UIComponentError,
|
||||
CustomComponent,
|
||||
Table,
|
||||
DataFetchDatasource,
|
||||
} from "@budibase/types"
|
||||
import { ActionTypes } from "@/constants"
|
||||
import { APIClient } from "@budibase/frontend-core"
|
||||
|
@ -75,14 +76,13 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
export type Context = Readable<Record<string, any>>
|
||||
|
||||
export interface SDK {
|
||||
API: APIClient
|
||||
styleable: any
|
||||
Provider: any
|
||||
ActionTypes: typeof ActionTypes
|
||||
fetchDatasourceSchema: any
|
||||
fetchDatasourceDefinition: (datasource: DataFetchDatasource) => Promise<Table>
|
||||
generateGoldenSample: any
|
||||
builderStore: typeof builderStore
|
||||
authStore: typeof authStore
|
||||
|
|
|
@ -6,4 +6,5 @@ export type Component = Readable<{
|
|||
styles: any
|
||||
editing: boolean
|
||||
errorState: boolean
|
||||
path: string[]
|
||||
}>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { Readable } from "svelte/store"
|
||||
|
||||
export * from "./components"
|
||||
export * from "./fields"
|
||||
export * from "./forms"
|
||||
|
||||
export type Context = Readable<Record<string, any>>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { User } from "@budibase/types"
|
|||
* @param key the key
|
||||
* @return the value or null if a value was not found for this key
|
||||
*/
|
||||
export const deepGet = (obj: { [x: string]: any }, key: string) => {
|
||||
export const deepGet = (obj: Record<string, any> | undefined, key: string) => {
|
||||
if (!obj || !key) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
FieldConstraints,
|
||||
type FieldSchema,
|
||||
type FormulaResponseType,
|
||||
} from "../"
|
||||
} from "../../"
|
||||
|
||||
export interface UIField {
|
||||
name: string
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./fields"
|
||||
export * from "./validationRules"
|
|
@ -0,0 +1,25 @@
|
|||
import { FieldType } from "../../documents"
|
||||
|
||||
export interface UIFieldValidationRule {
|
||||
type: `${FieldType}`
|
||||
constraint: FieldValidationRuleType
|
||||
value?: string | number | string[]
|
||||
error: string
|
||||
}
|
||||
|
||||
export type FieldValidationRuleType =
|
||||
| "required"
|
||||
| "minLength"
|
||||
| "maxLength"
|
||||
| "minValue"
|
||||
| "maxValue"
|
||||
| "inclusion"
|
||||
| "equal"
|
||||
| "notEqual"
|
||||
| "regex"
|
||||
| "notRegex"
|
||||
| "contains"
|
||||
| "notContains"
|
||||
| "json"
|
||||
| "maxFileSize"
|
||||
| "maxUploadSize"
|
Loading…
Reference in New Issue