@btst/plugin-form-builder 1.0.0 → 1.2.0-beta.1

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 (84) hide show
  1. package/dist/api/index.cjs +1 -0
  2. package/dist/api/index.d.cts +1 -1
  3. package/dist/api/index.d.mts +1 -1
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/index.mjs +1 -1
  6. package/dist/api/operations.cjs +64 -4
  7. package/dist/api/operations.mjs +64 -5
  8. package/dist/api/plugin.cjs +8 -0
  9. package/dist/api/plugin.mjs +8 -0
  10. package/dist/api/submissions-csv.cjs +69 -0
  11. package/dist/api/submissions-csv.mjs +64 -0
  12. package/dist/client/components/forms/form-renderer.cjs +31 -2
  13. package/dist/client/components/forms/form-renderer.mjs +31 -2
  14. package/dist/client/components/forms/media-file-field.cjs +48 -0
  15. package/dist/client/components/forms/media-file-field.mjs +46 -0
  16. package/dist/client/components/index.d.cts +4 -2
  17. package/dist/client/components/index.d.mts +4 -2
  18. package/dist/client/components/index.d.ts +4 -2
  19. package/dist/client/components/pages/ai-schema.cjs +120 -0
  20. package/dist/client/components/pages/ai-schema.mjs +118 -0
  21. package/dist/client/components/pages/form-builder-page.internal.cjs +40 -1
  22. package/dist/client/components/pages/form-builder-page.internal.mjs +41 -2
  23. package/dist/client/components/pages/form-list-page.internal.cjs +1 -0
  24. package/dist/client/components/pages/form-list-page.internal.mjs +1 -0
  25. package/dist/client/components/pages/submissions-page.internal.cjs +40 -0
  26. package/dist/client/components/pages/submissions-page.internal.mjs +41 -1
  27. package/dist/client/components/shared/pagination.cjs +19 -0
  28. package/dist/client/components/shared/pagination.mjs +20 -1
  29. package/dist/client/hooks/form-builder-hooks.cjs +4 -0
  30. package/dist/client/hooks/form-builder-hooks.mjs +4 -1
  31. package/dist/client/hooks/index.cjs +1 -0
  32. package/dist/client/hooks/index.d.cts +7 -2
  33. package/dist/client/hooks/index.d.mts +7 -2
  34. package/dist/client/hooks/index.d.ts +7 -2
  35. package/dist/client/hooks/index.mjs +1 -1
  36. package/dist/client/index.d.cts +42 -3
  37. package/dist/client/index.d.mts +42 -3
  38. package/dist/client/index.d.ts +42 -3
  39. package/dist/permissions.cjs +8 -0
  40. package/dist/permissions.d.cts +6 -0
  41. package/dist/permissions.d.mts +6 -0
  42. package/dist/permissions.d.ts +6 -0
  43. package/dist/permissions.mjs +8 -0
  44. package/dist/query-keys.cjs +7 -0
  45. package/dist/query-keys.d.cts +29 -1
  46. package/dist/query-keys.d.mts +29 -1
  47. package/dist/query-keys.d.ts +29 -1
  48. package/dist/query-keys.mjs +7 -0
  49. package/dist/shared/{plugin-form-builder.CSP5HR2O.d.ts → plugin-form-builder.65tLgfcd.d.ts} +46 -29
  50. package/dist/shared/{plugin-form-builder.Bbl9NufB.d.cts → plugin-form-builder.DXdOj9vM.d.cts} +46 -29
  51. package/dist/shared/{plugin-form-builder.lp98rFNl.d.mts → plugin-form-builder.PWs25HN1.d.mts} +46 -29
  52. package/dist/shared/{plugin-form-builder.DWrQ3lRX.d.ts → plugin-form-builder.sdjcoJcH.d.cts} +15 -1
  53. package/dist/shared/{plugin-form-builder.DWrQ3lRX.d.cts → plugin-form-builder.sdjcoJcH.d.mts} +15 -1
  54. package/dist/shared/{plugin-form-builder.DWrQ3lRX.d.mts → plugin-form-builder.sdjcoJcH.d.ts} +15 -1
  55. package/dist/ui/src/components/auto-form/config.cjs +2 -0
  56. package/dist/ui/src/components/auto-form/config.mjs +2 -0
  57. package/dist/ui/src/components/auto-form/fields/file.cjs +68 -0
  58. package/dist/ui/src/components/auto-form/fields/file.mjs +63 -0
  59. package/dist/ui/src/components/auto-form/fields/object.cjs +9 -2
  60. package/dist/ui/src/components/auto-form/fields/object.mjs +9 -2
  61. package/dist/ui/src/components/auto-form/helpers.cjs +9 -0
  62. package/dist/ui/src/components/auto-form/helpers.mjs +9 -0
  63. package/dist/ui/src/components/auto-form/index.cjs +29 -4
  64. package/dist/ui/src/components/auto-form/index.mjs +31 -6
  65. package/dist/ui/src/components/auto-form/stepped-auto-form.cjs +34 -29
  66. package/dist/ui/src/components/auto-form/stepped-auto-form.mjs +35 -30
  67. package/dist/ui/src/components/form-builder/canvas.cjs +9 -0
  68. package/dist/ui/src/components/form-builder/canvas.mjs +9 -0
  69. package/dist/ui/src/components/form-builder/components/index.cjs +12 -0
  70. package/dist/ui/src/components/form-builder/components/index.mjs +12 -1
  71. package/dist/ui/src/components/form-builder/condition-editor.cjs +73 -0
  72. package/dist/ui/src/components/form-builder/condition-editor.mjs +71 -0
  73. package/dist/ui/src/components/form-builder/edit-field-dialog.cjs +6 -2
  74. package/dist/ui/src/components/form-builder/edit-field-dialog.mjs +6 -2
  75. package/dist/ui/src/components/form-builder/index.cjs +6 -1
  76. package/dist/ui/src/components/form-builder/index.mjs +6 -1
  77. package/dist/ui/src/components/form-builder/schema-utils.cjs +2 -0
  78. package/dist/ui/src/components/form-builder/schema-utils.mjs +2 -0
  79. package/dist/ui/src/components/form.mjs +1 -1
  80. package/dist/ui/src/lib/form-visibility.cjs +20 -0
  81. package/dist/ui/src/lib/form-visibility.mjs +18 -0
  82. package/dist/ui/src/lib/schema-converter.cjs +101 -0
  83. package/dist/ui/src/lib/schema-converter.mjs +100 -1
  84. package/package.json +4 -4
@@ -1,15 +1,18 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import React__default, { useMemo, useState, useRef, useEffect, useCallback } from 'react';
3
+ import React__default, { useMemo, useState, useEffect, useRef, useCallback } from 'react';
4
4
  import { z } from 'zod';
5
5
  import AutoForm from './index.mjs';
6
+ import { getDefaultValues, getObjectFormSchema } from './helpers.mjs';
7
+ import { resolveVisibleSchema, formSchemaToZod } from '../../lib/schema-converter.mjs';
8
+ import { matchesShowIf } from '../../lib/form-visibility.mjs';
6
9
  import { Button } from '../button.mjs';
7
10
  import { Separator } from '../separator.mjs';
8
11
  import { cn } from '../../lib/utils.mjs';
9
12
 
10
13
  function extractStepsFromSchema(schema) {
11
14
  try {
12
- const jsonSchema = schema.toJSONSchema();
15
+ const jsonSchema = schema.meta()?.steps ? schema.meta() : schema.toJSONSchema();
13
16
  if (jsonSchema.steps && Array.isArray(jsonSchema.steps)) {
14
17
  return jsonSchema.steps;
15
18
  }
@@ -20,7 +23,7 @@ function extractStepsFromSchema(schema) {
20
23
  function getFieldStepAssignments(schema) {
21
24
  const assignments = /* @__PURE__ */ new Map();
22
25
  try {
23
- const jsonSchema = schema.toJSONSchema();
26
+ const jsonSchema = schema.meta()?.steps ? schema.meta() : schema.toJSONSchema();
24
27
  const stepGroupMap = jsonSchema.stepGroupMap;
25
28
  if (stepGroupMap) {
26
29
  for (const [fieldName, stepGroup] of Object.entries(stepGroupMap)) {
@@ -54,21 +57,6 @@ function createStepSchema(fullSchema, stepIndex, fieldAssignments) {
54
57
  }
55
58
  return fullSchema.pick(pickObject);
56
59
  }
57
- function getObjectSchema(schema) {
58
- if (schema instanceof z.ZodObject) {
59
- return schema;
60
- }
61
- const schemaDef = schema._zod?.def ?? schema._def;
62
- if (schemaDef) {
63
- if (schemaDef.schema) {
64
- return getObjectSchema(schemaDef.schema);
65
- }
66
- if (schemaDef.innerType) {
67
- return getObjectSchema(schemaDef.innerType);
68
- }
69
- }
70
- return null;
71
- }
72
60
  function DefaultStepper({
73
61
  steps,
74
62
  currentStepIndex,
@@ -116,16 +104,25 @@ function SteppedAutoForm({
116
104
  StepperComponent = DefaultStepper,
117
105
  isSubmitting = false
118
106
  }) {
119
- const steps = useMemo(() => extractStepsFromSchema(formSchema), [formSchema]);
120
- const hasMultipleSteps = steps.length > 1;
121
- const fieldAssignments = useMemo(
107
+ const allSteps = useMemo(() => extractStepsFromSchema(formSchema), [formSchema]);
108
+ const hasMultipleSteps = allSteps.length > 1;
109
+ const originalFieldAssignments = useMemo(
122
110
  () => getFieldStepAssignments(formSchema),
123
111
  [formSchema]
124
112
  );
125
113
  const [currentStepIndex, setCurrentStepIndex] = useState(0);
126
114
  const [accumulatedValues, setAccumulatedValues] = useState(
127
- initialValues ?? {}
115
+ () => ({ ...getDefaultValues(getObjectFormSchema(formSchema), fieldConfig) ?? {}, ...initialValues ?? {} })
128
116
  );
117
+ const steps = useMemo(() => {
118
+ const metadata = formSchema.meta();
119
+ const visibleValues = metadata?.properties ? resolveVisibleSchema(metadata, accumulatedValues).data : accumulatedValues;
120
+ return allSteps.map((step, originalIndex) => ({ ...step, originalIndex })).filter((step) => matchesShowIf(step.showIf, visibleValues));
121
+ }, [allSteps, accumulatedValues, formSchema]);
122
+ const fieldAssignments = useMemo(() => new Map(Array.from(originalFieldAssignments).map(([key, originalIndex]) => [key, steps.findIndex((step) => step.originalIndex === originalIndex)])), [originalFieldAssignments, steps]);
123
+ useEffect(() => {
124
+ setCurrentStepIndex((index) => Math.min(index, Math.max(0, steps.length - 1)));
125
+ }, [steps.length]);
129
126
  const [completedSteps, setCompletedSteps] = useState(/* @__PURE__ */ new Set());
130
127
  const [schemaErrors, setSchemaErrors] = useState(null);
131
128
  const accumulatedValuesRef = useRef(accumulatedValues);
@@ -146,13 +143,19 @@ function SteppedAutoForm({
146
143
  setAccumulatedValues(newValues);
147
144
  }
148
145
  }, [initialValues]);
149
- const objectSchema = useMemo(() => getObjectSchema(formSchema), [formSchema]);
146
+ const objectSchema = useMemo(() => getObjectFormSchema(formSchema), [formSchema]);
150
147
  const stepSchemas = useMemo(() => {
151
148
  if (!hasMultipleSteps || !objectSchema) return [];
152
- return steps.map(
153
- (_, index) => createStepSchema(objectSchema, index, fieldAssignments)
154
- );
155
- }, [hasMultipleSteps, objectSchema, steps, fieldAssignments]);
149
+ const metadata = formSchema.meta();
150
+ if (metadata?.properties) {
151
+ const visible = resolveVisibleSchema(metadata, accumulatedValues).schema;
152
+ return steps.map((_, index) => {
153
+ const properties = Object.fromEntries(Object.entries(visible.properties ?? {}).filter(([key]) => fieldAssignments.get(key) === index));
154
+ return formSchemaToZod({ ...visible, properties, required: visible.required?.filter((key) => key in properties), steps: void 0 });
155
+ });
156
+ }
157
+ return steps.map((_, index) => createStepSchema(objectSchema, index, fieldAssignments));
158
+ }, [hasMultipleSteps, objectSchema, steps, fieldAssignments, accumulatedValues, formSchema]);
156
159
  const currentStep = steps[currentStepIndex];
157
160
  const currentStepSchema = stepSchemas[currentStepIndex];
158
161
  const isFirst = currentStepIndex === 0;
@@ -160,14 +163,16 @@ function SteppedAutoForm({
160
163
  const currentStepFieldConfig = useMemo(() => {
161
164
  if (!fieldConfig) return void 0;
162
165
  const stepFields = Array.from(fieldAssignments.entries()).filter(([, step]) => step === currentStepIndex).map(([field]) => field);
166
+ const properties = formSchema.meta()?.properties;
163
167
  const filtered = {};
164
168
  for (const field of stepFields) {
165
169
  if (field in fieldConfig) {
166
- filtered[field] = fieldConfig[field];
170
+ const config = fieldConfig[field];
171
+ filtered[field] = properties?.[field]?.showIf ? { ...config, showIf: void 0 } : config;
167
172
  }
168
173
  }
169
174
  return filtered;
170
- }, [fieldConfig, fieldAssignments, currentStepIndex]);
175
+ }, [fieldConfig, fieldAssignments, currentStepIndex, formSchema]);
171
176
  const currentStepValues = useMemo(() => {
172
177
  const stepFields = Array.from(fieldAssignments.entries()).filter(([, step]) => step === currentStepIndex).map(([field]) => field);
173
178
  const values = {};
@@ -245,7 +250,7 @@ function SteppedAutoForm({
245
250
  setSchemaErrors(null);
246
251
  }
247
252
  }, [isFirst]);
248
- if (!hasMultipleSteps) {
253
+ if (!hasMultipleSteps || steps.length === 0) {
249
254
  return /* @__PURE__ */ jsxs(
250
255
  AutoForm,
251
256
  {
@@ -7,6 +7,7 @@ const core = require('@dnd-kit/core');
7
7
  const sortable = require('@dnd-kit/sortable');
8
8
  const utils = require('../../lib/utils.cjs');
9
9
  const sortableField = require('./sortable-field.cjs');
10
+ const conditionEditor = require('./condition-editor.cjs');
10
11
  const stepTabs = require('./step-tabs.cjs');
11
12
 
12
13
  function DropZone({ id, isDraggingFromPalette }) {
@@ -41,6 +42,7 @@ function Canvas({
41
42
  onAddStep,
42
43
  onDeleteStep,
43
44
  onRenameStep,
45
+ onStepConditionChange,
44
46
  hideStepControls
45
47
  }) {
46
48
  const { setNodeRef, isOver } = core.useDroppable({
@@ -67,6 +69,13 @@ function Canvas({
67
69
  hideStepControls
68
70
  }
69
71
  ),
72
+ !hideStepControls && steps.length > 1 && onStepConditionChange && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
73
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mb-2 text-sm font-medium", children: [
74
+ steps[activeStepIndex]?.title,
75
+ " visibility"
76
+ ] }),
77
+ /* @__PURE__ */ jsxRuntime.jsx(conditionEditor.ConditionEditor, { value: steps[activeStepIndex]?.showIf, onChange: (condition) => onStepConditionChange(activeStepIndex, condition), fieldIds: fields.filter((field) => (field.stepGroup ?? 0) < activeStepIndex).map((field) => field.id) })
78
+ ] }),
70
79
  /* @__PURE__ */ jsxRuntime.jsx(
71
80
  "div",
72
81
  {
@@ -5,6 +5,7 @@ import { useDroppable } from '@dnd-kit/core';
5
5
  import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
6
6
  import { cn } from '../../lib/utils.mjs';
7
7
  import { SortableField } from './sortable-field.mjs';
8
+ import { ConditionEditor } from './condition-editor.mjs';
8
9
  import { StepTabs } from './step-tabs.mjs';
9
10
 
10
11
  function DropZone({ id, isDraggingFromPalette }) {
@@ -39,6 +40,7 @@ function Canvas({
39
40
  onAddStep,
40
41
  onDeleteStep,
41
42
  onRenameStep,
43
+ onStepConditionChange,
42
44
  hideStepControls
43
45
  }) {
44
46
  const { setNodeRef, isOver } = useDroppable({
@@ -65,6 +67,13 @@ function Canvas({
65
67
  hideStepControls
66
68
  }
67
69
  ),
70
+ !hideStepControls && steps.length > 1 && onStepConditionChange && /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
71
+ /* @__PURE__ */ jsxs("p", { className: "mb-2 text-sm font-medium", children: [
72
+ steps[activeStepIndex]?.title,
73
+ " visibility"
74
+ ] }),
75
+ /* @__PURE__ */ jsx(ConditionEditor, { value: steps[activeStepIndex]?.showIf, onChange: (condition) => onStepConditionChange(activeStepIndex, condition), fieldIds: fields.filter((field) => (field.stepGroup ?? 0) < activeStepIndex).map((field) => field.id) })
76
+ ] }),
68
77
  /* @__PURE__ */ jsx(
69
78
  "div",
70
79
  {
@@ -23,6 +23,16 @@ function getPlaceholder(prop) {
23
23
  function getLabel(prop, key) {
24
24
  return prop.label || prop.title || helpers.beautifyObjectName(key);
25
25
  }
26
+ const fileFieldDefinition = types.defineComponent({
27
+ type: "file",
28
+ backingType: "string",
29
+ label: "File Upload",
30
+ icon: lucideReact.FolderOpen,
31
+ defaultProps: { label: "File", required: false },
32
+ propertiesSchema: validationSchemas.baseMetaSchema,
33
+ toJSONSchema: (props) => ({ type: "string", minLength: 1, fieldType: "file", label: props.label, description: props.description }),
34
+ fromJSONSchema: (prop, key, isRequired) => prop.fieldType === "file" ? { id: key, type: "file", props: { label: getLabel(prop, key), description: prop.description, required: isRequired } } : null
35
+ });
26
36
  const textFieldDefinition = types.defineComponent({
27
37
  type: "text",
28
38
  backingType: "string",
@@ -531,6 +541,7 @@ const arrayFieldDefinition = {
531
541
  }
532
542
  };
533
543
  const defaultComponents = [
544
+ fileFieldDefinition,
534
545
  // Container types (must be before primitives to match object/array JSON Schema)
535
546
  objectFieldDefinition,
536
547
  arrayFieldDefinition,
@@ -558,6 +569,7 @@ exports.checkboxFieldDefinition = checkboxFieldDefinition;
558
569
  exports.dateFieldDefinition = dateFieldDefinition;
559
570
  exports.defaultComponents = defaultComponents;
560
571
  exports.emailFieldDefinition = emailFieldDefinition;
572
+ exports.fileFieldDefinition = fileFieldDefinition;
561
573
  exports.getComponentByType = getComponentByType;
562
574
  exports.numberFieldDefinition = numberFieldDefinition;
563
575
  exports.objectFieldDefinition = objectFieldDefinition;
@@ -21,6 +21,16 @@ function getPlaceholder(prop) {
21
21
  function getLabel(prop, key) {
22
22
  return prop.label || prop.title || beautifyObjectName(key);
23
23
  }
24
+ const fileFieldDefinition = defineComponent({
25
+ type: "file",
26
+ backingType: "string",
27
+ label: "File Upload",
28
+ icon: FolderOpen,
29
+ defaultProps: { label: "File", required: false },
30
+ propertiesSchema: baseMetaSchema,
31
+ toJSONSchema: (props) => ({ type: "string", minLength: 1, fieldType: "file", label: props.label, description: props.description }),
32
+ fromJSONSchema: (prop, key, isRequired) => prop.fieldType === "file" ? { id: key, type: "file", props: { label: getLabel(prop, key), description: prop.description, required: isRequired } } : null
33
+ });
24
34
  const textFieldDefinition = defineComponent({
25
35
  type: "text",
26
36
  backingType: "string",
@@ -529,6 +539,7 @@ const arrayFieldDefinition = {
529
539
  }
530
540
  };
531
541
  const defaultComponents = [
542
+ fileFieldDefinition,
532
543
  // Container types (must be before primitives to match object/array JSON Schema)
533
544
  objectFieldDefinition,
534
545
  arrayFieldDefinition,
@@ -551,4 +562,4 @@ function getComponentByType(type, components = defaultComponents) {
551
562
  return components.find((c) => c.type === type);
552
563
  }
553
564
 
554
- export { arrayFieldDefinition, checkboxFieldDefinition, dateFieldDefinition, defaultComponents, emailFieldDefinition, getComponentByType, numberFieldDefinition, objectFieldDefinition, passwordFieldDefinition, phoneFieldDefinition, radioFieldDefinition, selectFieldDefinition, switchFieldDefinition, textFieldDefinition, textareaFieldDefinition, urlFieldDefinition };
565
+ export { arrayFieldDefinition, checkboxFieldDefinition, dateFieldDefinition, defaultComponents, emailFieldDefinition, fileFieldDefinition, getComponentByType, numberFieldDefinition, objectFieldDefinition, passwordFieldDefinition, phoneFieldDefinition, radioFieldDefinition, selectFieldDefinition, switchFieldDefinition, textFieldDefinition, textareaFieldDefinition, urlFieldDefinition };
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const textarea = require('../textarea.cjs');
7
+ const input = require('../input.cjs');
8
+ const label = require('../label.cjs');
9
+ const select = require('../select.cjs');
10
+
11
+ function ConditionEditor({ value, onChange, fieldIds }) {
12
+ const id = React.useId();
13
+ const [draft, setDraft] = React.useState();
14
+ const editingValue = draft?.condition === value;
15
+ const ValueInput = value?.operator === "in" ? textarea.Textarea : input.Input;
16
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: "flex flex-col gap-3 rounded-md border p-3", children: [
17
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "px-1 text-sm font-medium", children: "Visibility" }),
18
+ /* @__PURE__ */ jsxRuntime.jsx(label.Label, { htmlFor: `${id}-field`, children: "Show if field" }),
19
+ /* @__PURE__ */ jsxRuntime.jsxs(select.Select, { value: value?.field ?? "__always", onValueChange: (field) => {
20
+ setDraft(void 0);
21
+ onChange(field === "__always" ? void 0 : { field, operator: "isTruthy" });
22
+ }, children: [
23
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectTrigger, { id: `${id}-field`, children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectValue, {}) }),
24
+ /* @__PURE__ */ jsxRuntime.jsxs(select.SelectContent, { children: [
25
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: "__always", children: "Always show" }),
26
+ fieldIds.map((field) => /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: field, children: field }, field))
27
+ ] })
28
+ ] }),
29
+ value && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
30
+ /* @__PURE__ */ jsxRuntime.jsx(label.Label, { htmlFor: `${id}-operator`, children: "Condition" }),
31
+ /* @__PURE__ */ jsxRuntime.jsxs(select.Select, { value: value.operator, onValueChange: (operator) => {
32
+ setDraft(void 0);
33
+ onChange({ ...value, operator, value: operator === "in" ? [] : "" });
34
+ }, children: [
35
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectTrigger, { id: `${id}-operator`, children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectValue, {}) }),
36
+ /* @__PURE__ */ jsxRuntime.jsxs(select.SelectContent, { children: [
37
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: "equals", children: "Equals" }),
38
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: "notEquals", children: "Does not equal" }),
39
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: "in", children: "Is one of" }),
40
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: "isTruthy", children: "Has a truthy value" })
41
+ ] })
42
+ ] }),
43
+ value.operator !== "isTruthy" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
44
+ /* @__PURE__ */ jsxRuntime.jsx(label.Label, { htmlFor: `${id}-value`, children: value.operator === "in" ? "Values (one per line)" : "Value" }),
45
+ /* @__PURE__ */ jsxRuntime.jsx(
46
+ ValueInput,
47
+ {
48
+ id: `${id}-value`,
49
+ value: editingValue ? draft.text : value.operator === "in" ? Array.isArray(value.value) ? value.value.map((item) => typeof item === "string" ? JSON.stringify(item) : String(item)).join("\n") : "" : typeof value.value === "string" ? value.value : JSON.stringify(value.value) ?? "",
50
+ onChange: (event) => {
51
+ const parse = (text) => {
52
+ try {
53
+ return JSON.parse(text);
54
+ } catch {
55
+ return text;
56
+ }
57
+ };
58
+ const next = { ...value, value: value.operator === "in" ? event.target.value.split("\n").map((text) => parse(text.trim())) : parse(event.target.value) };
59
+ setDraft({ condition: next, text: event.target.value });
60
+ onChange(next);
61
+ }
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
65
+ value.operator === "in" ? "Enter one value per line. " : "",
66
+ "Numbers, true and false are compared as typed values. Quote them to compare text."
67
+ ] })
68
+ ] })
69
+ ] })
70
+ ] });
71
+ }
72
+
73
+ exports.ConditionEditor = ConditionEditor;
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { useId, useState } from 'react';
4
+ import { Textarea } from '../textarea.mjs';
5
+ import { Input } from '../input.mjs';
6
+ import { Label } from '../label.mjs';
7
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../select.mjs';
8
+
9
+ function ConditionEditor({ value, onChange, fieldIds }) {
10
+ const id = useId();
11
+ const [draft, setDraft] = useState();
12
+ const editingValue = draft?.condition === value;
13
+ const ValueInput = value?.operator === "in" ? Textarea : Input;
14
+ return /* @__PURE__ */ jsxs("fieldset", { className: "flex flex-col gap-3 rounded-md border p-3", children: [
15
+ /* @__PURE__ */ jsx("legend", { className: "px-1 text-sm font-medium", children: "Visibility" }),
16
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-field`, children: "Show if field" }),
17
+ /* @__PURE__ */ jsxs(Select, { value: value?.field ?? "__always", onValueChange: (field) => {
18
+ setDraft(void 0);
19
+ onChange(field === "__always" ? void 0 : { field, operator: "isTruthy" });
20
+ }, children: [
21
+ /* @__PURE__ */ jsx(SelectTrigger, { id: `${id}-field`, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
22
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
23
+ /* @__PURE__ */ jsx(SelectItem, { value: "__always", children: "Always show" }),
24
+ fieldIds.map((field) => /* @__PURE__ */ jsx(SelectItem, { value: field, children: field }, field))
25
+ ] })
26
+ ] }),
27
+ value && /* @__PURE__ */ jsxs(Fragment, { children: [
28
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-operator`, children: "Condition" }),
29
+ /* @__PURE__ */ jsxs(Select, { value: value.operator, onValueChange: (operator) => {
30
+ setDraft(void 0);
31
+ onChange({ ...value, operator, value: operator === "in" ? [] : "" });
32
+ }, children: [
33
+ /* @__PURE__ */ jsx(SelectTrigger, { id: `${id}-operator`, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
34
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
35
+ /* @__PURE__ */ jsx(SelectItem, { value: "equals", children: "Equals" }),
36
+ /* @__PURE__ */ jsx(SelectItem, { value: "notEquals", children: "Does not equal" }),
37
+ /* @__PURE__ */ jsx(SelectItem, { value: "in", children: "Is one of" }),
38
+ /* @__PURE__ */ jsx(SelectItem, { value: "isTruthy", children: "Has a truthy value" })
39
+ ] })
40
+ ] }),
41
+ value.operator !== "isTruthy" && /* @__PURE__ */ jsxs(Fragment, { children: [
42
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-value`, children: value.operator === "in" ? "Values (one per line)" : "Value" }),
43
+ /* @__PURE__ */ jsx(
44
+ ValueInput,
45
+ {
46
+ id: `${id}-value`,
47
+ value: editingValue ? draft.text : value.operator === "in" ? Array.isArray(value.value) ? value.value.map((item) => typeof item === "string" ? JSON.stringify(item) : String(item)).join("\n") : "" : typeof value.value === "string" ? value.value : JSON.stringify(value.value) ?? "",
48
+ onChange: (event) => {
49
+ const parse = (text) => {
50
+ try {
51
+ return JSON.parse(text);
52
+ } catch {
53
+ return text;
54
+ }
55
+ };
56
+ const next = { ...value, value: value.operator === "in" ? event.target.value.split("\n").map((text) => parse(text.trim())) : parse(event.target.value) };
57
+ setDraft({ condition: next, text: event.target.value });
58
+ onChange(next);
59
+ }
60
+ }
61
+ ),
62
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
63
+ value.operator === "in" ? "Enter one value per line. " : "",
64
+ "Numbers, true and false are compared as typed values. Quote them to compare text."
65
+ ] })
66
+ ] })
67
+ ] })
68
+ ] });
69
+ }
70
+
71
+ export { ConditionEditor };
@@ -5,6 +5,7 @@ const jsxRuntime = require('react/jsx-runtime');
5
5
  const React = require('react');
6
6
  const index = require('../auto-form/index.cjs');
7
7
  const helpers = require('../auto-form/helpers.cjs');
8
+ const conditionEditor = require('./condition-editor.cjs');
8
9
  const input = require('../input.cjs');
9
10
  const label = require('../label.cjs');
10
11
  const button = require('../button.cjs');
@@ -30,6 +31,7 @@ function EditFieldDialog({
30
31
  }, [field, open]);
31
32
  const [fieldNameOverride, setFieldNameOverride] = React.useState(null);
32
33
  const [localPropsOverride, setLocalPropsOverride] = React.useState(null);
34
+ const [conditionOverride, setConditionOverride] = React.useState(null);
33
35
  const [stepGroupOverride, setStepGroupOverride] = React.useState(null);
34
36
  const fieldName = fieldNameOverride ?? initialValues.fieldName;
35
37
  const localProps = localPropsOverride ?? initialValues.props;
@@ -44,6 +46,7 @@ function EditFieldDialog({
44
46
  setFieldNameOverride(null);
45
47
  setLocalPropsOverride(null);
46
48
  setStepGroupOverride(null);
49
+ setConditionOverride(null);
47
50
  }
48
51
  const formKey = initialValues.key;
49
52
  const handleValuesChange = React.useCallback(
@@ -54,7 +57,7 @@ function EditFieldDialog({
54
57
  );
55
58
  const handleSave = React.useCallback(() => {
56
59
  if (!field) return;
57
- const props = { ...localProps };
60
+ const props = { ...localProps, showIf: conditionOverride ? conditionOverride.value : field.props.showIf };
58
61
  if (typeof props.options === "string") {
59
62
  props.options = props.options.split("\n").map((s) => s.trim()).filter(Boolean);
60
63
  }
@@ -62,7 +65,7 @@ function EditFieldDialog({
62
65
  const stepGroup = stepGroupOverride !== null && stepGroupOverride !== field.stepGroup ? stepGroupOverride : void 0;
63
66
  onUpdate(field.id, props, newId, stepGroup);
64
67
  onOpenChange(false);
65
- }, [field, localProps, fieldName, onUpdate, onOpenChange, stepGroupOverride]);
68
+ }, [field, localProps, fieldName, onUpdate, onOpenChange, stepGroupOverride, conditionOverride]);
66
69
  if (!field || !component) {
67
70
  return null;
68
71
  }
@@ -109,6 +112,7 @@ function EditFieldDialog({
109
112
  ),
110
113
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Which step this field belongs to" })
111
114
  ] }),
115
+ /* @__PURE__ */ jsxRuntime.jsx(conditionEditor.ConditionEditor, { value: conditionOverride ? conditionOverride.value : field.props.showIf, onChange: (value) => setConditionOverride({ value }), fieldIds: allFieldIds.filter((id) => id !== field.id) }),
112
116
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
113
117
  index,
114
118
  {
@@ -3,6 +3,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import { useMemo, useState, useCallback } from 'react';
4
4
  import AutoForm from '../auto-form/index.mjs';
5
5
  import { buildFieldConfigFromJsonSchema } from '../auto-form/helpers.mjs';
6
+ import { ConditionEditor } from './condition-editor.mjs';
6
7
  import { Input } from '../input.mjs';
7
8
  import { Label } from '../label.mjs';
8
9
  import { Button } from '../button.mjs';
@@ -28,6 +29,7 @@ function EditFieldDialog({
28
29
  }, [field, open]);
29
30
  const [fieldNameOverride, setFieldNameOverride] = useState(null);
30
31
  const [localPropsOverride, setLocalPropsOverride] = useState(null);
32
+ const [conditionOverride, setConditionOverride] = useState(null);
31
33
  const [stepGroupOverride, setStepGroupOverride] = useState(null);
32
34
  const fieldName = fieldNameOverride ?? initialValues.fieldName;
33
35
  const localProps = localPropsOverride ?? initialValues.props;
@@ -42,6 +44,7 @@ function EditFieldDialog({
42
44
  setFieldNameOverride(null);
43
45
  setLocalPropsOverride(null);
44
46
  setStepGroupOverride(null);
47
+ setConditionOverride(null);
45
48
  }
46
49
  const formKey = initialValues.key;
47
50
  const handleValuesChange = useCallback(
@@ -52,7 +55,7 @@ function EditFieldDialog({
52
55
  );
53
56
  const handleSave = useCallback(() => {
54
57
  if (!field) return;
55
- const props = { ...localProps };
58
+ const props = { ...localProps, showIf: conditionOverride ? conditionOverride.value : field.props.showIf };
56
59
  if (typeof props.options === "string") {
57
60
  props.options = props.options.split("\n").map((s) => s.trim()).filter(Boolean);
58
61
  }
@@ -60,7 +63,7 @@ function EditFieldDialog({
60
63
  const stepGroup = stepGroupOverride !== null && stepGroupOverride !== field.stepGroup ? stepGroupOverride : void 0;
61
64
  onUpdate(field.id, props, newId, stepGroup);
62
65
  onOpenChange(false);
63
- }, [field, localProps, fieldName, onUpdate, onOpenChange, stepGroupOverride]);
66
+ }, [field, localProps, fieldName, onUpdate, onOpenChange, stepGroupOverride, conditionOverride]);
64
67
  if (!field || !component) {
65
68
  return null;
66
69
  }
@@ -107,6 +110,7 @@ function EditFieldDialog({
107
110
  ),
108
111
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Which step this field belongs to" })
109
112
  ] }),
113
+ /* @__PURE__ */ jsx(ConditionEditor, { value: conditionOverride ? conditionOverride.value : field.props.showIf, onChange: (value) => setConditionOverride({ value }), fieldIds: allFieldIds.filter((id) => id !== field.id) }),
110
114
  /* @__PURE__ */ jsx("div", { className: "border-t pt-4", children: /* @__PURE__ */ jsx(
111
115
  AutoForm,
112
116
  {
@@ -292,7 +292,12 @@ function FormBuilder({
292
292
  onActiveStepChange: setActiveStepIndex,
293
293
  onAddStep: handleAddStep,
294
294
  onDeleteStep: handleDeleteStep,
295
- onRenameStep: handleRenameStep
295
+ onRenameStep: handleRenameStep,
296
+ onStepConditionChange: (index, showIf) => {
297
+ const nextSteps = steps.map((step, i) => i === index ? { ...step, showIf } : step);
298
+ setSteps(nextSteps);
299
+ notifyChange(fields, nextSteps);
300
+ }
296
301
  }
297
302
  ) }),
298
303
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full flex-1 lg:w-96 border-t lg:border-t-0 lg:border-l lg:overflow-auto min-h-[300px]", children: /* @__PURE__ */ jsxRuntime.jsxs(tabs.Tabs, { defaultValue: "preview", className: "h-full flex flex-col", children: [
@@ -291,7 +291,12 @@ function FormBuilder({
291
291
  onActiveStepChange: setActiveStepIndex,
292
292
  onAddStep: handleAddStep,
293
293
  onDeleteStep: handleDeleteStep,
294
- onRenameStep: handleRenameStep
294
+ onRenameStep: handleRenameStep,
295
+ onStepConditionChange: (index, showIf) => {
296
+ const nextSteps = steps.map((step, i) => i === index ? { ...step, showIf } : step);
297
+ setSteps(nextSteps);
298
+ notifyChange(fields, nextSteps);
299
+ }
295
300
  }
296
301
  ) }),
297
302
  /* @__PURE__ */ jsx("div", { className: "w-full flex-1 lg:w-96 border-t lg:border-t-0 lg:border-l lg:overflow-auto min-h-[300px]", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "preview", className: "h-full flex flex-col", children: [
@@ -12,6 +12,7 @@ function fieldsToProperties(fields, components, includeStepGroup = false) {
12
12
  }
13
13
  const isRequired = field.props.required ?? false;
14
14
  const schemaProp = component.toJSONSchema(field.props, isRequired);
15
+ if (field.props.showIf) schemaProp.showIf = field.props.showIf;
15
16
  if (includeStepGroup && field.stepGroup !== void 0) {
16
17
  schemaProp.stepGroup = field.stepGroup;
17
18
  }
@@ -64,6 +65,7 @@ function propertiesToFields(properties, requiredSet, components, stepGroupMap) {
64
65
  }
65
66
  const resolvedStepGroup = prop.stepGroup !== void 0 ? prop.stepGroup : stepGroupMap?.[key];
66
67
  if (field) {
68
+ if (prop.showIf) field.props.showIf = prop.showIf;
67
69
  if (resolvedStepGroup !== void 0) {
68
70
  field.stepGroup = resolvedStepGroup;
69
71
  }
@@ -10,6 +10,7 @@ function fieldsToProperties(fields, components, includeStepGroup = false) {
10
10
  }
11
11
  const isRequired = field.props.required ?? false;
12
12
  const schemaProp = component.toJSONSchema(field.props, isRequired);
13
+ if (field.props.showIf) schemaProp.showIf = field.props.showIf;
13
14
  if (includeStepGroup && field.stepGroup !== void 0) {
14
15
  schemaProp.stepGroup = field.stepGroup;
15
16
  }
@@ -62,6 +63,7 @@ function propertiesToFields(properties, requiredSet, components, stepGroupMap) {
62
63
  }
63
64
  const resolvedStepGroup = prop.stepGroup !== void 0 ? prop.stepGroup : stepGroupMap?.[key];
64
65
  if (field) {
66
+ if (prop.showIf) field.props.showIf = prop.showIf;
65
67
  if (resolvedStepGroup !== void 0) {
66
68
  field.stepGroup = resolvedStepGroup;
67
69
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import * as React from 'react';
4
4
  import { Slot } from '@radix-ui/react-slot';
5
- import { FormProvider, Controller, useFormContext, useFormState } from 'react-hook-form';
5
+ import { FormProvider, useFormContext, useFormState, Controller } from 'react-hook-form';
6
6
  import { cn } from '../lib/utils.mjs';
7
7
  import { Label } from './label.mjs';
8
8
 
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ function matchesShowIf(condition, values) {
4
+ if (!condition) return true;
5
+ const value = Object.hasOwn(values, condition.field) ? values[condition.field] : void 0;
6
+ switch (condition.operator) {
7
+ case "equals":
8
+ return value === condition.value;
9
+ case "notEquals":
10
+ return value !== condition.value;
11
+ case "in":
12
+ return Array.isArray(condition.value) && condition.value.includes(value);
13
+ case "isTruthy":
14
+ return Boolean(value);
15
+ default:
16
+ return false;
17
+ }
18
+ }
19
+
20
+ exports.matchesShowIf = matchesShowIf;
@@ -0,0 +1,18 @@
1
+ function matchesShowIf(condition, values) {
2
+ if (!condition) return true;
3
+ const value = Object.hasOwn(values, condition.field) ? values[condition.field] : void 0;
4
+ switch (condition.operator) {
5
+ case "equals":
6
+ return value === condition.value;
7
+ case "notEquals":
8
+ return value !== condition.value;
9
+ case "in":
10
+ return Array.isArray(condition.value) && condition.value.includes(value);
11
+ case "isTruthy":
12
+ return Boolean(value);
13
+ default:
14
+ return false;
15
+ }
16
+ }
17
+
18
+ export { matchesShowIf };