@waypointjs/builder 0.1.4 → 0.1.5

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.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRuntimeStore, getNextStep, validateSchema, getPreviousStep, resolveTree } from '@waypointjs/core';
2
- import { useState, useRef, useEffect } from 'react';
2
+ import { createContext, useState, useRef, useEffect, useContext } from 'react';
3
3
  import { create } from 'zustand';
4
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { useWaypoint, useWaypointStep } from '@waypointjs/react';
6
6
  import { DevPanel } from '@waypointjs/devtools';
7
7
 
@@ -195,6 +195,44 @@ var useBuilderStore = create((set, _get) => ({
195
195
  selectedFieldId: s.selectedStepId === stepId ? null : s.selectedFieldId,
196
196
  isDirty: true
197
197
  })),
198
+ duplicateStep: (stepId) => {
199
+ const state = _get();
200
+ const step = state.schema.steps.find((s) => s.id === stepId);
201
+ if (!step) return;
202
+ const newStepId = generateId("step");
203
+ const fieldIdMap = {};
204
+ for (const f of step.fields) {
205
+ fieldIdMap[f.id] = generateId("field");
206
+ }
207
+ const newFields = step.fields.map((f) => ({
208
+ ...f,
209
+ id: fieldIdMap[f.id],
210
+ dependsOn: f.dependsOn?.map((dep) => {
211
+ if (dep.startsWith(`${stepId}.`)) {
212
+ const oldFieldId = dep.slice(stepId.length + 1);
213
+ const newFieldId = fieldIdMap[oldFieldId];
214
+ return newFieldId ? `${newStepId}.${newFieldId}` : dep;
215
+ }
216
+ return dep;
217
+ })
218
+ }));
219
+ const newStep = {
220
+ ...step,
221
+ id: newStepId,
222
+ title: `${step.title} (copy)`,
223
+ url: `${step.url}-copy`,
224
+ fields: newFields
225
+ };
226
+ const stepIndex = state.schema.steps.findIndex((s) => s.id === stepId);
227
+ const newSteps = [...state.schema.steps];
228
+ newSteps.splice(stepIndex + 1, 0, newStep);
229
+ set({
230
+ schema: { ...state.schema, steps: newSteps },
231
+ selectedStepId: newStepId,
232
+ selectedFieldId: null,
233
+ isDirty: true
234
+ });
235
+ },
198
236
  reorderSteps: (fromIndex, toIndex) => set((s) => {
199
237
  const steps = [...s.schema.steps];
200
238
  const [moved] = steps.splice(fromIndex, 1);
@@ -247,6 +285,32 @@ var useBuilderStore = create((set, _get) => ({
247
285
  selectedFieldId: s.selectedFieldId === fieldId ? null : s.selectedFieldId,
248
286
  isDirty: true
249
287
  })),
288
+ duplicateField: (stepId, fieldId) => {
289
+ const state = _get();
290
+ const step = state.schema.steps.find((s) => s.id === stepId);
291
+ if (!step) return;
292
+ const field = step.fields.find((f) => f.id === fieldId);
293
+ if (!field) return;
294
+ const newFieldId = generateId("field");
295
+ const newField = {
296
+ ...field,
297
+ id: newFieldId,
298
+ label: `${field.label} (copy)`
299
+ };
300
+ const fieldIndex = step.fields.findIndex((f) => f.id === fieldId);
301
+ const newFields = [...step.fields];
302
+ newFields.splice(fieldIndex + 1, 0, newField);
303
+ set((s) => ({
304
+ schema: {
305
+ ...s.schema,
306
+ steps: s.schema.steps.map(
307
+ (st) => st.id === stepId ? { ...st, fields: newFields } : st
308
+ )
309
+ },
310
+ selectedFieldId: newFieldId,
311
+ isDirty: true
312
+ }));
313
+ },
250
314
  reorderFields: (stepId, fromIndex, toIndex) => set((s) => ({
251
315
  schema: {
252
316
  ...s.schema,
@@ -345,6 +409,18 @@ var useBuilderStore = create((set, _get) => ({
345
409
  isDirty: true
346
410
  }))
347
411
  }));
412
+ var BuilderReadOnlyContext = createContext(false);
413
+ function useBuilderReadOnly() {
414
+ return useContext(BuilderReadOnlyContext);
415
+ }
416
+ var BuilderCustomTypesContext = createContext([]);
417
+ function useBuilderCustomTypes() {
418
+ return useContext(BuilderCustomTypesContext);
419
+ }
420
+ var BuilderExternalEnumsContext = createContext([]);
421
+ function useBuilderExternalEnums() {
422
+ return useContext(BuilderExternalEnumsContext);
423
+ }
348
424
  var BLANK_FORM = {
349
425
  id: "",
350
426
  label: "",
@@ -358,6 +434,7 @@ function ExternalVariablePanel() {
358
434
  updateExternalVariable,
359
435
  removeExternalVariable
360
436
  } = useBuilderStore();
437
+ const readOnly = useBuilderReadOnly();
361
438
  const variables = schema.externalVariables ?? [];
362
439
  const [isAdding, setIsAdding] = useState(false);
363
440
  const [editingId, setEditingId] = useState(null);
@@ -431,7 +508,7 @@ function ExternalVariablePanel() {
431
508
  return /* @__PURE__ */ jsxs("div", { style: panelStyle, children: [
432
509
  /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
433
510
  /* @__PURE__ */ jsx("span", { style: titleStyle, children: "External Variables" }),
434
- !isAdding && /* @__PURE__ */ jsx(
511
+ !readOnly && !isAdding && /* @__PURE__ */ jsx(
435
512
  "button",
436
513
  {
437
514
  style: addBtnStyle,
@@ -486,7 +563,7 @@ function ExternalVariablePanel() {
486
563
  /* @__PURE__ */ jsx("span", { style: varLabelStyle, children: v.label }),
487
564
  refs.length > 0 && /* @__PURE__ */ jsx("div", { style: refsStyle, children: refs.map((ref, i) => /* @__PURE__ */ jsx("span", { style: refChipStyle, children: ref }, i)) })
488
565
  ] }),
489
- /* @__PURE__ */ jsxs("div", { style: varActionsStyle, children: [
566
+ !readOnly && /* @__PURE__ */ jsxs("div", { style: varActionsStyle, children: [
490
567
  /* @__PURE__ */ jsx("button", { style: actionBtnStyle, onClick: () => startEdit(v), children: "Edit" }),
491
568
  /* @__PURE__ */ jsx(
492
569
  "button",
@@ -813,15 +890,23 @@ var submitBtnStyle = {
813
890
  // src/hooks/useAllFieldPaths.ts
814
891
  function useAllFieldPaths(excludeStepId, excludeFieldId) {
815
892
  const { schema } = useBuilderStore();
893
+ const externalEnums = useBuilderExternalEnums();
816
894
  const paths = [];
817
895
  for (const step of schema.steps) {
818
896
  for (const field of step.fields) {
819
897
  if (step.id === excludeStepId && field.id === excludeFieldId) continue;
898
+ let options;
899
+ if (field.externalEnumId) {
900
+ options = externalEnums.find((e) => e.id === field.externalEnumId)?.values;
901
+ } else if (field.options?.length) {
902
+ options = field.options;
903
+ }
820
904
  paths.push({
821
905
  path: `${step.id}.${field.id}`,
822
906
  label: `${step.title} \u2192 ${field.label}`,
823
907
  stepId: step.id,
824
- fieldId: field.id
908
+ fieldId: field.id,
909
+ options
825
910
  });
826
911
  }
827
912
  }
@@ -847,7 +932,9 @@ var OPERATORS = [
847
932
  { value: "notIn", label: "not in (comma list)", hasValue: true },
848
933
  { value: "matches", label: "matches regex", hasValue: true },
849
934
  { value: "exists", label: "exists", hasValue: false },
850
- { value: "notExists", label: "not exists", hasValue: false }
935
+ { value: "notExists", label: "not exists", hasValue: false },
936
+ { value: "inEnum", label: "is in enum", hasValue: true, isEnum: true },
937
+ { value: "notInEnum", label: "not in enum", hasValue: true, isEnum: true }
851
938
  ];
852
939
  function ConditionBuilder({
853
940
  value,
@@ -856,6 +943,7 @@ function ConditionBuilder({
856
943
  excludeFieldId
857
944
  }) {
858
945
  const allPaths = useAllFieldPaths(excludeStepId, excludeFieldId);
946
+ const externalEnums = useBuilderExternalEnums();
859
947
  const group = value ?? { combinator: "and", rules: [] };
860
948
  const updateRule = (index, updates) => {
861
949
  const rules = group.rules.map((r, i) => i === index ? { ...r, ...updates } : r);
@@ -918,15 +1006,48 @@ function ConditionBuilder({
918
1006
  children: OPERATORS.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
919
1007
  }
920
1008
  ),
921
- opDef?.hasValue && /* @__PURE__ */ jsx(
922
- "input",
1009
+ opDef?.hasValue && (opDef.isEnum ? /* @__PURE__ */ jsxs(
1010
+ "select",
923
1011
  {
924
- style: styles.valueInput,
925
- placeholder: "value",
1012
+ style: { ...styles.select, width: 140 },
926
1013
  value: rule.value != null ? String(rule.value) : "",
927
- onChange: (e) => updateRule(index, { value: e.target.value })
1014
+ onChange: (e) => updateRule(index, { value: e.target.value }),
1015
+ children: [
1016
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 pick enum \u2014" }),
1017
+ externalEnums.map((en) => /* @__PURE__ */ jsx("option", { value: en.id, children: en.label }, en.id))
1018
+ ]
928
1019
  }
929
- ),
1020
+ ) : /* @__PURE__ */ jsxs("div", { style: styles.valueGroup, children: [
1021
+ /* @__PURE__ */ jsx(
1022
+ "input",
1023
+ {
1024
+ style: styles.valueInput,
1025
+ placeholder: "value",
1026
+ value: rule.value != null ? String(rule.value) : "",
1027
+ onChange: (e) => updateRule(index, { value: e.target.value })
1028
+ }
1029
+ ),
1030
+ externalEnums.length > 0 && /* @__PURE__ */ jsxs(
1031
+ "select",
1032
+ {
1033
+ style: styles.enumPicker,
1034
+ title: "Pick a value from an enum",
1035
+ value: "",
1036
+ onChange: (e) => {
1037
+ if (e.target.value) updateRule(index, { value: e.target.value });
1038
+ },
1039
+ children: [
1040
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u229E" }),
1041
+ externalEnums.map((en) => /* @__PURE__ */ jsx("optgroup", { label: en.label, children: en.values.map((v) => /* @__PURE__ */ jsxs("option", { value: String(v.value), children: [
1042
+ v.label,
1043
+ " (",
1044
+ v.value,
1045
+ ")"
1046
+ ] }, String(v.value))) }, en.id))
1047
+ ]
1048
+ }
1049
+ )
1050
+ ] })),
930
1051
  /* @__PURE__ */ jsx("button", { style: styles.removeBtn, onClick: () => removeRule(index), children: "\u2715" })
931
1052
  ] }, index);
932
1053
  }),
@@ -1004,6 +1125,17 @@ var styles = {
1004
1125
  alignSelf: "flex-start",
1005
1126
  color: "var(--wp-text-secondary)"
1006
1127
  },
1128
+ valueGroup: { display: "flex", alignItems: "center", gap: 4 },
1129
+ enumPicker: {
1130
+ fontSize: 11,
1131
+ padding: "4px 4px",
1132
+ border: "1px solid var(--wp-border-muted)",
1133
+ borderRadius: "var(--wp-radius)",
1134
+ background: "var(--wp-canvas)",
1135
+ color: "var(--wp-primary)",
1136
+ cursor: "pointer",
1137
+ flexShrink: 0
1138
+ },
1007
1139
  preview: { marginTop: 4 },
1008
1140
  previewLabel: {
1009
1141
  fontSize: 10,
@@ -1025,6 +1157,190 @@ var styles = {
1025
1157
  color: "var(--wp-text-mono)"
1026
1158
  }
1027
1159
  };
1160
+ var VALIDATION_RULES = [
1161
+ { type: "required", label: "Required", hasValue: false },
1162
+ { type: "min", label: "Min value", hasValue: true },
1163
+ { type: "max", label: "Max value", hasValue: true },
1164
+ { type: "minLength", label: "Min length", hasValue: true },
1165
+ { type: "maxLength", label: "Max length", hasValue: true },
1166
+ { type: "email", label: "Email format", hasValue: false },
1167
+ { type: "url", label: "URL format", hasValue: false },
1168
+ { type: "regex", label: "Matches regex", hasValue: true },
1169
+ { type: "equals", label: "equals", hasValue: true },
1170
+ { type: "notEquals", label: "not equals", hasValue: true },
1171
+ { type: "greaterThan", label: ">", hasValue: true },
1172
+ { type: "greaterThanOrEqual", label: ">=", hasValue: true },
1173
+ { type: "lessThan", label: "<", hasValue: true },
1174
+ { type: "lessThanOrEqual", label: "<=", hasValue: true },
1175
+ { type: "contains", label: "contains", hasValue: true },
1176
+ { type: "notContains", label: "not contains", hasValue: true },
1177
+ { type: "matches", label: "matches regex", hasValue: true },
1178
+ { type: "inEnum", label: "is in enum", hasValue: true, isEnum: true },
1179
+ { type: "notInEnum", label: "not in enum", hasValue: true, isEnum: true },
1180
+ { type: "custom", label: "Custom validator", hasValue: false }
1181
+ ];
1182
+ function ValidationBuilder({ value, onChange }) {
1183
+ const externalEnums = useBuilderExternalEnums();
1184
+ const updateRule = (index, updates) => {
1185
+ onChange(value.map((r, i) => i === index ? { ...r, ...updates } : r));
1186
+ };
1187
+ const addRule = () => {
1188
+ onChange([...value, { type: "required", message: "This field is required" }]);
1189
+ };
1190
+ const removeRule = (index) => {
1191
+ onChange(value.filter((_, i) => i !== index));
1192
+ };
1193
+ return /* @__PURE__ */ jsxs("div", { style: styles2.container, children: [
1194
+ value.length === 0 && /* @__PURE__ */ jsx("div", { style: styles2.empty, children: "No rules \u2014 field is optional by default." }),
1195
+ value.map((rule, index) => {
1196
+ const def = VALIDATION_RULES.find((r) => r.type === rule.type);
1197
+ return /* @__PURE__ */ jsxs("div", { style: styles2.rule, children: [
1198
+ /* @__PURE__ */ jsx(
1199
+ "select",
1200
+ {
1201
+ style: styles2.typeSelect,
1202
+ value: rule.type,
1203
+ onChange: (e) => updateRule(index, { type: e.target.value }),
1204
+ children: VALIDATION_RULES.map((r) => /* @__PURE__ */ jsx("option", { value: r.type, children: r.label }, r.type))
1205
+ }
1206
+ ),
1207
+ def?.hasValue && (def.isEnum ? /* @__PURE__ */ jsxs(
1208
+ "select",
1209
+ {
1210
+ style: { ...styles2.typeSelect, flex: "0 0 140px" },
1211
+ value: rule.value != null ? String(rule.value) : "",
1212
+ onChange: (e) => updateRule(index, { value: e.target.value }),
1213
+ children: [
1214
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 pick enum \u2014" }),
1215
+ externalEnums.map((en) => /* @__PURE__ */ jsx("option", { value: en.id, children: en.label }, en.id))
1216
+ ]
1217
+ }
1218
+ ) : /* @__PURE__ */ jsxs("div", { style: styles2.valueGroup, children: [
1219
+ /* @__PURE__ */ jsx(
1220
+ "input",
1221
+ {
1222
+ style: styles2.valueInput,
1223
+ placeholder: "value",
1224
+ value: rule.value != null ? String(rule.value) : "",
1225
+ onChange: (e) => updateRule(index, { value: e.target.value })
1226
+ }
1227
+ ),
1228
+ externalEnums.length > 0 && /* @__PURE__ */ jsxs(
1229
+ "select",
1230
+ {
1231
+ style: styles2.enumPicker,
1232
+ title: "Pick a value from an enum",
1233
+ value: "",
1234
+ onChange: (e) => {
1235
+ if (e.target.value) updateRule(index, { value: e.target.value });
1236
+ },
1237
+ children: [
1238
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u229E" }),
1239
+ externalEnums.map((en) => /* @__PURE__ */ jsx("optgroup", { label: en.label, children: en.values.map((v) => /* @__PURE__ */ jsxs("option", { value: String(v.value), children: [
1240
+ v.label,
1241
+ " (",
1242
+ v.value,
1243
+ ")"
1244
+ ] }, String(v.value))) }, en.id))
1245
+ ]
1246
+ }
1247
+ )
1248
+ ] })),
1249
+ rule.type === "custom" && /* @__PURE__ */ jsx(
1250
+ "input",
1251
+ {
1252
+ style: styles2.valueInput,
1253
+ placeholder: "validatorId",
1254
+ value: rule.customValidatorId ?? "",
1255
+ onChange: (e) => updateRule(index, { customValidatorId: e.target.value })
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ jsx(
1259
+ "input",
1260
+ {
1261
+ style: styles2.messageInput,
1262
+ placeholder: "error message",
1263
+ value: rule.message,
1264
+ onChange: (e) => updateRule(index, { message: e.target.value })
1265
+ }
1266
+ ),
1267
+ /* @__PURE__ */ jsx("button", { style: styles2.removeBtn, onClick: () => removeRule(index), children: "\u2715" })
1268
+ ] }, index);
1269
+ }),
1270
+ /* @__PURE__ */ jsx("button", { style: styles2.addBtn, onClick: addRule, children: "+ Add rule" })
1271
+ ] });
1272
+ }
1273
+ var styles2 = {
1274
+ container: { display: "flex", flexDirection: "column", gap: 10 },
1275
+ empty: { fontSize: 13, color: "var(--wp-text-subtle)", textAlign: "center", padding: "12px 0" },
1276
+ rule: {
1277
+ display: "flex",
1278
+ alignItems: "center",
1279
+ gap: 8,
1280
+ background: "var(--wp-surface)",
1281
+ border: "1px solid var(--wp-border)",
1282
+ borderRadius: "var(--wp-radius-lg)",
1283
+ padding: "8px 10px"
1284
+ },
1285
+ typeSelect: {
1286
+ flex: "0 0 150px",
1287
+ fontSize: 12,
1288
+ padding: "5px 6px",
1289
+ border: "1px solid var(--wp-border-muted)",
1290
+ borderRadius: "var(--wp-radius)",
1291
+ background: "var(--wp-canvas)",
1292
+ color: "var(--wp-text)"
1293
+ },
1294
+ valueGroup: { display: "flex", alignItems: "center", gap: 4 },
1295
+ valueInput: {
1296
+ width: 90,
1297
+ fontSize: 12,
1298
+ padding: "5px 6px",
1299
+ border: "1px solid var(--wp-border-muted)",
1300
+ borderRadius: "var(--wp-radius)",
1301
+ background: "var(--wp-canvas)",
1302
+ color: "var(--wp-text)"
1303
+ },
1304
+ enumPicker: {
1305
+ fontSize: 11,
1306
+ padding: "4px 4px",
1307
+ border: "1px solid var(--wp-border-muted)",
1308
+ borderRadius: "var(--wp-radius)",
1309
+ background: "var(--wp-canvas)",
1310
+ color: "var(--wp-primary)",
1311
+ cursor: "pointer",
1312
+ flexShrink: 0
1313
+ },
1314
+ messageInput: {
1315
+ flex: 1,
1316
+ fontSize: 12,
1317
+ padding: "5px 6px",
1318
+ border: "1px solid var(--wp-border-muted)",
1319
+ borderRadius: "var(--wp-radius)",
1320
+ background: "var(--wp-canvas)",
1321
+ color: "var(--wp-text)",
1322
+ minWidth: 0
1323
+ },
1324
+ removeBtn: {
1325
+ border: "none",
1326
+ background: "transparent",
1327
+ color: "var(--wp-danger)",
1328
+ cursor: "pointer",
1329
+ fontSize: 13,
1330
+ flexShrink: 0
1331
+ },
1332
+ addBtn: {
1333
+ fontSize: 12,
1334
+ padding: "6px 12px",
1335
+ background: "var(--wp-surface-muted)",
1336
+ border: "1px solid var(--wp-border-muted)",
1337
+ borderRadius: "var(--wp-radius)",
1338
+ cursor: "pointer",
1339
+ fontWeight: 500,
1340
+ alignSelf: "flex-start",
1341
+ color: "var(--wp-text-secondary)"
1342
+ }
1343
+ };
1028
1344
  function DependsOnInput({
1029
1345
  value,
1030
1346
  onChange,
@@ -1057,17 +1373,17 @@ function DependsOnInput({
1057
1373
  }, []);
1058
1374
  const getLabel = (path) => allPaths.find((p) => p.path === path)?.label ?? path;
1059
1375
  const isExternal = (path) => path.startsWith("$ext.");
1060
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: styles2.container, children: [
1061
- /* @__PURE__ */ jsxs("div", { style: styles2.tags, children: [
1062
- value.map((path) => /* @__PURE__ */ jsxs("span", { style: { ...styles2.tag, ...isExternal(path) ? styles2.tagExt : {} }, children: [
1376
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: styles3.container, children: [
1377
+ /* @__PURE__ */ jsxs("div", { style: styles3.tags, children: [
1378
+ value.map((path) => /* @__PURE__ */ jsxs("span", { style: { ...styles3.tag, ...isExternal(path) ? styles3.tagExt : {} }, children: [
1063
1379
  getLabel(path),
1064
- /* @__PURE__ */ jsx("button", { style: styles2.tagRemove, onClick: () => remove(path), children: "\u2715" })
1380
+ /* @__PURE__ */ jsx("button", { style: styles3.tagRemove, onClick: () => remove(path), children: "\u2715" })
1065
1381
  ] }, path)),
1066
1382
  /* @__PURE__ */ jsx(
1067
1383
  "input",
1068
1384
  {
1069
1385
  ref: inputRef,
1070
- style: styles2.input,
1386
+ style: styles3.input,
1071
1387
  placeholder: value.length === 0 ? "Search fields or $ext vars\u2026" : "Add more\u2026",
1072
1388
  value: query,
1073
1389
  onChange: (e) => {
@@ -1078,28 +1394,28 @@ function DependsOnInput({
1078
1394
  }
1079
1395
  )
1080
1396
  ] }),
1081
- open && /* @__PURE__ */ jsxs("div", { style: styles2.dropdown, children: [
1082
- available.length === 0 && /* @__PURE__ */ jsx("div", { style: styles2.noResults, children: allPaths.length === 0 ? "No fields available in the tree yet." : "No matching fields." }),
1397
+ open && /* @__PURE__ */ jsxs("div", { style: styles3.dropdown, children: [
1398
+ available.length === 0 && /* @__PURE__ */ jsx("div", { style: styles3.noResults, children: allPaths.length === 0 ? "No fields available in the tree yet." : "No matching fields." }),
1083
1399
  available.map((p) => /* @__PURE__ */ jsxs(
1084
1400
  "button",
1085
1401
  {
1086
- style: styles2.option,
1402
+ style: styles3.option,
1087
1403
  onMouseDown: (e) => {
1088
1404
  e.preventDefault();
1089
1405
  add(p.path);
1090
1406
  },
1091
1407
  children: [
1092
- /* @__PURE__ */ jsx("span", { style: styles2.optionLabel, children: p.label }),
1093
- /* @__PURE__ */ jsx("span", { style: styles2.optionPath, children: p.path })
1408
+ /* @__PURE__ */ jsx("span", { style: styles3.optionLabel, children: p.label }),
1409
+ /* @__PURE__ */ jsx("span", { style: styles3.optionPath, children: p.path })
1094
1410
  ]
1095
1411
  },
1096
1412
  p.path
1097
1413
  ))
1098
1414
  ] }),
1099
- value.length > 0 && /* @__PURE__ */ jsx("div", { style: styles2.hint, children: "This field will be blocked until all dependencies have a value." })
1415
+ value.length > 0 && /* @__PURE__ */ jsx("div", { style: styles3.hint, children: "This field will be blocked until all dependencies have a value." })
1100
1416
  ] });
1101
1417
  }
1102
- var styles2 = {
1418
+ var styles3 = {
1103
1419
  container: { position: "relative" },
1104
1420
  tags: {
1105
1421
  display: "flex",
@@ -1181,15 +1497,15 @@ function Modal({ title, onClose, children, width = 560 }) {
1181
1497
  document.addEventListener("keydown", handler);
1182
1498
  return () => document.removeEventListener("keydown", handler);
1183
1499
  }, [onClose]);
1184
- return /* @__PURE__ */ jsx("div", { style: styles3.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { style: { ...styles3.panel, width }, onClick: (e) => e.stopPropagation(), children: [
1185
- /* @__PURE__ */ jsxs("div", { style: styles3.header, children: [
1186
- /* @__PURE__ */ jsx("span", { style: styles3.title, children: title }),
1187
- /* @__PURE__ */ jsx("button", { style: styles3.closeBtn, onClick: onClose, children: "\u2715" })
1500
+ return /* @__PURE__ */ jsx("div", { style: styles4.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { style: { ...styles4.panel, width }, onClick: (e) => e.stopPropagation(), children: [
1501
+ /* @__PURE__ */ jsxs("div", { style: styles4.header, children: [
1502
+ /* @__PURE__ */ jsx("span", { style: styles4.title, children: title }),
1503
+ /* @__PURE__ */ jsx("button", { style: styles4.closeBtn, onClick: onClose, children: "\u2715" })
1188
1504
  ] }),
1189
- /* @__PURE__ */ jsx("div", { style: styles3.body, children })
1505
+ /* @__PURE__ */ jsx("div", { style: styles4.body, children })
1190
1506
  ] }) });
1191
1507
  }
1192
- var styles3 = {
1508
+ var styles4 = {
1193
1509
  overlay: {
1194
1510
  position: "fixed",
1195
1511
  inset: 0,
@@ -1226,16 +1542,7 @@ var styles3 = {
1226
1542
  },
1227
1543
  body: { overflowY: "auto", padding: 20, color: "var(--wp-text)" }
1228
1544
  };
1229
- var VALIDATION_TYPES = [
1230
- { type: "required", label: "Required", hasValue: false },
1231
- { type: "min", label: "Min value", hasValue: true },
1232
- { type: "max", label: "Max value", hasValue: true },
1233
- { type: "minLength", label: "Min length", hasValue: true },
1234
- { type: "maxLength", label: "Max length", hasValue: true },
1235
- { type: "email", label: "Email format", hasValue: false },
1236
- { type: "url", label: "URL format", hasValue: false },
1237
- { type: "regex", label: "Regex pattern", hasValue: true }
1238
- ];
1545
+ var ENUM_FIELD_TYPES = ["select", "multiselect", "radio"];
1239
1546
  function FieldEditor() {
1240
1547
  const {
1241
1548
  schema,
@@ -1244,85 +1551,117 @@ function FieldEditor() {
1244
1551
  updateField,
1245
1552
  setFieldCondition
1246
1553
  } = useBuilderStore();
1247
- const [newValidationType, setNewValidationType] = useState("required");
1554
+ const readOnly = useBuilderReadOnly();
1555
+ const externalEnums = useBuilderExternalEnums();
1248
1556
  const [conditionModalOpen, setConditionModalOpen] = useState(false);
1557
+ const [validationModalOpen, setValidationModalOpen] = useState(false);
1249
1558
  const step = schema.steps.find((s) => s.id === selectedStepId);
1250
1559
  const field = step?.fields.find((f) => f.id === selectedFieldId);
1251
1560
  if (!field || !step) {
1252
- return /* @__PURE__ */ jsx("div", { style: styles4.empty, children: "Select a field in the middle panel to edit its properties." });
1561
+ return /* @__PURE__ */ jsx("div", { style: styles5.empty, children: "Select a field in the middle panel to edit its properties." });
1253
1562
  }
1254
1563
  const validation = field.validation ?? [];
1255
1564
  const isRequired = validation.some((v) => v.type === "required");
1256
1565
  const hasCondition = !!field.visibleWhen;
1257
1566
  const ruleCount = field.visibleWhen?.rules.length ?? 0;
1258
- const updateValidationRule = (index, updates) => {
1259
- const updated = validation.map((v, i) => i === index ? { ...v, ...updates } : v);
1260
- updateField(step.id, field.id, { validation: updated });
1261
- };
1262
- const removeValidationRule = (index) => {
1263
- const updated = validation.filter((_, i) => i !== index);
1264
- updateField(step.id, field.id, { validation: updated.length ? updated : void 0 });
1265
- };
1266
- const addValidationRule = () => {
1267
- const newRule = {
1268
- type: newValidationType,
1269
- message: `${newValidationType} error`
1270
- };
1271
- updateField(step.id, field.id, { validation: [...validation, newRule] });
1567
+ const handleValidationChange = (rules) => {
1568
+ updateField(step.id, field.id, { validation: rules.length ? rules : void 0 });
1272
1569
  };
1273
1570
  const handleDependsOnChange = (paths) => {
1274
1571
  updateField(step.id, field.id, { dependsOn: paths.length ? paths : void 0 });
1275
1572
  };
1276
- return /* @__PURE__ */ jsxs("div", { style: styles4.container, children: [
1277
- /* @__PURE__ */ jsxs("div", { style: styles4.header, children: [
1278
- /* @__PURE__ */ jsxs("div", { style: styles4.headerLeft, children: [
1279
- /* @__PURE__ */ jsx("span", { style: styles4.headerTitle, children: "Field Editor" }),
1280
- !isRequired && /* @__PURE__ */ jsx("span", { style: styles4.optionalBadge, children: "optional" }),
1281
- isRequired && /* @__PURE__ */ jsx("span", { style: styles4.requiredBadge, children: "required" })
1573
+ return /* @__PURE__ */ jsxs("div", { style: styles5.container, children: [
1574
+ /* @__PURE__ */ jsxs("div", { style: styles5.header, children: [
1575
+ /* @__PURE__ */ jsxs("div", { style: styles5.headerLeft, children: [
1576
+ /* @__PURE__ */ jsx("span", { style: styles5.headerTitle, children: "Field Editor" }),
1577
+ !isRequired && /* @__PURE__ */ jsx("span", { style: styles5.optionalBadge, children: "optional" }),
1578
+ isRequired && /* @__PURE__ */ jsx("span", { style: styles5.requiredBadge, children: "required" })
1282
1579
  ] }),
1283
- /* @__PURE__ */ jsxs("div", { style: styles4.fieldId, children: [
1580
+ /* @__PURE__ */ jsxs("div", { style: styles5.fieldId, children: [
1284
1581
  "id: ",
1285
1582
  field.id
1286
1583
  ] })
1287
1584
  ] }),
1288
- /* @__PURE__ */ jsxs("div", { style: styles4.body, children: [
1289
- /* @__PURE__ */ jsxs("div", { style: styles4.group, children: [
1290
- /* @__PURE__ */ jsx("label", { style: styles4.label, children: "Label" }),
1585
+ /* @__PURE__ */ jsxs("div", { style: styles5.body, children: [
1586
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1587
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Label" }),
1291
1588
  /* @__PURE__ */ jsx(
1292
1589
  "input",
1293
1590
  {
1294
- style: styles4.input,
1591
+ style: styles5.input,
1295
1592
  value: field.label,
1296
1593
  onChange: (e) => updateField(step.id, field.id, { label: e.target.value })
1297
1594
  }
1298
1595
  )
1299
1596
  ] }),
1300
- /* @__PURE__ */ jsxs("div", { style: styles4.group, children: [
1301
- /* @__PURE__ */ jsx("label", { style: styles4.label, children: "Placeholder" }),
1597
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1598
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Placeholder" }),
1302
1599
  /* @__PURE__ */ jsx(
1303
1600
  "input",
1304
1601
  {
1305
- style: styles4.input,
1602
+ style: styles5.input,
1306
1603
  value: field.placeholder ?? "",
1307
1604
  placeholder: "Optional",
1308
1605
  onChange: (e) => updateField(step.id, field.id, { placeholder: e.target.value || void 0 })
1309
1606
  }
1310
1607
  )
1311
1608
  ] }),
1312
- /* @__PURE__ */ jsxs("div", { style: styles4.group, children: [
1313
- /* @__PURE__ */ jsx("label", { style: styles4.label, children: "Default value" }),
1609
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1610
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Default value" }),
1314
1611
  /* @__PURE__ */ jsx(
1315
1612
  "input",
1316
1613
  {
1317
- style: styles4.input,
1614
+ style: styles5.input,
1318
1615
  value: field.defaultValue != null ? String(field.defaultValue) : "",
1319
1616
  placeholder: "Optional",
1320
1617
  onChange: (e) => updateField(step.id, field.id, { defaultValue: e.target.value || void 0 })
1321
1618
  }
1322
1619
  )
1323
1620
  ] }),
1324
- /* @__PURE__ */ jsxs("div", { style: styles4.group, children: [
1325
- /* @__PURE__ */ jsx("label", { style: styles4.label, children: "Depends on" }),
1621
+ ENUM_FIELD_TYPES.includes(field.type) && externalEnums.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1622
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Options source" }),
1623
+ /* @__PURE__ */ jsxs(
1624
+ "select",
1625
+ {
1626
+ style: styles5.input,
1627
+ disabled: readOnly,
1628
+ value: field.externalEnumId ?? "",
1629
+ onChange: (e) => {
1630
+ const enumId = e.target.value || void 0;
1631
+ updateField(step.id, field.id, {
1632
+ externalEnumId: enumId,
1633
+ // Clear hardcoded options when switching to an enum
1634
+ options: enumId ? void 0 : field.options
1635
+ });
1636
+ },
1637
+ children: [
1638
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Hardcoded options \u2014" }),
1639
+ externalEnums.map((en) => /* @__PURE__ */ jsxs("option", { value: en.id, children: [
1640
+ en.label,
1641
+ " (",
1642
+ en.values.length,
1643
+ " items)"
1644
+ ] }, en.id))
1645
+ ]
1646
+ }
1647
+ ),
1648
+ field.externalEnumId && /* @__PURE__ */ jsx("div", { style: styles5.enumInfo, children: (() => {
1649
+ const en = externalEnums.find((e) => e.id === field.externalEnumId);
1650
+ return en ? /* @__PURE__ */ jsxs("span", { style: styles5.enumBadge, children: [
1651
+ "\u229E ",
1652
+ en.label,
1653
+ " \xB7 ",
1654
+ en.values.length,
1655
+ " options"
1656
+ ] }) : /* @__PURE__ */ jsxs("span", { style: styles5.enumMissing, children: [
1657
+ '\u26A0 Enum "',
1658
+ field.externalEnumId,
1659
+ '" not found'
1660
+ ] });
1661
+ })() })
1662
+ ] }),
1663
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1664
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Depends on" }),
1326
1665
  /* @__PURE__ */ jsx(
1327
1666
  DependsOnInput,
1328
1667
  {
@@ -1333,92 +1672,79 @@ function FieldEditor() {
1333
1672
  }
1334
1673
  )
1335
1674
  ] }),
1336
- /* @__PURE__ */ jsx("div", { style: styles4.divider }),
1337
- /* @__PURE__ */ jsxs("div", { style: styles4.conditionRow, children: [
1338
- /* @__PURE__ */ jsxs("div", { style: styles4.conditionInfo, children: [
1339
- /* @__PURE__ */ jsx("div", { style: styles4.label, children: "Visibility condition" }),
1340
- hasCondition ? /* @__PURE__ */ jsxs("div", { style: styles4.conditionSummary, children: [
1341
- /* @__PURE__ */ jsxs("span", { style: styles4.conditionBadge, children: [
1675
+ /* @__PURE__ */ jsx("div", { style: styles5.divider }),
1676
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionRow, children: [
1677
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionInfo, children: [
1678
+ /* @__PURE__ */ jsx("div", { style: styles5.label, children: "Visibility condition" }),
1679
+ hasCondition ? /* @__PURE__ */ jsxs("div", { style: styles5.conditionSummary, children: [
1680
+ /* @__PURE__ */ jsxs("span", { style: styles5.conditionBadge, children: [
1342
1681
  ruleCount,
1343
1682
  " rule",
1344
1683
  ruleCount !== 1 ? "s" : "",
1345
1684
  " \xB7 ",
1346
1685
  field.visibleWhen.combinator.toUpperCase()
1347
1686
  ] }),
1348
- /* @__PURE__ */ jsx("span", { style: styles4.conditionDesc, children: "Field is conditional" })
1349
- ] }) : /* @__PURE__ */ jsx("div", { style: styles4.conditionNone, children: "Always visible" })
1687
+ /* @__PURE__ */ jsx("span", { style: styles5.conditionDesc, children: "Field is conditional" })
1688
+ ] }) : /* @__PURE__ */ jsx("div", { style: styles5.conditionNone, children: "Always visible" })
1350
1689
  ] }),
1351
- /* @__PURE__ */ jsxs("div", { style: styles4.conditionActions, children: [
1352
- /* @__PURE__ */ jsx("button", { style: styles4.editConditionBtn, onClick: () => setConditionModalOpen(true), children: hasCondition ? "Edit" : "Add" }),
1690
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionActions, children: [
1691
+ /* @__PURE__ */ jsx("button", { style: styles5.editConditionBtn, onClick: () => setConditionModalOpen(true), children: hasCondition ? "Edit" : "Add" }),
1353
1692
  hasCondition && /* @__PURE__ */ jsx(
1354
1693
  "button",
1355
1694
  {
1356
- style: styles4.clearConditionBtn,
1695
+ style: styles5.clearConditionBtn,
1357
1696
  onClick: () => setFieldCondition(step.id, field.id, void 0),
1358
1697
  children: "Clear"
1359
1698
  }
1360
1699
  )
1361
1700
  ] })
1362
1701
  ] }),
1363
- /* @__PURE__ */ jsx("div", { style: styles4.divider }),
1364
- /* @__PURE__ */ jsxs("div", { style: styles4.sectionTitle, children: [
1365
- "Validation",
1366
- !isRequired && /* @__PURE__ */ jsx("span", { style: styles4.optionalHint, children: '\u2014 no "required" rule \u2192 field is optional' })
1367
- ] }),
1368
- validation.length === 0 && /* @__PURE__ */ jsx("div", { style: styles4.noRules, children: "No rules \xB7 field is optional by default." }),
1369
- validation.map((rule, index) => {
1370
- const def = VALIDATION_TYPES.find((vt) => vt.type === rule.type);
1371
- return /* @__PURE__ */ jsxs("div", { style: styles4.ruleCard, children: [
1372
- /* @__PURE__ */ jsxs("div", { style: styles4.ruleHeader, children: [
1373
- /* @__PURE__ */ jsx(
1374
- "span",
1375
- {
1376
- style: {
1377
- ...styles4.ruleBadge,
1378
- ...rule.type === "required" ? styles4.requiredRuleBadge : {}
1379
- },
1380
- children: rule.type
1381
- }
1382
- ),
1383
- /* @__PURE__ */ jsx("button", { style: styles4.removeRuleBtn, onClick: () => removeValidationRule(index), children: "\u2715" })
1384
- ] }),
1385
- def?.hasValue && /* @__PURE__ */ jsxs("div", { style: styles4.ruleRow, children: [
1386
- /* @__PURE__ */ jsx("label", { style: styles4.ruleLabel, children: "Value" }),
1387
- /* @__PURE__ */ jsx(
1388
- "input",
1389
- {
1390
- style: styles4.ruleInput,
1391
- value: rule.value != null ? String(rule.value) : "",
1392
- onChange: (e) => updateValidationRule(index, { value: e.target.value })
1393
- }
1394
- )
1395
- ] }),
1396
- /* @__PURE__ */ jsxs("div", { style: styles4.ruleRow, children: [
1397
- /* @__PURE__ */ jsx("label", { style: styles4.ruleLabel, children: "Error message" }),
1398
- /* @__PURE__ */ jsx(
1399
- "input",
1400
- {
1401
- style: styles4.ruleInput,
1402
- value: rule.message,
1403
- onChange: (e) => updateValidationRule(index, { message: e.target.value })
1404
- }
1405
- )
1406
- ] })
1407
- ] }, index);
1408
- }),
1409
- /* @__PURE__ */ jsxs("div", { style: styles4.addRule, children: [
1410
- /* @__PURE__ */ jsx(
1411
- "select",
1412
- {
1413
- style: styles4.ruleSelect,
1414
- value: newValidationType,
1415
- onChange: (e) => setNewValidationType(e.target.value),
1416
- children: VALIDATION_TYPES.map((vt) => /* @__PURE__ */ jsx("option", { value: vt.type, children: vt.label }, vt.type))
1417
- }
1418
- ),
1419
- /* @__PURE__ */ jsx("button", { style: styles4.addRuleBtn, onClick: addValidationRule, children: "+ Add rule" })
1702
+ /* @__PURE__ */ jsx("div", { style: styles5.divider }),
1703
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionRow, children: [
1704
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionInfo, children: [
1705
+ /* @__PURE__ */ jsx("div", { style: styles5.label, children: "Validation" }),
1706
+ validation.length > 0 ? /* @__PURE__ */ jsxs("div", { style: styles5.conditionSummary, children: [
1707
+ /* @__PURE__ */ jsxs("span", { style: styles5.validationBadge, children: [
1708
+ validation.length,
1709
+ " rule",
1710
+ validation.length !== 1 ? "s" : "",
1711
+ isRequired ? " \xB7 required" : ""
1712
+ ] }),
1713
+ /* @__PURE__ */ jsx("span", { style: styles5.conditionDesc, children: validation.map((r) => r.type).join(", ") })
1714
+ ] }) : /* @__PURE__ */ jsx("div", { style: styles5.conditionNone, children: "No rules \xB7 field is optional" })
1715
+ ] }),
1716
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionActions, children: [
1717
+ /* @__PURE__ */ jsx("button", { style: styles5.editConditionBtn, onClick: () => setValidationModalOpen(true), children: validation.length > 0 ? "Edit" : "Add" }),
1718
+ validation.length > 0 && /* @__PURE__ */ jsx(
1719
+ "button",
1720
+ {
1721
+ style: styles5.clearConditionBtn,
1722
+ onClick: () => updateField(step.id, field.id, { validation: void 0 }),
1723
+ children: "Clear"
1724
+ }
1725
+ )
1726
+ ] })
1420
1727
  ] })
1421
1728
  ] }),
1729
+ validationModalOpen && /* @__PURE__ */ jsxs(
1730
+ Modal,
1731
+ {
1732
+ title: `Validation \u2014 "${field.label}"`,
1733
+ onClose: () => setValidationModalOpen(false),
1734
+ width: 680,
1735
+ children: [
1736
+ /* @__PURE__ */ jsx("p", { style: styles5.modalHint, children: "Define validation rules for this field. All rules must pass for the field to be valid." }),
1737
+ /* @__PURE__ */ jsx(
1738
+ ValidationBuilder,
1739
+ {
1740
+ value: validation,
1741
+ onChange: handleValidationChange
1742
+ }
1743
+ ),
1744
+ /* @__PURE__ */ jsx("div", { style: styles5.modalFooter, children: /* @__PURE__ */ jsx("button", { style: styles5.modalCloseBtn, onClick: () => setValidationModalOpen(false), children: "Done" }) })
1745
+ ]
1746
+ }
1747
+ ),
1422
1748
  conditionModalOpen && /* @__PURE__ */ jsxs(
1423
1749
  Modal,
1424
1750
  {
@@ -1426,7 +1752,7 @@ function FieldEditor() {
1426
1752
  onClose: () => setConditionModalOpen(false),
1427
1753
  width: 620,
1428
1754
  children: [
1429
- /* @__PURE__ */ jsx("p", { style: styles4.modalHint, children: "Define when this field is visible within its step." }),
1755
+ /* @__PURE__ */ jsx("p", { style: styles5.modalHint, children: "Define when this field is visible within its step." }),
1430
1756
  /* @__PURE__ */ jsx(
1431
1757
  ConditionBuilder,
1432
1758
  {
@@ -1436,13 +1762,13 @@ function FieldEditor() {
1436
1762
  excludeFieldId: field.id
1437
1763
  }
1438
1764
  ),
1439
- /* @__PURE__ */ jsx("div", { style: styles4.modalFooter, children: /* @__PURE__ */ jsx("button", { style: styles4.modalCloseBtn, onClick: () => setConditionModalOpen(false), children: "Done" }) })
1765
+ /* @__PURE__ */ jsx("div", { style: styles5.modalFooter, children: /* @__PURE__ */ jsx("button", { style: styles5.modalCloseBtn, onClick: () => setConditionModalOpen(false), children: "Done" }) })
1440
1766
  ]
1441
1767
  }
1442
1768
  )
1443
1769
  ] });
1444
1770
  }
1445
- var styles4 = {
1771
+ var styles5 = {
1446
1772
  container: { display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" },
1447
1773
  empty: {
1448
1774
  display: "flex",
@@ -1526,60 +1852,30 @@ var styles4 = {
1526
1852
  borderRadius: "var(--wp-radius)",
1527
1853
  cursor: "pointer"
1528
1854
  },
1529
- sectionTitle: { fontSize: 12, fontWeight: 700, color: "var(--wp-text-secondary)", textTransform: "uppercase", letterSpacing: "0.05em" },
1530
- optionalHint: { fontSize: 10, color: "var(--wp-text-subtle)", fontWeight: 400, textTransform: "none", letterSpacing: 0 },
1531
- noRules: { fontSize: 12, color: "var(--wp-text-subtle)" },
1532
- ruleCard: {
1533
- background: "var(--wp-surface)",
1534
- border: "1px solid var(--wp-border)",
1535
- borderRadius: "var(--wp-radius-lg)",
1536
- padding: 10,
1537
- display: "flex",
1538
- flexDirection: "column",
1539
- gap: 8
1540
- },
1541
- ruleHeader: { display: "flex", justifyContent: "space-between", alignItems: "center" },
1542
- ruleBadge: {
1855
+ validationBadge: {
1543
1856
  fontSize: 11,
1544
1857
  fontWeight: 700,
1545
- color: "var(--wp-primary-dark)",
1546
1858
  background: "var(--wp-primary-bg)",
1859
+ color: "var(--wp-primary-dark)",
1547
1860
  padding: "2px 8px",
1548
1861
  borderRadius: 4
1549
1862
  },
1550
- requiredRuleBadge: { background: "var(--wp-danger-bg-strong)", color: "var(--wp-danger)" },
1551
- removeRuleBtn: { border: "none", background: "transparent", color: "var(--wp-danger)", cursor: "pointer", fontSize: 12 },
1552
- ruleRow: { display: "flex", flexDirection: "column", gap: 3 },
1553
- ruleLabel: { fontSize: 10, fontWeight: 600, color: "var(--wp-text-subtle)", textTransform: "uppercase" },
1554
- ruleInput: {
1555
- fontSize: 12,
1556
- padding: "4px 6px",
1557
- border: "1px solid var(--wp-border-muted)",
1558
- borderRadius: 4,
1559
- outline: "none",
1560
- background: "var(--wp-canvas)",
1561
- color: "var(--wp-text)"
1562
- },
1563
- addRule: { display: "flex", gap: 8, alignItems: "center" },
1564
- ruleSelect: {
1565
- flex: 1,
1566
- fontSize: 12,
1567
- padding: "5px 6px",
1568
- border: "1px solid var(--wp-border-muted)",
1569
- borderRadius: "var(--wp-radius)",
1570
- background: "var(--wp-canvas)",
1571
- color: "var(--wp-text)"
1863
+ enumInfo: { marginTop: 4 },
1864
+ enumBadge: {
1865
+ fontSize: 11,
1866
+ fontWeight: 600,
1867
+ padding: "2px 8px",
1868
+ background: "var(--wp-info-bg)",
1869
+ color: "var(--wp-info-text)",
1870
+ borderRadius: 4
1572
1871
  },
1573
- addRuleBtn: {
1574
- fontSize: 12,
1575
- padding: "5px 10px",
1576
- background: "var(--wp-surface-muted)",
1577
- border: "1px solid var(--wp-border-muted)",
1578
- borderRadius: "var(--wp-radius)",
1579
- cursor: "pointer",
1580
- fontWeight: 500,
1581
- whiteSpace: "nowrap",
1582
- color: "var(--wp-text-secondary)"
1872
+ enumMissing: {
1873
+ fontSize: 11,
1874
+ fontWeight: 600,
1875
+ padding: "2px 8px",
1876
+ background: "var(--wp-warning-bg)",
1877
+ color: "var(--wp-warning)",
1878
+ borderRadius: 4
1583
1879
  },
1584
1880
  modalHint: { fontSize: 13, color: "var(--wp-text-muted)", marginBottom: 16, marginTop: 0 },
1585
1881
  modalFooter: { marginTop: 20, display: "flex", justifyContent: "flex-end" },
@@ -1713,10 +2009,14 @@ function FieldList() {
1713
2009
  selectedFieldId,
1714
2010
  addField,
1715
2011
  removeField,
2012
+ duplicateField,
1716
2013
  updateField,
1717
2014
  selectField,
1718
2015
  reorderFields
1719
2016
  } = useBuilderStore();
2017
+ const readOnly = useBuilderReadOnly();
2018
+ const appCustomTypes = useBuilderCustomTypes();
2019
+ const externalEnums = useBuilderExternalEnums();
1720
2020
  const [moveError, setMoveError] = useState(null);
1721
2021
  const step = schema.steps.find((s) => s.id === selectedStepId);
1722
2022
  const allDependencyTargets = /* @__PURE__ */ new Set();
@@ -1728,7 +2028,7 @@ function FieldList() {
1728
2028
  }
1729
2029
  }
1730
2030
  if (!step) {
1731
- return /* @__PURE__ */ jsx("div", { style: styles5.empty, children: "Select a step on the left to manage its fields." });
2031
+ return /* @__PURE__ */ jsx("div", { style: styles6.empty, children: "Select a step on the left to manage its fields." });
1732
2032
  }
1733
2033
  const tryMove = (fromIndex, toIndex) => {
1734
2034
  const check = isFieldMoveValid(step.fields, step.id, fromIndex, toIndex);
@@ -1740,30 +2040,31 @@ function FieldList() {
1740
2040
  setMoveError(null);
1741
2041
  reorderFields(step.id, fromIndex, toIndex);
1742
2042
  };
1743
- return /* @__PURE__ */ jsxs("div", { style: styles5.container, children: [
1744
- /* @__PURE__ */ jsxs("div", { style: styles5.header, children: [
2043
+ return /* @__PURE__ */ jsxs("div", { style: styles6.container, children: [
2044
+ /* @__PURE__ */ jsxs("div", { style: styles6.header, children: [
1745
2045
  /* @__PURE__ */ jsxs("div", { children: [
1746
- /* @__PURE__ */ jsx("div", { style: styles5.stepTitle, children: step.title }),
1747
- /* @__PURE__ */ jsxs("div", { style: styles5.stepSub, children: [
2046
+ /* @__PURE__ */ jsx("div", { style: styles6.stepTitle, children: step.title }),
2047
+ /* @__PURE__ */ jsxs("div", { style: styles6.stepSub, children: [
1748
2048
  step.fields.length,
1749
2049
  " field",
1750
2050
  step.fields.length !== 1 ? "s" : ""
1751
2051
  ] })
1752
2052
  ] }),
1753
- /* @__PURE__ */ jsx("button", { style: styles5.addBtn, onClick: () => addField(step.id), children: "+ Add field" })
2053
+ !readOnly && /* @__PURE__ */ jsx("button", { style: styles6.addBtn, onClick: () => addField(step.id), children: "+ Add field" })
1754
2054
  ] }),
1755
- moveError && /* @__PURE__ */ jsxs("div", { style: styles5.errorBanner, children: [
2055
+ moveError && /* @__PURE__ */ jsxs("div", { style: styles6.errorBanner, children: [
1756
2056
  /* @__PURE__ */ jsx("span", { children: "\u26A0" }),
1757
2057
  " ",
1758
2058
  moveError
1759
2059
  ] }),
1760
- /* @__PURE__ */ jsxs("div", { style: styles5.list, children: [
1761
- step.fields.length === 0 && /* @__PURE__ */ jsx("div", { style: styles5.emptyFields, children: 'No fields yet. Click "Add field" to start.' }),
2060
+ /* @__PURE__ */ jsxs("div", { style: styles6.list, children: [
2061
+ step.fields.length === 0 && /* @__PURE__ */ jsx("div", { style: styles6.emptyFields, children: 'No fields yet. Click "Add field" to start.' }),
1762
2062
  step.fields.map((field, index) => {
1763
2063
  const isSelected = field.id === selectedFieldId;
1764
2064
  const isRequired = field.validation?.some((v) => v.type === "required") ?? false;
1765
2065
  const hasCondition = !!field.visibleWhen;
1766
2066
  const hasDeps = (field.dependsOn?.length ?? 0) > 0;
2067
+ const enumDef = field.externalEnumId ? externalEnums.find((e) => e.id === field.externalEnumId) : void 0;
1767
2068
  const isUsedAsDep = allDependencyTargets.has(`${step.id}.${field.id}`);
1768
2069
  const canMoveUp = index > 0 && isFieldMoveValid(step.fields, step.id, index, index - 1).valid;
1769
2070
  const canMoveDown = index < step.fields.length - 1 && isFieldMoveValid(step.fields, step.id, index, index + 1).valid;
@@ -1777,29 +2078,45 @@ function FieldList() {
1777
2078
  return /* @__PURE__ */ jsxs(
1778
2079
  "div",
1779
2080
  {
1780
- style: { ...styles5.card, ...isSelected ? styles5.cardSelected : {} },
2081
+ style: { ...styles6.card, ...isSelected ? styles6.cardSelected : {} },
1781
2082
  onClick: () => selectField(field.id),
1782
2083
  children: [
1783
- /* @__PURE__ */ jsxs("div", { style: styles5.cardTop, children: [
1784
- /* @__PURE__ */ jsxs("div", { style: styles5.cardLeft, children: [
1785
- /* @__PURE__ */ jsx("span", { style: styles5.typeBadge, children: field.type }),
1786
- /* @__PURE__ */ jsx("span", { style: styles5.fieldLabel, children: field.label })
2084
+ /* @__PURE__ */ jsxs("div", { style: styles6.cardTop, children: [
2085
+ /* @__PURE__ */ jsxs("div", { style: styles6.cardLeft, children: [
2086
+ (() => {
2087
+ const ct = appCustomTypes.find((c) => c.id === field.type);
2088
+ return /* @__PURE__ */ jsx("span", { style: { ...styles6.typeBadge, ...ct ? styles6.typeBadgeCustom : {} }, children: ct ? `${ct.icon ? ct.icon + " " : ""}${ct.label}` : field.type });
2089
+ })(),
2090
+ /* @__PURE__ */ jsx("span", { style: styles6.fieldLabel, children: field.label })
1787
2091
  ] }),
1788
- /* @__PURE__ */ jsxs("div", { style: styles5.cardRight, children: [
1789
- /* @__PURE__ */ jsx(
2092
+ /* @__PURE__ */ jsxs("div", { style: styles6.cardRight, children: [
2093
+ !readOnly && /* @__PURE__ */ jsxs(
1790
2094
  "select",
1791
2095
  {
1792
- style: styles5.typeSelect,
2096
+ style: styles6.typeSelect,
1793
2097
  value: field.type,
1794
2098
  onClick: (e) => e.stopPropagation(),
1795
- onChange: (e) => updateField(step.id, field.id, { type: e.target.value }),
1796
- children: FIELD_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t))
2099
+ onChange: (e) => {
2100
+ const newType = e.target.value;
2101
+ const customType = appCustomTypes.find((ct) => ct.id === newType);
2102
+ updateField(step.id, field.id, {
2103
+ type: newType,
2104
+ ...customType?.defaultValidation ? { validation: customType.defaultValidation } : {}
2105
+ });
2106
+ },
2107
+ children: [
2108
+ FIELD_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t)),
2109
+ appCustomTypes.length > 0 && /* @__PURE__ */ jsx("optgroup", { label: "Custom", children: appCustomTypes.map((ct) => /* @__PURE__ */ jsxs("option", { value: ct.id, children: [
2110
+ ct.icon ? `${ct.icon} ` : "",
2111
+ ct.label
2112
+ ] }, ct.id)) })
2113
+ ]
1797
2114
  }
1798
2115
  ),
1799
- index > 0 && /* @__PURE__ */ jsx(
2116
+ !readOnly && index > 0 && /* @__PURE__ */ jsx(
1800
2117
  "button",
1801
2118
  {
1802
- style: { ...styles5.iconBtn, ...canMoveUp ? {} : styles5.iconBtnBlocked },
2119
+ style: { ...styles6.iconBtn, ...canMoveUp ? {} : styles6.iconBtnBlocked },
1803
2120
  title: canMoveUp ? "Move up" : "Can't move \u2014 dependency order required",
1804
2121
  onClick: (e) => {
1805
2122
  e.stopPropagation();
@@ -1808,10 +2125,10 @@ function FieldList() {
1808
2125
  children: "\u2191"
1809
2126
  }
1810
2127
  ),
1811
- index < step.fields.length - 1 && /* @__PURE__ */ jsx(
2128
+ !readOnly && index < step.fields.length - 1 && /* @__PURE__ */ jsx(
1812
2129
  "button",
1813
2130
  {
1814
- style: { ...styles5.iconBtn, ...canMoveDown ? {} : styles5.iconBtnBlocked },
2131
+ style: { ...styles6.iconBtn, ...canMoveDown ? {} : styles6.iconBtnBlocked },
1815
2132
  title: canMoveDown ? "Move down" : "Can't move \u2014 dependency order required",
1816
2133
  onClick: (e) => {
1817
2134
  e.stopPropagation();
@@ -1820,10 +2137,22 @@ function FieldList() {
1820
2137
  children: "\u2193"
1821
2138
  }
1822
2139
  ),
1823
- /* @__PURE__ */ jsx(
2140
+ !readOnly && /* @__PURE__ */ jsx(
1824
2141
  "button",
1825
2142
  {
1826
- style: { ...styles5.iconBtn, color: "var(--wp-danger)" },
2143
+ style: styles6.iconBtn,
2144
+ title: "Duplicate field",
2145
+ onClick: (e) => {
2146
+ e.stopPropagation();
2147
+ duplicateField(step.id, field.id);
2148
+ },
2149
+ children: "\u29C9"
2150
+ }
2151
+ ),
2152
+ !readOnly && /* @__PURE__ */ jsx(
2153
+ "button",
2154
+ {
2155
+ style: { ...styles6.iconBtn, color: "var(--wp-danger)" },
1827
2156
  title: "Remove field",
1828
2157
  onClick: (e) => {
1829
2158
  e.stopPropagation();
@@ -1834,23 +2163,27 @@ function FieldList() {
1834
2163
  )
1835
2164
  ] })
1836
2165
  ] }),
1837
- /* @__PURE__ */ jsxs("div", { style: styles5.badges, children: [
1838
- !isRequired && /* @__PURE__ */ jsx("span", { style: styles5.badgeOptional, children: "optional" }),
1839
- isRequired && /* @__PURE__ */ jsx("span", { style: styles5.badgeRequired, children: "required" }),
1840
- hasCondition && /* @__PURE__ */ jsx("span", { style: styles5.badgeCondition, children: "conditional" }),
1841
- hasDeps && /* @__PURE__ */ jsxs("span", { style: styles5.badgeDep, children: [
2166
+ /* @__PURE__ */ jsxs("div", { style: styles6.badges, children: [
2167
+ !isRequired && /* @__PURE__ */ jsx("span", { style: styles6.badgeOptional, children: "optional" }),
2168
+ isRequired && /* @__PURE__ */ jsx("span", { style: styles6.badgeRequired, children: "required" }),
2169
+ hasCondition && /* @__PURE__ */ jsx("span", { style: styles6.badgeCondition, children: "conditional" }),
2170
+ hasDeps && /* @__PURE__ */ jsxs("span", { style: styles6.badgeDep, children: [
1842
2171
  "depends on ",
1843
2172
  field.dependsOn.length
1844
2173
  ] }),
1845
- isUsedAsDep && /* @__PURE__ */ jsx("span", { style: styles5.badgeUsed, children: "\u2190 dependency" })
2174
+ isUsedAsDep && /* @__PURE__ */ jsx("span", { style: styles6.badgeUsed, children: "\u2190 dependency" }),
2175
+ field.externalEnumId && /* @__PURE__ */ jsxs("span", { style: styles6.badgeEnum, children: [
2176
+ "\u229E ",
2177
+ enumDef ? enumDef.label : field.externalEnumId
2178
+ ] })
1846
2179
  ] }),
1847
- intraStepDeps.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles5.depRow, children: [
1848
- /* @__PURE__ */ jsx("span", { style: styles5.depLabel, children: "needs:" }),
1849
- intraStepDeps.map((label) => /* @__PURE__ */ jsx("span", { style: styles5.depBadge, children: label }, label))
2180
+ intraStepDeps.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.depRow, children: [
2181
+ /* @__PURE__ */ jsx("span", { style: styles6.depLabel, children: "needs:" }),
2182
+ intraStepDeps.map((label) => /* @__PURE__ */ jsx("span", { style: styles6.depBadge, children: label }, label))
1850
2183
  ] }),
1851
- intraStepDependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles5.depRow, children: [
1852
- /* @__PURE__ */ jsx("span", { style: styles5.depLabelUsed, children: "used by:" }),
1853
- intraStepDependents.map((f) => /* @__PURE__ */ jsx("span", { style: styles5.depBadgeUsed, children: f.label }, f.id))
2184
+ intraStepDependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.depRow, children: [
2185
+ /* @__PURE__ */ jsx("span", { style: styles6.depLabelUsed, children: "used by:" }),
2186
+ intraStepDependents.map((f) => /* @__PURE__ */ jsx("span", { style: styles6.depBadgeUsed, children: f.label }, f.id))
1854
2187
  ] })
1855
2188
  ]
1856
2189
  },
@@ -1860,7 +2193,7 @@ function FieldList() {
1860
2193
  ] })
1861
2194
  ] });
1862
2195
  }
1863
- var styles5 = {
2196
+ var styles6 = {
1864
2197
  container: { display: "flex", flexDirection: "column", height: "100%" },
1865
2198
  empty: {
1866
2199
  display: "flex",
@@ -1929,6 +2262,10 @@ var styles5 = {
1929
2262
  fontWeight: 600,
1930
2263
  flexShrink: 0
1931
2264
  },
2265
+ typeBadgeCustom: {
2266
+ background: "var(--wp-success-bg)",
2267
+ color: "var(--wp-success)"
2268
+ },
1932
2269
  fieldLabel: {
1933
2270
  fontSize: 13,
1934
2271
  fontWeight: 600,
@@ -2003,6 +2340,14 @@ var styles5 = {
2003
2340
  borderRadius: 3,
2004
2341
  textTransform: "uppercase"
2005
2342
  },
2343
+ badgeEnum: {
2344
+ fontSize: 9,
2345
+ fontWeight: 600,
2346
+ padding: "1px 6px",
2347
+ background: "var(--wp-info-bg)",
2348
+ color: "var(--wp-info-text)",
2349
+ borderRadius: 3
2350
+ },
2006
2351
  depRow: { display: "flex", alignItems: "center", flexWrap: "wrap", gap: 4 },
2007
2352
  depLabel: { fontSize: 10, fontWeight: 600, color: "var(--wp-text-muted)", textTransform: "uppercase" },
2008
2353
  depLabelUsed: { fontSize: 10, fontWeight: 600, color: "var(--wp-success)", textTransform: "uppercase" },
@@ -2023,12 +2368,28 @@ var styles5 = {
2023
2368
  borderRadius: 4
2024
2369
  }
2025
2370
  };
2026
- function PreviewPanel({ store, schema }) {
2371
+ function PreviewPanel({ store, schema, externalEnums }) {
2027
2372
  const [done, setDone] = useState(false);
2028
- const { tree, currentStep, progress } = useWaypoint(store);
2373
+ const { tree, currentStep, progress } = useWaypoint(store, externalEnums);
2029
2374
  const stepId = currentStep?.definition.id ?? "";
2030
2375
  const { fields, stepData, setFieldValue } = useWaypointStep(store, stepId);
2031
2376
  const [errors, setErrors] = useState({});
2377
+ const extVarDefs = schema.externalVariables ?? [];
2378
+ const [mockVars, setMockVars] = useState(() => {
2379
+ const initial = {};
2380
+ for (const v of extVarDefs) {
2381
+ initial[v.id] = v.type === "boolean" ? false : v.type === "number" ? 0 : "";
2382
+ }
2383
+ return initial;
2384
+ });
2385
+ useEffect(() => {
2386
+ for (const [varId, value] of Object.entries(mockVars)) {
2387
+ store.getState().setExternalVar(varId, value);
2388
+ }
2389
+ }, [mockVars]);
2390
+ function handleMockVarChange(varId, value) {
2391
+ setMockVars((prev) => ({ ...prev, [varId]: value }));
2392
+ }
2032
2393
  function handleNext() {
2033
2394
  const newErrors = {};
2034
2395
  for (const field of fields) {
@@ -2049,7 +2410,7 @@ function PreviewPanel({ store, schema }) {
2049
2410
  const oldIds = tree.steps.map((s) => s.definition.id).join(",");
2050
2411
  store.getState().setStepData(stepId, stepData);
2051
2412
  const newData = store.getState().data;
2052
- const newTree = resolveTree(schema, newData, {});
2413
+ const newTree = resolveTree(schema, newData, store.getState().externalVars, externalEnums);
2053
2414
  const newIds = newTree.steps.map((s) => s.definition.id).join(",");
2054
2415
  if (oldIds !== newIds) {
2055
2416
  store.getState().truncateHistoryAt(stepId);
@@ -2067,6 +2428,9 @@ function PreviewPanel({ store, schema }) {
2067
2428
  }
2068
2429
  function handleRestart() {
2069
2430
  store.getState().init(schema);
2431
+ for (const [varId, value] of Object.entries(mockVars)) {
2432
+ store.getState().setExternalVar(varId, value);
2433
+ }
2070
2434
  setDone(false);
2071
2435
  setErrors({});
2072
2436
  }
@@ -2074,39 +2438,59 @@ function PreviewPanel({ store, schema }) {
2074
2438
  (s) => s.definition.id === currentStep?.definition.id
2075
2439
  );
2076
2440
  if (done) {
2077
- return /* @__PURE__ */ jsxs("div", { style: styles6.panel, children: [
2078
- /* @__PURE__ */ jsx("div", { style: styles6.leftCol, children: /* @__PURE__ */ jsx(
2441
+ return /* @__PURE__ */ jsxs("div", { style: styles7.panel, children: [
2442
+ /* @__PURE__ */ jsxs("div", { style: styles7.leftCol, children: [
2443
+ /* @__PURE__ */ jsx(
2444
+ StepList,
2445
+ {
2446
+ tree,
2447
+ currentIdx,
2448
+ onSelect: (id) => store.getState().setCurrentStep(id)
2449
+ }
2450
+ ),
2451
+ extVarDefs.length > 0 && /* @__PURE__ */ jsx(
2452
+ MockVarPanel,
2453
+ {
2454
+ extVarDefs,
2455
+ mockVars,
2456
+ onChange: handleMockVarChange
2457
+ }
2458
+ )
2459
+ ] }),
2460
+ /* @__PURE__ */ jsx("div", { style: styles7.divider }),
2461
+ /* @__PURE__ */ jsx("div", { style: styles7.rightCol, children: /* @__PURE__ */ jsxs("div", { style: styles7.doneScreen, children: [
2462
+ /* @__PURE__ */ jsx("div", { style: styles7.doneIcon, children: "\u2713" }),
2463
+ /* @__PURE__ */ jsx("div", { style: styles7.doneTitle, children: "Parcours termin\xE9 !" }),
2464
+ /* @__PURE__ */ jsx("p", { style: styles7.doneText, children: "Toutes les \xE9tapes ont \xE9t\xE9 compl\xE9t\xE9es avec succ\xE8s." }),
2465
+ /* @__PURE__ */ jsx("button", { style: styles7.primaryBtn, onClick: handleRestart, children: "Recommencer" })
2466
+ ] }) })
2467
+ ] });
2468
+ }
2469
+ return /* @__PURE__ */ jsxs("div", { style: styles7.panel, children: [
2470
+ /* @__PURE__ */ jsx(DevPanel, { store }),
2471
+ /* @__PURE__ */ jsxs("div", { style: styles7.leftCol, children: [
2472
+ /* @__PURE__ */ jsx(
2079
2473
  StepList,
2080
2474
  {
2081
2475
  tree,
2082
2476
  currentIdx,
2083
2477
  onSelect: (id) => store.getState().setCurrentStep(id)
2084
2478
  }
2085
- ) }),
2086
- /* @__PURE__ */ jsx("div", { style: styles6.divider }),
2087
- /* @__PURE__ */ jsx("div", { style: styles6.rightCol, children: /* @__PURE__ */ jsxs("div", { style: styles6.doneScreen, children: [
2088
- /* @__PURE__ */ jsx("div", { style: styles6.doneIcon, children: "\u2713" }),
2089
- /* @__PURE__ */ jsx("div", { style: styles6.doneTitle, children: "Parcours termin\xE9 !" }),
2090
- /* @__PURE__ */ jsx("p", { style: styles6.doneText, children: "Toutes les \xE9tapes ont \xE9t\xE9 compl\xE9t\xE9es avec succ\xE8s." }),
2091
- /* @__PURE__ */ jsx("button", { style: styles6.primaryBtn, onClick: handleRestart, children: "Recommencer" })
2092
- ] }) })
2093
- ] });
2094
- }
2095
- return /* @__PURE__ */ jsxs("div", { style: styles6.panel, children: [
2096
- /* @__PURE__ */ jsx(DevPanel, { store }),
2097
- /* @__PURE__ */ jsx("div", { style: styles6.leftCol, children: /* @__PURE__ */ jsx(
2098
- StepList,
2099
- {
2100
- tree,
2101
- currentIdx,
2102
- onSelect: (id) => store.getState().setCurrentStep(id)
2103
- }
2104
- ) }),
2105
- /* @__PURE__ */ jsx("div", { style: styles6.divider }),
2106
- /* @__PURE__ */ jsx("div", { style: styles6.rightCol, children: /* @__PURE__ */ jsxs("div", { style: styles6.stepRenderer, children: [
2107
- /* @__PURE__ */ jsx("div", { style: styles6.progressTrack, children: /* @__PURE__ */ jsx("div", { style: { ...styles6.progressFill, width: `${progress}%` } }) }),
2108
- /* @__PURE__ */ jsx("h2", { style: styles6.stepTitle, children: currentStep?.definition.title ?? "" }),
2109
- /* @__PURE__ */ jsx("div", { style: styles6.fieldsContainer, children: fields.map((field) => /* @__PURE__ */ jsx(
2479
+ ),
2480
+ extVarDefs.length > 0 && /* @__PURE__ */ jsx(
2481
+ MockVarPanel,
2482
+ {
2483
+ extVarDefs,
2484
+ mockVars,
2485
+ onChange: handleMockVarChange
2486
+ }
2487
+ )
2488
+ ] }),
2489
+ /* @__PURE__ */ jsx("div", { style: styles7.divider }),
2490
+ /* @__PURE__ */ jsx("div", { style: styles7.rightCol, children: /* @__PURE__ */ jsxs("div", { style: styles7.stepRenderer, children: [
2491
+ /* @__PURE__ */ jsx("div", { style: styles7.progressTrack, children: /* @__PURE__ */ jsx("div", { style: { ...styles7.progressFill, width: `${progress}%` } }) }),
2492
+ /* @__PURE__ */ jsx("h2", { style: styles7.stepTitle, children: currentStep?.definition.title ?? "" }),
2493
+ /* @__PURE__ */ jsx("div", { style: styles7.fieldsContainer, children: fields.map((field) => /* @__PURE__ */ jsx(
2110
2494
  FieldRenderer,
2111
2495
  {
2112
2496
  field,
@@ -2125,9 +2509,9 @@ function PreviewPanel({ store, schema }) {
2125
2509
  },
2126
2510
  field.definition.id
2127
2511
  )) }),
2128
- /* @__PURE__ */ jsxs("div", { style: styles6.navRow, children: [
2129
- currentIdx > 0 && /* @__PURE__ */ jsx("button", { style: styles6.secondaryBtn, onClick: handlePrev, children: "\u2190 Pr\xE9c\xE9dent" }),
2130
- /* @__PURE__ */ jsx("button", { style: { ...styles6.primaryBtn, marginLeft: "auto" }, onClick: handleNext, children: getNextStep(tree.steps, stepId) ? "Continuer \u2192" : "Terminer \u2713" })
2512
+ /* @__PURE__ */ jsxs("div", { style: styles7.navRow, children: [
2513
+ currentIdx > 0 && /* @__PURE__ */ jsx("button", { style: styles7.secondaryBtn, onClick: handlePrev, children: "\u2190 Pr\xE9c\xE9dent" }),
2514
+ /* @__PURE__ */ jsx("button", { style: { ...styles7.primaryBtn, marginLeft: "auto" }, onClick: handleNext, children: getNextStep(tree.steps, stepId) ? "Continuer \u2192" : "Terminer \u2713" })
2131
2515
  ] })
2132
2516
  ] }) })
2133
2517
  ] });
@@ -2137,8 +2521,8 @@ function StepList({ tree, currentIdx, onSelect }) {
2137
2521
  ...tree.steps.map((s) => ({ ...s, hidden: false })),
2138
2522
  ...tree.hiddenSteps.map((s) => ({ ...s, hidden: true }))
2139
2523
  ];
2140
- return /* @__PURE__ */ jsxs("div", { style: styles6.stepList, children: [
2141
- /* @__PURE__ */ jsx("div", { style: styles6.stepListTitle, children: "\xC9tapes" }),
2524
+ return /* @__PURE__ */ jsxs("div", { style: styles7.stepList, children: [
2525
+ /* @__PURE__ */ jsx("div", { style: styles7.stepListTitle, children: "\xC9tapes" }),
2142
2526
  allSteps.map((step) => {
2143
2527
  const isVisible = !step.hidden;
2144
2528
  const visIdx = tree.steps.findIndex((s) => s.definition.id === step.definition.id);
@@ -2152,23 +2536,23 @@ function StepList({ tree, currentIdx, onSelect }) {
2152
2536
  "div",
2153
2537
  {
2154
2538
  style: {
2155
- ...styles6.stepItem,
2156
- ...status === "current" ? styles6.stepItemCurrent : {},
2157
- ...status === "hidden" ? styles6.stepItemHidden : {},
2539
+ ...styles7.stepItem,
2540
+ ...status === "current" ? styles7.stepItemCurrent : {},
2541
+ ...status === "hidden" ? styles7.stepItemHidden : {},
2158
2542
  cursor: status === "done" ? "pointer" : "default"
2159
2543
  },
2160
2544
  onClick: () => {
2161
2545
  if (status === "done") onSelect(step.definition.id);
2162
2546
  },
2163
2547
  children: [
2164
- /* @__PURE__ */ jsxs("span", { style: styles6.stepStatus, children: [
2548
+ /* @__PURE__ */ jsxs("span", { style: styles7.stepStatus, children: [
2165
2549
  status === "done" && "\u2713",
2166
2550
  status === "current" && "\u2192",
2167
2551
  status === "upcoming" && "\u25CB",
2168
2552
  status === "hidden" && "\u2013"
2169
2553
  ] }),
2170
- /* @__PURE__ */ jsx("span", { style: styles6.stepName, children: step.definition.title }),
2171
- status === "hidden" && /* @__PURE__ */ jsx("span", { style: styles6.hiddenBadge, children: "hidden" })
2554
+ /* @__PURE__ */ jsx("span", { style: styles7.stepName, children: step.definition.title }),
2555
+ status === "hidden" && /* @__PURE__ */ jsx("span", { style: styles7.hiddenBadge, children: "hidden" })
2172
2556
  ]
2173
2557
  },
2174
2558
  step.definition.id
@@ -2176,13 +2560,51 @@ function StepList({ tree, currentIdx, onSelect }) {
2176
2560
  })
2177
2561
  ] });
2178
2562
  }
2563
+ function MockVarPanel({ extVarDefs, mockVars, onChange }) {
2564
+ return /* @__PURE__ */ jsxs("div", { style: styles7.mockPanel, children: [
2565
+ /* @__PURE__ */ jsxs("div", { style: styles7.mockPanelTitle, children: [
2566
+ /* @__PURE__ */ jsx("span", { children: "\u26A1 External Variables" }),
2567
+ /* @__PURE__ */ jsx("span", { style: styles7.mockPanelHint, children: "mock values" })
2568
+ ] }),
2569
+ extVarDefs.map((v) => /* @__PURE__ */ jsxs("div", { style: styles7.mockVarRow, children: [
2570
+ /* @__PURE__ */ jsxs("div", { style: styles7.mockVarLabel, children: [
2571
+ /* @__PURE__ */ jsxs("span", { style: styles7.mockVarId, children: [
2572
+ "$",
2573
+ `ext.${v.id}`
2574
+ ] }),
2575
+ v.blocking && /* @__PURE__ */ jsx("span", { style: styles7.mockBlockingBadge, children: "!" })
2576
+ ] }),
2577
+ v.type === "boolean" ? /* @__PURE__ */ jsxs("label", { style: styles7.mockCheckRow, children: [
2578
+ /* @__PURE__ */ jsx(
2579
+ "input",
2580
+ {
2581
+ type: "checkbox",
2582
+ checked: Boolean(mockVars[v.id]),
2583
+ onChange: (e) => onChange(v.id, e.target.checked)
2584
+ }
2585
+ ),
2586
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "var(--wp-text-muted)" }, children: String(mockVars[v.id]) })
2587
+ ] }) : /* @__PURE__ */ jsx(
2588
+ "input",
2589
+ {
2590
+ style: styles7.mockInput,
2591
+ type: v.type === "number" ? "number" : "text",
2592
+ value: String(mockVars[v.id] ?? ""),
2593
+ placeholder: v.label,
2594
+ onChange: (e) => onChange(v.id, v.type === "number" ? Number(e.target.value) : e.target.value)
2595
+ }
2596
+ )
2597
+ ] }, v.id))
2598
+ ] });
2599
+ }
2179
2600
  function FieldRenderer({ field, value, error, onChange }) {
2180
2601
  const { definition } = field;
2181
- const inputStyle2 = { ...styles6.input, ...error ? styles6.inputError : {} };
2182
- return /* @__PURE__ */ jsxs("div", { style: styles6.fieldGroup, children: [
2183
- /* @__PURE__ */ jsxs("label", { style: styles6.label, children: [
2602
+ const options = field.resolvedOptions ?? definition.options ?? [];
2603
+ const inputStyle2 = { ...styles7.input, ...error ? styles7.inputError : {} };
2604
+ return /* @__PURE__ */ jsxs("div", { style: styles7.fieldGroup, children: [
2605
+ /* @__PURE__ */ jsxs("label", { style: styles7.label, children: [
2184
2606
  definition.label,
2185
- definition.validation?.some((r) => r.type === "required") && /* @__PURE__ */ jsx("span", { style: styles6.required, children: " *" })
2607
+ definition.validation?.some((r) => r.type === "required") && /* @__PURE__ */ jsx("span", { style: styles7.required, children: " *" })
2186
2608
  ] }),
2187
2609
  (definition.type === "text" || definition.type === "email" || definition.type === "tel" || definition.type === "password" || definition.type === "url" || definition.type === "number" || definition.type === "date") && /* @__PURE__ */ jsx(
2188
2610
  "input",
@@ -2211,7 +2633,7 @@ function FieldRenderer({ field, value, error, onChange }) {
2211
2633
  onChange: (e) => onChange(e.target.value),
2212
2634
  children: [
2213
2635
  /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Choisir \u2014" }),
2214
- definition.options?.map((opt) => /* @__PURE__ */ jsx("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
2636
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
2215
2637
  ]
2216
2638
  }
2217
2639
  ),
@@ -2225,10 +2647,10 @@ function FieldRenderer({ field, value, error, onChange }) {
2225
2647
  const selected = Array.from(e.target.selectedOptions).map((o) => o.value);
2226
2648
  onChange(selected);
2227
2649
  },
2228
- children: definition.options?.map((opt) => /* @__PURE__ */ jsx("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
2650
+ children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
2229
2651
  }
2230
2652
  ),
2231
- definition.type === "radio" && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: definition.options?.map((opt) => /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13 }, children: [
2653
+ definition.type === "radio" && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: options.map((opt) => /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13 }, children: [
2232
2654
  /* @__PURE__ */ jsx(
2233
2655
  "input",
2234
2656
  {
@@ -2252,10 +2674,10 @@ function FieldRenderer({ field, value, error, onChange }) {
2252
2674
  ),
2253
2675
  definition.placeholder ?? definition.label
2254
2676
  ] }),
2255
- error && /* @__PURE__ */ jsx("div", { style: styles6.errorMsg, children: error })
2677
+ error && /* @__PURE__ */ jsx("div", { style: styles7.errorMsg, children: error })
2256
2678
  ] });
2257
2679
  }
2258
- var styles6 = {
2680
+ var styles7 = {
2259
2681
  panel: {
2260
2682
  display: "flex",
2261
2683
  flex: 1,
@@ -2424,6 +2846,80 @@ var styles6 = {
2424
2846
  letterSpacing: "0.3px",
2425
2847
  flexShrink: 0
2426
2848
  },
2849
+ // Mock var panel
2850
+ mockPanel: {
2851
+ borderTop: "1px solid var(--wp-border)",
2852
+ padding: "12px",
2853
+ display: "flex",
2854
+ flexDirection: "column",
2855
+ gap: 8
2856
+ },
2857
+ mockPanelTitle: {
2858
+ display: "flex",
2859
+ alignItems: "center",
2860
+ justifyContent: "space-between",
2861
+ fontSize: 11,
2862
+ fontWeight: 700,
2863
+ color: "var(--wp-text-muted)",
2864
+ textTransform: "uppercase",
2865
+ letterSpacing: "0.5px",
2866
+ marginBottom: 4
2867
+ },
2868
+ mockPanelHint: {
2869
+ fontSize: 9,
2870
+ fontWeight: 500,
2871
+ color: "var(--wp-warning)",
2872
+ textTransform: "none",
2873
+ background: "var(--wp-warning-bg)",
2874
+ padding: "1px 5px",
2875
+ borderRadius: 4
2876
+ },
2877
+ mockVarRow: {
2878
+ display: "flex",
2879
+ flexDirection: "column",
2880
+ gap: 3
2881
+ },
2882
+ mockVarLabel: {
2883
+ display: "flex",
2884
+ alignItems: "center",
2885
+ gap: 4
2886
+ },
2887
+ mockVarId: {
2888
+ fontSize: 10,
2889
+ fontFamily: "monospace",
2890
+ fontWeight: 600,
2891
+ color: "var(--wp-text-secondary)"
2892
+ },
2893
+ mockBlockingBadge: {
2894
+ fontSize: 9,
2895
+ fontWeight: 700,
2896
+ width: 13,
2897
+ height: 13,
2898
+ borderRadius: "50%",
2899
+ background: "var(--wp-danger-bg-strong)",
2900
+ color: "var(--wp-danger)",
2901
+ display: "flex",
2902
+ alignItems: "center",
2903
+ justifyContent: "center",
2904
+ flexShrink: 0
2905
+ },
2906
+ mockInput: {
2907
+ fontSize: 11,
2908
+ padding: "4px 6px",
2909
+ border: "1px solid var(--wp-border-muted)",
2910
+ borderRadius: "var(--wp-radius)",
2911
+ background: "var(--wp-canvas)",
2912
+ color: "var(--wp-text)",
2913
+ outline: "none",
2914
+ width: "100%",
2915
+ boxSizing: "border-box"
2916
+ },
2917
+ mockCheckRow: {
2918
+ display: "flex",
2919
+ alignItems: "center",
2920
+ gap: 6,
2921
+ cursor: "pointer"
2922
+ },
2427
2923
  // Done screen
2428
2924
  doneScreen: {
2429
2925
  display: "flex",
@@ -2460,83 +2956,87 @@ var styles6 = {
2460
2956
  };
2461
2957
  function StepEditor() {
2462
2958
  const { schema, selectedStepId, updateStep, setStepCondition } = useBuilderStore();
2959
+ const readOnly = useBuilderReadOnly();
2463
2960
  const [conditionModalOpen, setConditionModalOpen] = useState(false);
2464
2961
  const step = schema.steps.find((s) => s.id === selectedStepId);
2465
2962
  if (!step) {
2466
- return /* @__PURE__ */ jsx("div", { style: styles7.empty, children: "Select a step to configure its properties." });
2963
+ return /* @__PURE__ */ jsx("div", { style: styles8.empty, children: "Select a step to configure its properties." });
2467
2964
  }
2468
2965
  const hasCondition = !!step.visibleWhen;
2469
2966
  const ruleCount = step.visibleWhen?.rules.length ?? 0;
2470
- return /* @__PURE__ */ jsxs("div", { style: styles7.container, children: [
2471
- /* @__PURE__ */ jsxs("div", { style: styles7.header, children: [
2472
- /* @__PURE__ */ jsx("div", { style: styles7.headerTitle, children: "Step Config" }),
2473
- /* @__PURE__ */ jsxs("div", { style: styles7.stepId, children: [
2967
+ return /* @__PURE__ */ jsxs("div", { style: styles8.container, children: [
2968
+ /* @__PURE__ */ jsxs("div", { style: styles8.header, children: [
2969
+ /* @__PURE__ */ jsx("div", { style: styles8.headerTitle, children: "Step Config" }),
2970
+ /* @__PURE__ */ jsxs("div", { style: styles8.stepId, children: [
2474
2971
  "id: ",
2475
2972
  step.id
2476
2973
  ] })
2477
2974
  ] }),
2478
- /* @__PURE__ */ jsxs("div", { style: styles7.body, children: [
2479
- /* @__PURE__ */ jsxs("div", { style: styles7.group, children: [
2480
- /* @__PURE__ */ jsx("label", { style: styles7.label, children: "Title" }),
2975
+ /* @__PURE__ */ jsxs("div", { style: styles8.body, children: [
2976
+ /* @__PURE__ */ jsxs("div", { style: styles8.group, children: [
2977
+ /* @__PURE__ */ jsx("label", { style: styles8.label, children: "Title" }),
2481
2978
  /* @__PURE__ */ jsx(
2482
2979
  "input",
2483
2980
  {
2484
- style: styles7.input,
2981
+ style: styles8.input,
2485
2982
  value: step.title,
2486
2983
  placeholder: "Step title",
2487
- onChange: (e) => updateStep(step.id, { title: e.target.value })
2984
+ readOnly,
2985
+ onChange: readOnly ? void 0 : (e) => updateStep(step.id, { title: e.target.value })
2488
2986
  }
2489
2987
  )
2490
2988
  ] }),
2491
- /* @__PURE__ */ jsxs("div", { style: styles7.group, children: [
2492
- /* @__PURE__ */ jsx("label", { style: styles7.label, children: "URL" }),
2989
+ /* @__PURE__ */ jsxs("div", { style: styles8.group, children: [
2990
+ /* @__PURE__ */ jsx("label", { style: styles8.label, children: "URL" }),
2493
2991
  /* @__PURE__ */ jsx(
2494
2992
  "input",
2495
2993
  {
2496
- style: styles7.input,
2994
+ style: styles8.input,
2497
2995
  value: step.url,
2498
2996
  placeholder: "/onboarding/step-name",
2499
- onChange: (e) => updateStep(step.id, { url: e.target.value })
2997
+ readOnly,
2998
+ onChange: readOnly ? void 0 : (e) => updateStep(step.id, { url: e.target.value })
2500
2999
  }
2501
3000
  ),
2502
- /* @__PURE__ */ jsxs("div", { style: styles7.hint, children: [
3001
+ /* @__PURE__ */ jsxs("div", { style: styles8.hint, children: [
2503
3002
  "Supports ",
2504
3003
  "{{PARAM}}",
2505
3004
  " placeholders"
2506
3005
  ] })
2507
3006
  ] }),
2508
- /* @__PURE__ */ jsxs("div", { style: styles7.checkRow, children: [
3007
+ /* @__PURE__ */ jsxs("div", { style: styles8.checkRow, children: [
2509
3008
  /* @__PURE__ */ jsx(
2510
3009
  "input",
2511
3010
  {
2512
3011
  type: "checkbox",
2513
3012
  id: `resume-${step.id}`,
2514
3013
  checked: !!step.enableResumeFromHere,
2515
- onChange: (e) => updateStep(step.id, { enableResumeFromHere: e.target.checked || void 0 })
3014
+ disabled: readOnly,
3015
+ onChange: readOnly ? void 0 : (e) => updateStep(step.id, { enableResumeFromHere: e.target.checked || void 0 })
2516
3016
  }
2517
3017
  ),
2518
- /* @__PURE__ */ jsx("label", { htmlFor: `resume-${step.id}`, style: styles7.checkLabel, children: "Resume from this step" })
3018
+ /* @__PURE__ */ jsx("label", { htmlFor: `resume-${step.id}`, style: styles8.checkLabel, children: "Resume from this step" })
2519
3019
  ] }),
2520
- /* @__PURE__ */ jsx("div", { style: styles7.divider }),
2521
- /* @__PURE__ */ jsxs("div", { style: styles7.conditionRow, children: [
2522
- /* @__PURE__ */ jsxs("div", { style: styles7.conditionInfo, children: [
2523
- /* @__PURE__ */ jsx("div", { style: styles7.label, children: "Visibility condition" }),
2524
- hasCondition ? /* @__PURE__ */ jsxs("div", { style: styles7.conditionSummary, children: [
2525
- /* @__PURE__ */ jsxs("span", { style: styles7.conditionBadge, children: [
3020
+ /* @__PURE__ */ jsx("div", { style: styles8.divider }),
3021
+ /* @__PURE__ */ jsxs("div", { style: styles8.conditionRow, children: [
3022
+ /* @__PURE__ */ jsxs("div", { style: styles8.conditionInfo, children: [
3023
+ /* @__PURE__ */ jsx("div", { style: styles8.label, children: "Visibility condition" }),
3024
+ hasCondition ? /* @__PURE__ */ jsxs("div", { style: styles8.conditionSummary, children: [
3025
+ /* @__PURE__ */ jsxs("span", { style: styles8.conditionBadge, children: [
2526
3026
  ruleCount,
2527
3027
  " rule",
2528
3028
  ruleCount !== 1 ? "s" : "",
2529
3029
  " \xB7 ",
2530
3030
  step.visibleWhen.combinator.toUpperCase()
2531
3031
  ] }),
2532
- /* @__PURE__ */ jsx("span", { style: styles7.conditionDesc, children: "Step is conditional" })
2533
- ] }) : /* @__PURE__ */ jsx("div", { style: styles7.conditionNone, children: "Always visible" })
3032
+ /* @__PURE__ */ jsx("span", { style: styles8.conditionDesc, children: "Step is conditional" })
3033
+ ] }) : /* @__PURE__ */ jsx("div", { style: styles8.conditionNone, children: "Always visible" })
2534
3034
  ] }),
2535
- /* @__PURE__ */ jsxs("div", { style: styles7.conditionActions, children: [
3035
+ !readOnly && /* @__PURE__ */ jsxs("div", { style: styles8.conditionActions, children: [
2536
3036
  /* @__PURE__ */ jsx(
2537
3037
  "button",
2538
3038
  {
2539
- style: styles7.editConditionBtn,
3039
+ style: styles8.editConditionBtn,
2540
3040
  onClick: () => setConditionModalOpen(true),
2541
3041
  children: hasCondition ? "Edit" : "Add condition"
2542
3042
  }
@@ -2544,7 +3044,7 @@ function StepEditor() {
2544
3044
  hasCondition && /* @__PURE__ */ jsx(
2545
3045
  "button",
2546
3046
  {
2547
- style: styles7.clearConditionBtn,
3047
+ style: styles8.clearConditionBtn,
2548
3048
  onClick: () => setStepCondition(step.id, void 0),
2549
3049
  children: "Clear"
2550
3050
  }
@@ -2559,7 +3059,7 @@ function StepEditor() {
2559
3059
  onClose: () => setConditionModalOpen(false),
2560
3060
  width: 620,
2561
3061
  children: [
2562
- /* @__PURE__ */ jsx("p", { style: styles7.modalHint, children: "Define when this step is visible. Leave empty to always show it." }),
3062
+ /* @__PURE__ */ jsx("p", { style: styles8.modalHint, children: "Define when this step is visible. Leave empty to always show it." }),
2563
3063
  /* @__PURE__ */ jsx(
2564
3064
  ConditionBuilder,
2565
3065
  {
@@ -2567,10 +3067,10 @@ function StepEditor() {
2567
3067
  onChange: (c) => setStepCondition(step.id, c)
2568
3068
  }
2569
3069
  ),
2570
- /* @__PURE__ */ jsx("div", { style: styles7.modalFooter, children: /* @__PURE__ */ jsx(
3070
+ /* @__PURE__ */ jsx("div", { style: styles8.modalFooter, children: /* @__PURE__ */ jsx(
2571
3071
  "button",
2572
3072
  {
2573
- style: styles7.modalCloseBtn,
3073
+ style: styles8.modalCloseBtn,
2574
3074
  onClick: () => setConditionModalOpen(false),
2575
3075
  children: "Done"
2576
3076
  }
@@ -2580,7 +3080,7 @@ function StepEditor() {
2580
3080
  )
2581
3081
  ] });
2582
3082
  }
2583
- var styles7 = {
3083
+ var styles8 = {
2584
3084
  container: { display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" },
2585
3085
  empty: {
2586
3086
  display: "flex",
@@ -2669,7 +3169,8 @@ var styles7 = {
2669
3169
  }
2670
3170
  };
2671
3171
  function StepList2() {
2672
- const { schema, selectedStepId, addStep, removeStep, selectStep, reorderSteps } = useBuilderStore();
3172
+ const { schema, selectedStepId, addStep, removeStep, duplicateStep, selectStep, reorderSteps } = useBuilderStore();
3173
+ const readOnly = useBuilderReadOnly();
2673
3174
  const [moveError, setMoveError] = useState(null);
2674
3175
  const steps = schema.steps;
2675
3176
  const deps = computeStepDependencies(schema);
@@ -2683,21 +3184,21 @@ function StepList2() {
2683
3184
  setMoveError(null);
2684
3185
  reorderSteps(fromIndex, toIndex);
2685
3186
  };
2686
- return /* @__PURE__ */ jsxs("div", { style: styles8.container, children: [
2687
- /* @__PURE__ */ jsxs("div", { style: styles8.header, children: [
2688
- /* @__PURE__ */ jsxs("span", { style: styles8.title, children: [
3187
+ return /* @__PURE__ */ jsxs("div", { style: styles9.container, children: [
3188
+ /* @__PURE__ */ jsxs("div", { style: styles9.header, children: [
3189
+ /* @__PURE__ */ jsxs("span", { style: styles9.title, children: [
2689
3190
  "Steps (",
2690
3191
  steps.length,
2691
3192
  ")"
2692
3193
  ] }),
2693
- /* @__PURE__ */ jsx("button", { style: styles8.addBtn, onClick: () => addStep(), children: "+ Add step" })
3194
+ !readOnly && /* @__PURE__ */ jsx("button", { style: styles9.addBtn, onClick: () => addStep(), children: "+ Add step" })
2694
3195
  ] }),
2695
- moveError && /* @__PURE__ */ jsxs("div", { style: styles8.errorBanner, children: [
2696
- /* @__PURE__ */ jsx("span", { style: styles8.errorIcon, children: "\u26A0" }),
3196
+ moveError && /* @__PURE__ */ jsxs("div", { style: styles9.errorBanner, children: [
3197
+ /* @__PURE__ */ jsx("span", { style: styles9.errorIcon, children: "\u26A0" }),
2697
3198
  moveError
2698
3199
  ] }),
2699
- /* @__PURE__ */ jsxs("div", { style: styles8.list, children: [
2700
- steps.length === 0 && /* @__PURE__ */ jsx("div", { style: styles8.empty, children: 'No steps yet. Click "Add step" to start.' }),
3200
+ /* @__PURE__ */ jsxs("div", { style: styles9.list, children: [
3201
+ steps.length === 0 && /* @__PURE__ */ jsx("div", { style: styles9.empty, children: 'No steps yet. Click "Add step" to start.' }),
2701
3202
  steps.map((step, index) => {
2702
3203
  const isSelected = step.id === selectedStepId;
2703
3204
  const hasCondition = !!step.visibleWhen;
@@ -2711,31 +3212,31 @@ function StepList2() {
2711
3212
  "div",
2712
3213
  {
2713
3214
  style: {
2714
- ...styles8.card,
2715
- ...isSelected ? styles8.cardSelected : {}
3215
+ ...styles9.card,
3216
+ ...isSelected ? styles9.cardSelected : {}
2716
3217
  },
2717
3218
  onClick: () => selectStep(step.id),
2718
3219
  children: [
2719
- /* @__PURE__ */ jsxs("div", { style: styles8.cardMain, children: [
2720
- /* @__PURE__ */ jsxs("div", { style: styles8.cardLeft, children: [
2721
- /* @__PURE__ */ jsx("div", { style: styles8.cardIndex, children: index + 1 }),
3220
+ /* @__PURE__ */ jsxs("div", { style: styles9.cardMain, children: [
3221
+ /* @__PURE__ */ jsxs("div", { style: styles9.cardLeft, children: [
3222
+ /* @__PURE__ */ jsx("div", { style: styles9.cardIndex, children: index + 1 }),
2722
3223
  /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
2723
- /* @__PURE__ */ jsx("div", { style: styles8.cardTitle, children: step.title }),
2724
- /* @__PURE__ */ jsxs("div", { style: styles8.cardMeta, children: [
3224
+ /* @__PURE__ */ jsx("div", { style: styles9.cardTitle, children: step.title }),
3225
+ /* @__PURE__ */ jsxs("div", { style: styles9.cardMeta, children: [
2725
3226
  step.fields.length,
2726
3227
  " field",
2727
3228
  step.fields.length !== 1 ? "s" : "",
2728
- hasCondition && /* @__PURE__ */ jsx("span", { style: styles8.conditionBadge, children: "conditional" })
3229
+ hasCondition && /* @__PURE__ */ jsx("span", { style: styles9.conditionBadge, children: "conditional" })
2729
3230
  ] })
2730
3231
  ] })
2731
3232
  ] }),
2732
- /* @__PURE__ */ jsxs("div", { style: styles8.cardActions, children: [
3233
+ !readOnly && /* @__PURE__ */ jsxs("div", { style: styles9.cardActions, children: [
2733
3234
  index > 0 && /* @__PURE__ */ jsx(
2734
3235
  "button",
2735
3236
  {
2736
3237
  style: {
2737
- ...styles8.iconBtn,
2738
- ...canMoveUp ? {} : styles8.iconBtnBlocked
3238
+ ...styles9.iconBtn,
3239
+ ...canMoveUp ? {} : styles9.iconBtnBlocked
2739
3240
  },
2740
3241
  title: canMoveUp ? "Move up" : `Can't move up \u2014 dependency order required`,
2741
3242
  onClick: (e) => {
@@ -2749,8 +3250,8 @@ function StepList2() {
2749
3250
  "button",
2750
3251
  {
2751
3252
  style: {
2752
- ...styles8.iconBtn,
2753
- ...canMoveDown ? {} : styles8.iconBtnBlocked
3253
+ ...styles9.iconBtn,
3254
+ ...canMoveDown ? {} : styles9.iconBtnBlocked
2754
3255
  },
2755
3256
  title: canMoveDown ? "Move down" : `Can't move down \u2014 dependency order required`,
2756
3257
  onClick: (e) => {
@@ -2763,7 +3264,19 @@ function StepList2() {
2763
3264
  /* @__PURE__ */ jsx(
2764
3265
  "button",
2765
3266
  {
2766
- style: { ...styles8.iconBtn, ...styles8.deleteBtn },
3267
+ style: styles9.iconBtn,
3268
+ title: "Duplicate step",
3269
+ onClick: (e) => {
3270
+ e.stopPropagation();
3271
+ duplicateStep(step.id);
3272
+ },
3273
+ children: "\u29C9"
3274
+ }
3275
+ ),
3276
+ /* @__PURE__ */ jsx(
3277
+ "button",
3278
+ {
3279
+ style: { ...styles9.iconBtn, ...styles9.deleteBtn },
2767
3280
  title: "Remove step",
2768
3281
  onClick: (e) => {
2769
3282
  e.stopPropagation();
@@ -2774,13 +3287,13 @@ function StepList2() {
2774
3287
  )
2775
3288
  ] })
2776
3289
  ] }),
2777
- depLabels.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles8.depRow, children: [
2778
- /* @__PURE__ */ jsx("span", { style: styles8.depLabel, children: "needs:" }),
2779
- depLabels.map((label) => /* @__PURE__ */ jsx("span", { style: styles8.depBadge, children: label }, label))
3290
+ depLabels.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles9.depRow, children: [
3291
+ /* @__PURE__ */ jsx("span", { style: styles9.depLabel, children: "needs:" }),
3292
+ depLabels.map((label) => /* @__PURE__ */ jsx("span", { style: styles9.depBadge, children: label }, label))
2780
3293
  ] }),
2781
- dependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles8.depRow, children: [
2782
- /* @__PURE__ */ jsx("span", { style: styles8.depLabelUsed, children: "used by:" }),
2783
- dependents.map((s) => /* @__PURE__ */ jsx("span", { style: styles8.depBadgeUsed, children: s.title }, s.id))
3294
+ dependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles9.depRow, children: [
3295
+ /* @__PURE__ */ jsx("span", { style: styles9.depLabelUsed, children: "used by:" }),
3296
+ dependents.map((s) => /* @__PURE__ */ jsx("span", { style: styles9.depBadgeUsed, children: s.title }, s.id))
2784
3297
  ] })
2785
3298
  ]
2786
3299
  },
@@ -2790,7 +3303,7 @@ function StepList2() {
2790
3303
  ] })
2791
3304
  ] });
2792
3305
  }
2793
- var styles8 = {
3306
+ var styles9 = {
2794
3307
  container: { display: "flex", flexDirection: "column", height: "100%" },
2795
3308
  header: {
2796
3309
  display: "flex",
@@ -2910,7 +3423,7 @@ var styles8 = {
2910
3423
  borderRadius: 4
2911
3424
  }
2912
3425
  };
2913
- function Toolbar({ onSave, previewMode, onTest, isMobile }) {
3426
+ function Toolbar({ onSave, previewMode, onTest, isMobile, readOnly }) {
2914
3427
  const { schema, isDirty, resetSchema } = useBuilderStore();
2915
3428
  const handleExport = () => {
2916
3429
  const json = JSON.stringify(schema, null, 2);
@@ -2950,31 +3463,31 @@ ${result.errors.map((e2) => `\u2022 ${e2}`).join("\n")}`);
2950
3463
  input.click();
2951
3464
  };
2952
3465
  if (previewMode) {
2953
- return /* @__PURE__ */ jsxs("div", { style: styles9.toolbar, children: [
2954
- /* @__PURE__ */ jsxs("div", { style: styles9.left, children: [
2955
- /* @__PURE__ */ jsx("span", { style: styles9.logo, children: "\u25C8 waypoint" }),
2956
- /* @__PURE__ */ jsx("span", { style: styles9.separator, children: "/" }),
3466
+ return /* @__PURE__ */ jsxs("div", { style: styles10.toolbar, children: [
3467
+ /* @__PURE__ */ jsxs("div", { style: styles10.left, children: [
3468
+ /* @__PURE__ */ jsx("span", { style: styles10.logo, children: "\u25C8 waypoint" }),
3469
+ /* @__PURE__ */ jsx("span", { style: styles10.separator, children: "/" }),
2957
3470
  /* @__PURE__ */ jsx(
2958
3471
  "button",
2959
3472
  {
2960
- style: { ...styles9.btn, ...styles9.editBtn },
3473
+ style: { ...styles10.btn, ...styles10.editBtn },
2961
3474
  onClick: onTest,
2962
3475
  children: "\u2190 \xC9diter"
2963
3476
  }
2964
3477
  )
2965
3478
  ] }),
2966
- /* @__PURE__ */ jsx("div", { style: styles9.right, children: /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "var(--wp-text-muted)", fontStyle: "italic" }, children: "Mode aper\xE7u" }) })
3479
+ /* @__PURE__ */ jsx("div", { style: styles10.right, children: /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "var(--wp-text-muted)", fontStyle: "italic" }, children: "Mode aper\xE7u" }) })
2967
3480
  ] });
2968
3481
  }
2969
- return /* @__PURE__ */ jsxs("div", { style: styles9.toolbar, children: [
2970
- /* @__PURE__ */ jsxs("div", { style: styles9.left, children: [
2971
- !isMobile && /* @__PURE__ */ jsx("span", { style: styles9.logo, children: "\u25C8 waypoint" }),
2972
- !isMobile && /* @__PURE__ */ jsx("span", { style: styles9.separator, children: "/" }),
3482
+ return /* @__PURE__ */ jsxs("div", { style: styles10.toolbar, children: [
3483
+ /* @__PURE__ */ jsxs("div", { style: styles10.left, children: [
3484
+ !isMobile && /* @__PURE__ */ jsx("span", { style: styles10.logo, children: "\u25C8 waypoint" }),
3485
+ !isMobile && /* @__PURE__ */ jsx("span", { style: styles10.separator, children: "/" }),
2973
3486
  /* @__PURE__ */ jsx(
2974
3487
  "input",
2975
3488
  {
2976
3489
  style: {
2977
- ...styles9.journeyName,
3490
+ ...styles10.journeyName,
2978
3491
  ...isMobile ? { maxWidth: 120, fontSize: 12 } : {}
2979
3492
  },
2980
3493
  value: schema.name,
@@ -2985,25 +3498,26 @@ ${result.errors.map((e2) => `\u2022 ${e2}`).join("\n")}`);
2985
3498
  }))
2986
3499
  }
2987
3500
  ),
2988
- isDirty && /* @__PURE__ */ jsx("span", { style: styles9.dirtyDot, title: "Unsaved changes" })
3501
+ isDirty && /* @__PURE__ */ jsx("span", { style: styles10.dirtyDot, title: "Unsaved changes" })
2989
3502
  ] }),
2990
- /* @__PURE__ */ jsxs("div", { style: styles9.right, children: [
2991
- onTest && /* @__PURE__ */ jsx("button", { style: { ...styles9.btn, ...styles9.testBtn }, onClick: onTest, title: "Tester", children: isMobile ? "\u25B6" : "\u25B6 Tester" }),
2992
- /* @__PURE__ */ jsx("button", { style: styles9.btn, onClick: handleImport, title: "Import", children: isMobile ? "\u2193" : "Import" }),
2993
- /* @__PURE__ */ jsx("button", { style: styles9.btn, onClick: handleExport, title: "Export JSON", children: isMobile ? "\u2191" : "Export JSON" }),
2994
- onSave && /* @__PURE__ */ jsx(
3503
+ /* @__PURE__ */ jsxs("div", { style: styles10.right, children: [
3504
+ readOnly && /* @__PURE__ */ jsx("span", { style: styles10.readOnlyBadge, children: "View only" }),
3505
+ !readOnly && onTest && /* @__PURE__ */ jsx("button", { style: { ...styles10.btn, ...styles10.testBtn }, onClick: onTest, title: "Tester", children: isMobile ? "\u25B6" : "\u25B6 Tester" }),
3506
+ !readOnly && /* @__PURE__ */ jsx("button", { style: styles10.btn, onClick: handleImport, title: "Import", children: isMobile ? "\u2193" : "Import" }),
3507
+ /* @__PURE__ */ jsx("button", { style: styles10.btn, onClick: handleExport, title: "Export JSON", children: isMobile ? "\u2191" : "Export JSON" }),
3508
+ !readOnly && onSave && /* @__PURE__ */ jsx(
2995
3509
  "button",
2996
3510
  {
2997
- style: { ...styles9.btn, ...styles9.saveBtn },
3511
+ style: { ...styles10.btn, ...styles10.saveBtn },
2998
3512
  onClick: () => onSave(schema),
2999
3513
  title: "Save",
3000
3514
  children: isMobile ? "\u2713" : "Save"
3001
3515
  }
3002
3516
  ),
3003
- /* @__PURE__ */ jsx(
3517
+ !readOnly && /* @__PURE__ */ jsx(
3004
3518
  "button",
3005
3519
  {
3006
- style: { ...styles9.btn, color: "var(--wp-danger)" },
3520
+ style: { ...styles10.btn, color: "var(--wp-danger)" },
3007
3521
  title: "Reset",
3008
3522
  onClick: () => {
3009
3523
  if (confirm("Reset the journey? All changes will be lost.")) resetSchema();
@@ -3014,7 +3528,7 @@ ${result.errors.map((e2) => `\u2022 ${e2}`).join("\n")}`);
3014
3528
  ] })
3015
3529
  ] });
3016
3530
  }
3017
- var styles9 = {
3531
+ var styles10 = {
3018
3532
  toolbar: {
3019
3533
  display: "flex",
3020
3534
  alignItems: "center",
@@ -3056,6 +3570,15 @@ var styles9 = {
3056
3570
  fontWeight: 500
3057
3571
  },
3058
3572
  saveBtn: { background: "var(--wp-primary)", color: "var(--wp-canvas)", border: "1px solid var(--wp-primary)" },
3573
+ readOnlyBadge: {
3574
+ fontSize: 11,
3575
+ fontWeight: 600,
3576
+ color: "var(--wp-text-subtle)",
3577
+ background: "var(--wp-surface)",
3578
+ border: "1px solid var(--wp-border)",
3579
+ borderRadius: "var(--wp-radius)",
3580
+ padding: "3px 8px"
3581
+ },
3059
3582
  testBtn: { background: "var(--wp-success, #22c55e)", color: "#fff", border: "1px solid var(--wp-success, #22c55e)", fontWeight: 600 },
3060
3583
  editBtn: { background: "transparent", color: "var(--wp-toolbar-text)", border: "none", fontWeight: 600, cursor: "pointer", fontSize: 13, padding: "5px 0" }
3061
3584
  };
@@ -3065,7 +3588,10 @@ function WaypointBuilder({
3065
3588
  onSave,
3066
3589
  theme,
3067
3590
  className,
3068
- style
3591
+ style,
3592
+ readOnly = false,
3593
+ appCustomTypes = [],
3594
+ externalEnums = []
3069
3595
  }) {
3070
3596
  const { loadSchema, schema, selectedStepId, selectedFieldId } = useBuilderStore();
3071
3597
  const [previewMode, setPreviewMode] = useState(false);
@@ -3098,14 +3624,15 @@ function WaypointBuilder({
3098
3624
  setPreviewMode(true);
3099
3625
  }
3100
3626
  const themeVars = buildThemeVars(theme);
3101
- return /* @__PURE__ */ jsxs("div", { className, style: { ...rootStyle, ...themeVars, ...style }, children: [
3627
+ return /* @__PURE__ */ jsx(BuilderReadOnlyContext.Provider, { value: readOnly, children: /* @__PURE__ */ jsx(BuilderCustomTypesContext.Provider, { value: appCustomTypes, children: /* @__PURE__ */ jsx(BuilderExternalEnumsContext.Provider, { value: externalEnums, children: /* @__PURE__ */ jsxs("div", { className, style: { ...rootStyle, ...themeVars, ...style }, children: [
3102
3628
  /* @__PURE__ */ jsx(
3103
3629
  Toolbar,
3104
3630
  {
3105
3631
  isMobile,
3106
- onSave: !previewMode && onSave ? () => onSave(schema) : void 0,
3632
+ readOnly,
3633
+ onSave: !previewMode && !readOnly && onSave ? () => onSave(schema) : void 0,
3107
3634
  previewMode,
3108
- onTest: previewMode ? () => setPreviewMode(false) : handleTest
3635
+ onTest: !readOnly ? previewMode ? () => setPreviewMode(false) : handleTest : void 0
3109
3636
  }
3110
3637
  ),
3111
3638
  previewMode ? /* @__PURE__ */ jsx(
@@ -3113,6 +3640,7 @@ function WaypointBuilder({
3113
3640
  {
3114
3641
  store: previewStoreRef.current,
3115
3642
  schema,
3643
+ externalEnums,
3116
3644
  onEdit: () => setPreviewMode(false)
3117
3645
  }
3118
3646
  ) : isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -3150,7 +3678,7 @@ function WaypointBuilder({
3150
3678
  /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsx(FieldEditor, {}) })
3151
3679
  ] })
3152
3680
  ] })
3153
- ] });
3681
+ ] }) }) }) });
3154
3682
  }
3155
3683
  var rootStyle = {
3156
3684
  display: "flex",