@waypointjs/builder 0.1.4 → 0.1.6

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,10 @@
1
1
  import { createRuntimeStore, getNextStep, validateSchema, getPreviousStep, resolveTree } from '@waypointjs/core';
2
- import { useState, useRef, useEffect } from 'react';
2
+ import { createContext, useState, useRef, useEffect, useCallback, 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
+ import { useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, DndContext, closestCenter, DragOverlay } from '@dnd-kit/core';
6
+ import { SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
7
+ import { CSS } from '@dnd-kit/utilities';
5
8
  import { useWaypoint, useWaypointStep } from '@waypointjs/react';
6
9
  import { DevPanel } from '@waypointjs/devtools';
7
10
 
@@ -195,6 +198,44 @@ var useBuilderStore = create((set, _get) => ({
195
198
  selectedFieldId: s.selectedStepId === stepId ? null : s.selectedFieldId,
196
199
  isDirty: true
197
200
  })),
201
+ duplicateStep: (stepId) => {
202
+ const state = _get();
203
+ const step = state.schema.steps.find((s) => s.id === stepId);
204
+ if (!step) return;
205
+ const newStepId = generateId("step");
206
+ const fieldIdMap = {};
207
+ for (const f of step.fields) {
208
+ fieldIdMap[f.id] = generateId("field");
209
+ }
210
+ const newFields = step.fields.map((f) => ({
211
+ ...f,
212
+ id: fieldIdMap[f.id],
213
+ dependsOn: f.dependsOn?.map((dep) => {
214
+ if (dep.startsWith(`${stepId}.`)) {
215
+ const oldFieldId = dep.slice(stepId.length + 1);
216
+ const newFieldId = fieldIdMap[oldFieldId];
217
+ return newFieldId ? `${newStepId}.${newFieldId}` : dep;
218
+ }
219
+ return dep;
220
+ })
221
+ }));
222
+ const newStep = {
223
+ ...step,
224
+ id: newStepId,
225
+ title: `${step.title} (copy)`,
226
+ url: `${step.url}-copy`,
227
+ fields: newFields
228
+ };
229
+ const stepIndex = state.schema.steps.findIndex((s) => s.id === stepId);
230
+ const newSteps = [...state.schema.steps];
231
+ newSteps.splice(stepIndex + 1, 0, newStep);
232
+ set({
233
+ schema: { ...state.schema, steps: newSteps },
234
+ selectedStepId: newStepId,
235
+ selectedFieldId: null,
236
+ isDirty: true
237
+ });
238
+ },
198
239
  reorderSteps: (fromIndex, toIndex) => set((s) => {
199
240
  const steps = [...s.schema.steps];
200
241
  const [moved] = steps.splice(fromIndex, 1);
@@ -247,6 +288,32 @@ var useBuilderStore = create((set, _get) => ({
247
288
  selectedFieldId: s.selectedFieldId === fieldId ? null : s.selectedFieldId,
248
289
  isDirty: true
249
290
  })),
291
+ duplicateField: (stepId, fieldId) => {
292
+ const state = _get();
293
+ const step = state.schema.steps.find((s) => s.id === stepId);
294
+ if (!step) return;
295
+ const field = step.fields.find((f) => f.id === fieldId);
296
+ if (!field) return;
297
+ const newFieldId = generateId("field");
298
+ const newField = {
299
+ ...field,
300
+ id: newFieldId,
301
+ label: `${field.label} (copy)`
302
+ };
303
+ const fieldIndex = step.fields.findIndex((f) => f.id === fieldId);
304
+ const newFields = [...step.fields];
305
+ newFields.splice(fieldIndex + 1, 0, newField);
306
+ set((s) => ({
307
+ schema: {
308
+ ...s.schema,
309
+ steps: s.schema.steps.map(
310
+ (st) => st.id === stepId ? { ...st, fields: newFields } : st
311
+ )
312
+ },
313
+ selectedFieldId: newFieldId,
314
+ isDirty: true
315
+ }));
316
+ },
250
317
  reorderFields: (stepId, fromIndex, toIndex) => set((s) => ({
251
318
  schema: {
252
319
  ...s.schema,
@@ -345,6 +412,18 @@ var useBuilderStore = create((set, _get) => ({
345
412
  isDirty: true
346
413
  }))
347
414
  }));
415
+ var BuilderReadOnlyContext = createContext(false);
416
+ function useBuilderReadOnly() {
417
+ return useContext(BuilderReadOnlyContext);
418
+ }
419
+ var BuilderCustomTypesContext = createContext([]);
420
+ function useBuilderCustomTypes() {
421
+ return useContext(BuilderCustomTypesContext);
422
+ }
423
+ var BuilderExternalEnumsContext = createContext([]);
424
+ function useBuilderExternalEnums() {
425
+ return useContext(BuilderExternalEnumsContext);
426
+ }
348
427
  var BLANK_FORM = {
349
428
  id: "",
350
429
  label: "",
@@ -358,6 +437,7 @@ function ExternalVariablePanel() {
358
437
  updateExternalVariable,
359
438
  removeExternalVariable
360
439
  } = useBuilderStore();
440
+ const readOnly = useBuilderReadOnly();
361
441
  const variables = schema.externalVariables ?? [];
362
442
  const [isAdding, setIsAdding] = useState(false);
363
443
  const [editingId, setEditingId] = useState(null);
@@ -431,7 +511,7 @@ function ExternalVariablePanel() {
431
511
  return /* @__PURE__ */ jsxs("div", { style: panelStyle, children: [
432
512
  /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
433
513
  /* @__PURE__ */ jsx("span", { style: titleStyle, children: "External Variables" }),
434
- !isAdding && /* @__PURE__ */ jsx(
514
+ !readOnly && !isAdding && /* @__PURE__ */ jsx(
435
515
  "button",
436
516
  {
437
517
  style: addBtnStyle,
@@ -486,7 +566,7 @@ function ExternalVariablePanel() {
486
566
  /* @__PURE__ */ jsx("span", { style: varLabelStyle, children: v.label }),
487
567
  refs.length > 0 && /* @__PURE__ */ jsx("div", { style: refsStyle, children: refs.map((ref, i) => /* @__PURE__ */ jsx("span", { style: refChipStyle, children: ref }, i)) })
488
568
  ] }),
489
- /* @__PURE__ */ jsxs("div", { style: varActionsStyle, children: [
569
+ !readOnly && /* @__PURE__ */ jsxs("div", { style: varActionsStyle, children: [
490
570
  /* @__PURE__ */ jsx("button", { style: actionBtnStyle, onClick: () => startEdit(v), children: "Edit" }),
491
571
  /* @__PURE__ */ jsx(
492
572
  "button",
@@ -813,15 +893,23 @@ var submitBtnStyle = {
813
893
  // src/hooks/useAllFieldPaths.ts
814
894
  function useAllFieldPaths(excludeStepId, excludeFieldId) {
815
895
  const { schema } = useBuilderStore();
896
+ const externalEnums = useBuilderExternalEnums();
816
897
  const paths = [];
817
898
  for (const step of schema.steps) {
818
899
  for (const field of step.fields) {
819
900
  if (step.id === excludeStepId && field.id === excludeFieldId) continue;
901
+ let options;
902
+ if (field.externalEnumId) {
903
+ options = externalEnums.find((e) => e.id === field.externalEnumId)?.values;
904
+ } else if (field.options?.length) {
905
+ options = field.options;
906
+ }
820
907
  paths.push({
821
908
  path: `${step.id}.${field.id}`,
822
909
  label: `${step.title} \u2192 ${field.label}`,
823
910
  stepId: step.id,
824
- fieldId: field.id
911
+ fieldId: field.id,
912
+ options
825
913
  });
826
914
  }
827
915
  }
@@ -847,7 +935,9 @@ var OPERATORS = [
847
935
  { value: "notIn", label: "not in (comma list)", hasValue: true },
848
936
  { value: "matches", label: "matches regex", hasValue: true },
849
937
  { value: "exists", label: "exists", hasValue: false },
850
- { value: "notExists", label: "not exists", hasValue: false }
938
+ { value: "notExists", label: "not exists", hasValue: false },
939
+ { value: "inEnum", label: "is in enum", hasValue: true, isEnum: true },
940
+ { value: "notInEnum", label: "not in enum", hasValue: true, isEnum: true }
851
941
  ];
852
942
  function ConditionBuilder({
853
943
  value,
@@ -856,6 +946,7 @@ function ConditionBuilder({
856
946
  excludeFieldId
857
947
  }) {
858
948
  const allPaths = useAllFieldPaths(excludeStepId, excludeFieldId);
949
+ const externalEnums = useBuilderExternalEnums();
859
950
  const group = value ?? { combinator: "and", rules: [] };
860
951
  const updateRule = (index, updates) => {
861
952
  const rules = group.rules.map((r, i) => i === index ? { ...r, ...updates } : r);
@@ -918,15 +1009,48 @@ function ConditionBuilder({
918
1009
  children: OPERATORS.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
919
1010
  }
920
1011
  ),
921
- opDef?.hasValue && /* @__PURE__ */ jsx(
922
- "input",
1012
+ opDef?.hasValue && (opDef.isEnum ? /* @__PURE__ */ jsxs(
1013
+ "select",
923
1014
  {
924
- style: styles.valueInput,
925
- placeholder: "value",
1015
+ style: { ...styles.select, width: 140 },
926
1016
  value: rule.value != null ? String(rule.value) : "",
927
- onChange: (e) => updateRule(index, { value: e.target.value })
1017
+ onChange: (e) => updateRule(index, { value: e.target.value }),
1018
+ children: [
1019
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 pick enum \u2014" }),
1020
+ externalEnums.map((en) => /* @__PURE__ */ jsx("option", { value: en.id, children: en.label }, en.id))
1021
+ ]
928
1022
  }
929
- ),
1023
+ ) : /* @__PURE__ */ jsxs("div", { style: styles.valueGroup, children: [
1024
+ /* @__PURE__ */ jsx(
1025
+ "input",
1026
+ {
1027
+ style: styles.valueInput,
1028
+ placeholder: "value",
1029
+ value: rule.value != null ? String(rule.value) : "",
1030
+ onChange: (e) => updateRule(index, { value: e.target.value })
1031
+ }
1032
+ ),
1033
+ externalEnums.length > 0 && /* @__PURE__ */ jsxs(
1034
+ "select",
1035
+ {
1036
+ style: styles.enumPicker,
1037
+ title: "Pick a value from an enum",
1038
+ value: "",
1039
+ onChange: (e) => {
1040
+ if (e.target.value) updateRule(index, { value: e.target.value });
1041
+ },
1042
+ children: [
1043
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u229E" }),
1044
+ externalEnums.map((en) => /* @__PURE__ */ jsx("optgroup", { label: en.label, children: en.values.map((v) => /* @__PURE__ */ jsxs("option", { value: String(v.value), children: [
1045
+ v.label,
1046
+ " (",
1047
+ v.value,
1048
+ ")"
1049
+ ] }, String(v.value))) }, en.id))
1050
+ ]
1051
+ }
1052
+ )
1053
+ ] })),
930
1054
  /* @__PURE__ */ jsx("button", { style: styles.removeBtn, onClick: () => removeRule(index), children: "\u2715" })
931
1055
  ] }, index);
932
1056
  }),
@@ -1004,6 +1128,17 @@ var styles = {
1004
1128
  alignSelf: "flex-start",
1005
1129
  color: "var(--wp-text-secondary)"
1006
1130
  },
1131
+ valueGroup: { display: "flex", alignItems: "center", gap: 4 },
1132
+ enumPicker: {
1133
+ fontSize: 11,
1134
+ padding: "4px 4px",
1135
+ border: "1px solid var(--wp-border-muted)",
1136
+ borderRadius: "var(--wp-radius)",
1137
+ background: "var(--wp-canvas)",
1138
+ color: "var(--wp-primary)",
1139
+ cursor: "pointer",
1140
+ flexShrink: 0
1141
+ },
1007
1142
  preview: { marginTop: 4 },
1008
1143
  previewLabel: {
1009
1144
  fontSize: 10,
@@ -1025,6 +1160,244 @@ var styles = {
1025
1160
  color: "var(--wp-text-mono)"
1026
1161
  }
1027
1162
  };
1163
+ var VALIDATION_RULES = [
1164
+ { type: "required", label: "Required", hasValue: false },
1165
+ { type: "min", label: "Min value", hasValue: true },
1166
+ { type: "max", label: "Max value", hasValue: true },
1167
+ { type: "minLength", label: "Min length", hasValue: true },
1168
+ { type: "maxLength", label: "Max length", hasValue: true },
1169
+ { type: "email", label: "Email format", hasValue: false },
1170
+ { type: "url", label: "URL format", hasValue: false },
1171
+ { type: "regex", label: "Matches regex", hasValue: true },
1172
+ { type: "equals", label: "equals", hasValue: true },
1173
+ { type: "notEquals", label: "not equals", hasValue: true },
1174
+ { type: "greaterThan", label: ">", hasValue: true },
1175
+ { type: "greaterThanOrEqual", label: ">=", hasValue: true },
1176
+ { type: "lessThan", label: "<", hasValue: true },
1177
+ { type: "lessThanOrEqual", label: "<=", hasValue: true },
1178
+ { type: "contains", label: "contains", hasValue: true },
1179
+ { type: "notContains", label: "not contains", hasValue: true },
1180
+ { type: "matches", label: "matches regex", hasValue: true },
1181
+ { type: "inEnum", label: "is in enum", hasValue: true, isEnum: true },
1182
+ { type: "notInEnum", label: "not in enum", hasValue: true, isEnum: true },
1183
+ { type: "custom", label: "Custom validator", hasValue: false }
1184
+ ];
1185
+ var COMPARATOR_TYPES = /* @__PURE__ */ new Set([
1186
+ "equals",
1187
+ "notEquals",
1188
+ "greaterThan",
1189
+ "greaterThanOrEqual",
1190
+ "lessThan",
1191
+ "lessThanOrEqual",
1192
+ "contains",
1193
+ "notContains"
1194
+ ]);
1195
+ function ValidationBuilder({ value, onChange }) {
1196
+ const externalEnums = useBuilderExternalEnums();
1197
+ const allFieldPaths = useAllFieldPaths();
1198
+ const updateRule = (index, updates) => {
1199
+ onChange(value.map((r, i) => i === index ? { ...r, ...updates } : r));
1200
+ };
1201
+ const addRule = () => {
1202
+ onChange([...value, { type: "required", message: "This field is required" }]);
1203
+ };
1204
+ const removeRule = (index) => {
1205
+ onChange(value.filter((_, i) => i !== index));
1206
+ };
1207
+ return /* @__PURE__ */ jsxs("div", { style: styles2.container, children: [
1208
+ value.length === 0 && /* @__PURE__ */ jsx("div", { style: styles2.empty, children: "No rules \u2014 field is optional by default." }),
1209
+ value.map((rule, index) => {
1210
+ const def = VALIDATION_RULES.find((r) => r.type === rule.type);
1211
+ return /* @__PURE__ */ jsxs("div", { style: styles2.rule, children: [
1212
+ /* @__PURE__ */ jsx(
1213
+ "select",
1214
+ {
1215
+ style: styles2.typeSelect,
1216
+ value: rule.type,
1217
+ onChange: (e) => updateRule(index, { type: e.target.value }),
1218
+ children: VALIDATION_RULES.map((r) => /* @__PURE__ */ jsx("option", { value: r.type, children: r.label }, r.type))
1219
+ }
1220
+ ),
1221
+ def?.hasValue && (def.isEnum ? /* @__PURE__ */ jsxs(
1222
+ "select",
1223
+ {
1224
+ style: { ...styles2.typeSelect, flex: "0 0 140px" },
1225
+ value: rule.value != null ? String(rule.value) : "",
1226
+ onChange: (e) => updateRule(index, { value: e.target.value }),
1227
+ children: [
1228
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 pick enum \u2014" }),
1229
+ externalEnums.map((en) => /* @__PURE__ */ jsx("option", { value: en.id, children: en.label }, en.id))
1230
+ ]
1231
+ }
1232
+ ) : /* @__PURE__ */ jsxs("div", { style: styles2.valueGroup, children: [
1233
+ COMPARATOR_TYPES.has(rule.type) && /* @__PURE__ */ jsx(
1234
+ "button",
1235
+ {
1236
+ type: "button",
1237
+ style: {
1238
+ ...styles2.refToggle,
1239
+ background: rule.refField !== void 0 ? "var(--wp-primary)" : "var(--wp-surface-muted)",
1240
+ color: rule.refField !== void 0 ? "#fff" : "var(--wp-text-secondary)"
1241
+ },
1242
+ title: rule.refField !== void 0 ? "Comparing to field \u2014 click for static value" : "Static value \u2014 click to compare to another field",
1243
+ onClick: () => {
1244
+ if (rule.refField !== void 0) {
1245
+ updateRule(index, { refField: void 0, value: "" });
1246
+ } else {
1247
+ updateRule(index, { refField: "", value: void 0 });
1248
+ }
1249
+ },
1250
+ children: "\u21C4"
1251
+ }
1252
+ ),
1253
+ rule.refField !== void 0 ? /* @__PURE__ */ jsxs(
1254
+ "select",
1255
+ {
1256
+ style: { ...styles2.typeSelect, flex: "1 1 auto" },
1257
+ value: rule.refField,
1258
+ onChange: (e) => updateRule(index, { refField: e.target.value }),
1259
+ children: [
1260
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 pick field \u2014" }),
1261
+ allFieldPaths.filter((fp) => !fp.isExternal).map((fp) => /* @__PURE__ */ jsx("option", { value: fp.path, children: fp.label }, fp.path))
1262
+ ]
1263
+ }
1264
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1265
+ /* @__PURE__ */ jsx(
1266
+ "input",
1267
+ {
1268
+ style: styles2.valueInput,
1269
+ placeholder: "value",
1270
+ value: rule.value != null ? String(rule.value) : "",
1271
+ onChange: (e) => updateRule(index, { value: e.target.value })
1272
+ }
1273
+ ),
1274
+ externalEnums.length > 0 && /* @__PURE__ */ jsxs(
1275
+ "select",
1276
+ {
1277
+ style: styles2.enumPicker,
1278
+ title: "Pick a value from an enum",
1279
+ value: "",
1280
+ onChange: (e) => {
1281
+ if (e.target.value) updateRule(index, { value: e.target.value });
1282
+ },
1283
+ children: [
1284
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u229E" }),
1285
+ externalEnums.map((en) => /* @__PURE__ */ jsx("optgroup", { label: en.label, children: en.values.map((v) => /* @__PURE__ */ jsxs("option", { value: String(v.value), children: [
1286
+ v.label,
1287
+ " (",
1288
+ v.value,
1289
+ ")"
1290
+ ] }, String(v.value))) }, en.id))
1291
+ ]
1292
+ }
1293
+ )
1294
+ ] })
1295
+ ] })),
1296
+ rule.type === "custom" && /* @__PURE__ */ jsx(
1297
+ "input",
1298
+ {
1299
+ style: styles2.valueInput,
1300
+ placeholder: "validatorId",
1301
+ value: rule.customValidatorId ?? "",
1302
+ onChange: (e) => updateRule(index, { customValidatorId: e.target.value })
1303
+ }
1304
+ ),
1305
+ /* @__PURE__ */ jsx(
1306
+ "input",
1307
+ {
1308
+ style: styles2.messageInput,
1309
+ placeholder: "error message",
1310
+ value: rule.message,
1311
+ onChange: (e) => updateRule(index, { message: e.target.value })
1312
+ }
1313
+ ),
1314
+ /* @__PURE__ */ jsx("button", { style: styles2.removeBtn, onClick: () => removeRule(index), children: "\u2715" })
1315
+ ] }, index);
1316
+ }),
1317
+ /* @__PURE__ */ jsx("button", { style: styles2.addBtn, onClick: addRule, children: "+ Add rule" })
1318
+ ] });
1319
+ }
1320
+ var styles2 = {
1321
+ container: { display: "flex", flexDirection: "column", gap: 10 },
1322
+ empty: { fontSize: 13, color: "var(--wp-text-subtle)", textAlign: "center", padding: "12px 0" },
1323
+ rule: {
1324
+ display: "flex",
1325
+ alignItems: "center",
1326
+ gap: 8,
1327
+ background: "var(--wp-surface)",
1328
+ border: "1px solid var(--wp-border)",
1329
+ borderRadius: "var(--wp-radius-lg)",
1330
+ padding: "8px 10px"
1331
+ },
1332
+ typeSelect: {
1333
+ flex: "0 0 150px",
1334
+ fontSize: 12,
1335
+ padding: "5px 6px",
1336
+ border: "1px solid var(--wp-border-muted)",
1337
+ borderRadius: "var(--wp-radius)",
1338
+ background: "var(--wp-canvas)",
1339
+ color: "var(--wp-text)"
1340
+ },
1341
+ valueGroup: { display: "flex", alignItems: "center", gap: 4, flex: "1 1 auto" },
1342
+ refToggle: {
1343
+ border: "1px solid var(--wp-border-muted)",
1344
+ borderRadius: "var(--wp-radius)",
1345
+ cursor: "pointer",
1346
+ fontSize: 11,
1347
+ padding: "4px 6px",
1348
+ flexShrink: 0,
1349
+ fontWeight: 600,
1350
+ lineHeight: 1
1351
+ },
1352
+ valueInput: {
1353
+ width: 90,
1354
+ fontSize: 12,
1355
+ padding: "5px 6px",
1356
+ border: "1px solid var(--wp-border-muted)",
1357
+ borderRadius: "var(--wp-radius)",
1358
+ background: "var(--wp-canvas)",
1359
+ color: "var(--wp-text)"
1360
+ },
1361
+ enumPicker: {
1362
+ fontSize: 11,
1363
+ padding: "4px 4px",
1364
+ border: "1px solid var(--wp-border-muted)",
1365
+ borderRadius: "var(--wp-radius)",
1366
+ background: "var(--wp-canvas)",
1367
+ color: "var(--wp-primary)",
1368
+ cursor: "pointer",
1369
+ flexShrink: 0
1370
+ },
1371
+ messageInput: {
1372
+ flex: 1,
1373
+ fontSize: 12,
1374
+ padding: "5px 6px",
1375
+ border: "1px solid var(--wp-border-muted)",
1376
+ borderRadius: "var(--wp-radius)",
1377
+ background: "var(--wp-canvas)",
1378
+ color: "var(--wp-text)",
1379
+ minWidth: 0
1380
+ },
1381
+ removeBtn: {
1382
+ border: "none",
1383
+ background: "transparent",
1384
+ color: "var(--wp-danger)",
1385
+ cursor: "pointer",
1386
+ fontSize: 13,
1387
+ flexShrink: 0
1388
+ },
1389
+ addBtn: {
1390
+ fontSize: 12,
1391
+ padding: "6px 12px",
1392
+ background: "var(--wp-surface-muted)",
1393
+ border: "1px solid var(--wp-border-muted)",
1394
+ borderRadius: "var(--wp-radius)",
1395
+ cursor: "pointer",
1396
+ fontWeight: 500,
1397
+ alignSelf: "flex-start",
1398
+ color: "var(--wp-text-secondary)"
1399
+ }
1400
+ };
1028
1401
  function DependsOnInput({
1029
1402
  value,
1030
1403
  onChange,
@@ -1057,17 +1430,17 @@ function DependsOnInput({
1057
1430
  }, []);
1058
1431
  const getLabel = (path) => allPaths.find((p) => p.path === path)?.label ?? path;
1059
1432
  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: [
1433
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: styles3.container, children: [
1434
+ /* @__PURE__ */ jsxs("div", { style: styles3.tags, children: [
1435
+ value.map((path) => /* @__PURE__ */ jsxs("span", { style: { ...styles3.tag, ...isExternal(path) ? styles3.tagExt : {} }, children: [
1063
1436
  getLabel(path),
1064
- /* @__PURE__ */ jsx("button", { style: styles2.tagRemove, onClick: () => remove(path), children: "\u2715" })
1437
+ /* @__PURE__ */ jsx("button", { style: styles3.tagRemove, onClick: () => remove(path), children: "\u2715" })
1065
1438
  ] }, path)),
1066
1439
  /* @__PURE__ */ jsx(
1067
1440
  "input",
1068
1441
  {
1069
1442
  ref: inputRef,
1070
- style: styles2.input,
1443
+ style: styles3.input,
1071
1444
  placeholder: value.length === 0 ? "Search fields or $ext vars\u2026" : "Add more\u2026",
1072
1445
  value: query,
1073
1446
  onChange: (e) => {
@@ -1078,28 +1451,28 @@ function DependsOnInput({
1078
1451
  }
1079
1452
  )
1080
1453
  ] }),
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." }),
1454
+ open && /* @__PURE__ */ jsxs("div", { style: styles3.dropdown, children: [
1455
+ available.length === 0 && /* @__PURE__ */ jsx("div", { style: styles3.noResults, children: allPaths.length === 0 ? "No fields available in the tree yet." : "No matching fields." }),
1083
1456
  available.map((p) => /* @__PURE__ */ jsxs(
1084
1457
  "button",
1085
1458
  {
1086
- style: styles2.option,
1459
+ style: styles3.option,
1087
1460
  onMouseDown: (e) => {
1088
1461
  e.preventDefault();
1089
1462
  add(p.path);
1090
1463
  },
1091
1464
  children: [
1092
- /* @__PURE__ */ jsx("span", { style: styles2.optionLabel, children: p.label }),
1093
- /* @__PURE__ */ jsx("span", { style: styles2.optionPath, children: p.path })
1465
+ /* @__PURE__ */ jsx("span", { style: styles3.optionLabel, children: p.label }),
1466
+ /* @__PURE__ */ jsx("span", { style: styles3.optionPath, children: p.path })
1094
1467
  ]
1095
1468
  },
1096
1469
  p.path
1097
1470
  ))
1098
1471
  ] }),
1099
- value.length > 0 && /* @__PURE__ */ jsx("div", { style: styles2.hint, children: "This field will be blocked until all dependencies have a value." })
1472
+ value.length > 0 && /* @__PURE__ */ jsx("div", { style: styles3.hint, children: "This field will be blocked until all dependencies have a value." })
1100
1473
  ] });
1101
1474
  }
1102
- var styles2 = {
1475
+ var styles3 = {
1103
1476
  container: { position: "relative" },
1104
1477
  tags: {
1105
1478
  display: "flex",
@@ -1181,15 +1554,15 @@ function Modal({ title, onClose, children, width = 560 }) {
1181
1554
  document.addEventListener("keydown", handler);
1182
1555
  return () => document.removeEventListener("keydown", handler);
1183
1556
  }, [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" })
1557
+ return /* @__PURE__ */ jsx("div", { style: styles4.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { style: { ...styles4.panel, width }, onClick: (e) => e.stopPropagation(), children: [
1558
+ /* @__PURE__ */ jsxs("div", { style: styles4.header, children: [
1559
+ /* @__PURE__ */ jsx("span", { style: styles4.title, children: title }),
1560
+ /* @__PURE__ */ jsx("button", { style: styles4.closeBtn, onClick: onClose, children: "\u2715" })
1188
1561
  ] }),
1189
- /* @__PURE__ */ jsx("div", { style: styles3.body, children })
1562
+ /* @__PURE__ */ jsx("div", { style: styles4.body, children })
1190
1563
  ] }) });
1191
1564
  }
1192
- var styles3 = {
1565
+ var styles4 = {
1193
1566
  overlay: {
1194
1567
  position: "fixed",
1195
1568
  inset: 0,
@@ -1226,16 +1599,7 @@ var styles3 = {
1226
1599
  },
1227
1600
  body: { overflowY: "auto", padding: 20, color: "var(--wp-text)" }
1228
1601
  };
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
- ];
1602
+ var ENUM_FIELD_TYPES = ["select", "multiselect", "radio"];
1239
1603
  function FieldEditor() {
1240
1604
  const {
1241
1605
  schema,
@@ -1244,85 +1608,171 @@ function FieldEditor() {
1244
1608
  updateField,
1245
1609
  setFieldCondition
1246
1610
  } = useBuilderStore();
1247
- const [newValidationType, setNewValidationType] = useState("required");
1611
+ const readOnly = useBuilderReadOnly();
1612
+ const externalEnums = useBuilderExternalEnums();
1248
1613
  const [conditionModalOpen, setConditionModalOpen] = useState(false);
1614
+ const [validationModalOpen, setValidationModalOpen] = useState(false);
1615
+ const [dynDefaultModalOpen, setDynDefaultModalOpen] = useState(false);
1616
+ const [editingDynIdx, setEditingDynIdx] = useState(null);
1249
1617
  const step = schema.steps.find((s) => s.id === selectedStepId);
1250
1618
  const field = step?.fields.find((f) => f.id === selectedFieldId);
1251
1619
  if (!field || !step) {
1252
- return /* @__PURE__ */ jsx("div", { style: styles4.empty, children: "Select a field in the middle panel to edit its properties." });
1620
+ return /* @__PURE__ */ jsx("div", { style: styles5.empty, children: "Select a field in the middle panel to edit its properties." });
1253
1621
  }
1254
1622
  const validation = field.validation ?? [];
1255
1623
  const isRequired = validation.some((v) => v.type === "required");
1256
1624
  const hasCondition = !!field.visibleWhen;
1257
1625
  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] });
1626
+ const handleValidationChange = (rules) => {
1627
+ updateField(step.id, field.id, { validation: rules.length ? rules : void 0 });
1272
1628
  };
1273
1629
  const handleDependsOnChange = (paths) => {
1274
1630
  updateField(step.id, field.id, { dependsOn: paths.length ? paths : void 0 });
1275
1631
  };
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" })
1632
+ return /* @__PURE__ */ jsxs("div", { style: styles5.container, children: [
1633
+ /* @__PURE__ */ jsxs("div", { style: styles5.header, children: [
1634
+ /* @__PURE__ */ jsxs("div", { style: styles5.headerLeft, children: [
1635
+ /* @__PURE__ */ jsx("span", { style: styles5.headerTitle, children: "Field Editor" }),
1636
+ !isRequired && /* @__PURE__ */ jsx("span", { style: styles5.optionalBadge, children: "optional" }),
1637
+ isRequired && /* @__PURE__ */ jsx("span", { style: styles5.requiredBadge, children: "required" })
1282
1638
  ] }),
1283
- /* @__PURE__ */ jsxs("div", { style: styles4.fieldId, children: [
1639
+ /* @__PURE__ */ jsxs("div", { style: styles5.fieldId, children: [
1284
1640
  "id: ",
1285
1641
  field.id
1286
1642
  ] })
1287
1643
  ] }),
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" }),
1644
+ /* @__PURE__ */ jsxs("div", { style: styles5.body, children: [
1645
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1646
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Label" }),
1291
1647
  /* @__PURE__ */ jsx(
1292
1648
  "input",
1293
1649
  {
1294
- style: styles4.input,
1650
+ style: styles5.input,
1295
1651
  value: field.label,
1296
1652
  onChange: (e) => updateField(step.id, field.id, { label: e.target.value })
1297
1653
  }
1298
1654
  )
1299
1655
  ] }),
1300
- /* @__PURE__ */ jsxs("div", { style: styles4.group, children: [
1301
- /* @__PURE__ */ jsx("label", { style: styles4.label, children: "Placeholder" }),
1656
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1657
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Placeholder" }),
1302
1658
  /* @__PURE__ */ jsx(
1303
1659
  "input",
1304
1660
  {
1305
- style: styles4.input,
1661
+ style: styles5.input,
1306
1662
  value: field.placeholder ?? "",
1307
1663
  placeholder: "Optional",
1308
1664
  onChange: (e) => updateField(step.id, field.id, { placeholder: e.target.value || void 0 })
1309
1665
  }
1310
1666
  )
1311
1667
  ] }),
1312
- /* @__PURE__ */ jsxs("div", { style: styles4.group, children: [
1313
- /* @__PURE__ */ jsx("label", { style: styles4.label, children: "Default value" }),
1668
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1669
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Default value" }),
1314
1670
  /* @__PURE__ */ jsx(
1315
1671
  "input",
1316
1672
  {
1317
- style: styles4.input,
1673
+ style: styles5.input,
1318
1674
  value: field.defaultValue != null ? String(field.defaultValue) : "",
1319
1675
  placeholder: "Optional",
1320
1676
  onChange: (e) => updateField(step.id, field.id, { defaultValue: e.target.value || void 0 })
1321
1677
  }
1322
1678
  )
1323
1679
  ] }),
1324
- /* @__PURE__ */ jsxs("div", { style: styles4.group, children: [
1325
- /* @__PURE__ */ jsx("label", { style: styles4.label, children: "Depends on" }),
1680
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionRow, children: [
1681
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionInfo, children: [
1682
+ /* @__PURE__ */ jsx("div", { style: styles5.label, children: "Dynamic defaults" }),
1683
+ (field.dynamicDefault?.length ?? 0) > 0 ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: field.dynamicDefault.map((rule, i) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
1684
+ /* @__PURE__ */ jsxs("span", { style: styles5.conditionBadge, children: [
1685
+ rule.when.rules.length,
1686
+ " rule",
1687
+ rule.when.rules.length !== 1 ? "s" : ""
1688
+ ] }),
1689
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, color: "var(--wp-text-subtle)" }, children: [
1690
+ "\u2192 ",
1691
+ JSON.stringify(rule.value)
1692
+ ] }),
1693
+ !readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
1694
+ /* @__PURE__ */ jsx(
1695
+ "button",
1696
+ {
1697
+ style: { ...styles5.editConditionBtn, fontSize: 10, padding: "2px 6px" },
1698
+ onClick: () => {
1699
+ setEditingDynIdx(i);
1700
+ setDynDefaultModalOpen(true);
1701
+ },
1702
+ children: "Edit"
1703
+ }
1704
+ ),
1705
+ /* @__PURE__ */ jsx(
1706
+ "button",
1707
+ {
1708
+ style: { ...styles5.clearConditionBtn, fontSize: 10, padding: "2px 6px" },
1709
+ onClick: () => {
1710
+ const updated = field.dynamicDefault.filter((_, j) => j !== i);
1711
+ updateField(step.id, field.id, { dynamicDefault: updated.length ? updated : void 0 });
1712
+ },
1713
+ children: "\xD7"
1714
+ }
1715
+ )
1716
+ ] })
1717
+ ] }, i)) }) : /* @__PURE__ */ jsx("div", { style: styles5.conditionNone, children: "No dynamic defaults" })
1718
+ ] }),
1719
+ !readOnly && /* @__PURE__ */ jsx("div", { style: styles5.conditionActions, children: /* @__PURE__ */ jsx(
1720
+ "button",
1721
+ {
1722
+ style: styles5.editConditionBtn,
1723
+ onClick: () => {
1724
+ setEditingDynIdx(null);
1725
+ setDynDefaultModalOpen(true);
1726
+ },
1727
+ children: "Add"
1728
+ }
1729
+ ) })
1730
+ ] }),
1731
+ /* @__PURE__ */ jsx("div", { style: styles5.divider }),
1732
+ ENUM_FIELD_TYPES.includes(field.type) && externalEnums.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1733
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Options source" }),
1734
+ /* @__PURE__ */ jsxs(
1735
+ "select",
1736
+ {
1737
+ style: styles5.input,
1738
+ disabled: readOnly,
1739
+ value: field.externalEnumId ?? "",
1740
+ onChange: (e) => {
1741
+ const enumId = e.target.value || void 0;
1742
+ updateField(step.id, field.id, {
1743
+ externalEnumId: enumId,
1744
+ // Clear hardcoded options when switching to an enum
1745
+ options: enumId ? void 0 : field.options
1746
+ });
1747
+ },
1748
+ children: [
1749
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Hardcoded options \u2014" }),
1750
+ externalEnums.map((en) => /* @__PURE__ */ jsxs("option", { value: en.id, children: [
1751
+ en.label,
1752
+ " (",
1753
+ en.values.length,
1754
+ " items)"
1755
+ ] }, en.id))
1756
+ ]
1757
+ }
1758
+ ),
1759
+ field.externalEnumId && /* @__PURE__ */ jsx("div", { style: styles5.enumInfo, children: (() => {
1760
+ const en = externalEnums.find((e) => e.id === field.externalEnumId);
1761
+ return en ? /* @__PURE__ */ jsxs("span", { style: styles5.enumBadge, children: [
1762
+ "\u229E ",
1763
+ en.label,
1764
+ " \xB7 ",
1765
+ en.values.length,
1766
+ " options"
1767
+ ] }) : /* @__PURE__ */ jsxs("span", { style: styles5.enumMissing, children: [
1768
+ '\u26A0 Enum "',
1769
+ field.externalEnumId,
1770
+ '" not found'
1771
+ ] });
1772
+ })() })
1773
+ ] }),
1774
+ /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1775
+ /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Depends on" }),
1326
1776
  /* @__PURE__ */ jsx(
1327
1777
  DependsOnInput,
1328
1778
  {
@@ -1333,92 +1783,101 @@ function FieldEditor() {
1333
1783
  }
1334
1784
  )
1335
1785
  ] }),
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: [
1786
+ /* @__PURE__ */ jsx("div", { style: styles5.divider }),
1787
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionRow, children: [
1788
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionInfo, children: [
1789
+ /* @__PURE__ */ jsx("div", { style: styles5.label, children: "Visibility condition" }),
1790
+ hasCondition ? /* @__PURE__ */ jsxs("div", { style: styles5.conditionSummary, children: [
1791
+ /* @__PURE__ */ jsxs("span", { style: styles5.conditionBadge, children: [
1342
1792
  ruleCount,
1343
1793
  " rule",
1344
1794
  ruleCount !== 1 ? "s" : "",
1345
1795
  " \xB7 ",
1346
1796
  field.visibleWhen.combinator.toUpperCase()
1347
1797
  ] }),
1348
- /* @__PURE__ */ jsx("span", { style: styles4.conditionDesc, children: "Field is conditional" })
1349
- ] }) : /* @__PURE__ */ jsx("div", { style: styles4.conditionNone, children: "Always visible" })
1798
+ /* @__PURE__ */ jsx("span", { style: styles5.conditionDesc, children: "Field is conditional" })
1799
+ ] }) : /* @__PURE__ */ jsx("div", { style: styles5.conditionNone, children: "Always visible" })
1350
1800
  ] }),
1351
- /* @__PURE__ */ jsxs("div", { style: styles4.conditionActions, children: [
1352
- /* @__PURE__ */ jsx("button", { style: styles4.editConditionBtn, onClick: () => setConditionModalOpen(true), children: hasCondition ? "Edit" : "Add" }),
1801
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionActions, children: [
1802
+ /* @__PURE__ */ jsx("button", { style: styles5.editConditionBtn, onClick: () => setConditionModalOpen(true), children: hasCondition ? "Edit" : "Add" }),
1353
1803
  hasCondition && /* @__PURE__ */ jsx(
1354
1804
  "button",
1355
1805
  {
1356
- style: styles4.clearConditionBtn,
1806
+ style: styles5.clearConditionBtn,
1357
1807
  onClick: () => setFieldCondition(step.id, field.id, void 0),
1358
1808
  children: "Clear"
1359
1809
  }
1360
1810
  )
1361
1811
  ] })
1362
1812
  ] }),
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" })
1813
+ /* @__PURE__ */ jsx("div", { style: styles5.divider }),
1814
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionRow, children: [
1815
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionInfo, children: [
1816
+ /* @__PURE__ */ jsx("div", { style: styles5.label, children: "Validation" }),
1817
+ validation.length > 0 ? /* @__PURE__ */ jsxs("div", { style: styles5.conditionSummary, children: [
1818
+ /* @__PURE__ */ jsxs("span", { style: styles5.validationBadge, children: [
1819
+ validation.length,
1820
+ " rule",
1821
+ validation.length !== 1 ? "s" : "",
1822
+ isRequired ? " \xB7 required" : ""
1823
+ ] }),
1824
+ /* @__PURE__ */ jsx("span", { style: styles5.conditionDesc, children: validation.map((r) => r.type).join(", ") })
1825
+ ] }) : /* @__PURE__ */ jsx("div", { style: styles5.conditionNone, children: "No rules \xB7 field is optional" })
1826
+ ] }),
1827
+ /* @__PURE__ */ jsxs("div", { style: styles5.conditionActions, children: [
1828
+ /* @__PURE__ */ jsx("button", { style: styles5.editConditionBtn, onClick: () => setValidationModalOpen(true), children: validation.length > 0 ? "Edit" : "Add" }),
1829
+ validation.length > 0 && /* @__PURE__ */ jsx(
1830
+ "button",
1831
+ {
1832
+ style: styles5.clearConditionBtn,
1833
+ onClick: () => updateField(step.id, field.id, { validation: void 0 }),
1834
+ children: "Clear"
1835
+ }
1836
+ )
1837
+ ] })
1420
1838
  ] })
1421
1839
  ] }),
1840
+ validationModalOpen && /* @__PURE__ */ jsxs(
1841
+ Modal,
1842
+ {
1843
+ title: `Validation \u2014 "${field.label}"`,
1844
+ onClose: () => setValidationModalOpen(false),
1845
+ width: 680,
1846
+ children: [
1847
+ /* @__PURE__ */ jsx("p", { style: styles5.modalHint, children: "Define validation rules for this field. All rules must pass for the field to be valid." }),
1848
+ /* @__PURE__ */ jsx(
1849
+ ValidationBuilder,
1850
+ {
1851
+ value: validation,
1852
+ onChange: handleValidationChange
1853
+ }
1854
+ ),
1855
+ /* @__PURE__ */ jsx("div", { style: styles5.modalFooter, children: /* @__PURE__ */ jsx("button", { style: styles5.modalCloseBtn, onClick: () => setValidationModalOpen(false), children: "Done" }) })
1856
+ ]
1857
+ }
1858
+ ),
1859
+ dynDefaultModalOpen && /* @__PURE__ */ jsx(
1860
+ DynDefaultModal,
1861
+ {
1862
+ rule: editingDynIdx !== null ? field.dynamicDefault?.[editingDynIdx] : void 0,
1863
+ onSave: (rule) => {
1864
+ const current = field.dynamicDefault ?? [];
1865
+ let updated;
1866
+ if (editingDynIdx !== null) {
1867
+ updated = current.map((r, i) => i === editingDynIdx ? rule : r);
1868
+ } else {
1869
+ updated = [...current, rule];
1870
+ }
1871
+ updateField(step.id, field.id, { dynamicDefault: updated });
1872
+ setDynDefaultModalOpen(false);
1873
+ setEditingDynIdx(null);
1874
+ },
1875
+ onClose: () => {
1876
+ setDynDefaultModalOpen(false);
1877
+ setEditingDynIdx(null);
1878
+ }
1879
+ }
1880
+ ),
1422
1881
  conditionModalOpen && /* @__PURE__ */ jsxs(
1423
1882
  Modal,
1424
1883
  {
@@ -1426,7 +1885,7 @@ function FieldEditor() {
1426
1885
  onClose: () => setConditionModalOpen(false),
1427
1886
  width: 620,
1428
1887
  children: [
1429
- /* @__PURE__ */ jsx("p", { style: styles4.modalHint, children: "Define when this field is visible within its step." }),
1888
+ /* @__PURE__ */ jsx("p", { style: styles5.modalHint, children: "Define when this field is visible within its step." }),
1430
1889
  /* @__PURE__ */ jsx(
1431
1890
  ConditionBuilder,
1432
1891
  {
@@ -1436,13 +1895,103 @@ function FieldEditor() {
1436
1895
  excludeFieldId: field.id
1437
1896
  }
1438
1897
  ),
1439
- /* @__PURE__ */ jsx("div", { style: styles4.modalFooter, children: /* @__PURE__ */ jsx("button", { style: styles4.modalCloseBtn, onClick: () => setConditionModalOpen(false), children: "Done" }) })
1898
+ /* @__PURE__ */ jsx("div", { style: styles5.modalFooter, children: /* @__PURE__ */ jsx("button", { style: styles5.modalCloseBtn, onClick: () => setConditionModalOpen(false), children: "Done" }) })
1440
1899
  ]
1441
1900
  }
1442
1901
  )
1443
1902
  ] });
1444
1903
  }
1445
- var styles4 = {
1904
+ function DynDefaultModal({
1905
+ rule,
1906
+ onSave,
1907
+ onClose
1908
+ }) {
1909
+ const [condition, setCondition] = useState(rule?.when);
1910
+ const [value, setValue] = useState(rule?.value != null ? String(rule.value) : "");
1911
+ const canSave = condition && condition.rules.length > 0 && value !== "";
1912
+ return /* @__PURE__ */ jsxs(
1913
+ Modal,
1914
+ {
1915
+ title: rule ? "Edit dynamic default" : "Add dynamic default",
1916
+ onClose,
1917
+ width: 620,
1918
+ children: [
1919
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: "var(--wp-text-muted)", margin: "0 0 16px" }, children: "When the condition matches, this value will be used as the field default." }),
1920
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
1921
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 600, color: "var(--wp-text-muted)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 6 }, children: "Condition" }),
1922
+ /* @__PURE__ */ jsx(ConditionBuilder, { value: condition, onChange: setCondition })
1923
+ ] }),
1924
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 20 }, children: [
1925
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 600, color: "var(--wp-text-muted)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 6 }, children: "Default value when condition matches" }),
1926
+ /* @__PURE__ */ jsx(
1927
+ "input",
1928
+ {
1929
+ style: {
1930
+ fontSize: 13,
1931
+ padding: "6px 8px",
1932
+ border: "1px solid var(--wp-border-muted)",
1933
+ borderRadius: "var(--wp-radius)",
1934
+ outline: "none",
1935
+ width: "100%",
1936
+ boxSizing: "border-box",
1937
+ background: "var(--wp-canvas)",
1938
+ color: "var(--wp-text)"
1939
+ },
1940
+ value,
1941
+ placeholder: "Value to set as default",
1942
+ onChange: (e) => setValue(e.target.value)
1943
+ }
1944
+ )
1945
+ ] }),
1946
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 }, children: [
1947
+ /* @__PURE__ */ jsx(
1948
+ "button",
1949
+ {
1950
+ style: {
1951
+ fontSize: 13,
1952
+ padding: "7px 16px",
1953
+ background: "transparent",
1954
+ color: "var(--wp-text-muted)",
1955
+ border: "1px solid var(--wp-border)",
1956
+ borderRadius: "var(--wp-radius-lg)",
1957
+ cursor: "pointer"
1958
+ },
1959
+ onClick: onClose,
1960
+ children: "Cancel"
1961
+ }
1962
+ ),
1963
+ /* @__PURE__ */ jsx(
1964
+ "button",
1965
+ {
1966
+ disabled: !canSave,
1967
+ style: {
1968
+ fontSize: 13,
1969
+ padding: "7px 20px",
1970
+ background: canSave ? "var(--wp-primary)" : "var(--wp-border)",
1971
+ color: "var(--wp-canvas)",
1972
+ border: "none",
1973
+ borderRadius: "var(--wp-radius-lg)",
1974
+ cursor: canSave ? "pointer" : "not-allowed",
1975
+ fontWeight: 600,
1976
+ opacity: canSave ? 1 : 0.5
1977
+ },
1978
+ onClick: () => {
1979
+ if (!canSave || !condition) return;
1980
+ let parsed = value;
1981
+ if (value === "true") parsed = true;
1982
+ else if (value === "false") parsed = false;
1983
+ else if (!isNaN(Number(value)) && value.trim() !== "") parsed = Number(value);
1984
+ onSave({ when: condition, value: parsed });
1985
+ },
1986
+ children: rule ? "Update" : "Add"
1987
+ }
1988
+ )
1989
+ ] })
1990
+ ]
1991
+ }
1992
+ );
1993
+ }
1994
+ var styles5 = {
1446
1995
  container: { display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" },
1447
1996
  empty: {
1448
1997
  display: "flex",
@@ -1526,60 +2075,30 @@ var styles4 = {
1526
2075
  borderRadius: "var(--wp-radius)",
1527
2076
  cursor: "pointer"
1528
2077
  },
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: {
2078
+ validationBadge: {
1543
2079
  fontSize: 11,
1544
2080
  fontWeight: 700,
1545
- color: "var(--wp-primary-dark)",
1546
2081
  background: "var(--wp-primary-bg)",
2082
+ color: "var(--wp-primary-dark)",
1547
2083
  padding: "2px 8px",
1548
- borderRadius: 4
1549
- },
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)"
2084
+ borderRadius: 4
1572
2085
  },
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)"
2086
+ enumInfo: { marginTop: 4 },
2087
+ enumBadge: {
2088
+ fontSize: 11,
2089
+ fontWeight: 600,
2090
+ padding: "2px 8px",
2091
+ background: "var(--wp-info-bg)",
2092
+ color: "var(--wp-info-text)",
2093
+ borderRadius: 4
2094
+ },
2095
+ enumMissing: {
2096
+ fontSize: 11,
2097
+ fontWeight: 600,
2098
+ padding: "2px 8px",
2099
+ background: "var(--wp-warning-bg)",
2100
+ color: "var(--wp-warning)",
2101
+ borderRadius: 4
1583
2102
  },
1584
2103
  modalHint: { fontSize: 13, color: "var(--wp-text-muted)", marginBottom: 16, marginTop: 0 },
1585
2104
  modalFooter: { marginTop: 20, display: "flex", justifyContent: "flex-end" },
@@ -1691,6 +2210,107 @@ function getStepDependencyLabels(stepId, deps, schema) {
1691
2210
  const required = deps.get(stepId) ?? /* @__PURE__ */ new Set();
1692
2211
  return [...required].map((id) => schema.steps.find((s) => s.id === id)?.title ?? id).filter(Boolean);
1693
2212
  }
2213
+ function SortableItem({ id, disabled, children }) {
2214
+ const {
2215
+ attributes,
2216
+ listeners,
2217
+ setNodeRef,
2218
+ transform,
2219
+ transition,
2220
+ isDragging
2221
+ } = useSortable({ id, disabled });
2222
+ const style = {
2223
+ transform: CSS.Transform.toString(transform),
2224
+ transition,
2225
+ opacity: isDragging ? 0.4 : 1,
2226
+ position: "relative"
2227
+ };
2228
+ return /* @__PURE__ */ jsx("div", { ref: setNodeRef, style, children: children({
2229
+ handleProps: { ...attributes, ...listeners },
2230
+ isDragging
2231
+ }) });
2232
+ }
2233
+ function DragHandle({ handleProps, disabled }) {
2234
+ return /* @__PURE__ */ jsx(
2235
+ "button",
2236
+ {
2237
+ type: "button",
2238
+ ...handleProps,
2239
+ style: {
2240
+ ...dragHandleStyle,
2241
+ ...disabled ? dragHandleDisabledStyle : {}
2242
+ },
2243
+ title: "Drag to reorder",
2244
+ "aria-label": "Drag handle",
2245
+ children: "\u283F"
2246
+ }
2247
+ );
2248
+ }
2249
+ var dragHandleStyle = {
2250
+ cursor: "grab",
2251
+ touchAction: "none",
2252
+ border: "none",
2253
+ background: "transparent",
2254
+ color: "var(--wp-text-muted)",
2255
+ fontSize: 16,
2256
+ padding: "2px 4px",
2257
+ borderRadius: 4,
2258
+ display: "flex",
2259
+ alignItems: "center",
2260
+ justifyContent: "center",
2261
+ flexShrink: 0,
2262
+ lineHeight: 1
2263
+ };
2264
+ var dragHandleDisabledStyle = {
2265
+ cursor: "not-allowed",
2266
+ opacity: 0.3
2267
+ };
2268
+ function SortableList({
2269
+ items,
2270
+ disabled,
2271
+ onReorder,
2272
+ renderItem,
2273
+ renderOverlay
2274
+ }) {
2275
+ const [activeId, setActiveId] = useState(null);
2276
+ const sensors = useSensors(
2277
+ useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
2278
+ useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 5 } }),
2279
+ useSensor(KeyboardSensor)
2280
+ );
2281
+ const handleDragStart = useCallback((event) => {
2282
+ setActiveId(String(event.active.id));
2283
+ }, []);
2284
+ const handleDragEnd = useCallback(
2285
+ (event) => {
2286
+ setActiveId(null);
2287
+ const { active, over } = event;
2288
+ if (!over || active.id === over.id) return;
2289
+ const fromIndex = items.indexOf(String(active.id));
2290
+ const toIndex = items.indexOf(String(over.id));
2291
+ if (fromIndex === -1 || toIndex === -1) return;
2292
+ onReorder(fromIndex, toIndex);
2293
+ },
2294
+ [items, onReorder]
2295
+ );
2296
+ const handleDragCancel = useCallback(() => {
2297
+ setActiveId(null);
2298
+ }, []);
2299
+ return /* @__PURE__ */ jsxs(
2300
+ DndContext,
2301
+ {
2302
+ sensors,
2303
+ collisionDetection: closestCenter,
2304
+ onDragStart: handleDragStart,
2305
+ onDragEnd: handleDragEnd,
2306
+ onDragCancel: handleDragCancel,
2307
+ children: [
2308
+ /* @__PURE__ */ jsx(SortableContext, { items, strategy: verticalListSortingStrategy, disabled, children: items.map((id, index) => renderItem(id, index)) }),
2309
+ /* @__PURE__ */ jsx(DragOverlay, { dropAnimation: null, children: activeId && renderOverlay ? renderOverlay(activeId) : null })
2310
+ ]
2311
+ }
2312
+ );
2313
+ }
1694
2314
  var FIELD_TYPES = [
1695
2315
  "text",
1696
2316
  "number",
@@ -1713,10 +2333,14 @@ function FieldList() {
1713
2333
  selectedFieldId,
1714
2334
  addField,
1715
2335
  removeField,
2336
+ duplicateField,
1716
2337
  updateField,
1717
2338
  selectField,
1718
2339
  reorderFields
1719
2340
  } = useBuilderStore();
2341
+ const readOnly = useBuilderReadOnly();
2342
+ const appCustomTypes = useBuilderCustomTypes();
2343
+ const externalEnums = useBuilderExternalEnums();
1720
2344
  const [moveError, setMoveError] = useState(null);
1721
2345
  const step = schema.steps.find((s) => s.id === selectedStepId);
1722
2346
  const allDependencyTargets = /* @__PURE__ */ new Set();
@@ -1728,7 +2352,7 @@ function FieldList() {
1728
2352
  }
1729
2353
  }
1730
2354
  if (!step) {
1731
- return /* @__PURE__ */ jsx("div", { style: styles5.empty, children: "Select a step on the left to manage its fields." });
2355
+ return /* @__PURE__ */ jsx("div", { style: styles6.empty, children: "Select a step on the left to manage its fields." });
1732
2356
  }
1733
2357
  const tryMove = (fromIndex, toIndex) => {
1734
2358
  const check = isFieldMoveValid(step.fields, step.id, fromIndex, toIndex);
@@ -1740,127 +2364,180 @@ function FieldList() {
1740
2364
  setMoveError(null);
1741
2365
  reorderFields(step.id, fromIndex, toIndex);
1742
2366
  };
1743
- return /* @__PURE__ */ jsxs("div", { style: styles5.container, children: [
1744
- /* @__PURE__ */ jsxs("div", { style: styles5.header, children: [
2367
+ const fieldIds = step.fields.map((f) => f.id);
2368
+ const renderOverlay = (id) => {
2369
+ const field = step.fields.find((f) => f.id === id);
2370
+ if (!field) return null;
2371
+ const ct = appCustomTypes.find((c) => c.id === field.type);
2372
+ return /* @__PURE__ */ jsx("div", { style: { ...styles6.card, ...styles6.cardDragOverlay }, children: /* @__PURE__ */ jsx("div", { style: styles6.cardTop, children: /* @__PURE__ */ jsxs("div", { style: styles6.cardLeft, children: [
2373
+ /* @__PURE__ */ jsx("span", { style: { ...styles6.typeBadge, ...ct ? styles6.typeBadgeCustom : {} }, children: ct ? `${ct.icon ? ct.icon + " " : ""}${ct.label}` : field.type }),
2374
+ /* @__PURE__ */ jsx("span", { style: styles6.fieldLabel, children: field.label })
2375
+ ] }) }) });
2376
+ };
2377
+ return /* @__PURE__ */ jsxs("div", { style: styles6.container, children: [
2378
+ /* @__PURE__ */ jsxs("div", { style: styles6.header, children: [
1745
2379
  /* @__PURE__ */ jsxs("div", { children: [
1746
- /* @__PURE__ */ jsx("div", { style: styles5.stepTitle, children: step.title }),
1747
- /* @__PURE__ */ jsxs("div", { style: styles5.stepSub, children: [
2380
+ /* @__PURE__ */ jsx("div", { style: styles6.stepTitle, children: step.title }),
2381
+ /* @__PURE__ */ jsxs("div", { style: styles6.stepSub, children: [
1748
2382
  step.fields.length,
1749
2383
  " field",
1750
2384
  step.fields.length !== 1 ? "s" : ""
1751
2385
  ] })
1752
2386
  ] }),
1753
- /* @__PURE__ */ jsx("button", { style: styles5.addBtn, onClick: () => addField(step.id), children: "+ Add field" })
2387
+ !readOnly && /* @__PURE__ */ jsx("button", { style: styles6.addBtn, onClick: () => addField(step.id), children: "+ Add field" })
1754
2388
  ] }),
1755
- moveError && /* @__PURE__ */ jsxs("div", { style: styles5.errorBanner, children: [
2389
+ moveError && /* @__PURE__ */ jsxs("div", { style: styles6.errorBanner, children: [
1756
2390
  /* @__PURE__ */ jsx("span", { children: "\u26A0" }),
1757
2391
  " ",
1758
2392
  moveError
1759
2393
  ] }),
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.' }),
1762
- step.fields.map((field, index) => {
1763
- const isSelected = field.id === selectedFieldId;
1764
- const isRequired = field.validation?.some((v) => v.type === "required") ?? false;
1765
- const hasCondition = !!field.visibleWhen;
1766
- const hasDeps = (field.dependsOn?.length ?? 0) > 0;
1767
- const isUsedAsDep = allDependencyTargets.has(`${step.id}.${field.id}`);
1768
- const canMoveUp = index > 0 && isFieldMoveValid(step.fields, step.id, index, index - 1).valid;
1769
- const canMoveDown = index < step.fields.length - 1 && isFieldMoveValid(step.fields, step.id, index, index + 1).valid;
1770
- const intraStepDeps = (field.dependsOn ?? []).filter((p) => p.startsWith(`${step.id}.`)).map((p) => {
1771
- const fieldId = p.slice(step.id.length + 1);
1772
- return step.fields.find((f) => f.id === fieldId)?.label ?? fieldId;
1773
- });
1774
- const intraStepDependents = step.fields.filter(
1775
- (f) => f.id !== field.id && (f.dependsOn ?? []).includes(`${step.id}.${field.id}`)
1776
- );
1777
- return /* @__PURE__ */ jsxs(
1778
- "div",
1779
- {
1780
- style: { ...styles5.card, ...isSelected ? styles5.cardSelected : {} },
1781
- onClick: () => selectField(field.id),
1782
- 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 })
1787
- ] }),
1788
- /* @__PURE__ */ jsxs("div", { style: styles5.cardRight, children: [
1789
- /* @__PURE__ */ jsx(
1790
- "select",
1791
- {
1792
- style: styles5.typeSelect,
1793
- value: field.type,
1794
- 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))
1797
- }
1798
- ),
1799
- index > 0 && /* @__PURE__ */ jsx(
1800
- "button",
1801
- {
1802
- style: { ...styles5.iconBtn, ...canMoveUp ? {} : styles5.iconBtnBlocked },
1803
- title: canMoveUp ? "Move up" : "Can't move \u2014 dependency order required",
1804
- onClick: (e) => {
1805
- e.stopPropagation();
1806
- tryMove(index, index - 1);
1807
- },
1808
- children: "\u2191"
1809
- }
1810
- ),
1811
- index < step.fields.length - 1 && /* @__PURE__ */ jsx(
1812
- "button",
1813
- {
1814
- style: { ...styles5.iconBtn, ...canMoveDown ? {} : styles5.iconBtnBlocked },
1815
- title: canMoveDown ? "Move down" : "Can't move \u2014 dependency order required",
1816
- onClick: (e) => {
1817
- e.stopPropagation();
1818
- tryMove(index, index + 1);
1819
- },
1820
- children: "\u2193"
1821
- }
1822
- ),
1823
- /* @__PURE__ */ jsx(
1824
- "button",
1825
- {
1826
- style: { ...styles5.iconBtn, color: "var(--wp-danger)" },
1827
- title: "Remove field",
1828
- onClick: (e) => {
1829
- e.stopPropagation();
1830
- removeField(step.id, field.id);
1831
- },
1832
- children: "\u2715"
1833
- }
1834
- )
1835
- ] })
1836
- ] }),
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: [
1842
- "depends on ",
1843
- field.dependsOn.length
1844
- ] }),
1845
- isUsedAsDep && /* @__PURE__ */ jsx("span", { style: styles5.badgeUsed, children: "\u2190 dependency" })
1846
- ] }),
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))
1850
- ] }),
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))
1854
- ] })
1855
- ]
1856
- },
1857
- field.id
1858
- );
1859
- })
2394
+ /* @__PURE__ */ jsxs("div", { style: styles6.list, children: [
2395
+ step.fields.length === 0 && /* @__PURE__ */ jsx("div", { style: styles6.emptyFields, children: 'No fields yet. Click "Add field" to start.' }),
2396
+ /* @__PURE__ */ jsx(
2397
+ SortableList,
2398
+ {
2399
+ items: fieldIds,
2400
+ disabled: readOnly,
2401
+ onReorder: tryMove,
2402
+ renderOverlay,
2403
+ renderItem: (id, index) => {
2404
+ const field = step.fields[index];
2405
+ const isSelected = field.id === selectedFieldId;
2406
+ const isRequired = field.validation?.some((v) => v.type === "required") ?? false;
2407
+ const hasCondition = !!field.visibleWhen;
2408
+ const hasDeps = (field.dependsOn?.length ?? 0) > 0;
2409
+ const enumDef = field.externalEnumId ? externalEnums.find((e) => e.id === field.externalEnumId) : void 0;
2410
+ const isUsedAsDep = allDependencyTargets.has(`${step.id}.${field.id}`);
2411
+ const canMoveUp = index > 0 && isFieldMoveValid(step.fields, step.id, index, index - 1).valid;
2412
+ const canMoveDown = index < step.fields.length - 1 && isFieldMoveValid(step.fields, step.id, index, index + 1).valid;
2413
+ const intraStepDeps = (field.dependsOn ?? []).filter((p) => p.startsWith(`${step.id}.`)).map((p) => {
2414
+ const fieldId = p.slice(step.id.length + 1);
2415
+ return step.fields.find((f) => f.id === fieldId)?.label ?? fieldId;
2416
+ });
2417
+ const intraStepDependents = step.fields.filter(
2418
+ (f) => f.id !== field.id && (f.dependsOn ?? []).includes(`${step.id}.${field.id}`)
2419
+ );
2420
+ return /* @__PURE__ */ jsx(SortableItem, { id: field.id, disabled: readOnly, children: ({ handleProps }) => /* @__PURE__ */ jsxs(
2421
+ "div",
2422
+ {
2423
+ style: { ...styles6.card, ...isSelected ? styles6.cardSelected : {} },
2424
+ onClick: () => selectField(field.id),
2425
+ children: [
2426
+ /* @__PURE__ */ jsxs("div", { style: styles6.cardTop, children: [
2427
+ /* @__PURE__ */ jsxs("div", { style: styles6.cardLeft, children: [
2428
+ !readOnly && /* @__PURE__ */ jsx(DragHandle, { handleProps }),
2429
+ (() => {
2430
+ const ct = appCustomTypes.find((c) => c.id === field.type);
2431
+ return /* @__PURE__ */ jsx("span", { style: { ...styles6.typeBadge, ...ct ? styles6.typeBadgeCustom : {} }, children: ct ? `${ct.icon ? ct.icon + " " : ""}${ct.label}` : field.type });
2432
+ })(),
2433
+ /* @__PURE__ */ jsx("span", { style: styles6.fieldLabel, children: field.label })
2434
+ ] }),
2435
+ /* @__PURE__ */ jsxs("div", { style: styles6.cardRight, children: [
2436
+ !readOnly && /* @__PURE__ */ jsxs(
2437
+ "select",
2438
+ {
2439
+ style: styles6.typeSelect,
2440
+ value: field.type,
2441
+ onClick: (e) => e.stopPropagation(),
2442
+ onChange: (e) => {
2443
+ const newType = e.target.value;
2444
+ const customType = appCustomTypes.find((ct) => ct.id === newType);
2445
+ updateField(step.id, field.id, {
2446
+ type: newType,
2447
+ ...customType?.defaultValidation ? { validation: customType.defaultValidation } : {}
2448
+ });
2449
+ },
2450
+ children: [
2451
+ FIELD_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t)),
2452
+ appCustomTypes.length > 0 && /* @__PURE__ */ jsx("optgroup", { label: "Custom", children: appCustomTypes.map((ct) => /* @__PURE__ */ jsxs("option", { value: ct.id, children: [
2453
+ ct.icon ? `${ct.icon} ` : "",
2454
+ ct.label
2455
+ ] }, ct.id)) })
2456
+ ]
2457
+ }
2458
+ ),
2459
+ !readOnly && index > 0 && /* @__PURE__ */ jsx(
2460
+ "button",
2461
+ {
2462
+ style: { ...styles6.iconBtn, ...canMoveUp ? {} : styles6.iconBtnBlocked },
2463
+ title: canMoveUp ? "Move up" : "Can't move \u2014 dependency order required",
2464
+ onClick: (e) => {
2465
+ e.stopPropagation();
2466
+ tryMove(index, index - 1);
2467
+ },
2468
+ children: "\u2191"
2469
+ }
2470
+ ),
2471
+ !readOnly && index < step.fields.length - 1 && /* @__PURE__ */ jsx(
2472
+ "button",
2473
+ {
2474
+ style: { ...styles6.iconBtn, ...canMoveDown ? {} : styles6.iconBtnBlocked },
2475
+ title: canMoveDown ? "Move down" : "Can't move \u2014 dependency order required",
2476
+ onClick: (e) => {
2477
+ e.stopPropagation();
2478
+ tryMove(index, index + 1);
2479
+ },
2480
+ children: "\u2193"
2481
+ }
2482
+ ),
2483
+ !readOnly && /* @__PURE__ */ jsx(
2484
+ "button",
2485
+ {
2486
+ style: styles6.iconBtn,
2487
+ title: "Duplicate field",
2488
+ onClick: (e) => {
2489
+ e.stopPropagation();
2490
+ duplicateField(step.id, field.id);
2491
+ },
2492
+ children: "\u29C9"
2493
+ }
2494
+ ),
2495
+ !readOnly && /* @__PURE__ */ jsx(
2496
+ "button",
2497
+ {
2498
+ style: { ...styles6.iconBtn, color: "var(--wp-danger)" },
2499
+ title: "Remove field",
2500
+ onClick: (e) => {
2501
+ e.stopPropagation();
2502
+ removeField(step.id, field.id);
2503
+ },
2504
+ children: "\u2715"
2505
+ }
2506
+ )
2507
+ ] })
2508
+ ] }),
2509
+ /* @__PURE__ */ jsxs("div", { style: styles6.badges, children: [
2510
+ !isRequired && /* @__PURE__ */ jsx("span", { style: styles6.badgeOptional, children: "optional" }),
2511
+ isRequired && /* @__PURE__ */ jsx("span", { style: styles6.badgeRequired, children: "required" }),
2512
+ hasCondition && /* @__PURE__ */ jsx("span", { style: styles6.badgeCondition, children: "conditional" }),
2513
+ hasDeps && /* @__PURE__ */ jsxs("span", { style: styles6.badgeDep, children: [
2514
+ "depends on ",
2515
+ field.dependsOn.length
2516
+ ] }),
2517
+ isUsedAsDep && /* @__PURE__ */ jsx("span", { style: styles6.badgeUsed, children: "\u2190 dependency" }),
2518
+ field.externalEnumId && /* @__PURE__ */ jsxs("span", { style: styles6.badgeEnum, children: [
2519
+ "\u229E ",
2520
+ enumDef ? enumDef.label : field.externalEnumId
2521
+ ] })
2522
+ ] }),
2523
+ intraStepDeps.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.depRow, children: [
2524
+ /* @__PURE__ */ jsx("span", { style: styles6.depLabel, children: "needs:" }),
2525
+ intraStepDeps.map((label) => /* @__PURE__ */ jsx("span", { style: styles6.depBadge, children: label }, label))
2526
+ ] }),
2527
+ intraStepDependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.depRow, children: [
2528
+ /* @__PURE__ */ jsx("span", { style: styles6.depLabelUsed, children: "used by:" }),
2529
+ intraStepDependents.map((f) => /* @__PURE__ */ jsx("span", { style: styles6.depBadgeUsed, children: f.label }, f.id))
2530
+ ] })
2531
+ ]
2532
+ }
2533
+ ) }, field.id);
2534
+ }
2535
+ }
2536
+ )
1860
2537
  ] })
1861
2538
  ] });
1862
2539
  }
1863
- var styles5 = {
2540
+ var styles6 = {
1864
2541
  container: { display: "flex", flexDirection: "column", height: "100%" },
1865
2542
  empty: {
1866
2543
  display: "flex",
@@ -1917,6 +2594,12 @@ var styles5 = {
1917
2594
  gap: 6
1918
2595
  },
1919
2596
  cardSelected: { background: "var(--wp-primary-muted)", border: "1px solid var(--wp-primary-border)" },
2597
+ cardDragOverlay: {
2598
+ boxShadow: "0 4px 16px rgba(0,0,0,0.25)",
2599
+ border: "1px solid var(--wp-primary-border)",
2600
+ background: "var(--wp-surface)",
2601
+ opacity: 0.95
2602
+ },
1920
2603
  cardTop: { display: "flex", alignItems: "center", justifyContent: "space-between" },
1921
2604
  cardLeft: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 },
1922
2605
  cardRight: { display: "flex", alignItems: "center", gap: 4, flexShrink: 0 },
@@ -1929,6 +2612,10 @@ var styles5 = {
1929
2612
  fontWeight: 600,
1930
2613
  flexShrink: 0
1931
2614
  },
2615
+ typeBadgeCustom: {
2616
+ background: "var(--wp-success-bg)",
2617
+ color: "var(--wp-success)"
2618
+ },
1932
2619
  fieldLabel: {
1933
2620
  fontSize: 13,
1934
2621
  fontWeight: 600,
@@ -2003,6 +2690,14 @@ var styles5 = {
2003
2690
  borderRadius: 3,
2004
2691
  textTransform: "uppercase"
2005
2692
  },
2693
+ badgeEnum: {
2694
+ fontSize: 9,
2695
+ fontWeight: 600,
2696
+ padding: "1px 6px",
2697
+ background: "var(--wp-info-bg)",
2698
+ color: "var(--wp-info-text)",
2699
+ borderRadius: 3
2700
+ },
2006
2701
  depRow: { display: "flex", alignItems: "center", flexWrap: "wrap", gap: 4 },
2007
2702
  depLabel: { fontSize: 10, fontWeight: 600, color: "var(--wp-text-muted)", textTransform: "uppercase" },
2008
2703
  depLabelUsed: { fontSize: 10, fontWeight: 600, color: "var(--wp-success)", textTransform: "uppercase" },
@@ -2023,12 +2718,28 @@ var styles5 = {
2023
2718
  borderRadius: 4
2024
2719
  }
2025
2720
  };
2026
- function PreviewPanel({ store, schema }) {
2721
+ function PreviewPanel({ store, schema, externalEnums }) {
2027
2722
  const [done, setDone] = useState(false);
2028
- const { tree, currentStep, progress } = useWaypoint(store);
2723
+ const { tree, currentStep, progress } = useWaypoint(store, externalEnums);
2029
2724
  const stepId = currentStep?.definition.id ?? "";
2030
2725
  const { fields, stepData, setFieldValue } = useWaypointStep(store, stepId);
2031
2726
  const [errors, setErrors] = useState({});
2727
+ const extVarDefs = schema.externalVariables ?? [];
2728
+ const [mockVars, setMockVars] = useState(() => {
2729
+ const initial = {};
2730
+ for (const v of extVarDefs) {
2731
+ initial[v.id] = v.type === "boolean" ? false : v.type === "number" ? 0 : "";
2732
+ }
2733
+ return initial;
2734
+ });
2735
+ useEffect(() => {
2736
+ for (const [varId, value] of Object.entries(mockVars)) {
2737
+ store.getState().setExternalVar(varId, value);
2738
+ }
2739
+ }, [mockVars]);
2740
+ function handleMockVarChange(varId, value) {
2741
+ setMockVars((prev) => ({ ...prev, [varId]: value }));
2742
+ }
2032
2743
  function handleNext() {
2033
2744
  const newErrors = {};
2034
2745
  for (const field of fields) {
@@ -2048,8 +2759,11 @@ function PreviewPanel({ store, schema }) {
2048
2759
  setErrors({});
2049
2760
  const oldIds = tree.steps.map((s) => s.definition.id).join(",");
2050
2761
  store.getState().setStepData(stepId, stepData);
2762
+ if ((store.getState().skippedSteps ?? []).includes(stepId)) {
2763
+ store.getState().unskipStep(stepId);
2764
+ }
2051
2765
  const newData = store.getState().data;
2052
- const newTree = resolveTree(schema, newData, {});
2766
+ const newTree = resolveTree(schema, newData, store.getState().externalVars, externalEnums);
2053
2767
  const newIds = newTree.steps.map((s) => s.definition.id).join(",");
2054
2768
  if (oldIds !== newIds) {
2055
2769
  store.getState().truncateHistoryAt(stepId);
@@ -2067,6 +2781,9 @@ function PreviewPanel({ store, schema }) {
2067
2781
  }
2068
2782
  function handleRestart() {
2069
2783
  store.getState().init(schema);
2784
+ for (const [varId, value] of Object.entries(mockVars)) {
2785
+ store.getState().setExternalVar(varId, value);
2786
+ }
2070
2787
  setDone(false);
2071
2788
  setErrors({});
2072
2789
  }
@@ -2074,77 +2791,124 @@ function PreviewPanel({ store, schema }) {
2074
2791
  (s) => s.definition.id === currentStep?.definition.id
2075
2792
  );
2076
2793
  if (done) {
2077
- return /* @__PURE__ */ jsxs("div", { style: styles6.panel, children: [
2078
- /* @__PURE__ */ jsx("div", { style: styles6.leftCol, children: /* @__PURE__ */ jsx(
2794
+ return /* @__PURE__ */ jsxs("div", { style: styles7.panel, children: [
2795
+ /* @__PURE__ */ jsxs("div", { style: styles7.leftCol, children: [
2796
+ /* @__PURE__ */ jsx(
2797
+ StepList,
2798
+ {
2799
+ tree,
2800
+ currentIdx,
2801
+ onSelect: (id) => store.getState().setCurrentStep(id),
2802
+ skippedStepIds: store.getState().skippedSteps
2803
+ }
2804
+ ),
2805
+ extVarDefs.length > 0 && /* @__PURE__ */ jsx(
2806
+ MockVarPanel,
2807
+ {
2808
+ extVarDefs,
2809
+ mockVars,
2810
+ onChange: handleMockVarChange
2811
+ }
2812
+ )
2813
+ ] }),
2814
+ /* @__PURE__ */ jsx("div", { style: styles7.divider }),
2815
+ /* @__PURE__ */ jsx("div", { style: styles7.rightCol, children: /* @__PURE__ */ jsxs("div", { style: styles7.doneScreen, children: [
2816
+ /* @__PURE__ */ jsx("div", { style: styles7.doneIcon, children: "\u2713" }),
2817
+ /* @__PURE__ */ jsx("div", { style: styles7.doneTitle, children: "Parcours termin\xE9 !" }),
2818
+ /* @__PURE__ */ jsx("p", { style: styles7.doneText, children: "Toutes les \xE9tapes ont \xE9t\xE9 compl\xE9t\xE9es avec succ\xE8s." }),
2819
+ /* @__PURE__ */ jsx("button", { style: styles7.primaryBtn, onClick: handleRestart, children: "Recommencer" })
2820
+ ] }) })
2821
+ ] });
2822
+ }
2823
+ return /* @__PURE__ */ jsxs("div", { style: styles7.panel, children: [
2824
+ /* @__PURE__ */ jsx(DevPanel, { store }),
2825
+ /* @__PURE__ */ jsxs("div", { style: styles7.leftCol, children: [
2826
+ /* @__PURE__ */ jsx(
2079
2827
  StepList,
2080
2828
  {
2081
2829
  tree,
2082
2830
  currentIdx,
2083
2831
  onSelect: (id) => store.getState().setCurrentStep(id)
2084
2832
  }
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(
2110
- FieldRenderer,
2833
+ ),
2834
+ extVarDefs.length > 0 && /* @__PURE__ */ jsx(
2835
+ MockVarPanel,
2111
2836
  {
2112
- field,
2113
- value: stepData[field.definition.id],
2114
- error: errors[field.definition.id],
2115
- onChange: (val) => {
2116
- setFieldValue(field.definition.id, val);
2117
- if (errors[field.definition.id]) {
2118
- setErrors((prev) => {
2119
- const next = { ...prev };
2120
- delete next[field.definition.id];
2121
- return next;
2122
- });
2837
+ extVarDefs,
2838
+ mockVars,
2839
+ onChange: handleMockVarChange
2840
+ }
2841
+ )
2842
+ ] }),
2843
+ /* @__PURE__ */ jsx("div", { style: styles7.divider }),
2844
+ /* @__PURE__ */ jsx("div", { style: styles7.rightCol, children: /* @__PURE__ */ jsxs("div", { style: styles7.stepRenderer, children: [
2845
+ /* @__PURE__ */ jsx("div", { style: styles7.progressTrack, children: /* @__PURE__ */ jsx("div", { style: { ...styles7.progressFill, width: `${progress}%` } }) }),
2846
+ /* @__PURE__ */ jsx("h2", { style: styles7.stepTitle, children: currentStep?.definition.title ?? "" }),
2847
+ /* @__PURE__ */ jsx("div", { style: styles7.fieldsContainer, children: fields.map((field) => {
2848
+ const storedVal = stepData[field.definition.id];
2849
+ const displayVal = storedVal !== void 0 && storedVal !== null && storedVal !== "" ? storedVal : field.resolvedDefaultValue ?? field.definition.defaultValue ?? void 0;
2850
+ return /* @__PURE__ */ jsx(
2851
+ FieldRenderer,
2852
+ {
2853
+ field,
2854
+ value: displayVal,
2855
+ error: errors[field.definition.id],
2856
+ onChange: (val) => {
2857
+ setFieldValue(field.definition.id, val);
2858
+ if (errors[field.definition.id]) {
2859
+ setErrors((prev) => {
2860
+ const next = { ...prev };
2861
+ delete next[field.definition.id];
2862
+ return next;
2863
+ });
2864
+ }
2123
2865
  }
2124
- }
2125
- },
2126
- field.definition.id
2127
- )) }),
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" })
2866
+ },
2867
+ field.definition.id
2868
+ );
2869
+ }) }),
2870
+ /* @__PURE__ */ jsxs("div", { style: styles7.navRow, children: [
2871
+ currentIdx > 0 && /* @__PURE__ */ jsx("button", { style: styles7.secondaryBtn, onClick: handlePrev, children: "\u2190 Pr\xE9c\xE9dent" }),
2872
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginLeft: "auto" }, children: [
2873
+ currentStep?.definition.skippable && /* @__PURE__ */ jsx(
2874
+ "button",
2875
+ {
2876
+ style: styles7.secondaryBtn,
2877
+ onClick: () => {
2878
+ setErrors({});
2879
+ store.getState().skipStep(stepId);
2880
+ const next = getNextStep(tree.steps, stepId);
2881
+ if (next) {
2882
+ store.getState().setCurrentStep(next.definition.id);
2883
+ } else {
2884
+ setDone(true);
2885
+ }
2886
+ },
2887
+ children: "Passer"
2888
+ }
2889
+ ),
2890
+ /* @__PURE__ */ jsx("button", { style: styles7.primaryBtn, onClick: handleNext, children: getNextStep(tree.steps, stepId) ? "Continuer \u2192" : "Terminer \u2713" })
2891
+ ] })
2131
2892
  ] })
2132
2893
  ] }) })
2133
2894
  ] });
2134
2895
  }
2135
- function StepList({ tree, currentIdx, onSelect }) {
2896
+ function StepList({ tree, currentIdx, onSelect, skippedStepIds }) {
2897
+ const skipped = new Set(skippedStepIds ?? []);
2136
2898
  const allSteps = [
2137
2899
  ...tree.steps.map((s) => ({ ...s, hidden: false })),
2138
2900
  ...tree.hiddenSteps.map((s) => ({ ...s, hidden: true }))
2139
2901
  ];
2140
- return /* @__PURE__ */ jsxs("div", { style: styles6.stepList, children: [
2141
- /* @__PURE__ */ jsx("div", { style: styles6.stepListTitle, children: "\xC9tapes" }),
2902
+ return /* @__PURE__ */ jsxs("div", { style: styles7.stepList, children: [
2903
+ /* @__PURE__ */ jsx("div", { style: styles7.stepListTitle, children: "\xC9tapes" }),
2142
2904
  allSteps.map((step) => {
2143
- const isVisible = !step.hidden;
2905
+ const isStepVisible = !step.hidden;
2144
2906
  const visIdx = tree.steps.findIndex((s) => s.definition.id === step.definition.id);
2907
+ const isSkipped = skipped.has(step.definition.id);
2145
2908
  let status = "hidden";
2146
- if (isVisible) {
2147
- if (visIdx < currentIdx) status = "done";
2909
+ if (isStepVisible) {
2910
+ if (isSkipped && visIdx < currentIdx) status = "skipped";
2911
+ else if (visIdx < currentIdx) status = "done";
2148
2912
  else if (visIdx === currentIdx) status = "current";
2149
2913
  else status = "upcoming";
2150
2914
  }
@@ -2152,23 +2916,26 @@ function StepList({ tree, currentIdx, onSelect }) {
2152
2916
  "div",
2153
2917
  {
2154
2918
  style: {
2155
- ...styles6.stepItem,
2156
- ...status === "current" ? styles6.stepItemCurrent : {},
2157
- ...status === "hidden" ? styles6.stepItemHidden : {},
2158
- cursor: status === "done" ? "pointer" : "default"
2919
+ ...styles7.stepItem,
2920
+ ...status === "current" ? styles7.stepItemCurrent : {},
2921
+ ...status === "hidden" ? styles7.stepItemHidden : {},
2922
+ ...status === "skipped" ? { opacity: 0.6 } : {},
2923
+ cursor: status === "done" || status === "skipped" ? "pointer" : "default"
2159
2924
  },
2160
2925
  onClick: () => {
2161
- if (status === "done") onSelect(step.definition.id);
2926
+ if (status === "done" || status === "skipped") onSelect(step.definition.id);
2162
2927
  },
2163
2928
  children: [
2164
- /* @__PURE__ */ jsxs("span", { style: styles6.stepStatus, children: [
2929
+ /* @__PURE__ */ jsxs("span", { style: styles7.stepStatus, children: [
2165
2930
  status === "done" && "\u2713",
2931
+ status === "skipped" && "\u23ED",
2166
2932
  status === "current" && "\u2192",
2167
2933
  status === "upcoming" && "\u25CB",
2168
2934
  status === "hidden" && "\u2013"
2169
2935
  ] }),
2170
- /* @__PURE__ */ jsx("span", { style: styles6.stepName, children: step.definition.title }),
2171
- status === "hidden" && /* @__PURE__ */ jsx("span", { style: styles6.hiddenBadge, children: "hidden" })
2936
+ /* @__PURE__ */ jsx("span", { style: styles7.stepName, children: step.definition.title }),
2937
+ status === "hidden" && /* @__PURE__ */ jsx("span", { style: styles7.hiddenBadge, children: "hidden" }),
2938
+ status === "skipped" && /* @__PURE__ */ jsx("span", { style: { ...styles7.hiddenBadge, color: "var(--wp-warning)", background: "var(--wp-warning-bg)" }, children: "skipped" })
2172
2939
  ]
2173
2940
  },
2174
2941
  step.definition.id
@@ -2176,13 +2943,51 @@ function StepList({ tree, currentIdx, onSelect }) {
2176
2943
  })
2177
2944
  ] });
2178
2945
  }
2946
+ function MockVarPanel({ extVarDefs, mockVars, onChange }) {
2947
+ return /* @__PURE__ */ jsxs("div", { style: styles7.mockPanel, children: [
2948
+ /* @__PURE__ */ jsxs("div", { style: styles7.mockPanelTitle, children: [
2949
+ /* @__PURE__ */ jsx("span", { children: "\u26A1 External Variables" }),
2950
+ /* @__PURE__ */ jsx("span", { style: styles7.mockPanelHint, children: "mock values" })
2951
+ ] }),
2952
+ extVarDefs.map((v) => /* @__PURE__ */ jsxs("div", { style: styles7.mockVarRow, children: [
2953
+ /* @__PURE__ */ jsxs("div", { style: styles7.mockVarLabel, children: [
2954
+ /* @__PURE__ */ jsxs("span", { style: styles7.mockVarId, children: [
2955
+ "$",
2956
+ `ext.${v.id}`
2957
+ ] }),
2958
+ v.blocking && /* @__PURE__ */ jsx("span", { style: styles7.mockBlockingBadge, children: "!" })
2959
+ ] }),
2960
+ v.type === "boolean" ? /* @__PURE__ */ jsxs("label", { style: styles7.mockCheckRow, children: [
2961
+ /* @__PURE__ */ jsx(
2962
+ "input",
2963
+ {
2964
+ type: "checkbox",
2965
+ checked: Boolean(mockVars[v.id]),
2966
+ onChange: (e) => onChange(v.id, e.target.checked)
2967
+ }
2968
+ ),
2969
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "var(--wp-text-muted)" }, children: String(mockVars[v.id]) })
2970
+ ] }) : /* @__PURE__ */ jsx(
2971
+ "input",
2972
+ {
2973
+ style: styles7.mockInput,
2974
+ type: v.type === "number" ? "number" : "text",
2975
+ value: String(mockVars[v.id] ?? ""),
2976
+ placeholder: v.label,
2977
+ onChange: (e) => onChange(v.id, v.type === "number" ? Number(e.target.value) : e.target.value)
2978
+ }
2979
+ )
2980
+ ] }, v.id))
2981
+ ] });
2982
+ }
2179
2983
  function FieldRenderer({ field, value, error, onChange }) {
2180
2984
  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: [
2985
+ const options = field.resolvedOptions ?? definition.options ?? [];
2986
+ const inputStyle2 = { ...styles7.input, ...error ? styles7.inputError : {} };
2987
+ return /* @__PURE__ */ jsxs("div", { style: styles7.fieldGroup, children: [
2988
+ /* @__PURE__ */ jsxs("label", { style: styles7.label, children: [
2184
2989
  definition.label,
2185
- definition.validation?.some((r) => r.type === "required") && /* @__PURE__ */ jsx("span", { style: styles6.required, children: " *" })
2990
+ definition.validation?.some((r) => r.type === "required") && /* @__PURE__ */ jsx("span", { style: styles7.required, children: " *" })
2186
2991
  ] }),
2187
2992
  (definition.type === "text" || definition.type === "email" || definition.type === "tel" || definition.type === "password" || definition.type === "url" || definition.type === "number" || definition.type === "date") && /* @__PURE__ */ jsx(
2188
2993
  "input",
@@ -2211,7 +3016,7 @@ function FieldRenderer({ field, value, error, onChange }) {
2211
3016
  onChange: (e) => onChange(e.target.value),
2212
3017
  children: [
2213
3018
  /* @__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)))
3019
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
2215
3020
  ]
2216
3021
  }
2217
3022
  ),
@@ -2225,10 +3030,10 @@ function FieldRenderer({ field, value, error, onChange }) {
2225
3030
  const selected = Array.from(e.target.selectedOptions).map((o) => o.value);
2226
3031
  onChange(selected);
2227
3032
  },
2228
- children: definition.options?.map((opt) => /* @__PURE__ */ jsx("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
3033
+ children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
2229
3034
  }
2230
3035
  ),
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: [
3036
+ 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
3037
  /* @__PURE__ */ jsx(
2233
3038
  "input",
2234
3039
  {
@@ -2252,10 +3057,10 @@ function FieldRenderer({ field, value, error, onChange }) {
2252
3057
  ),
2253
3058
  definition.placeholder ?? definition.label
2254
3059
  ] }),
2255
- error && /* @__PURE__ */ jsx("div", { style: styles6.errorMsg, children: error })
3060
+ error && /* @__PURE__ */ jsx("div", { style: styles7.errorMsg, children: error })
2256
3061
  ] });
2257
3062
  }
2258
- var styles6 = {
3063
+ var styles7 = {
2259
3064
  panel: {
2260
3065
  display: "flex",
2261
3066
  flex: 1,
@@ -2424,6 +3229,80 @@ var styles6 = {
2424
3229
  letterSpacing: "0.3px",
2425
3230
  flexShrink: 0
2426
3231
  },
3232
+ // Mock var panel
3233
+ mockPanel: {
3234
+ borderTop: "1px solid var(--wp-border)",
3235
+ padding: "12px",
3236
+ display: "flex",
3237
+ flexDirection: "column",
3238
+ gap: 8
3239
+ },
3240
+ mockPanelTitle: {
3241
+ display: "flex",
3242
+ alignItems: "center",
3243
+ justifyContent: "space-between",
3244
+ fontSize: 11,
3245
+ fontWeight: 700,
3246
+ color: "var(--wp-text-muted)",
3247
+ textTransform: "uppercase",
3248
+ letterSpacing: "0.5px",
3249
+ marginBottom: 4
3250
+ },
3251
+ mockPanelHint: {
3252
+ fontSize: 9,
3253
+ fontWeight: 500,
3254
+ color: "var(--wp-warning)",
3255
+ textTransform: "none",
3256
+ background: "var(--wp-warning-bg)",
3257
+ padding: "1px 5px",
3258
+ borderRadius: 4
3259
+ },
3260
+ mockVarRow: {
3261
+ display: "flex",
3262
+ flexDirection: "column",
3263
+ gap: 3
3264
+ },
3265
+ mockVarLabel: {
3266
+ display: "flex",
3267
+ alignItems: "center",
3268
+ gap: 4
3269
+ },
3270
+ mockVarId: {
3271
+ fontSize: 10,
3272
+ fontFamily: "monospace",
3273
+ fontWeight: 600,
3274
+ color: "var(--wp-text-secondary)"
3275
+ },
3276
+ mockBlockingBadge: {
3277
+ fontSize: 9,
3278
+ fontWeight: 700,
3279
+ width: 13,
3280
+ height: 13,
3281
+ borderRadius: "50%",
3282
+ background: "var(--wp-danger-bg-strong)",
3283
+ color: "var(--wp-danger)",
3284
+ display: "flex",
3285
+ alignItems: "center",
3286
+ justifyContent: "center",
3287
+ flexShrink: 0
3288
+ },
3289
+ mockInput: {
3290
+ fontSize: 11,
3291
+ padding: "4px 6px",
3292
+ border: "1px solid var(--wp-border-muted)",
3293
+ borderRadius: "var(--wp-radius)",
3294
+ background: "var(--wp-canvas)",
3295
+ color: "var(--wp-text)",
3296
+ outline: "none",
3297
+ width: "100%",
3298
+ boxSizing: "border-box"
3299
+ },
3300
+ mockCheckRow: {
3301
+ display: "flex",
3302
+ alignItems: "center",
3303
+ gap: 6,
3304
+ cursor: "pointer"
3305
+ },
2427
3306
  // Done screen
2428
3307
  doneScreen: {
2429
3308
  display: "flex",
@@ -2460,83 +3339,100 @@ var styles6 = {
2460
3339
  };
2461
3340
  function StepEditor() {
2462
3341
  const { schema, selectedStepId, updateStep, setStepCondition } = useBuilderStore();
3342
+ const readOnly = useBuilderReadOnly();
2463
3343
  const [conditionModalOpen, setConditionModalOpen] = useState(false);
2464
3344
  const step = schema.steps.find((s) => s.id === selectedStepId);
2465
3345
  if (!step) {
2466
- return /* @__PURE__ */ jsx("div", { style: styles7.empty, children: "Select a step to configure its properties." });
3346
+ return /* @__PURE__ */ jsx("div", { style: styles8.empty, children: "Select a step to configure its properties." });
2467
3347
  }
2468
3348
  const hasCondition = !!step.visibleWhen;
2469
3349
  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: [
3350
+ return /* @__PURE__ */ jsxs("div", { style: styles8.container, children: [
3351
+ /* @__PURE__ */ jsxs("div", { style: styles8.header, children: [
3352
+ /* @__PURE__ */ jsx("div", { style: styles8.headerTitle, children: "Step Config" }),
3353
+ /* @__PURE__ */ jsxs("div", { style: styles8.stepId, children: [
2474
3354
  "id: ",
2475
3355
  step.id
2476
3356
  ] })
2477
3357
  ] }),
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" }),
3358
+ /* @__PURE__ */ jsxs("div", { style: styles8.body, children: [
3359
+ /* @__PURE__ */ jsxs("div", { style: styles8.group, children: [
3360
+ /* @__PURE__ */ jsx("label", { style: styles8.label, children: "Title" }),
2481
3361
  /* @__PURE__ */ jsx(
2482
3362
  "input",
2483
3363
  {
2484
- style: styles7.input,
3364
+ style: styles8.input,
2485
3365
  value: step.title,
2486
3366
  placeholder: "Step title",
2487
- onChange: (e) => updateStep(step.id, { title: e.target.value })
3367
+ readOnly,
3368
+ onChange: readOnly ? void 0 : (e) => updateStep(step.id, { title: e.target.value })
2488
3369
  }
2489
3370
  )
2490
3371
  ] }),
2491
- /* @__PURE__ */ jsxs("div", { style: styles7.group, children: [
2492
- /* @__PURE__ */ jsx("label", { style: styles7.label, children: "URL" }),
3372
+ /* @__PURE__ */ jsxs("div", { style: styles8.group, children: [
3373
+ /* @__PURE__ */ jsx("label", { style: styles8.label, children: "URL" }),
2493
3374
  /* @__PURE__ */ jsx(
2494
3375
  "input",
2495
3376
  {
2496
- style: styles7.input,
3377
+ style: styles8.input,
2497
3378
  value: step.url,
2498
3379
  placeholder: "/onboarding/step-name",
2499
- onChange: (e) => updateStep(step.id, { url: e.target.value })
3380
+ readOnly,
3381
+ onChange: readOnly ? void 0 : (e) => updateStep(step.id, { url: e.target.value })
2500
3382
  }
2501
3383
  ),
2502
- /* @__PURE__ */ jsxs("div", { style: styles7.hint, children: [
3384
+ /* @__PURE__ */ jsxs("div", { style: styles8.hint, children: [
2503
3385
  "Supports ",
2504
3386
  "{{PARAM}}",
2505
3387
  " placeholders"
2506
3388
  ] })
2507
3389
  ] }),
2508
- /* @__PURE__ */ jsxs("div", { style: styles7.checkRow, children: [
3390
+ /* @__PURE__ */ jsxs("div", { style: styles8.checkRow, children: [
2509
3391
  /* @__PURE__ */ jsx(
2510
3392
  "input",
2511
3393
  {
2512
3394
  type: "checkbox",
2513
3395
  id: `resume-${step.id}`,
2514
3396
  checked: !!step.enableResumeFromHere,
2515
- onChange: (e) => updateStep(step.id, { enableResumeFromHere: e.target.checked || void 0 })
3397
+ disabled: readOnly,
3398
+ onChange: readOnly ? void 0 : (e) => updateStep(step.id, { enableResumeFromHere: e.target.checked || void 0 })
2516
3399
  }
2517
3400
  ),
2518
- /* @__PURE__ */ jsx("label", { htmlFor: `resume-${step.id}`, style: styles7.checkLabel, children: "Resume from this step" })
3401
+ /* @__PURE__ */ jsx("label", { htmlFor: `resume-${step.id}`, style: styles8.checkLabel, children: "Resume from this step" })
2519
3402
  ] }),
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: [
3403
+ /* @__PURE__ */ jsxs("div", { style: styles8.checkRow, children: [
3404
+ /* @__PURE__ */ jsx(
3405
+ "input",
3406
+ {
3407
+ type: "checkbox",
3408
+ id: `skippable-${step.id}`,
3409
+ checked: !!step.skippable,
3410
+ disabled: readOnly,
3411
+ onChange: readOnly ? void 0 : (e) => updateStep(step.id, { skippable: e.target.checked || void 0 })
3412
+ }
3413
+ ),
3414
+ /* @__PURE__ */ jsx("label", { htmlFor: `skippable-${step.id}`, style: styles8.checkLabel, children: "Skippable (user can bypass this step)" })
3415
+ ] }),
3416
+ /* @__PURE__ */ jsx("div", { style: styles8.divider }),
3417
+ /* @__PURE__ */ jsxs("div", { style: styles8.conditionRow, children: [
3418
+ /* @__PURE__ */ jsxs("div", { style: styles8.conditionInfo, children: [
3419
+ /* @__PURE__ */ jsx("div", { style: styles8.label, children: "Visibility condition" }),
3420
+ hasCondition ? /* @__PURE__ */ jsxs("div", { style: styles8.conditionSummary, children: [
3421
+ /* @__PURE__ */ jsxs("span", { style: styles8.conditionBadge, children: [
2526
3422
  ruleCount,
2527
3423
  " rule",
2528
3424
  ruleCount !== 1 ? "s" : "",
2529
3425
  " \xB7 ",
2530
3426
  step.visibleWhen.combinator.toUpperCase()
2531
3427
  ] }),
2532
- /* @__PURE__ */ jsx("span", { style: styles7.conditionDesc, children: "Step is conditional" })
2533
- ] }) : /* @__PURE__ */ jsx("div", { style: styles7.conditionNone, children: "Always visible" })
3428
+ /* @__PURE__ */ jsx("span", { style: styles8.conditionDesc, children: "Step is conditional" })
3429
+ ] }) : /* @__PURE__ */ jsx("div", { style: styles8.conditionNone, children: "Always visible" })
2534
3430
  ] }),
2535
- /* @__PURE__ */ jsxs("div", { style: styles7.conditionActions, children: [
3431
+ !readOnly && /* @__PURE__ */ jsxs("div", { style: styles8.conditionActions, children: [
2536
3432
  /* @__PURE__ */ jsx(
2537
3433
  "button",
2538
3434
  {
2539
- style: styles7.editConditionBtn,
3435
+ style: styles8.editConditionBtn,
2540
3436
  onClick: () => setConditionModalOpen(true),
2541
3437
  children: hasCondition ? "Edit" : "Add condition"
2542
3438
  }
@@ -2544,7 +3440,7 @@ function StepEditor() {
2544
3440
  hasCondition && /* @__PURE__ */ jsx(
2545
3441
  "button",
2546
3442
  {
2547
- style: styles7.clearConditionBtn,
3443
+ style: styles8.clearConditionBtn,
2548
3444
  onClick: () => setStepCondition(step.id, void 0),
2549
3445
  children: "Clear"
2550
3446
  }
@@ -2559,7 +3455,7 @@ function StepEditor() {
2559
3455
  onClose: () => setConditionModalOpen(false),
2560
3456
  width: 620,
2561
3457
  children: [
2562
- /* @__PURE__ */ jsx("p", { style: styles7.modalHint, children: "Define when this step is visible. Leave empty to always show it." }),
3458
+ /* @__PURE__ */ jsx("p", { style: styles8.modalHint, children: "Define when this step is visible. Leave empty to always show it." }),
2563
3459
  /* @__PURE__ */ jsx(
2564
3460
  ConditionBuilder,
2565
3461
  {
@@ -2567,10 +3463,10 @@ function StepEditor() {
2567
3463
  onChange: (c) => setStepCondition(step.id, c)
2568
3464
  }
2569
3465
  ),
2570
- /* @__PURE__ */ jsx("div", { style: styles7.modalFooter, children: /* @__PURE__ */ jsx(
3466
+ /* @__PURE__ */ jsx("div", { style: styles8.modalFooter, children: /* @__PURE__ */ jsx(
2571
3467
  "button",
2572
3468
  {
2573
- style: styles7.modalCloseBtn,
3469
+ style: styles8.modalCloseBtn,
2574
3470
  onClick: () => setConditionModalOpen(false),
2575
3471
  children: "Done"
2576
3472
  }
@@ -2580,7 +3476,7 @@ function StepEditor() {
2580
3476
  )
2581
3477
  ] });
2582
3478
  }
2583
- var styles7 = {
3479
+ var styles8 = {
2584
3480
  container: { display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" },
2585
3481
  empty: {
2586
3482
  display: "flex",
@@ -2669,128 +3565,164 @@ var styles7 = {
2669
3565
  }
2670
3566
  };
2671
3567
  function StepList2() {
2672
- const { schema, selectedStepId, addStep, removeStep, selectStep, reorderSteps } = useBuilderStore();
3568
+ const { schema, selectedStepId, addStep, removeStep, duplicateStep, selectStep, reorderSteps } = useBuilderStore();
3569
+ const readOnly = useBuilderReadOnly();
2673
3570
  const [moveError, setMoveError] = useState(null);
2674
3571
  const steps = schema.steps;
2675
3572
  const deps = computeStepDependencies(schema);
2676
- const tryMove = (fromIndex, toIndex) => {
2677
- const check = isMoveValid(steps, deps, fromIndex, toIndex);
2678
- if (!check.valid) {
2679
- setMoveError(check.reason ?? "Invalid move");
2680
- setTimeout(() => setMoveError(null), 3e3);
2681
- return;
2682
- }
2683
- setMoveError(null);
2684
- reorderSteps(fromIndex, toIndex);
3573
+ const tryMove = useCallback(
3574
+ (fromIndex, toIndex) => {
3575
+ const check = isMoveValid(steps, deps, fromIndex, toIndex);
3576
+ if (!check.valid) {
3577
+ setMoveError(check.reason ?? "Invalid move");
3578
+ setTimeout(() => setMoveError(null), 3e3);
3579
+ return;
3580
+ }
3581
+ setMoveError(null);
3582
+ reorderSteps(fromIndex, toIndex);
3583
+ },
3584
+ [steps, deps, reorderSteps]
3585
+ );
3586
+ const stepIds = steps.map((s) => s.id);
3587
+ const renderOverlay = (id) => {
3588
+ const step = steps.find((s) => s.id === id);
3589
+ if (!step) return null;
3590
+ const index = steps.indexOf(step);
3591
+ return /* @__PURE__ */ jsx("div", { style: { ...styles9.card, ...styles9.cardDragOverlay }, children: /* @__PURE__ */ jsx("div", { style: styles9.cardMain, children: /* @__PURE__ */ jsxs("div", { style: styles9.cardLeft, children: [
3592
+ /* @__PURE__ */ jsx("div", { style: styles9.cardIndex, children: index + 1 }),
3593
+ /* @__PURE__ */ jsx("div", { style: styles9.cardTitle, children: step.title })
3594
+ ] }) }) });
2685
3595
  };
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: [
3596
+ return /* @__PURE__ */ jsxs("div", { style: styles9.container, children: [
3597
+ /* @__PURE__ */ jsxs("div", { style: styles9.header, children: [
3598
+ /* @__PURE__ */ jsxs("span", { style: styles9.title, children: [
2689
3599
  "Steps (",
2690
3600
  steps.length,
2691
3601
  ")"
2692
3602
  ] }),
2693
- /* @__PURE__ */ jsx("button", { style: styles8.addBtn, onClick: () => addStep(), children: "+ Add step" })
3603
+ !readOnly && /* @__PURE__ */ jsx("button", { style: styles9.addBtn, onClick: () => addStep(), children: "+ Add step" })
2694
3604
  ] }),
2695
- moveError && /* @__PURE__ */ jsxs("div", { style: styles8.errorBanner, children: [
2696
- /* @__PURE__ */ jsx("span", { style: styles8.errorIcon, children: "\u26A0" }),
3605
+ moveError && /* @__PURE__ */ jsxs("div", { style: styles9.errorBanner, children: [
3606
+ /* @__PURE__ */ jsx("span", { style: styles9.errorIcon, children: "\u26A0" }),
2697
3607
  moveError
2698
3608
  ] }),
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.' }),
2701
- steps.map((step, index) => {
2702
- const isSelected = step.id === selectedStepId;
2703
- const hasCondition = !!step.visibleWhen;
2704
- const depLabels = getStepDependencyLabels(step.id, deps, schema);
2705
- const canMoveUp = index > 0 && isMoveValid(steps, deps, index, index - 1).valid;
2706
- const canMoveDown = index < steps.length - 1 && isMoveValid(steps, deps, index, index + 1).valid;
2707
- const dependents = steps.filter(
2708
- (s) => (deps.get(s.id) ?? /* @__PURE__ */ new Set()).has(step.id)
2709
- );
2710
- return /* @__PURE__ */ jsxs(
2711
- "div",
2712
- {
2713
- style: {
2714
- ...styles8.card,
2715
- ...isSelected ? styles8.cardSelected : {}
2716
- },
2717
- onClick: () => selectStep(step.id),
2718
- 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 }),
2722
- /* @__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: [
2725
- step.fields.length,
2726
- " field",
2727
- step.fields.length !== 1 ? "s" : "",
2728
- hasCondition && /* @__PURE__ */ jsx("span", { style: styles8.conditionBadge, children: "conditional" })
3609
+ /* @__PURE__ */ jsxs("div", { style: styles9.list, children: [
3610
+ steps.length === 0 && /* @__PURE__ */ jsx("div", { style: styles9.empty, children: 'No steps yet. Click "Add step" to start.' }),
3611
+ /* @__PURE__ */ jsx(
3612
+ SortableList,
3613
+ {
3614
+ items: stepIds,
3615
+ disabled: readOnly,
3616
+ onReorder: tryMove,
3617
+ renderOverlay,
3618
+ renderItem: (id, index) => {
3619
+ const step = steps[index];
3620
+ const isSelected = step.id === selectedStepId;
3621
+ const hasCondition = !!step.visibleWhen;
3622
+ const depLabels = getStepDependencyLabels(step.id, deps, schema);
3623
+ const canMoveUp = index > 0 && isMoveValid(steps, deps, index, index - 1).valid;
3624
+ const canMoveDown = index < steps.length - 1 && isMoveValid(steps, deps, index, index + 1).valid;
3625
+ const dependents = steps.filter(
3626
+ (s) => (deps.get(s.id) ?? /* @__PURE__ */ new Set()).has(step.id)
3627
+ );
3628
+ return /* @__PURE__ */ jsx(SortableItem, { id: step.id, disabled: readOnly, children: ({ handleProps }) => /* @__PURE__ */ jsxs(
3629
+ "div",
3630
+ {
3631
+ style: {
3632
+ ...styles9.card,
3633
+ ...isSelected ? styles9.cardSelected : {}
3634
+ },
3635
+ onClick: () => selectStep(step.id),
3636
+ children: [
3637
+ /* @__PURE__ */ jsxs("div", { style: styles9.cardMain, children: [
3638
+ /* @__PURE__ */ jsxs("div", { style: styles9.cardLeft, children: [
3639
+ !readOnly && /* @__PURE__ */ jsx(DragHandle, { handleProps }),
3640
+ /* @__PURE__ */ jsx("div", { style: styles9.cardIndex, children: index + 1 }),
3641
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
3642
+ /* @__PURE__ */ jsx("div", { style: styles9.cardTitle, children: step.title }),
3643
+ /* @__PURE__ */ jsxs("div", { style: styles9.cardMeta, children: [
3644
+ step.fields.length,
3645
+ " field",
3646
+ step.fields.length !== 1 ? "s" : "",
3647
+ hasCondition && /* @__PURE__ */ jsx("span", { style: styles9.conditionBadge, children: "conditional" })
3648
+ ] })
3649
+ ] })
3650
+ ] }),
3651
+ !readOnly && /* @__PURE__ */ jsxs("div", { style: styles9.cardActions, children: [
3652
+ index > 0 && /* @__PURE__ */ jsx(
3653
+ "button",
3654
+ {
3655
+ style: {
3656
+ ...styles9.iconBtn,
3657
+ ...canMoveUp ? {} : styles9.iconBtnBlocked
3658
+ },
3659
+ title: canMoveUp ? "Move up" : `Can't move up \u2014 dependency order required`,
3660
+ onClick: (e) => {
3661
+ e.stopPropagation();
3662
+ tryMove(index, index - 1);
3663
+ },
3664
+ children: "\u2191"
3665
+ }
3666
+ ),
3667
+ index < steps.length - 1 && /* @__PURE__ */ jsx(
3668
+ "button",
3669
+ {
3670
+ style: {
3671
+ ...styles9.iconBtn,
3672
+ ...canMoveDown ? {} : styles9.iconBtnBlocked
3673
+ },
3674
+ title: canMoveDown ? "Move down" : `Can't move down \u2014 dependency order required`,
3675
+ onClick: (e) => {
3676
+ e.stopPropagation();
3677
+ tryMove(index, index + 1);
3678
+ },
3679
+ children: "\u2193"
3680
+ }
3681
+ ),
3682
+ /* @__PURE__ */ jsx(
3683
+ "button",
3684
+ {
3685
+ style: styles9.iconBtn,
3686
+ title: "Duplicate step",
3687
+ onClick: (e) => {
3688
+ e.stopPropagation();
3689
+ duplicateStep(step.id);
3690
+ },
3691
+ children: "\u29C9"
3692
+ }
3693
+ ),
3694
+ /* @__PURE__ */ jsx(
3695
+ "button",
3696
+ {
3697
+ style: { ...styles9.iconBtn, ...styles9.deleteBtn },
3698
+ title: "Remove step",
3699
+ onClick: (e) => {
3700
+ e.stopPropagation();
3701
+ removeStep(step.id);
3702
+ },
3703
+ children: "\u2715"
3704
+ }
3705
+ )
2729
3706
  ] })
3707
+ ] }),
3708
+ depLabels.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles9.depRow, children: [
3709
+ /* @__PURE__ */ jsx("span", { style: styles9.depLabel, children: "needs:" }),
3710
+ depLabels.map((label) => /* @__PURE__ */ jsx("span", { style: styles9.depBadge, children: label }, label))
3711
+ ] }),
3712
+ dependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles9.depRow, children: [
3713
+ /* @__PURE__ */ jsx("span", { style: styles9.depLabelUsed, children: "used by:" }),
3714
+ dependents.map((s) => /* @__PURE__ */ jsx("span", { style: styles9.depBadgeUsed, children: s.title }, s.id))
2730
3715
  ] })
2731
- ] }),
2732
- /* @__PURE__ */ jsxs("div", { style: styles8.cardActions, children: [
2733
- index > 0 && /* @__PURE__ */ jsx(
2734
- "button",
2735
- {
2736
- style: {
2737
- ...styles8.iconBtn,
2738
- ...canMoveUp ? {} : styles8.iconBtnBlocked
2739
- },
2740
- title: canMoveUp ? "Move up" : `Can't move up \u2014 dependency order required`,
2741
- onClick: (e) => {
2742
- e.stopPropagation();
2743
- tryMove(index, index - 1);
2744
- },
2745
- children: "\u2191"
2746
- }
2747
- ),
2748
- index < steps.length - 1 && /* @__PURE__ */ jsx(
2749
- "button",
2750
- {
2751
- style: {
2752
- ...styles8.iconBtn,
2753
- ...canMoveDown ? {} : styles8.iconBtnBlocked
2754
- },
2755
- title: canMoveDown ? "Move down" : `Can't move down \u2014 dependency order required`,
2756
- onClick: (e) => {
2757
- e.stopPropagation();
2758
- tryMove(index, index + 1);
2759
- },
2760
- children: "\u2193"
2761
- }
2762
- ),
2763
- /* @__PURE__ */ jsx(
2764
- "button",
2765
- {
2766
- style: { ...styles8.iconBtn, ...styles8.deleteBtn },
2767
- title: "Remove step",
2768
- onClick: (e) => {
2769
- e.stopPropagation();
2770
- removeStep(step.id);
2771
- },
2772
- children: "\u2715"
2773
- }
2774
- )
2775
- ] })
2776
- ] }),
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))
2780
- ] }),
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))
2784
- ] })
2785
- ]
2786
- },
2787
- step.id
2788
- );
2789
- })
3716
+ ]
3717
+ }
3718
+ ) }, step.id);
3719
+ }
3720
+ }
3721
+ )
2790
3722
  ] })
2791
3723
  ] });
2792
3724
  }
2793
- var styles8 = {
3725
+ var styles9 = {
2794
3726
  container: { display: "flex", flexDirection: "column", height: "100%" },
2795
3727
  header: {
2796
3728
  display: "flex",
@@ -2838,6 +3770,12 @@ var styles8 = {
2838
3770
  gap: 6
2839
3771
  },
2840
3772
  cardSelected: { background: "var(--wp-primary-muted)", border: "1px solid var(--wp-primary-border)" },
3773
+ cardDragOverlay: {
3774
+ boxShadow: "0 4px 16px rgba(0,0,0,0.25)",
3775
+ border: "1px solid var(--wp-primary-border)",
3776
+ background: "var(--wp-surface)",
3777
+ opacity: 0.95
3778
+ },
2841
3779
  cardMain: { display: "flex", alignItems: "center", justifyContent: "space-between" },
2842
3780
  cardLeft: { display: "flex", alignItems: "center", gap: 10, minWidth: 0, flex: 1 },
2843
3781
  cardIndex: {
@@ -2910,7 +3848,7 @@ var styles8 = {
2910
3848
  borderRadius: 4
2911
3849
  }
2912
3850
  };
2913
- function Toolbar({ onSave, previewMode, onTest, isMobile }) {
3851
+ function Toolbar({ onSave, previewMode, onTest, isMobile, readOnly }) {
2914
3852
  const { schema, isDirty, resetSchema } = useBuilderStore();
2915
3853
  const handleExport = () => {
2916
3854
  const json = JSON.stringify(schema, null, 2);
@@ -2950,31 +3888,31 @@ ${result.errors.map((e2) => `\u2022 ${e2}`).join("\n")}`);
2950
3888
  input.click();
2951
3889
  };
2952
3890
  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: "/" }),
3891
+ return /* @__PURE__ */ jsxs("div", { style: styles10.toolbar, children: [
3892
+ /* @__PURE__ */ jsxs("div", { style: styles10.left, children: [
3893
+ /* @__PURE__ */ jsx("span", { style: styles10.logo, children: "\u25C8 waypoint" }),
3894
+ /* @__PURE__ */ jsx("span", { style: styles10.separator, children: "/" }),
2957
3895
  /* @__PURE__ */ jsx(
2958
3896
  "button",
2959
3897
  {
2960
- style: { ...styles9.btn, ...styles9.editBtn },
3898
+ style: { ...styles10.btn, ...styles10.editBtn },
2961
3899
  onClick: onTest,
2962
3900
  children: "\u2190 \xC9diter"
2963
3901
  }
2964
3902
  )
2965
3903
  ] }),
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" }) })
3904
+ /* @__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
3905
  ] });
2968
3906
  }
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: "/" }),
3907
+ return /* @__PURE__ */ jsxs("div", { style: styles10.toolbar, children: [
3908
+ /* @__PURE__ */ jsxs("div", { style: styles10.left, children: [
3909
+ !isMobile && /* @__PURE__ */ jsx("span", { style: styles10.logo, children: "\u25C8 waypoint" }),
3910
+ !isMobile && /* @__PURE__ */ jsx("span", { style: styles10.separator, children: "/" }),
2973
3911
  /* @__PURE__ */ jsx(
2974
3912
  "input",
2975
3913
  {
2976
3914
  style: {
2977
- ...styles9.journeyName,
3915
+ ...styles10.journeyName,
2978
3916
  ...isMobile ? { maxWidth: 120, fontSize: 12 } : {}
2979
3917
  },
2980
3918
  value: schema.name,
@@ -2985,25 +3923,26 @@ ${result.errors.map((e2) => `\u2022 ${e2}`).join("\n")}`);
2985
3923
  }))
2986
3924
  }
2987
3925
  ),
2988
- isDirty && /* @__PURE__ */ jsx("span", { style: styles9.dirtyDot, title: "Unsaved changes" })
3926
+ isDirty && /* @__PURE__ */ jsx("span", { style: styles10.dirtyDot, title: "Unsaved changes" })
2989
3927
  ] }),
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(
3928
+ /* @__PURE__ */ jsxs("div", { style: styles10.right, children: [
3929
+ readOnly && /* @__PURE__ */ jsx("span", { style: styles10.readOnlyBadge, children: "View only" }),
3930
+ !readOnly && onTest && /* @__PURE__ */ jsx("button", { style: { ...styles10.btn, ...styles10.testBtn }, onClick: onTest, title: "Tester", children: isMobile ? "\u25B6" : "\u25B6 Tester" }),
3931
+ !readOnly && /* @__PURE__ */ jsx("button", { style: styles10.btn, onClick: handleImport, title: "Import", children: isMobile ? "\u2193" : "Import" }),
3932
+ /* @__PURE__ */ jsx("button", { style: styles10.btn, onClick: handleExport, title: "Export JSON", children: isMobile ? "\u2191" : "Export JSON" }),
3933
+ !readOnly && onSave && /* @__PURE__ */ jsx(
2995
3934
  "button",
2996
3935
  {
2997
- style: { ...styles9.btn, ...styles9.saveBtn },
3936
+ style: { ...styles10.btn, ...styles10.saveBtn },
2998
3937
  onClick: () => onSave(schema),
2999
3938
  title: "Save",
3000
3939
  children: isMobile ? "\u2713" : "Save"
3001
3940
  }
3002
3941
  ),
3003
- /* @__PURE__ */ jsx(
3942
+ !readOnly && /* @__PURE__ */ jsx(
3004
3943
  "button",
3005
3944
  {
3006
- style: { ...styles9.btn, color: "var(--wp-danger)" },
3945
+ style: { ...styles10.btn, color: "var(--wp-danger)" },
3007
3946
  title: "Reset",
3008
3947
  onClick: () => {
3009
3948
  if (confirm("Reset the journey? All changes will be lost.")) resetSchema();
@@ -3014,7 +3953,7 @@ ${result.errors.map((e2) => `\u2022 ${e2}`).join("\n")}`);
3014
3953
  ] })
3015
3954
  ] });
3016
3955
  }
3017
- var styles9 = {
3956
+ var styles10 = {
3018
3957
  toolbar: {
3019
3958
  display: "flex",
3020
3959
  alignItems: "center",
@@ -3056,6 +3995,15 @@ var styles9 = {
3056
3995
  fontWeight: 500
3057
3996
  },
3058
3997
  saveBtn: { background: "var(--wp-primary)", color: "var(--wp-canvas)", border: "1px solid var(--wp-primary)" },
3998
+ readOnlyBadge: {
3999
+ fontSize: 11,
4000
+ fontWeight: 600,
4001
+ color: "var(--wp-text-subtle)",
4002
+ background: "var(--wp-surface)",
4003
+ border: "1px solid var(--wp-border)",
4004
+ borderRadius: "var(--wp-radius)",
4005
+ padding: "3px 8px"
4006
+ },
3059
4007
  testBtn: { background: "var(--wp-success, #22c55e)", color: "#fff", border: "1px solid var(--wp-success, #22c55e)", fontWeight: 600 },
3060
4008
  editBtn: { background: "transparent", color: "var(--wp-toolbar-text)", border: "none", fontWeight: 600, cursor: "pointer", fontSize: 13, padding: "5px 0" }
3061
4009
  };
@@ -3065,7 +4013,10 @@ function WaypointBuilder({
3065
4013
  onSave,
3066
4014
  theme,
3067
4015
  className,
3068
- style
4016
+ style,
4017
+ readOnly = false,
4018
+ appCustomTypes = [],
4019
+ externalEnums = []
3069
4020
  }) {
3070
4021
  const { loadSchema, schema, selectedStepId, selectedFieldId } = useBuilderStore();
3071
4022
  const [previewMode, setPreviewMode] = useState(false);
@@ -3098,14 +4049,15 @@ function WaypointBuilder({
3098
4049
  setPreviewMode(true);
3099
4050
  }
3100
4051
  const themeVars = buildThemeVars(theme);
3101
- return /* @__PURE__ */ jsxs("div", { className, style: { ...rootStyle, ...themeVars, ...style }, children: [
4052
+ 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
4053
  /* @__PURE__ */ jsx(
3103
4054
  Toolbar,
3104
4055
  {
3105
4056
  isMobile,
3106
- onSave: !previewMode && onSave ? () => onSave(schema) : void 0,
4057
+ readOnly,
4058
+ onSave: !previewMode && !readOnly && onSave ? () => onSave(schema) : void 0,
3107
4059
  previewMode,
3108
- onTest: previewMode ? () => setPreviewMode(false) : handleTest
4060
+ onTest: !readOnly ? previewMode ? () => setPreviewMode(false) : handleTest : void 0
3109
4061
  }
3110
4062
  ),
3111
4063
  previewMode ? /* @__PURE__ */ jsx(
@@ -3113,6 +4065,7 @@ function WaypointBuilder({
3113
4065
  {
3114
4066
  store: previewStoreRef.current,
3115
4067
  schema,
4068
+ externalEnums,
3116
4069
  onEdit: () => setPreviewMode(false)
3117
4070
  }
3118
4071
  ) : isMobile ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -3150,7 +4103,7 @@ function WaypointBuilder({
3150
4103
  /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsx(FieldEditor, {}) })
3151
4104
  ] })
3152
4105
  ] })
3153
- ] });
4106
+ ] }) }) }) });
3154
4107
  }
3155
4108
  var rootStyle = {
3156
4109
  display: "flex",