Skip to content

Commit b063fab

Browse files
authored
Merge pull request #15706 from Budibase/BUDI-9077/type-fields
Type fields
2 parents 12e87f0 + 6092d57 commit b063fab

File tree

11 files changed

+153
-80
lines changed

11 files changed

+153
-80
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
1-
<script>
1+
<script lang="ts">
22
import { sdk } from "@budibase/shared-core"
33
import { FieldType } from "@budibase/types"
44
import RelationshipField from "./RelationshipField.svelte"
55
6-
export let defaultValue
6+
export let defaultValue: string
77
export let type = FieldType.BB_REFERENCE
88
9-
function updateUserIDs(value) {
9+
function updateUserIDs(value: string | string[]) {
1010
if (Array.isArray(value)) {
11-
return value.map(val => sdk.users.getGlobalUserID(val))
11+
return value.map(val => sdk.users.getGlobalUserID(val)!)
1212
} else {
1313
return sdk.users.getGlobalUserID(value)
1414
}
1515
}
1616
17-
function updateReferences(value) {
17+
function updateReferences(value: string) {
1818
if (sdk.users.containsUserID(value)) {
1919
return updateUserIDs(value)
2020
}
2121
return value
2222
}
23+
24+
$: updatedDefaultValue = updateReferences(defaultValue)
25+
26+
// This cannot be typed, as svelte does not provide typed inheritance
27+
$: allProps = $$props as any
2328
</script>
2429

2530
<RelationshipField
26-
{...$$props}
31+
{...allProps}
2732
{type}
2833
datasourceType={"user"}
2934
primaryDisplay={"email"}
30-
defaultValue={updateReferences(defaultValue)}
35+
defaultValue={updatedDefaultValue}
3136
/>

packages/client/src/components/app/forms/Field.svelte

+45-22
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,60 @@
11
<script lang="ts">
22
import { getContext, onDestroy } from "svelte"
3+
import type { Readable } from "svelte/store"
34
import { writable } from "svelte/store"
45
import { Icon } from "@budibase/bbui"
56
import { memo } from "@budibase/frontend-core"
67
import Placeholder from "../Placeholder.svelte"
78
import InnerForm from "./InnerForm.svelte"
8-
import type { FieldApi } from "."
9+
import type { FieldSchema, FieldType } from "@budibase/types"
10+
import type {
11+
FieldApi,
12+
FieldState,
13+
FieldValidation,
14+
FormField,
15+
} from "@/types"
16+
17+
interface FieldInfo {
18+
field: string
19+
type: FieldType
20+
defaultValue: string | undefined
21+
disabled: boolean
22+
readonly: boolean
23+
validation?: FieldValidation
24+
formStep: number
25+
}
926
1027
export let label: string | undefined = undefined
1128
export let field: string | undefined = undefined
12-
export let fieldState: any
13-
export let fieldApi: FieldApi
14-
export let fieldSchema: any
29+
export let fieldState: FieldState | undefined
30+
export let fieldApi: FieldApi | undefined
31+
export let fieldSchema: FieldSchema | undefined
1532
export let defaultValue: string | undefined = undefined
16-
export let type: any
33+
export let type: FieldType
1734
export let disabled = false
1835
export let readonly = false
19-
export let validation: any
36+
export let validation: FieldValidation | undefined
2037
export let span = 6
2138
export let helpText: string | undefined = undefined
2239
2340
// Get contexts
24-
const formContext: any = getContext("form")
25-
const formStepContext: any = getContext("form-step")
26-
const fieldGroupContext: any = getContext("field-group")
41+
const formContext = getContext("form")
42+
const formStepContext = getContext("form-step")
43+
const fieldGroupContext = getContext("field-group")
2744
const { styleable, builderStore, Provider } = getContext("sdk")
28-
const component: any = getContext("component")
45+
const component = getContext("component")
2946
3047
// Register field with form
3148
const formApi = formContext?.formApi
3249
const labelPos = fieldGroupContext?.labelPosition || "above"
3350
34-
let formField: any
51+
let formField: Readable<FormField> | undefined
3552
let touched = false
36-
let labelNode: any
53+
let labelNode: HTMLElement | undefined
3754
3855
// Memoize values required to register the field to avoid loops
3956
const formStep = formStepContext || writable(1)
40-
const fieldInfo = memo({
57+
const fieldInfo = memo<FieldInfo>({
4158
field: field || $component.name,
4259
type,
4360
defaultValue,
@@ -66,16 +83,22 @@
6683
$: $component.editing && labelNode?.focus()
6784
6885
// Update form properties in parent component on every store change
69-
$: unsubscribe = formField?.subscribe((value: any) => {
70-
fieldState = value?.fieldState
71-
fieldApi = value?.fieldApi
72-
fieldSchema = value?.fieldSchema
73-
})
86+
$: unsubscribe = formField?.subscribe(
87+
(value?: {
88+
fieldState: FieldState
89+
fieldApi: FieldApi
90+
fieldSchema: FieldSchema
91+
}) => {
92+
fieldState = value?.fieldState
93+
fieldApi = value?.fieldApi
94+
fieldSchema = value?.fieldSchema
95+
}
96+
)
7497
7598
// Determine label class from position
7699
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
77100
78-
const registerField = (info: any) => {
101+
const registerField = (info: FieldInfo) => {
79102
formField = formApi?.registerField(
80103
info.field,
81104
info.type,
@@ -87,10 +110,10 @@
87110
)
88111
}
89112
90-
const updateLabel = (e: any) => {
113+
const updateLabel = (e: Event) => {
91114
if (touched) {
92-
// @ts-expect-error and TODO updateProp isn't recognised - need builder TS conversion
93-
builderStore.actions.updateProp("label", e.target.textContent)
115+
const label = e.target as HTMLLabelElement
116+
builderStore.actions.updateProp("label", label.textContent)
94117
}
95118
touched = false
96119
}

packages/client/src/components/app/forms/RelationshipField.svelte

+11-9
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@
99
RelationshipFieldMetadata,
1010
Row,
1111
} from "@budibase/types"
12-
import type { FieldApi, FieldState } from "."
12+
import type { FieldApi, FieldState, FieldValidation } from "@/types"
13+
14+
type ValueType = string | string[]
1315
1416
export let field: string | undefined = undefined
1517
export let label: string | undefined = undefined
1618
export let placeholder: string | undefined = undefined
1719
export let disabled: boolean = false
1820
export let readonly: boolean = false
19-
export let validation: any
21+
export let validation: FieldValidation | undefined = undefined
2022
export let autocomplete: boolean = true
21-
export let defaultValue: string | string[] | undefined = undefined
22-
export let onChange: any
23+
export let defaultValue: ValueType | undefined = undefined
24+
export let onChange: (_props: { value: ValueType }) => void
2325
export let filter: SearchFilter[]
2426
export let datasourceType: "table" | "user" = "table"
2527
export let primaryDisplay: string | undefined = undefined
@@ -88,14 +90,14 @@
8890
// Ensure backwards compatibility
8991
$: enrichedDefaultValue = enrichDefaultValue(defaultValue)
9092
93+
$: emptyValue = multiselect ? [] : undefined
9194
// We need to cast value to pass it down, as those components aren't typed
92-
$: emptyValue = multiselect ? [] : null
93-
$: displayValue = missingIDs.length ? emptyValue : (selectedValue as any)
95+
$: displayValue = (missingIDs.length ? emptyValue : selectedValue) as any
9496
9597
// Ensures that we flatten any objects so that only the IDs of the selected
9698
// rows are passed down. Not sure how this can be an object to begin with?
9799
const parseSelectedValue = (
98-
value: any,
100+
value: ValueType | undefined,
99101
multiselect: boolean
100102
): undefined | string | string[] => {
101103
return multiselect ? flatten(value) : flatten(value)[0]
@@ -140,7 +142,7 @@
140142
141143
// Builds a map of all available options, in a consistent structure
142144
const processOptions = (
143-
realValue: any | any[],
145+
realValue: ValueType | undefined,
144146
rows: Row[],
145147
primaryDisplay?: string
146148
) => {
@@ -171,7 +173,7 @@
171173
172174
// Parses a row-like structure into a properly shaped option
173175
const parseOption = (
174-
option: any | BasicRelatedRow | Row,
176+
option: string | BasicRelatedRow | Row,
175177
primaryDisplay?: string
176178
): BasicRelatedRow | null => {
177179
if (!option || typeof option !== "object" || !option?._id) {

packages/client/src/components/app/forms/index.ts

-12
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,3 @@ export { default as codescanner } from "./CodeScannerField.svelte"
1919
export { default as signaturesinglefield } from "./SignatureField.svelte"
2020
export { default as bbreferencefield } from "./BBReferenceField.svelte"
2121
export { default as bbreferencesinglefield } from "./BBReferenceSingleField.svelte"
22-
23-
export interface FieldApi {
24-
setValue(value: any): boolean
25-
deregister(): void
26-
}
27-
28-
export interface FieldState<T> {
29-
value: T
30-
fieldId: string
31-
disabled: boolean
32-
readonly: boolean
33-
}

packages/client/src/context.d.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { Component, Context, SDK } from "."
1+
import { Writable } from "svelte"
2+
import { Component, FieldGroupContext, FormContext, SDK } from "@/types"
3+
import { Readable } from "svelte/store"
24

35
declare module "svelte" {
46
export function getContext(key: "sdk"): SDK
57
export function getContext(key: "component"): Component
6-
export function getContext(key: "context"): Context
8+
export function getContext(key: "context"): Readable<Record<string, any>>
9+
export function getContext(key: "form"): FormContext | undefined
10+
export function getContext(key: "form-step"): Writable<number> | undefined
11+
export function getContext(key: "field-group"): FieldGroupContext | undefined
712
}

packages/client/src/index.ts

+1-28
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import {
1414
} from "@/stores"
1515
import { get } from "svelte/store"
1616
import { initWebsocket } from "@/websocket"
17-
import { APIClient } from "@budibase/frontend-core"
18-
import type { ActionTypes } from "@/constants"
1917
import { Readable } from "svelte/store"
2018
import {
2119
Screen,
@@ -39,6 +37,7 @@ window.svelte = svelte
3937
// Initialise spectrum icons
4038
// eslint-disable-next-line local-rules/no-budibase-imports
4139
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
40+
4241
loadSpectrumIcons()
4342

4443
// Extend global window scope
@@ -73,32 +72,6 @@ declare global {
7372
}
7473
}
7574

76-
export interface SDK {
77-
API: APIClient
78-
styleable: any
79-
Provider: any
80-
ActionTypes: typeof ActionTypes
81-
fetchDatasourceSchema: any
82-
generateGoldenSample: any
83-
builderStore: Readable<{
84-
inBuilder: boolean
85-
}> & {
86-
actions: {
87-
highlightSetting: (key: string) => void
88-
addParentComponent: (
89-
componentId: string,
90-
fullAncestorType: string
91-
) => void
92-
}
93-
}
94-
}
95-
96-
export type Component = Readable<{
97-
id: string
98-
styles: any
99-
errorState: boolean
100-
}>
101-
10275
export type Context = Readable<Record<string, any>>
10376

10477
let app: ClientApp
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Readable } from "svelte/store"
2+
3+
export type Component = Readable<{
4+
id: string
5+
name: string
6+
styles: any
7+
editing: boolean
8+
errorState: boolean
9+
}>

packages/client/src/types/fields.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface FieldGroupContext {
2+
labelPosition: string
3+
}

packages/client/src/types/forms.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Readable } from "svelte/store"
2+
import { FieldSchema, FieldType } from "@budibase/types"
3+
4+
export interface FormContext {
5+
formApi?: {
6+
registerField: (
7+
field: string,
8+
type: FieldType,
9+
defaultValue: string | undefined,
10+
disabled: boolean,
11+
readonly: boolean,
12+
validation: FieldValidation | undefined,
13+
formStep: number
14+
) => Readable<FormField>
15+
}
16+
}
17+
18+
export type FieldValidation = () => string | undefined
19+
20+
export interface FormField {
21+
fieldState: FieldState
22+
fieldApi: FieldApi
23+
fieldSchema: FieldSchema
24+
}
25+
26+
export interface FieldApi {
27+
setValue(value: any): boolean
28+
deregister(): void
29+
}
30+
31+
export interface FieldState<T = any> {
32+
value: T
33+
fieldId: string
34+
disabled: boolean
35+
readonly: boolean
36+
error?: string
37+
}

packages/client/src/types/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from "./components"
2+
export * from "./fields"
3+
export * from "./forms"
4+
export * from "./sdk"

packages/client/src/types/sdk.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ActionTypes } from "@/constants"
2+
import { APIClient } from "@budibase/frontend-core"
3+
import { Readable } from "svelte/store"
4+
5+
export interface SDK {
6+
API: APIClient
7+
styleable: any
8+
Provider: any
9+
ActionTypes: typeof ActionTypes
10+
fetchDatasourceSchema: any
11+
generateGoldenSample: any
12+
builderStore: Readable<{
13+
inBuilder: boolean
14+
}> & {
15+
actions: {
16+
highlightSetting: (key: string) => void
17+
addParentComponent: (
18+
componentId: string,
19+
fullAncestorType: string
20+
) => void
21+
updateProp: (key: string, value: any) => void
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)