@waypointjs/builder 0.1.5 → 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 { createContext, useState, useRef, useEffect, useContext } from 'react';
2
+ import { createContext, useState, useRef, useEffect, useCallback, useContext } from 'react';
3
3
  import { create } from 'zustand';
4
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
 
@@ -1179,8 +1182,19 @@ var VALIDATION_RULES = [
1179
1182
  { type: "notInEnum", label: "not in enum", hasValue: true, isEnum: true },
1180
1183
  { type: "custom", label: "Custom validator", hasValue: false }
1181
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
+ ]);
1182
1195
  function ValidationBuilder({ value, onChange }) {
1183
1196
  const externalEnums = useBuilderExternalEnums();
1197
+ const allFieldPaths = useAllFieldPaths();
1184
1198
  const updateRule = (index, updates) => {
1185
1199
  onChange(value.map((r, i) => i === index ? { ...r, ...updates } : r));
1186
1200
  };
@@ -1216,35 +1230,68 @@ function ValidationBuilder({ value, onChange }) {
1216
1230
  ]
1217
1231
  }
1218
1232
  ) : /* @__PURE__ */ jsxs("div", { style: styles2.valueGroup, children: [
1219
- /* @__PURE__ */ jsx(
1220
- "input",
1233
+ COMPARATOR_TYPES.has(rule.type) && /* @__PURE__ */ jsx(
1234
+ "button",
1221
1235
  {
1222
- style: styles2.valueInput,
1223
- placeholder: "value",
1224
- value: rule.value != null ? String(rule.value) : "",
1225
- onChange: (e) => updateRule(index, { value: e.target.value })
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"
1226
1251
  }
1227
1252
  ),
1228
- externalEnums.length > 0 && /* @__PURE__ */ jsxs(
1253
+ rule.refField !== void 0 ? /* @__PURE__ */ jsxs(
1229
1254
  "select",
1230
1255
  {
1231
- style: styles2.enumPicker,
1232
- title: "Pick a value from an enum",
1233
- value: "",
1234
- onChange: (e) => {
1235
- if (e.target.value) updateRule(index, { value: e.target.value });
1236
- },
1256
+ style: { ...styles2.typeSelect, flex: "1 1 auto" },
1257
+ value: rule.refField,
1258
+ onChange: (e) => updateRule(index, { refField: e.target.value }),
1237
1259
  children: [
1238
- /* @__PURE__ */ jsx("option", { value: "", children: "\u229E" }),
1239
- externalEnums.map((en) => /* @__PURE__ */ jsx("optgroup", { label: en.label, children: en.values.map((v) => /* @__PURE__ */ jsxs("option", { value: String(v.value), children: [
1240
- v.label,
1241
- " (",
1242
- v.value,
1243
- ")"
1244
- ] }, String(v.value))) }, en.id))
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))
1245
1262
  ]
1246
1263
  }
1247
- )
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
+ ] })
1248
1295
  ] })),
1249
1296
  rule.type === "custom" && /* @__PURE__ */ jsx(
1250
1297
  "input",
@@ -1291,7 +1338,17 @@ var styles2 = {
1291
1338
  background: "var(--wp-canvas)",
1292
1339
  color: "var(--wp-text)"
1293
1340
  },
1294
- valueGroup: { display: "flex", alignItems: "center", gap: 4 },
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
+ },
1295
1352
  valueInput: {
1296
1353
  width: 90,
1297
1354
  fontSize: 12,
@@ -1555,6 +1612,8 @@ function FieldEditor() {
1555
1612
  const externalEnums = useBuilderExternalEnums();
1556
1613
  const [conditionModalOpen, setConditionModalOpen] = useState(false);
1557
1614
  const [validationModalOpen, setValidationModalOpen] = useState(false);
1615
+ const [dynDefaultModalOpen, setDynDefaultModalOpen] = useState(false);
1616
+ const [editingDynIdx, setEditingDynIdx] = useState(null);
1558
1617
  const step = schema.steps.find((s) => s.id === selectedStepId);
1559
1618
  const field = step?.fields.find((f) => f.id === selectedFieldId);
1560
1619
  if (!field || !step) {
@@ -1618,6 +1677,58 @@ function FieldEditor() {
1618
1677
  }
1619
1678
  )
1620
1679
  ] }),
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 }),
1621
1732
  ENUM_FIELD_TYPES.includes(field.type) && externalEnums.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles5.group, children: [
1622
1733
  /* @__PURE__ */ jsx("label", { style: styles5.label, children: "Options source" }),
1623
1734
  /* @__PURE__ */ jsxs(
@@ -1745,6 +1856,28 @@ function FieldEditor() {
1745
1856
  ]
1746
1857
  }
1747
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
+ ),
1748
1881
  conditionModalOpen && /* @__PURE__ */ jsxs(
1749
1882
  Modal,
1750
1883
  {
@@ -1768,6 +1901,96 @@ function FieldEditor() {
1768
1901
  )
1769
1902
  ] });
1770
1903
  }
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
+ }
1771
1994
  var styles5 = {
1772
1995
  container: { display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" },
1773
1996
  empty: {
@@ -1987,6 +2210,107 @@ function getStepDependencyLabels(stepId, deps, schema) {
1987
2210
  const required = deps.get(stepId) ?? /* @__PURE__ */ new Set();
1988
2211
  return [...required].map((id) => schema.steps.find((s) => s.id === id)?.title ?? id).filter(Boolean);
1989
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
+ }
1990
2314
  var FIELD_TYPES = [
1991
2315
  "text",
1992
2316
  "number",
@@ -2040,6 +2364,16 @@ function FieldList() {
2040
2364
  setMoveError(null);
2041
2365
  reorderFields(step.id, fromIndex, toIndex);
2042
2366
  };
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
+ };
2043
2377
  return /* @__PURE__ */ jsxs("div", { style: styles6.container, children: [
2044
2378
  /* @__PURE__ */ jsxs("div", { style: styles6.header, children: [
2045
2379
  /* @__PURE__ */ jsxs("div", { children: [
@@ -2059,137 +2393,147 @@ function FieldList() {
2059
2393
  ] }),
2060
2394
  /* @__PURE__ */ jsxs("div", { style: styles6.list, children: [
2061
2395
  step.fields.length === 0 && /* @__PURE__ */ jsx("div", { style: styles6.emptyFields, children: 'No fields yet. Click "Add field" to start.' }),
2062
- step.fields.map((field, index) => {
2063
- const isSelected = field.id === selectedFieldId;
2064
- const isRequired = field.validation?.some((v) => v.type === "required") ?? false;
2065
- const hasCondition = !!field.visibleWhen;
2066
- const hasDeps = (field.dependsOn?.length ?? 0) > 0;
2067
- const enumDef = field.externalEnumId ? externalEnums.find((e) => e.id === field.externalEnumId) : void 0;
2068
- const isUsedAsDep = allDependencyTargets.has(`${step.id}.${field.id}`);
2069
- const canMoveUp = index > 0 && isFieldMoveValid(step.fields, step.id, index, index - 1).valid;
2070
- const canMoveDown = index < step.fields.length - 1 && isFieldMoveValid(step.fields, step.id, index, index + 1).valid;
2071
- const intraStepDeps = (field.dependsOn ?? []).filter((p) => p.startsWith(`${step.id}.`)).map((p) => {
2072
- const fieldId = p.slice(step.id.length + 1);
2073
- return step.fields.find((f) => f.id === fieldId)?.label ?? fieldId;
2074
- });
2075
- const intraStepDependents = step.fields.filter(
2076
- (f) => f.id !== field.id && (f.dependsOn ?? []).includes(`${step.id}.${field.id}`)
2077
- );
2078
- return /* @__PURE__ */ jsxs(
2079
- "div",
2080
- {
2081
- style: { ...styles6.card, ...isSelected ? styles6.cardSelected : {} },
2082
- onClick: () => selectField(field.id),
2083
- children: [
2084
- /* @__PURE__ */ jsxs("div", { style: styles6.cardTop, children: [
2085
- /* @__PURE__ */ jsxs("div", { style: styles6.cardLeft, children: [
2086
- (() => {
2087
- const ct = appCustomTypes.find((c) => c.id === field.type);
2088
- return /* @__PURE__ */ jsx("span", { style: { ...styles6.typeBadge, ...ct ? styles6.typeBadgeCustom : {} }, children: ct ? `${ct.icon ? ct.icon + " " : ""}${ct.label}` : field.type });
2089
- })(),
2090
- /* @__PURE__ */ jsx("span", { style: styles6.fieldLabel, children: field.label })
2091
- ] }),
2092
- /* @__PURE__ */ jsxs("div", { style: styles6.cardRight, children: [
2093
- !readOnly && /* @__PURE__ */ jsxs(
2094
- "select",
2095
- {
2096
- style: styles6.typeSelect,
2097
- value: field.type,
2098
- onClick: (e) => e.stopPropagation(),
2099
- onChange: (e) => {
2100
- const newType = e.target.value;
2101
- const customType = appCustomTypes.find((ct) => ct.id === newType);
2102
- updateField(step.id, field.id, {
2103
- type: newType,
2104
- ...customType?.defaultValidation ? { validation: customType.defaultValidation } : {}
2105
- });
2106
- },
2107
- children: [
2108
- FIELD_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t)),
2109
- appCustomTypes.length > 0 && /* @__PURE__ */ jsx("optgroup", { label: "Custom", children: appCustomTypes.map((ct) => /* @__PURE__ */ jsxs("option", { value: ct.id, children: [
2110
- ct.icon ? `${ct.icon} ` : "",
2111
- ct.label
2112
- ] }, ct.id)) })
2113
- ]
2114
- }
2115
- ),
2116
- !readOnly && index > 0 && /* @__PURE__ */ jsx(
2117
- "button",
2118
- {
2119
- style: { ...styles6.iconBtn, ...canMoveUp ? {} : styles6.iconBtnBlocked },
2120
- title: canMoveUp ? "Move up" : "Can't move \u2014 dependency order required",
2121
- onClick: (e) => {
2122
- e.stopPropagation();
2123
- tryMove(index, index - 1);
2124
- },
2125
- children: "\u2191"
2126
- }
2127
- ),
2128
- !readOnly && index < step.fields.length - 1 && /* @__PURE__ */ jsx(
2129
- "button",
2130
- {
2131
- style: { ...styles6.iconBtn, ...canMoveDown ? {} : styles6.iconBtnBlocked },
2132
- title: canMoveDown ? "Move down" : "Can't move \u2014 dependency order required",
2133
- onClick: (e) => {
2134
- e.stopPropagation();
2135
- tryMove(index, index + 1);
2136
- },
2137
- children: "\u2193"
2138
- }
2139
- ),
2140
- !readOnly && /* @__PURE__ */ jsx(
2141
- "button",
2142
- {
2143
- style: styles6.iconBtn,
2144
- title: "Duplicate field",
2145
- onClick: (e) => {
2146
- e.stopPropagation();
2147
- duplicateField(step.id, field.id);
2148
- },
2149
- children: "\u29C9"
2150
- }
2151
- ),
2152
- !readOnly && /* @__PURE__ */ jsx(
2153
- "button",
2154
- {
2155
- style: { ...styles6.iconBtn, color: "var(--wp-danger)" },
2156
- title: "Remove field",
2157
- onClick: (e) => {
2158
- e.stopPropagation();
2159
- removeField(step.id, field.id);
2160
- },
2161
- children: "\u2715"
2162
- }
2163
- )
2164
- ] })
2165
- ] }),
2166
- /* @__PURE__ */ jsxs("div", { style: styles6.badges, children: [
2167
- !isRequired && /* @__PURE__ */ jsx("span", { style: styles6.badgeOptional, children: "optional" }),
2168
- isRequired && /* @__PURE__ */ jsx("span", { style: styles6.badgeRequired, children: "required" }),
2169
- hasCondition && /* @__PURE__ */ jsx("span", { style: styles6.badgeCondition, children: "conditional" }),
2170
- hasDeps && /* @__PURE__ */ jsxs("span", { style: styles6.badgeDep, children: [
2171
- "depends on ",
2172
- field.dependsOn.length
2173
- ] }),
2174
- isUsedAsDep && /* @__PURE__ */ jsx("span", { style: styles6.badgeUsed, children: "\u2190 dependency" }),
2175
- field.externalEnumId && /* @__PURE__ */ jsxs("span", { style: styles6.badgeEnum, children: [
2176
- "\u229E ",
2177
- enumDef ? enumDef.label : field.externalEnumId
2178
- ] })
2179
- ] }),
2180
- intraStepDeps.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.depRow, children: [
2181
- /* @__PURE__ */ jsx("span", { style: styles6.depLabel, children: "needs:" }),
2182
- intraStepDeps.map((label) => /* @__PURE__ */ jsx("span", { style: styles6.depBadge, children: label }, label))
2183
- ] }),
2184
- intraStepDependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.depRow, children: [
2185
- /* @__PURE__ */ jsx("span", { style: styles6.depLabelUsed, children: "used by:" }),
2186
- intraStepDependents.map((f) => /* @__PURE__ */ jsx("span", { style: styles6.depBadgeUsed, children: f.label }, f.id))
2187
- ] })
2188
- ]
2189
- },
2190
- field.id
2191
- );
2192
- })
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
+ )
2193
2537
  ] })
2194
2538
  ] });
2195
2539
  }
@@ -2250,6 +2594,12 @@ var styles6 = {
2250
2594
  gap: 6
2251
2595
  },
2252
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
+ },
2253
2603
  cardTop: { display: "flex", alignItems: "center", justifyContent: "space-between" },
2254
2604
  cardLeft: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 },
2255
2605
  cardRight: { display: "flex", alignItems: "center", gap: 4, flexShrink: 0 },
@@ -2409,6 +2759,9 @@ function PreviewPanel({ store, schema, externalEnums }) {
2409
2759
  setErrors({});
2410
2760
  const oldIds = tree.steps.map((s) => s.definition.id).join(",");
2411
2761
  store.getState().setStepData(stepId, stepData);
2762
+ if ((store.getState().skippedSteps ?? []).includes(stepId)) {
2763
+ store.getState().unskipStep(stepId);
2764
+ }
2412
2765
  const newData = store.getState().data;
2413
2766
  const newTree = resolveTree(schema, newData, store.getState().externalVars, externalEnums);
2414
2767
  const newIds = newTree.steps.map((s) => s.definition.id).join(",");
@@ -2445,7 +2798,8 @@ function PreviewPanel({ store, schema, externalEnums }) {
2445
2798
  {
2446
2799
  tree,
2447
2800
  currentIdx,
2448
- onSelect: (id) => store.getState().setCurrentStep(id)
2801
+ onSelect: (id) => store.getState().setCurrentStep(id),
2802
+ skippedStepIds: store.getState().skippedSteps
2449
2803
  }
2450
2804
  ),
2451
2805
  extVarDefs.length > 0 && /* @__PURE__ */ jsx(
@@ -2490,33 +2844,57 @@ function PreviewPanel({ store, schema, externalEnums }) {
2490
2844
  /* @__PURE__ */ jsx("div", { style: styles7.rightCol, children: /* @__PURE__ */ jsxs("div", { style: styles7.stepRenderer, children: [
2491
2845
  /* @__PURE__ */ jsx("div", { style: styles7.progressTrack, children: /* @__PURE__ */ jsx("div", { style: { ...styles7.progressFill, width: `${progress}%` } }) }),
2492
2846
  /* @__PURE__ */ jsx("h2", { style: styles7.stepTitle, children: currentStep?.definition.title ?? "" }),
2493
- /* @__PURE__ */ jsx("div", { style: styles7.fieldsContainer, children: fields.map((field) => /* @__PURE__ */ jsx(
2494
- FieldRenderer,
2495
- {
2496
- field,
2497
- value: stepData[field.definition.id],
2498
- error: errors[field.definition.id],
2499
- onChange: (val) => {
2500
- setFieldValue(field.definition.id, val);
2501
- if (errors[field.definition.id]) {
2502
- setErrors((prev) => {
2503
- const next = { ...prev };
2504
- delete next[field.definition.id];
2505
- return next;
2506
- });
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
+ }
2507
2865
  }
2508
- }
2509
- },
2510
- field.definition.id
2511
- )) }),
2866
+ },
2867
+ field.definition.id
2868
+ );
2869
+ }) }),
2512
2870
  /* @__PURE__ */ jsxs("div", { style: styles7.navRow, children: [
2513
2871
  currentIdx > 0 && /* @__PURE__ */ jsx("button", { style: styles7.secondaryBtn, onClick: handlePrev, children: "\u2190 Pr\xE9c\xE9dent" }),
2514
- /* @__PURE__ */ jsx("button", { style: { ...styles7.primaryBtn, marginLeft: "auto" }, onClick: handleNext, children: getNextStep(tree.steps, stepId) ? "Continuer \u2192" : "Terminer \u2713" })
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
+ ] })
2515
2892
  ] })
2516
2893
  ] }) })
2517
2894
  ] });
2518
2895
  }
2519
- function StepList({ tree, currentIdx, onSelect }) {
2896
+ function StepList({ tree, currentIdx, onSelect, skippedStepIds }) {
2897
+ const skipped = new Set(skippedStepIds ?? []);
2520
2898
  const allSteps = [
2521
2899
  ...tree.steps.map((s) => ({ ...s, hidden: false })),
2522
2900
  ...tree.hiddenSteps.map((s) => ({ ...s, hidden: true }))
@@ -2524,11 +2902,13 @@ function StepList({ tree, currentIdx, onSelect }) {
2524
2902
  return /* @__PURE__ */ jsxs("div", { style: styles7.stepList, children: [
2525
2903
  /* @__PURE__ */ jsx("div", { style: styles7.stepListTitle, children: "\xC9tapes" }),
2526
2904
  allSteps.map((step) => {
2527
- const isVisible = !step.hidden;
2905
+ const isStepVisible = !step.hidden;
2528
2906
  const visIdx = tree.steps.findIndex((s) => s.definition.id === step.definition.id);
2907
+ const isSkipped = skipped.has(step.definition.id);
2529
2908
  let status = "hidden";
2530
- if (isVisible) {
2531
- if (visIdx < currentIdx) status = "done";
2909
+ if (isStepVisible) {
2910
+ if (isSkipped && visIdx < currentIdx) status = "skipped";
2911
+ else if (visIdx < currentIdx) status = "done";
2532
2912
  else if (visIdx === currentIdx) status = "current";
2533
2913
  else status = "upcoming";
2534
2914
  }
@@ -2539,20 +2919,23 @@ function StepList({ tree, currentIdx, onSelect }) {
2539
2919
  ...styles7.stepItem,
2540
2920
  ...status === "current" ? styles7.stepItemCurrent : {},
2541
2921
  ...status === "hidden" ? styles7.stepItemHidden : {},
2542
- cursor: status === "done" ? "pointer" : "default"
2922
+ ...status === "skipped" ? { opacity: 0.6 } : {},
2923
+ cursor: status === "done" || status === "skipped" ? "pointer" : "default"
2543
2924
  },
2544
2925
  onClick: () => {
2545
- if (status === "done") onSelect(step.definition.id);
2926
+ if (status === "done" || status === "skipped") onSelect(step.definition.id);
2546
2927
  },
2547
2928
  children: [
2548
2929
  /* @__PURE__ */ jsxs("span", { style: styles7.stepStatus, children: [
2549
2930
  status === "done" && "\u2713",
2931
+ status === "skipped" && "\u23ED",
2550
2932
  status === "current" && "\u2192",
2551
2933
  status === "upcoming" && "\u25CB",
2552
2934
  status === "hidden" && "\u2013"
2553
2935
  ] }),
2554
2936
  /* @__PURE__ */ jsx("span", { style: styles7.stepName, children: step.definition.title }),
2555
- status === "hidden" && /* @__PURE__ */ jsx("span", { style: styles7.hiddenBadge, children: "hidden" })
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" })
2556
2939
  ]
2557
2940
  },
2558
2941
  step.definition.id
@@ -3017,6 +3400,19 @@ function StepEditor() {
3017
3400
  ),
3018
3401
  /* @__PURE__ */ jsx("label", { htmlFor: `resume-${step.id}`, style: styles8.checkLabel, children: "Resume from this step" })
3019
3402
  ] }),
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
+ ] }),
3020
3416
  /* @__PURE__ */ jsx("div", { style: styles8.divider }),
3021
3417
  /* @__PURE__ */ jsxs("div", { style: styles8.conditionRow, children: [
3022
3418
  /* @__PURE__ */ jsxs("div", { style: styles8.conditionInfo, children: [
@@ -3174,15 +3570,28 @@ function StepList2() {
3174
3570
  const [moveError, setMoveError] = useState(null);
3175
3571
  const steps = schema.steps;
3176
3572
  const deps = computeStepDependencies(schema);
3177
- const tryMove = (fromIndex, toIndex) => {
3178
- const check = isMoveValid(steps, deps, fromIndex, toIndex);
3179
- if (!check.valid) {
3180
- setMoveError(check.reason ?? "Invalid move");
3181
- setTimeout(() => setMoveError(null), 3e3);
3182
- return;
3183
- }
3184
- setMoveError(null);
3185
- 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
+ ] }) }) });
3186
3595
  };
3187
3596
  return /* @__PURE__ */ jsxs("div", { style: styles9.container, children: [
3188
3597
  /* @__PURE__ */ jsxs("div", { style: styles9.header, children: [
@@ -3199,107 +3608,117 @@ function StepList2() {
3199
3608
  ] }),
3200
3609
  /* @__PURE__ */ jsxs("div", { style: styles9.list, children: [
3201
3610
  steps.length === 0 && /* @__PURE__ */ jsx("div", { style: styles9.empty, children: 'No steps yet. Click "Add step" to start.' }),
3202
- steps.map((step, index) => {
3203
- const isSelected = step.id === selectedStepId;
3204
- const hasCondition = !!step.visibleWhen;
3205
- const depLabels = getStepDependencyLabels(step.id, deps, schema);
3206
- const canMoveUp = index > 0 && isMoveValid(steps, deps, index, index - 1).valid;
3207
- const canMoveDown = index < steps.length - 1 && isMoveValid(steps, deps, index, index + 1).valid;
3208
- const dependents = steps.filter(
3209
- (s) => (deps.get(s.id) ?? /* @__PURE__ */ new Set()).has(step.id)
3210
- );
3211
- return /* @__PURE__ */ jsxs(
3212
- "div",
3213
- {
3214
- style: {
3215
- ...styles9.card,
3216
- ...isSelected ? styles9.cardSelected : {}
3217
- },
3218
- onClick: () => selectStep(step.id),
3219
- children: [
3220
- /* @__PURE__ */ jsxs("div", { style: styles9.cardMain, children: [
3221
- /* @__PURE__ */ jsxs("div", { style: styles9.cardLeft, children: [
3222
- /* @__PURE__ */ jsx("div", { style: styles9.cardIndex, children: index + 1 }),
3223
- /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
3224
- /* @__PURE__ */ jsx("div", { style: styles9.cardTitle, children: step.title }),
3225
- /* @__PURE__ */ jsxs("div", { style: styles9.cardMeta, children: [
3226
- step.fields.length,
3227
- " field",
3228
- step.fields.length !== 1 ? "s" : "",
3229
- hasCondition && /* @__PURE__ */ jsx("span", { style: styles9.conditionBadge, children: "conditional" })
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
+ )
3230
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))
3231
3715
  ] })
3232
- ] }),
3233
- !readOnly && /* @__PURE__ */ jsxs("div", { style: styles9.cardActions, children: [
3234
- index > 0 && /* @__PURE__ */ jsx(
3235
- "button",
3236
- {
3237
- style: {
3238
- ...styles9.iconBtn,
3239
- ...canMoveUp ? {} : styles9.iconBtnBlocked
3240
- },
3241
- title: canMoveUp ? "Move up" : `Can't move up \u2014 dependency order required`,
3242
- onClick: (e) => {
3243
- e.stopPropagation();
3244
- tryMove(index, index - 1);
3245
- },
3246
- children: "\u2191"
3247
- }
3248
- ),
3249
- index < steps.length - 1 && /* @__PURE__ */ jsx(
3250
- "button",
3251
- {
3252
- style: {
3253
- ...styles9.iconBtn,
3254
- ...canMoveDown ? {} : styles9.iconBtnBlocked
3255
- },
3256
- title: canMoveDown ? "Move down" : `Can't move down \u2014 dependency order required`,
3257
- onClick: (e) => {
3258
- e.stopPropagation();
3259
- tryMove(index, index + 1);
3260
- },
3261
- children: "\u2193"
3262
- }
3263
- ),
3264
- /* @__PURE__ */ jsx(
3265
- "button",
3266
- {
3267
- style: styles9.iconBtn,
3268
- title: "Duplicate step",
3269
- onClick: (e) => {
3270
- e.stopPropagation();
3271
- duplicateStep(step.id);
3272
- },
3273
- children: "\u29C9"
3274
- }
3275
- ),
3276
- /* @__PURE__ */ jsx(
3277
- "button",
3278
- {
3279
- style: { ...styles9.iconBtn, ...styles9.deleteBtn },
3280
- title: "Remove step",
3281
- onClick: (e) => {
3282
- e.stopPropagation();
3283
- removeStep(step.id);
3284
- },
3285
- children: "\u2715"
3286
- }
3287
- )
3288
- ] })
3289
- ] }),
3290
- depLabels.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles9.depRow, children: [
3291
- /* @__PURE__ */ jsx("span", { style: styles9.depLabel, children: "needs:" }),
3292
- depLabels.map((label) => /* @__PURE__ */ jsx("span", { style: styles9.depBadge, children: label }, label))
3293
- ] }),
3294
- dependents.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles9.depRow, children: [
3295
- /* @__PURE__ */ jsx("span", { style: styles9.depLabelUsed, children: "used by:" }),
3296
- dependents.map((s) => /* @__PURE__ */ jsx("span", { style: styles9.depBadgeUsed, children: s.title }, s.id))
3297
- ] })
3298
- ]
3299
- },
3300
- step.id
3301
- );
3302
- })
3716
+ ]
3717
+ }
3718
+ ) }, step.id);
3719
+ }
3720
+ }
3721
+ )
3303
3722
  ] })
3304
3723
  ] });
3305
3724
  }
@@ -3351,6 +3770,12 @@ var styles9 = {
3351
3770
  gap: 6
3352
3771
  },
3353
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
+ },
3354
3779
  cardMain: { display: "flex", alignItems: "center", justifyContent: "space-between" },
3355
3780
  cardLeft: { display: "flex", alignItems: "center", gap: 10, minWidth: 0, flex: 1 },
3356
3781
  cardIndex: {