@morscherlab/mld-sdk 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
  2. package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
  3. package/dist/components/BaseButton.vue.d.ts +1 -1
  4. package/dist/components/BasePill.vue.d.ts +1 -1
  5. package/dist/components/DropdownButton.vue.d.ts +1 -1
  6. package/dist/components/FormActions.vue.d.ts +33 -0
  7. package/dist/components/FormActions.vue.js +76 -0
  8. package/dist/components/FormActions.vue.js.map +1 -0
  9. package/dist/components/FormActions.vue3.js +6 -0
  10. package/dist/components/FormActions.vue3.js.map +1 -0
  11. package/dist/components/FormBuilder.vue.js +205 -0
  12. package/dist/components/FormBuilder.vue.js.map +1 -0
  13. package/dist/components/FormBuilder.vue3.js +6 -0
  14. package/dist/components/FormBuilder.vue3.js.map +1 -0
  15. package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
  16. package/dist/components/FormFieldRenderer.vue.js +48 -0
  17. package/dist/components/FormFieldRenderer.vue.js.map +1 -0
  18. package/dist/components/FormFieldRenderer.vue2.js +5 -0
  19. package/dist/components/FormFieldRenderer.vue2.js.map +1 -0
  20. package/dist/components/FormSection.vue.d.ts +43 -0
  21. package/dist/components/FormSection.vue.js +117 -0
  22. package/dist/components/FormSection.vue.js.map +1 -0
  23. package/dist/components/FormSection.vue3.js +6 -0
  24. package/dist/components/FormSection.vue3.js.map +1 -0
  25. package/dist/components/IconButton.vue.d.ts +1 -1
  26. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  27. package/dist/components/ProgressBar.vue.d.ts +1 -1
  28. package/dist/components/ReagentList.vue.d.ts +2 -2
  29. package/dist/components/ResourceCard.vue.d.ts +1 -1
  30. package/dist/components/SegmentedControl.vue.d.ts +1 -1
  31. package/dist/components/WellEditPopup.vue.d.ts +2 -2
  32. package/dist/components/index.d.ts +4 -0
  33. package/dist/components/index.js +19 -8
  34. package/dist/components/index.js.map +1 -1
  35. package/dist/composables/formBuilderRegistry.d.ts +13 -0
  36. package/dist/composables/formBuilderRegistry.js +87 -0
  37. package/dist/composables/formBuilderRegistry.js.map +1 -0
  38. package/dist/composables/index.d.ts +2 -0
  39. package/dist/composables/index.js +6 -0
  40. package/dist/composables/index.js.map +1 -1
  41. package/dist/composables/useFormBuilder.d.ts +23 -0
  42. package/dist/composables/useFormBuilder.js +264 -0
  43. package/dist/composables/useFormBuilder.js.map +1 -0
  44. package/dist/styles.css +239 -4
  45. package/dist/types/form-builder.d.ts +167 -0
  46. package/dist/types/index.d.ts +1 -0
  47. package/package.json +1 -1
  48. package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
  49. package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
  50. package/src/components/FormActions.vue +92 -0
  51. package/src/components/FormBuilder.vue +214 -0
  52. package/src/components/FormFieldRenderer.vue +58 -0
  53. package/src/components/FormSection.vue +90 -0
  54. package/src/components/index.ts +6 -0
  55. package/src/composables/formBuilderRegistry.ts +79 -0
  56. package/src/composables/index.ts +6 -0
  57. package/src/composables/useFormBuilder.ts +382 -0
  58. package/src/styles/components/app-container.css +1 -0
  59. package/src/styles/components/app-layout.css +1 -2
  60. package/src/styles/components/form-builder.css +69 -0
  61. package/src/styles/index.css +1 -0
  62. package/src/types/form-builder.ts +197 -0
  63. package/src/types/index.ts +14 -0
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Action bar rendered at the bottom of a FormBuilder.
4
+ *
5
+ * Supports two modes:
6
+ * - Flat form: shows an optional Cancel button and a Submit button.
7
+ * - Wizard (`isWizard=true`): shows Back / Next on intermediate steps and
8
+ * Submit only on the last step. Back is hidden on the first step.
9
+ *
10
+ * The `canProceed` prop gates the primary action (Next or Submit) to prevent
11
+ * advancing when the current step has validation errors.
12
+ */
13
+ import BaseButton from './BaseButton.vue'
14
+
15
+ interface Props {
16
+ isWizard?: boolean
17
+ isFirst?: boolean
18
+ isLast?: boolean
19
+ canProceed?: boolean
20
+ loading?: boolean
21
+ disabled?: boolean
22
+ submitLabel?: string
23
+ cancelLabel?: string
24
+ showCancel?: boolean
25
+ }
26
+
27
+ withDefaults(defineProps<Props>(), {
28
+ isWizard: false,
29
+ isFirst: true,
30
+ isLast: true,
31
+ canProceed: true,
32
+ loading: false,
33
+ disabled: false,
34
+ submitLabel: 'Submit',
35
+ cancelLabel: 'Cancel',
36
+ showCancel: false,
37
+ })
38
+
39
+ const emit = defineEmits<{
40
+ submit: []
41
+ cancel: []
42
+ back: []
43
+ next: []
44
+ }>()
45
+ </script>
46
+
47
+ <template>
48
+ <div class="mld-form-actions">
49
+ <BaseButton
50
+ v-if="showCancel"
51
+ variant="ghost"
52
+ :disabled="loading || disabled"
53
+ @click="emit('cancel')"
54
+ >
55
+ {{ cancelLabel }}
56
+ </BaseButton>
57
+
58
+ <div style="flex: 1" />
59
+
60
+ <BaseButton
61
+ v-if="isWizard && !isFirst"
62
+ variant="secondary"
63
+ :disabled="loading || disabled"
64
+ @click="emit('back')"
65
+ >
66
+ Back
67
+ </BaseButton>
68
+
69
+ <BaseButton
70
+ v-if="isWizard && !isLast"
71
+ variant="primary"
72
+ :disabled="!canProceed || loading || disabled"
73
+ @click="emit('next')"
74
+ >
75
+ Next
76
+ </BaseButton>
77
+
78
+ <BaseButton
79
+ v-if="!isWizard || isLast"
80
+ variant="primary"
81
+ :loading="loading"
82
+ :disabled="!canProceed || loading || disabled"
83
+ @click="emit('submit')"
84
+ >
85
+ {{ submitLabel }}
86
+ </BaseButton>
87
+ </div>
88
+ </template>
89
+
90
+ <style>
91
+ @import '../styles/components/form-builder.css';
92
+ </style>
@@ -0,0 +1,214 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Schema-driven form component that renders flat or multi-step wizard forms.
4
+ *
5
+ * Pass a `FormSchema` with either `sections` (flat) or `steps` (wizard).
6
+ * Use `v-model` for two-way binding of the form data; the `submit` event
7
+ * carries only the data for currently-visible fields. Supply `enhancements`
8
+ * for dynamic options, custom validators, a submit handler, and field-change
9
+ * callbacks that cannot be expressed in JSON.
10
+ *
11
+ * Exposes `form`, `validate`, `reset`, `goNext`, `goBack`, `goToStep`, and
12
+ * `builder` for imperative control via template refs.
13
+ *
14
+ * Slots:
15
+ * - `field:<name>` — override a single field's input component
16
+ * - `section:<id>` — replace an entire section body
17
+ * - `section:<id>:after` — inject content after a section
18
+ * - `actions` — replace the default FormActions bar
19
+ */
20
+ import { ref, computed, watch } from 'vue'
21
+ import type { FormSchema, FormEnhancements, UseFormBuilderReturn } from '../types/form-builder'
22
+ import type { WizardStep } from '../types'
23
+ import { useFormBuilder } from '../composables/useFormBuilder'
24
+ import StepWizard from './StepWizard.vue'
25
+ import FormSection from './FormSection.vue'
26
+ import FormActions from './FormActions.vue'
27
+
28
+ interface Props {
29
+ schema: FormSchema
30
+ modelValue?: Record<string, unknown>
31
+ enhancements?: FormEnhancements<Record<string, unknown>>
32
+ loading?: boolean
33
+ disabled?: boolean
34
+ size?: 'sm' | 'md' | 'lg'
35
+ readonly?: boolean
36
+ }
37
+
38
+ const props = withDefaults(defineProps<Props>(), {
39
+ loading: false,
40
+ disabled: false,
41
+ readonly: false,
42
+ })
43
+
44
+ const emit = defineEmits<{
45
+ 'update:modelValue': [data: Record<string, unknown>]
46
+ submit: [data: Record<string, unknown>]
47
+ cancel: []
48
+ }>()
49
+
50
+ const builder = useFormBuilder(
51
+ props.schema,
52
+ props.modelValue,
53
+ props.enhancements,
54
+ )
55
+
56
+ // Sync modelValue changes back to parent
57
+ watch(
58
+ () => ({ ...builder.form.data }),
59
+ (data) => emit('update:modelValue', data as Record<string, unknown>),
60
+ { deep: true },
61
+ )
62
+
63
+ // Wizard support
64
+ const isWizard = computed(() => !!props.schema.steps)
65
+ const wizardRef = ref<InstanceType<typeof StepWizard> | null>(null)
66
+
67
+ const wizardSteps = computed<WizardStep[]>(() => {
68
+ if (!props.schema.steps) return []
69
+ return props.schema.steps.map((step) => ({
70
+ id: step.id,
71
+ label: step.label,
72
+ description: step.description,
73
+ icon: step.icon,
74
+ optional: step.optional,
75
+ }))
76
+ })
77
+
78
+ // Sync step validation state with StepWizard
79
+ watch(
80
+ () => builder.isCurrentStepValid.value,
81
+ (valid) => {
82
+ wizardRef.value?.setStepValid(builder.currentStep.value, valid)
83
+ },
84
+ )
85
+
86
+ function handleNext() {
87
+ const success = builder.goNext()
88
+ if (success) {
89
+ wizardRef.value?.setStepValid(builder.currentStep.value - 1, true)
90
+ }
91
+ }
92
+
93
+ async function handleSubmit() {
94
+ await builder.submit()
95
+ const formData = builder.form.data as Record<string, unknown>
96
+ const visibleData = Object.fromEntries(
97
+ builder.fields
98
+ .filter((f) => builder.isFieldVisible(f.name))
99
+ .map((f) => [f.name, formData[f.name]]),
100
+ )
101
+ emit('submit', visibleData)
102
+ }
103
+
104
+ function handleCancel() {
105
+ emit('cancel')
106
+ }
107
+
108
+ defineExpose({
109
+ form: builder.form,
110
+ validate: builder.validate,
111
+ reset: builder.reset,
112
+ goNext: builder.goNext,
113
+ goBack: builder.goBack,
114
+ goToStep: builder.goToStep,
115
+ builder,
116
+ })
117
+ </script>
118
+
119
+ <template>
120
+ <div :class="['mld-form-builder', size ? `mld-form-builder--${size}` : '']">
121
+ <!-- Wizard mode -->
122
+ <template v-if="isWizard && schema.steps">
123
+ <StepWizard
124
+ ref="wizardRef"
125
+ :steps="wizardSteps"
126
+ :model-value="builder.currentStep.value"
127
+ @update:model-value="builder.goToStep($event)"
128
+ >
129
+ <template v-for="step in schema.steps" :key="step.id" #[`step-${step.id}`]>
130
+ <div class="mld-form-builder__step">
131
+ <template v-for="section in step.sections" :key="section.id">
132
+ <FormSection
133
+ v-if="builder.isSectionVisible(section.id)"
134
+ :section="section"
135
+ :builder="(builder as UseFormBuilderReturn<Record<string, unknown>>)"
136
+ >
137
+ <!-- Forward field slots -->
138
+ <template v-for="field in section.fields" :key="field.name" #[`field:${field.name}`]="slotProps">
139
+ <slot :name="`field:${field.name}`" v-bind="slotProps" />
140
+ </template>
141
+ <!-- Forward section slots -->
142
+ <template #[`section:${section.id}`]="slotProps">
143
+ <slot :name="`section:${section.id}`" v-bind="slotProps" />
144
+ </template>
145
+ <template #[`section:${section.id}:after`]="slotProps">
146
+ <slot :name="`section:${section.id}:after`" v-bind="slotProps" />
147
+ </template>
148
+ </FormSection>
149
+ </template>
150
+ </div>
151
+ </template>
152
+
153
+ <template #navigation="{ isFirst, isLast }">
154
+ <slot name="actions" :form="builder.form" :builder="builder">
155
+ <FormActions
156
+ is-wizard
157
+ :is-first="isFirst"
158
+ :is-last="isLast"
159
+ :can-proceed="builder.isCurrentStepValid.value"
160
+ :loading="loading || builder.form.isSubmitting.value"
161
+ :disabled="disabled"
162
+ :submit-label="schema.submitLabel ?? 'Submit'"
163
+ :cancel-label="schema.cancelLabel ?? 'Cancel'"
164
+ :show-cancel="schema.showCancel ?? false"
165
+ @next="handleNext"
166
+ @back="builder.goBack"
167
+ @submit="handleSubmit"
168
+ @cancel="handleCancel"
169
+ />
170
+ </slot>
171
+ </template>
172
+ </StepWizard>
173
+ </template>
174
+
175
+ <!-- Flat mode -->
176
+ <template v-else-if="schema.sections">
177
+ <template v-for="section in schema.sections" :key="section.id">
178
+ <FormSection
179
+ v-if="builder.isSectionVisible(section.id)"
180
+ :section="section"
181
+ :builder="(builder as UseFormBuilderReturn<Record<string, unknown>>)"
182
+ >
183
+ <!-- Forward field slots -->
184
+ <template v-for="field in section.fields" :key="field.name" #[`field:${field.name}`]="slotProps">
185
+ <slot :name="`field:${field.name}`" v-bind="slotProps" />
186
+ </template>
187
+ <!-- Forward section slots -->
188
+ <template #[`section:${section.id}`]="slotProps">
189
+ <slot :name="`section:${section.id}`" v-bind="slotProps" />
190
+ </template>
191
+ <template #[`section:${section.id}:after`]="slotProps">
192
+ <slot :name="`section:${section.id}:after`" v-bind="slotProps" />
193
+ </template>
194
+ </FormSection>
195
+ </template>
196
+
197
+ <slot name="actions" :form="builder.form" :builder="builder">
198
+ <FormActions
199
+ :loading="loading || builder.form.isSubmitting.value"
200
+ :disabled="disabled"
201
+ :submit-label="schema.submitLabel ?? 'Submit'"
202
+ :cancel-label="schema.cancelLabel ?? 'Cancel'"
203
+ :show-cancel="schema.showCancel ?? false"
204
+ @submit="handleSubmit"
205
+ @cancel="handleCancel"
206
+ />
207
+ </slot>
208
+ </template>
209
+ </div>
210
+ </template>
211
+
212
+ <style>
213
+ @import '../styles/components/form-builder.css';
214
+ </style>
@@ -0,0 +1,58 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Renders a single form field inside a FormField wrapper.
4
+ *
5
+ * Resolves the SDK component for the field type from the registry, shows
6
+ * validation errors only after the field has been touched, and handles the
7
+ * FileUploader's event-based upload pattern via `handleUpload`. Consumers can
8
+ * override rendering via the `field:<name>` slot.
9
+ */
10
+ import { computed } from 'vue'
11
+ import type { FormFieldSchema } from '../types/form-builder'
12
+ import type { UseFormReturn } from '../composables/useForm'
13
+ import { getFieldRegistryEntry } from '../composables/formBuilderRegistry'
14
+ import FormField from './FormField.vue'
15
+
16
+ interface Props {
17
+ field: FormFieldSchema
18
+ resolvedProps: Record<string, unknown>
19
+ form: UseFormReturn<Record<string, unknown>>
20
+ }
21
+
22
+ const props = defineProps<Props>()
23
+
24
+ const entry = computed(() => getFieldRegistryEntry(props.field.type))
25
+
26
+ const errorMessage = computed(() => {
27
+ const name = props.field.name
28
+ return props.form.touched[name] ? props.form.errors[name] : null
29
+ })
30
+
31
+ function handleUpload(files: File[]) {
32
+ props.form.setFieldValue(props.field.name, files)
33
+ }
34
+ </script>
35
+
36
+ <template>
37
+ <FormField
38
+ :label="field.label"
39
+ :error="errorMessage ?? undefined"
40
+ :hint="field.hint"
41
+ :required="!!field.validation?.required"
42
+ :html-for="field.name"
43
+ >
44
+ <slot :name="`field:${field.name}`" :field="field" :form="form" :field-props="form.getFieldProps(field.name)">
45
+ <component
46
+ :is="entry.component"
47
+ v-if="entry.vModel"
48
+ v-bind="resolvedProps"
49
+ />
50
+ <component
51
+ :is="entry.component"
52
+ v-else
53
+ v-bind="resolvedProps"
54
+ @upload="handleUpload"
55
+ />
56
+ </slot>
57
+ </FormField>
58
+ </template>
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Renders a single `FormSectionSchema` inside a FormBuilder.
4
+ *
5
+ * Filters out hidden fields before rendering, applies the section's column
6
+ * grid and per-field `colSpan`, and switches between a collapsible
7
+ * (`CollapsibleCard`) and a static layout based on `section.collapsible`.
8
+ * The entire section is hidden when all its fields are hidden. Consumers can
9
+ * replace the section body via `section:<id>` or inject content after it via
10
+ * `section:<id>:after`, and override individual fields via `field:<name>`.
11
+ */
12
+ import { computed } from 'vue'
13
+ import type { FormSectionSchema, UseFormBuilderReturn } from '../types/form-builder'
14
+ import CollapsibleCard from './CollapsibleCard.vue'
15
+ import FormFieldRenderer from './FormFieldRenderer.vue'
16
+
17
+ interface Props {
18
+ section: FormSectionSchema
19
+ builder: UseFormBuilderReturn<Record<string, unknown>>
20
+ }
21
+
22
+ const props = defineProps<Props>()
23
+
24
+ const visibleFields = computed(() =>
25
+ props.section.fields.filter((f) => props.builder.isFieldVisible(f.name)),
26
+ )
27
+
28
+ const gridStyle = computed(() => ({
29
+ gridTemplateColumns: `repeat(${props.section.columns ?? 1}, 1fr)`,
30
+ }))
31
+ </script>
32
+
33
+ <template>
34
+ <div v-if="visibleFields.length > 0" class="mld-form-section">
35
+ <slot :name="`section:${section.id}`" :section="section" :form="builder.form">
36
+ <!-- Collapsible variant -->
37
+ <CollapsibleCard
38
+ v-if="section.collapsible"
39
+ :title="section.title"
40
+ :subtitle="section.description"
41
+ :default-open="section.defaultOpen ?? true"
42
+ >
43
+ <div class="mld-form-section__grid" :style="gridStyle">
44
+ <div
45
+ v-for="field in visibleFields"
46
+ :key="field.name"
47
+ :style="field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined"
48
+ >
49
+ <slot :name="`field:${field.name}`" :field="field" :form="builder.form" :field-props="builder.form.getFieldProps(field.name)">
50
+ <FormFieldRenderer
51
+ :field="field"
52
+ :resolved-props="builder.getResolvedFieldProps(field)"
53
+ :form="builder.form"
54
+ />
55
+ </slot>
56
+ </div>
57
+ </div>
58
+ </CollapsibleCard>
59
+
60
+ <!-- Static variant -->
61
+ <div v-else class="mld-form-section--static">
62
+ <div v-if="section.title || section.description" class="mld-form-section__header">
63
+ <h3 v-if="section.title" class="mld-form-section__title">{{ section.title }}</h3>
64
+ <p v-if="section.description" class="mld-form-section__description">{{ section.description }}</p>
65
+ </div>
66
+ <div class="mld-form-section__grid" :style="gridStyle">
67
+ <div
68
+ v-for="field in visibleFields"
69
+ :key="field.name"
70
+ :style="field.colSpan ? { gridColumn: `span ${field.colSpan}` } : undefined"
71
+ >
72
+ <slot :name="`field:${field.name}`" :field="field" :form="builder.form" :field-props="builder.form.getFieldProps(field.name)">
73
+ <FormFieldRenderer
74
+ :field="field"
75
+ :resolved-props="builder.getResolvedFieldProps(field)"
76
+ :form="builder.form"
77
+ />
78
+ </slot>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </slot>
83
+
84
+ <slot :name="`section:${section.id}:after`" :form="builder.form" />
85
+ </div>
86
+ </template>
87
+
88
+ <style>
89
+ @import '../styles/components/form-builder.css';
90
+ </style>
@@ -89,6 +89,12 @@ export { default as StepWizard } from './StepWizard.vue'
89
89
  export { default as AuditTrail } from './AuditTrail.vue'
90
90
  export { default as BatchProgressList } from './BatchProgressList.vue'
91
91
 
92
+ // Form builder components
93
+ export { default as FormBuilder } from './FormBuilder.vue'
94
+ export { default as FormSection } from './FormSection.vue'
95
+ export { default as FormActions } from './FormActions.vue'
96
+ export { default as FormFieldRenderer } from './FormFieldRenderer.vue'
97
+
92
98
  // Scheduling / booking components
93
99
  export { default as DateTimePicker } from './DateTimePicker.vue'
94
100
  export { default as TimeRangeInput } from './TimeRangeInput.vue'
@@ -0,0 +1,79 @@
1
+ import type { Component } from 'vue'
2
+ import type { FormFieldType } from '../types/form-builder'
3
+
4
+ import BaseInput from '../components/BaseInput.vue'
5
+ import BaseTextarea from '../components/BaseTextarea.vue'
6
+ import BaseSelect from '../components/BaseSelect.vue'
7
+ import MultiSelect from '../components/MultiSelect.vue'
8
+ import BaseCheckbox from '../components/BaseCheckbox.vue'
9
+ import BaseToggle from '../components/BaseToggle.vue'
10
+ import BaseRadioGroup from '../components/BaseRadioGroup.vue'
11
+ import BaseSlider from '../components/BaseSlider.vue'
12
+ import TagsInput from '../components/TagsInput.vue'
13
+ import NumberInput from '../components/NumberInput.vue'
14
+ import DatePicker from '../components/DatePicker.vue'
15
+ import TimePicker from '../components/TimePicker.vue'
16
+ import DateTimePicker from '../components/DateTimePicker.vue'
17
+ import FileUploader from '../components/FileUploader.vue'
18
+ import FormulaInput from '../components/FormulaInput.vue'
19
+ import SequenceInput from '../components/SequenceInput.vue'
20
+ import MoleculeInput from '../components/MoleculeInput.vue'
21
+ import ConcentrationInput from '../components/ConcentrationInput.vue'
22
+ import UnitInput from '../components/UnitInput.vue'
23
+
24
+ export interface RegistryEntry {
25
+ component: Component
26
+ /** Default props applied to the component. */
27
+ defaults: Record<string, unknown>
28
+ /** Whether the component uses v-model (false for event-only components like FileUploader). */
29
+ vModel: boolean
30
+ }
31
+
32
+ const registry: Record<FormFieldType, RegistryEntry> = {
33
+ text: { component: BaseInput, defaults: { type: 'text' }, vModel: true },
34
+ email: { component: BaseInput, defaults: { type: 'email' }, vModel: true },
35
+ password: { component: BaseInput, defaults: { type: 'password' }, vModel: true },
36
+ tel: { component: BaseInput, defaults: { type: 'tel' }, vModel: true },
37
+ url: { component: BaseInput, defaults: { type: 'url' }, vModel: true },
38
+ search: { component: BaseInput, defaults: { type: 'search' }, vModel: true },
39
+ number: { component: NumberInput, defaults: {}, vModel: true },
40
+ textarea: { component: BaseTextarea, defaults: {}, vModel: true },
41
+ select: { component: BaseSelect, defaults: {}, vModel: true },
42
+ multiselect: { component: MultiSelect, defaults: {}, vModel: true },
43
+ checkbox: { component: BaseCheckbox, defaults: {}, vModel: true },
44
+ toggle: { component: BaseToggle, defaults: {}, vModel: true },
45
+ radio: { component: BaseRadioGroup, defaults: {}, vModel: true },
46
+ slider: { component: BaseSlider, defaults: {}, vModel: true },
47
+ tags: { component: TagsInput, defaults: {}, vModel: true },
48
+ date: { component: DatePicker, defaults: {}, vModel: true },
49
+ time: { component: TimePicker, defaults: {}, vModel: true },
50
+ datetime: { component: DateTimePicker, defaults: {}, vModel: true },
51
+ file: { component: FileUploader, defaults: {}, vModel: false },
52
+ formula: { component: FormulaInput, defaults: {}, vModel: true },
53
+ sequence: { component: SequenceInput, defaults: {}, vModel: true },
54
+ molecule: { component: MoleculeInput, defaults: {}, vModel: true },
55
+ concentration: { component: ConcentrationInput, defaults: {}, vModel: true },
56
+ unit: { component: UnitInput, defaults: {}, vModel: true },
57
+ }
58
+
59
+ /** Return the registry entry for a given field type. Throws if the type is unregistered. */
60
+ export function getFieldRegistryEntry(type: FormFieldType): RegistryEntry {
61
+ return registry[type]
62
+ }
63
+
64
+ /** Get the default empty value for a given field type. */
65
+ export function getTypeDefault(type: FormFieldType): unknown {
66
+ switch (type) {
67
+ case 'checkbox':
68
+ case 'toggle':
69
+ return false
70
+ case 'number':
71
+ case 'slider':
72
+ return undefined
73
+ case 'multiselect':
74
+ case 'tags':
75
+ return []
76
+ default:
77
+ return ''
78
+ }
79
+ }
@@ -85,3 +85,9 @@ export {
85
85
  compareTime,
86
86
  } from './useTimeUtils'
87
87
  export { useScheduleDrag } from './useScheduleDrag'
88
+ export { useFormBuilder, evaluateCondition } from './useFormBuilder'
89
+ export {
90
+ getFieldRegistryEntry,
91
+ getTypeDefault,
92
+ type RegistryEntry,
93
+ } from './formBuilderRegistry'