@timeax/digital-service-engine 0.3.5 → 0.3.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.
@@ -666,6 +666,9 @@ function normalise(input, opts = {}) {
666
666
  const excludes_for_buttons = toStringArrayMap(
667
667
  obj.excludes_for_buttons
668
668
  );
669
+ const option_effects_for_buttons = toOptionEffectMap(
670
+ obj.option_effects_for_buttons
671
+ );
669
672
  const orderKinds = toStringMap(obj.orderKinds);
670
673
  const notices = toNoticeArray(obj.notices);
671
674
  let filters = rawFilters.map((t) => coerceTag(t, constraints));
@@ -681,6 +684,9 @@ function normalise(input, opts = {}) {
681
684
  ...isNonEmpty(orderKinds) && { orderKinds },
682
685
  ...isNonEmpty(includes_for_buttons) && { includes_for_buttons },
683
686
  ...isNonEmpty(excludes_for_buttons) && { excludes_for_buttons },
687
+ ...isNonEmpty(option_effects_for_buttons) && {
688
+ option_effects_for_buttons
689
+ },
684
690
  ...fallbacks && (isNonEmpty(fallbacks.nodes) || isNonEmpty(fallbacks.global)) && {
685
691
  fallbacks
686
692
  },
@@ -835,6 +841,7 @@ function coerceOption(src, inheritRole) {
835
841
  const value = typeof src.value === "string" || typeof src.value === "number" ? src.value : void 0;
836
842
  const pricing_role = src.pricing_role === "utility" || src.pricing_role === "base" ? src.pricing_role : inheritRole;
837
843
  const meta = src.meta && typeof src.meta === "object" ? src.meta : void 0;
844
+ const children = Array.isArray(src.children) ? src.children.map((child) => coerceOption(child, pricing_role)) : void 0;
838
845
  const option = {
839
846
  id: "",
840
847
  label: "",
@@ -843,7 +850,8 @@ function coerceOption(src, inheritRole) {
843
850
  ...value !== void 0 && { value },
844
851
  ...service_id !== void 0 && { service_id },
845
852
  pricing_role,
846
- ...meta && { meta }
853
+ ...meta && { meta },
854
+ ...children && children.length && { children }
847
855
  };
848
856
  return option;
849
857
  }
@@ -908,6 +916,35 @@ function toStringArrayMap(src) {
908
916
  }
909
917
  return Object.keys(out).length ? out : void 0;
910
918
  }
919
+ function toOptionEffectMap(src) {
920
+ var _a, _b;
921
+ if (!src || typeof src !== "object") return void 0;
922
+ const out = {};
923
+ for (const [triggerId, rawTargets] of Object.entries(src)) {
924
+ if (!triggerId || !rawTargets || typeof rawTargets !== "object") {
925
+ continue;
926
+ }
927
+ const targets = {};
928
+ for (const [fieldId, rawEffect] of Object.entries(rawTargets)) {
929
+ if (!fieldId || !rawEffect || typeof rawEffect !== "object") {
930
+ continue;
931
+ }
932
+ const effect = rawEffect;
933
+ const include2 = toStringArray(effect.include);
934
+ const exclude2 = toStringArray(effect.exclude);
935
+ const next = {
936
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
937
+ ...include2.length ? { include: dedupe(include2) } : {},
938
+ ...exclude2.length ? { exclude: dedupe(exclude2) } : {}
939
+ };
940
+ if (next.forceVisible === true || ((_a = next.include) == null ? void 0 : _a.length) || ((_b = next.exclude) == null ? void 0 : _b.length)) {
941
+ targets[fieldId] = next;
942
+ }
943
+ }
944
+ if (Object.keys(targets).length) out[triggerId] = targets;
945
+ }
946
+ return Object.keys(out).length ? out : void 0;
947
+ }
911
948
  function toStringArray(v) {
912
949
  if (!Array.isArray(v)) return [];
913
950
  return v.map((x) => String(x)).filter((s) => !!s && s.trim().length > 0);
@@ -982,6 +1019,72 @@ function normalizeFieldValidation(input) {
982
1019
  return one ? [one] : void 0;
983
1020
  }
984
1021
 
1022
+ // src/core/options.ts
1023
+ function walkFieldOptions(field) {
1024
+ const out = [];
1025
+ const visit = (options, depth, parentId) => {
1026
+ for (const option of options != null ? options : []) {
1027
+ out.push({
1028
+ field,
1029
+ fieldId: field.id,
1030
+ option,
1031
+ optionId: option.id,
1032
+ depth,
1033
+ parentId
1034
+ });
1035
+ visit(option.children, depth + 1, option.id);
1036
+ }
1037
+ };
1038
+ visit(field.options, 0);
1039
+ return out;
1040
+ }
1041
+ function fieldOptionIds(field) {
1042
+ return walkFieldOptions(field).map((visit) => visit.optionId);
1043
+ }
1044
+ function fieldOptionIdSet(field) {
1045
+ return new Set(fieldOptionIds(field));
1046
+ }
1047
+ function findFieldOption(field, optionId) {
1048
+ var _a;
1049
+ if (!field) return void 0;
1050
+ return (_a = walkFieldOptions(field).find((visit) => visit.optionId === optionId)) == null ? void 0 : _a.option;
1051
+ }
1052
+ function findOptionOwnerField(fields, optionId) {
1053
+ for (const field of fields) {
1054
+ if (findFieldOption(field, optionId)) return field;
1055
+ }
1056
+ return void 0;
1057
+ }
1058
+ function optionOwnerMap(fields) {
1059
+ const out = /* @__PURE__ */ new Map();
1060
+ for (const field of fields) {
1061
+ for (const visit of walkFieldOptions(field)) {
1062
+ if (!out.has(visit.optionId)) {
1063
+ out.set(visit.optionId, {
1064
+ fieldId: field.id,
1065
+ option: visit.option
1066
+ });
1067
+ }
1068
+ }
1069
+ }
1070
+ return out;
1071
+ }
1072
+ function filterFieldOptionsById(options, allowed) {
1073
+ if (!Array.isArray(options)) return void 0;
1074
+ const out = [];
1075
+ for (const option of options) {
1076
+ const children = filterFieldOptionsById(option.children, allowed);
1077
+ if (!allowed.has(option.id) && (!children || children.length === 0)) {
1078
+ continue;
1079
+ }
1080
+ out.push({
1081
+ ...option,
1082
+ ...children ? { children } : {}
1083
+ });
1084
+ }
1085
+ return out;
1086
+ }
1087
+
985
1088
  // src/core/validate/shared.ts
986
1089
  function isFiniteNumber(v) {
987
1090
  return typeof v === "number" && Number.isFinite(v);
@@ -990,8 +1093,9 @@ function isServiceIdRef(v) {
990
1093
  return typeof v === "string" && v.trim().length > 0 || typeof v === "number" && Number.isFinite(v);
991
1094
  }
992
1095
  function hasAnyServiceOption(f) {
993
- var _a;
994
- return ((_a = f.options) != null ? _a : []).some((o) => isServiceIdRef(o.service_id));
1096
+ return walkFieldOptions(f).some(
1097
+ (visit) => isServiceIdRef(visit.option.service_id)
1098
+ );
995
1099
  }
996
1100
  function getByPath(obj, path) {
997
1101
  if (!path) return void 0;
@@ -1080,14 +1184,14 @@ function withAffected(details, ids) {
1080
1184
 
1081
1185
  // src/core/node-map.ts
1082
1186
  function buildNodeMap(props) {
1083
- var _a, _b, _c;
1187
+ var _a, _b;
1084
1188
  const map = /* @__PURE__ */ new Map();
1085
1189
  for (const t of (_a = props.filters) != null ? _a : []) {
1086
1190
  if (!map.has(t.id)) map.set(t.id, { kind: "tag", id: t.id, node: t });
1087
1191
  }
1088
1192
  for (const f of (_b = props.fields) != null ? _b : []) {
1089
1193
  if (!map.has(f.id)) map.set(f.id, { kind: "field", id: f.id, node: f });
1090
- for (const o of (_c = f.options) != null ? _c : []) {
1194
+ for (const { option: o } of walkFieldOptions(f)) {
1091
1195
  if (!map.has(o.id))
1092
1196
  map.set(o.id, {
1093
1197
  kind: "option",
@@ -1100,12 +1204,6 @@ function buildNodeMap(props) {
1100
1204
  return map;
1101
1205
  }
1102
1206
  function resolveTrigger(trigger, nodeMap) {
1103
- const idx = trigger.indexOf("::");
1104
- if (idx !== -1) {
1105
- const fieldId = trigger.slice(0, idx);
1106
- const optionId = trigger.slice(idx + 2);
1107
- return { kind: "composite", triggerKey: trigger, fieldId, optionId };
1108
- }
1109
1207
  const direct = nodeMap.get(trigger);
1110
1208
  if (!direct) return void 0;
1111
1209
  if (direct.kind === "option") {
@@ -1157,11 +1255,6 @@ function visibleFieldIdsUnder(props, tagId, opts = {}) {
1157
1255
  const ownerDepthForTriggerKey = (triggerKey) => {
1158
1256
  const t = resolveTrigger(triggerKey, nodeMap);
1159
1257
  if (!t) return void 0;
1160
- if (t.kind === "composite") {
1161
- const f = fieldById.get(t.fieldId);
1162
- if (!f) return void 0;
1163
- return ownerDepthForField(f);
1164
- }
1165
1258
  if (t.kind === "field") {
1166
1259
  const f = fieldById.get(t.id);
1167
1260
  if (!f || f.button !== true) return void 0;
@@ -1244,6 +1337,84 @@ function visibleFieldsUnder(props, tagId, opts = {}) {
1244
1337
  const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((f) => [f.id, f]));
1245
1338
  return ids.map((id) => fieldById.get(id)).filter(Boolean);
1246
1339
  }
1340
+ function resolveVisibility(props, tagId, selectedKeys) {
1341
+ var _a, _b, _c, _d;
1342
+ const selected = new Set(selectedKeys != null ? selectedKeys : []);
1343
+ const baseFieldIds = visibleFieldIdsUnder(props, tagId, { selectedKeys: selected });
1344
+ const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((field) => [field.id, field]));
1345
+ const visible = new Set(baseFieldIds);
1346
+ const forced = /* @__PURE__ */ new Set();
1347
+ const optionsByFieldId = {};
1348
+ const optionIdsByFieldId = /* @__PURE__ */ new Map();
1349
+ const getOptionIds = (field) => {
1350
+ let ids = optionIdsByFieldId.get(field.id);
1351
+ if (!ids) {
1352
+ ids = fieldOptionIds(field);
1353
+ optionIdsByFieldId.set(field.id, ids);
1354
+ }
1355
+ return ids;
1356
+ };
1357
+ const ensureOptions = (field) => {
1358
+ const ids = getOptionIds(field);
1359
+ if (!ids.length) return void 0;
1360
+ if (!optionsByFieldId[field.id]) optionsByFieldId[field.id] = [...ids];
1361
+ return optionsByFieldId[field.id];
1362
+ };
1363
+ for (const fieldId of baseFieldIds) {
1364
+ const field = fieldById.get(fieldId);
1365
+ if (field) ensureOptions(field);
1366
+ }
1367
+ const effects = (_b = props.option_effects_for_buttons) != null ? _b : {};
1368
+ for (const triggerId of selected) {
1369
+ const targetRules = effects[triggerId];
1370
+ if (!targetRules) continue;
1371
+ for (const [targetFieldId, rule] of Object.entries(targetRules)) {
1372
+ const field = fieldById.get(targetFieldId);
1373
+ if (!field) continue;
1374
+ const isVisible = visible.has(targetFieldId);
1375
+ if (!isVisible && rule.forceVisible !== true) continue;
1376
+ if (!isVisible && rule.forceVisible === true) {
1377
+ visible.add(targetFieldId);
1378
+ forced.add(targetFieldId);
1379
+ }
1380
+ const orderedOptionIds = getOptionIds(field);
1381
+ if (!orderedOptionIds.length) continue;
1382
+ const known = new Set(orderedOptionIds);
1383
+ let allowed = (_c = optionsByFieldId[targetFieldId]) != null ? _c : [...orderedOptionIds];
1384
+ if (Array.isArray(rule.include) && rule.include.length) {
1385
+ const include2 = new Set(
1386
+ rule.include.filter((optionId) => known.has(optionId))
1387
+ );
1388
+ allowed = orderedOptionIds.filter(
1389
+ (optionId) => include2.has(optionId) && allowed.includes(optionId)
1390
+ );
1391
+ }
1392
+ if (Array.isArray(rule.exclude) && rule.exclude.length) {
1393
+ const exclude2 = new Set(
1394
+ rule.exclude.filter((optionId) => known.has(optionId))
1395
+ );
1396
+ allowed = allowed.filter((optionId) => !exclude2.has(optionId));
1397
+ }
1398
+ optionsByFieldId[targetFieldId] = allowed;
1399
+ }
1400
+ }
1401
+ const visibleFieldIds = baseFieldIds.filter((fieldId) => visible.has(fieldId));
1402
+ const seen = new Set(visibleFieldIds);
1403
+ for (const field of (_d = props.fields) != null ? _d : []) {
1404
+ if (!visible.has(field.id) || seen.has(field.id)) continue;
1405
+ seen.add(field.id);
1406
+ visibleFieldIds.push(field.id);
1407
+ ensureOptions(field);
1408
+ }
1409
+ for (const fieldId of Object.keys(optionsByFieldId)) {
1410
+ if (!visible.has(fieldId)) delete optionsByFieldId[fieldId];
1411
+ }
1412
+ return {
1413
+ fieldIds: visibleFieldIds,
1414
+ optionsByFieldId,
1415
+ forcedFieldIds: visibleFieldIds.filter((fieldId) => forced.has(fieldId))
1416
+ };
1417
+ }
1247
1418
 
1248
1419
  // src/core/validate/steps/visibility.ts
1249
1420
  function createFieldsVisibleUnder(v) {
@@ -1261,7 +1432,6 @@ function resolveRootTags(tags) {
1261
1432
  return roots.length ? roots : tags.slice(0, 1);
1262
1433
  }
1263
1434
  function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
1264
- var _a;
1265
1435
  const visible = visibleFieldsUnder(v.props, tagId, {
1266
1436
  selectedKeys
1267
1437
  });
@@ -1271,7 +1441,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
1271
1441
  const t = f.id;
1272
1442
  if (effectfulKeys.has(t)) triggers.push(t);
1273
1443
  }
1274
- for (const o of (_a = f.options) != null ? _a : []) {
1444
+ for (const { option: o } of walkFieldOptions(f)) {
1275
1445
  const t = o.id;
1276
1446
  if (effectfulKeys.has(t)) triggers.push(t);
1277
1447
  }
@@ -1280,7 +1450,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
1280
1450
  return triggers;
1281
1451
  }
1282
1452
  function runVisibilityRulesOnce(v) {
1283
- var _a, _b, _c, _d, _e;
1453
+ var _a, _b, _c, _d;
1284
1454
  for (const t of v.tags) {
1285
1455
  const visible = v.fieldsVisibleUnder(t.id);
1286
1456
  const seen = /* @__PURE__ */ new Map();
@@ -1330,9 +1500,9 @@ function runVisibilityRulesOnce(v) {
1330
1500
  let hasUtility = false;
1331
1501
  const utilityOptionIds = [];
1332
1502
  for (const f of visible) {
1333
- for (const o of (_c = f.options) != null ? _c : []) {
1503
+ for (const { option: o } of walkFieldOptions(f)) {
1334
1504
  if (!isServiceIdRef(o.service_id)) continue;
1335
- const role = (_e = (_d = o.pricing_role) != null ? _d : f.pricing_role) != null ? _e : "base";
1505
+ const role = (_d = (_c = o.pricing_role) != null ? _c : f.pricing_role) != null ? _d : "base";
1336
1506
  if (role === "base") hasBase = true;
1337
1507
  else if (role === "utility") {
1338
1508
  hasUtility = true;
@@ -1373,7 +1543,7 @@ function dedupeErrorsInPlace(v, startIndex) {
1373
1543
  v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
1374
1544
  }
1375
1545
  function validateVisibility(v, options = {}) {
1376
- var _a, _b, _c, _d, _e;
1546
+ var _a, _b, _c, _d, _e, _f;
1377
1547
  v.simulatedVisibilityContexts = [];
1378
1548
  const simulate = options.simulate === true;
1379
1549
  if (!simulate) {
@@ -1398,10 +1568,13 @@ function validateVisibility(v, options = {}) {
1398
1568
  for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
1399
1569
  effectfulKeys.add(key);
1400
1570
  }
1571
+ for (const key of Object.keys((_e = v.props.option_effects_for_buttons) != null ? _e : {})) {
1572
+ effectfulKeys.add(key);
1573
+ }
1401
1574
  }
1402
1575
  const roots = resolveRootTags(v.tags);
1403
1576
  const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
1404
- const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
1577
+ const originalSelected = new Set((_f = v.selectedKeys) != null ? _f : []);
1405
1578
  const errorsStart = v.errors.length;
1406
1579
  const visited = /* @__PURE__ */ new Set();
1407
1580
  const seenContexts = /* @__PURE__ */ new Set();
@@ -1542,7 +1715,7 @@ function validateStructure(v) {
1542
1715
 
1543
1716
  // src/core/validate/steps/identity.ts
1544
1717
  function validateIdentity(v) {
1545
- var _a, _b;
1718
+ var _a;
1546
1719
  const tags = v.tags;
1547
1720
  const fields = v.fields;
1548
1721
  {
@@ -1642,7 +1815,7 @@ function validateIdentity(v) {
1642
1815
  }
1643
1816
  }
1644
1817
  for (const f of fields) {
1645
- for (const o of (_b = f.options) != null ? _b : []) {
1818
+ for (const { option: o } of walkFieldOptions(f)) {
1646
1819
  if (!o.label || !o.label.trim()) {
1647
1820
  v.errors.push({
1648
1821
  code: "label_missing",
@@ -1657,25 +1830,11 @@ function validateIdentity(v) {
1657
1830
  }
1658
1831
 
1659
1832
  // src/core/validate/steps/option-maps.ts
1660
- function parseFieldOptionKey(key) {
1661
- const idx = key.indexOf("::");
1662
- if (idx === -1) return null;
1663
- const fieldId = key.slice(0, idx).trim();
1664
- const optionId = key.slice(idx + 2).trim();
1665
- if (!fieldId || !optionId) return null;
1666
- return { fieldId, optionId };
1667
- }
1668
- function hasOption(v, fid, oid) {
1669
- var _a;
1670
- const f = v.fieldById.get(fid);
1671
- if (!f) return false;
1672
- return !!((_a = f.options) != null ? _a : []).find((o) => o.id === oid);
1673
- }
1674
1833
  function validateOptionMaps(v) {
1675
- var _a, _b;
1834
+ var _a, _b, _c;
1676
1835
  const incMap = (_a = v.props.includes_for_buttons) != null ? _a : {};
1677
1836
  const excMap = (_b = v.props.excludes_for_buttons) != null ? _b : {};
1678
- const badKeyMessage = (key) => `Invalid trigger-map key "${key}". Expected a known node id (option or button-field), or "fieldId::optionId" pointing to an existing option.`;
1837
+ const badKeyMessage = (key) => `Invalid trigger-map key "${key}". Expected a known option id or button-field id.`;
1679
1838
  const validateTriggerKey = (key) => {
1680
1839
  const ref = v.nodeMap.get(key);
1681
1840
  if (ref) {
@@ -1694,19 +1853,7 @@ function validateOptionMaps(v) {
1694
1853
  }
1695
1854
  return { ok: false, nodeId: ref.id, affected: [ref.id] };
1696
1855
  }
1697
- const p = parseFieldOptionKey(key);
1698
- if (!p) return { ok: false };
1699
- if (!hasOption(v, p.fieldId, p.optionId))
1700
- return {
1701
- ok: false,
1702
- nodeId: p.fieldId,
1703
- affected: [p.fieldId, p.optionId]
1704
- };
1705
- return {
1706
- ok: true,
1707
- nodeId: p.fieldId,
1708
- affected: [p.fieldId, p.optionId]
1709
- };
1856
+ return { ok: false };
1710
1857
  };
1711
1858
  for (const k of Object.keys(incMap)) {
1712
1859
  const r = validateTriggerKey(k);
@@ -1732,6 +1879,57 @@ function validateOptionMaps(v) {
1732
1879
  });
1733
1880
  }
1734
1881
  }
1882
+ const effectMap = (_c = v.props.option_effects_for_buttons) != null ? _c : {};
1883
+ for (const [triggerKey, targets] of Object.entries(effectMap)) {
1884
+ const trigger = validateTriggerKey(triggerKey);
1885
+ if (!trigger.ok) {
1886
+ v.errors.push({
1887
+ code: "bad_option_effect_key",
1888
+ severity: "error",
1889
+ message: badKeyMessage(triggerKey),
1890
+ nodeId: trigger.nodeId,
1891
+ details: withAffected({ key: triggerKey }, trigger.affected)
1892
+ });
1893
+ }
1894
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
1895
+ const field = v.fieldById.get(targetFieldId);
1896
+ if (!field) {
1897
+ v.errors.push({
1898
+ code: "bad_option_effect_target",
1899
+ severity: "error",
1900
+ message: `Option effect trigger "${triggerKey}" targets unknown field "${targetFieldId}".`,
1901
+ details: withAffected(
1902
+ { key: triggerKey, targetFieldId },
1903
+ trigger.affected
1904
+ )
1905
+ });
1906
+ continue;
1907
+ }
1908
+ const validOptionIds = fieldOptionIdSet(field);
1909
+ const checkTargetOptions = (kind, optionIds) => {
1910
+ for (const optionId of optionIds != null ? optionIds : []) {
1911
+ if (validOptionIds.has(optionId)) continue;
1912
+ v.errors.push({
1913
+ code: "bad_option_effect_option",
1914
+ severity: "error",
1915
+ message: `Option effect trigger "${triggerKey}" references unknown ${kind} option "${optionId}" for field "${targetFieldId}".`,
1916
+ nodeId: targetFieldId,
1917
+ details: withAffected(
1918
+ {
1919
+ key: triggerKey,
1920
+ targetFieldId,
1921
+ optionId,
1922
+ kind
1923
+ },
1924
+ [targetFieldId, optionId]
1925
+ )
1926
+ });
1927
+ }
1928
+ };
1929
+ checkTargetOptions("include", effect == null ? void 0 : effect.include);
1930
+ checkTargetOptions("exclude", effect == null ? void 0 : effect.exclude);
1931
+ }
1932
+ }
1735
1933
  for (const k of Object.keys(incMap)) {
1736
1934
  if (!(k in excMap)) continue;
1737
1935
  const r = validateTriggerKey(k);
@@ -1745,27 +1943,231 @@ function validateOptionMaps(v) {
1745
1943
  }
1746
1944
  }
1747
1945
 
1748
- // src/utils/order-kind.ts
1749
- function normalizeSelectedTriggerKey(key, nodeMap) {
1750
- if (!key) return void 0;
1751
- const compositeIdx = key.indexOf("::");
1752
- if (compositeIdx !== -1) {
1753
- const fieldId = key.slice(0, compositeIdx).trim();
1754
- const optionId = key.slice(compositeIdx + 2).trim();
1755
- if (optionId) {
1756
- const optionRef = nodeMap.get(optionId);
1757
- if ((optionRef == null ? void 0 : optionRef.kind) === "option") {
1758
- return { nodeId: optionRef.id, nodeKind: "option" };
1946
+ // src/core/validate/steps/visibility-cycles.ts
1947
+ var MAX_VISIBILITY_CYCLE_DEPTH = 20;
1948
+ function validateVisibilityCycles(v) {
1949
+ const triggerById = buildTriggerIndex(v.fields);
1950
+ if (!triggerById.size) return;
1951
+ const fieldTriggers = buildFieldTriggerIndex(v.fields);
1952
+ const revealTargetsByTrigger = buildRevealIndex(v, triggerById);
1953
+ const reported = /* @__PURE__ */ new Set();
1954
+ for (const rootTriggerId of Array.from(triggerById.keys()).sort()) {
1955
+ const required = makeRequiredState(triggerById, [rootTriggerId]);
1956
+ walkFromTrigger({
1957
+ v,
1958
+ triggerById,
1959
+ fieldTriggers,
1960
+ revealTargetsByTrigger,
1961
+ rootTriggerId,
1962
+ currentTriggerId: rootTriggerId,
1963
+ required,
1964
+ path: [rootTriggerId],
1965
+ visited: /* @__PURE__ */ new Set(),
1966
+ reported,
1967
+ depth: 0
1968
+ });
1969
+ }
1970
+ }
1971
+ function buildTriggerIndex(fields) {
1972
+ const out = /* @__PURE__ */ new Map();
1973
+ const owners = optionOwnerMap(fields);
1974
+ for (const field of fields) {
1975
+ if (field.button === true) {
1976
+ out.set(field.id, {
1977
+ kind: "field",
1978
+ id: field.id,
1979
+ ownerFieldId: field.id
1980
+ });
1981
+ }
1982
+ }
1983
+ for (const [optionId, owner] of owners) {
1984
+ out.set(optionId, {
1985
+ kind: "option",
1986
+ id: optionId,
1987
+ ownerFieldId: owner.fieldId
1988
+ });
1989
+ }
1990
+ return out;
1991
+ }
1992
+ function buildFieldTriggerIndex(fields) {
1993
+ const out = /* @__PURE__ */ new Map();
1994
+ for (const field of fields) {
1995
+ const triggers = [];
1996
+ if (field.button === true) triggers.push(field.id);
1997
+ for (const visit of walkFieldOptions(field)) {
1998
+ triggers.push(visit.optionId);
1999
+ }
2000
+ out.set(field.id, triggers);
2001
+ }
2002
+ return out;
2003
+ }
2004
+ function buildRevealIndex(v, triggerById) {
2005
+ var _a, _b;
2006
+ const out = /* @__PURE__ */ new Map();
2007
+ const addReveal = (triggerId, targetFieldId) => {
2008
+ var _a2;
2009
+ if (!triggerById.has(triggerId)) return;
2010
+ if (!v.fieldById.has(targetFieldId)) return;
2011
+ const set = (_a2 = out.get(triggerId)) != null ? _a2 : /* @__PURE__ */ new Set();
2012
+ set.add(targetFieldId);
2013
+ out.set(triggerId, set);
2014
+ };
2015
+ for (const [triggerId, targetIds] of Object.entries(
2016
+ (_a = v.props.includes_for_buttons) != null ? _a : {}
2017
+ )) {
2018
+ for (const targetId of targetIds != null ? targetIds : []) addReveal(triggerId, targetId);
2019
+ }
2020
+ for (const [triggerId, targets] of Object.entries(
2021
+ (_b = v.props.option_effects_for_buttons) != null ? _b : {}
2022
+ )) {
2023
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
2024
+ if ((effect == null ? void 0 : effect.forceVisible) === true)
2025
+ addReveal(triggerId, targetFieldId);
2026
+ }
2027
+ }
2028
+ return new Map(
2029
+ Array.from(out.entries()).map(([triggerId, fieldIds]) => [
2030
+ triggerId,
2031
+ Array.from(fieldIds).sort()
2032
+ ])
2033
+ );
2034
+ }
2035
+ function walkFromTrigger(args) {
2036
+ var _a, _b, _c;
2037
+ if (args.depth >= MAX_VISIBILITY_CYCLE_DEPTH) return;
2038
+ const visitedKey = `${args.rootTriggerId}::${args.currentTriggerId}::${args.path.join(">")}`;
2039
+ if (args.visited.has(visitedKey)) return;
2040
+ args.visited.add(visitedKey);
2041
+ const revealedFieldIds = (_a = args.revealTargetsByTrigger.get(args.currentTriggerId)) != null ? _a : [];
2042
+ for (const revealedFieldId of revealedFieldIds) {
2043
+ const reachableTriggers = (_c = (_b = args.fieldTriggers.get(revealedFieldId)) == null ? void 0 : _b.slice().sort()) != null ? _c : [];
2044
+ for (const reachableTriggerId of reachableTriggers) {
2045
+ const invalidation = invalidatesRequiredPath(
2046
+ args.v,
2047
+ args.triggerById,
2048
+ reachableTriggerId,
2049
+ args.required
2050
+ );
2051
+ if (invalidation) {
2052
+ emitCycleError({
2053
+ v: args.v,
2054
+ rootTriggerId: args.rootTriggerId,
2055
+ revealedFieldId,
2056
+ conflictingTriggerId: reachableTriggerId,
2057
+ invalidatedId: invalidation.invalidatedId,
2058
+ path: [...args.path, reachableTriggerId],
2059
+ reported: args.reported
2060
+ });
2061
+ }
2062
+ if (args.path.includes(reachableTriggerId)) continue;
2063
+ walkFromTrigger({
2064
+ ...args,
2065
+ currentTriggerId: reachableTriggerId,
2066
+ required: addRequiredTrigger(
2067
+ args.triggerById,
2068
+ args.required,
2069
+ reachableTriggerId
2070
+ ),
2071
+ path: [...args.path, reachableTriggerId],
2072
+ depth: args.depth + 1
2073
+ });
2074
+ }
2075
+ }
2076
+ }
2077
+ function makeRequiredState(triggerById, triggerIds) {
2078
+ let required = {
2079
+ triggers: /* @__PURE__ */ new Set(),
2080
+ ownerFields: /* @__PURE__ */ new Set()
2081
+ };
2082
+ for (const triggerId of triggerIds) {
2083
+ required = addRequiredTrigger(triggerById, required, triggerId);
2084
+ }
2085
+ return required;
2086
+ }
2087
+ function addRequiredTrigger(triggerById, current, triggerId) {
2088
+ const next = {
2089
+ triggers: new Set(current.triggers),
2090
+ ownerFields: new Set(current.ownerFields)
2091
+ };
2092
+ const trigger = triggerById.get(triggerId);
2093
+ if (!trigger) return next;
2094
+ next.triggers.add(triggerId);
2095
+ next.ownerFields.add(trigger.ownerFieldId);
2096
+ return next;
2097
+ }
2098
+ function invalidatesRequiredPath(v, triggerById, conflictingTriggerId, required) {
2099
+ var _a, _b, _c, _d, _e, _f;
2100
+ for (const targetId of (_b = (_a = v.props.excludes_for_buttons) == null ? void 0 : _a[conflictingTriggerId]) != null ? _b : []) {
2101
+ if (required.ownerFields.has(targetId)) {
2102
+ return { invalidatedId: targetId };
2103
+ }
2104
+ const targetTrigger = triggerById.get(targetId);
2105
+ if ((targetTrigger == null ? void 0 : targetTrigger.kind) === "option" && required.triggers.has(targetId)) {
2106
+ return { invalidatedId: targetId };
2107
+ }
2108
+ }
2109
+ const effects = (_d = (_c = v.props.option_effects_for_buttons) == null ? void 0 : _c[conflictingTriggerId]) != null ? _d : {};
2110
+ for (const [targetFieldId, effect] of Object.entries(effects)) {
2111
+ if (!v.fieldById.has(targetFieldId)) continue;
2112
+ if ((_e = effect == null ? void 0 : effect.exclude) == null ? void 0 : _e.length) {
2113
+ const excluded = new Set(effect.exclude);
2114
+ for (const requiredTriggerId of required.triggers) {
2115
+ const requiredTrigger = triggerById.get(requiredTriggerId);
2116
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
2117
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
2118
+ if (excluded.has(requiredTriggerId)) {
2119
+ return { invalidatedId: requiredTriggerId };
2120
+ }
1759
2121
  }
1760
2122
  }
1761
- if (fieldId) {
1762
- const fieldRef = nodeMap.get(fieldId);
1763
- if ((fieldRef == null ? void 0 : fieldRef.kind) === "field") {
1764
- return { nodeId: fieldRef.id, nodeKind: "field" };
2123
+ if ((_f = effect == null ? void 0 : effect.include) == null ? void 0 : _f.length) {
2124
+ const included = new Set(effect.include);
2125
+ for (const requiredTriggerId of required.triggers) {
2126
+ const requiredTrigger = triggerById.get(requiredTriggerId);
2127
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
2128
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
2129
+ if (!included.has(requiredTriggerId)) {
2130
+ return { invalidatedId: requiredTriggerId };
2131
+ }
1765
2132
  }
1766
2133
  }
1767
- return void 0;
1768
2134
  }
2135
+ return void 0;
2136
+ }
2137
+ function emitCycleError(args) {
2138
+ const key = [
2139
+ args.rootTriggerId,
2140
+ args.conflictingTriggerId,
2141
+ args.invalidatedId,
2142
+ args.path.join(">")
2143
+ ].join("::");
2144
+ if (args.reported.has(key)) return;
2145
+ args.reported.add(key);
2146
+ args.v.errors.push({
2147
+ code: "visibility_dependency_cycle",
2148
+ severity: "error",
2149
+ message: `Visibility dependency cycle: trigger "${args.rootTriggerId}" reveals "${args.revealedFieldId}", but reachable trigger "${args.conflictingTriggerId}" can hide or remove "${args.invalidatedId}".`,
2150
+ nodeId: args.conflictingTriggerId,
2151
+ details: withAffected(
2152
+ {
2153
+ rootTriggerId: args.rootTriggerId,
2154
+ conflictingTriggerId: args.conflictingTriggerId,
2155
+ invalidatedId: args.invalidatedId,
2156
+ path: args.path
2157
+ },
2158
+ [
2159
+ args.rootTriggerId,
2160
+ args.revealedFieldId,
2161
+ args.conflictingTriggerId,
2162
+ args.invalidatedId
2163
+ ]
2164
+ )
2165
+ });
2166
+ }
2167
+
2168
+ // src/utils/order-kind.ts
2169
+ function normalizeSelectedTriggerKey(key, nodeMap) {
2170
+ if (!key) return void 0;
1769
2171
  const ref = nodeMap.get(key);
1770
2172
  if (!ref) return void 0;
1771
2173
  if (ref.kind !== "field" && ref.kind !== "option") return void 0;
@@ -1924,8 +2326,7 @@ function validateUtilityMarkers(v) {
1924
2326
  "percent"
1925
2327
  ]);
1926
2328
  for (const f of v.fields) {
1927
- const optsArr = Array.isArray(f.options) ? f.options : [];
1928
- for (const o of optsArr) {
2329
+ for (const { option: o } of walkFieldOptions(f)) {
1929
2330
  const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
1930
2331
  const hasService = isServiceIdRef(o.service_id);
1931
2332
  const util = (_c = o.meta) == null ? void 0 : _c.utility;
@@ -2161,13 +2562,13 @@ function normalizeServiceRef(value) {
2161
2562
 
2162
2563
  // src/core/validate/steps/rates.ts
2163
2564
  function validateRates(v) {
2164
- var _a, _b, _c;
2565
+ var _a, _b;
2165
2566
  const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
2166
2567
  for (const f of v.fields) {
2167
2568
  if (!isMultiField(f)) continue;
2168
2569
  const baseRates = [];
2169
- for (const o of (_a = f.options) != null ? _a : []) {
2170
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
2570
+ for (const { option: o } of walkFieldOptions(f)) {
2571
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
2171
2572
  if (role !== "base") continue;
2172
2573
  const sid = o.service_id;
2173
2574
  if (!isServiceIdRef(sid)) continue;
@@ -2578,7 +2979,7 @@ function effectiveConstraints(v, tagId) {
2578
2979
  return out;
2579
2980
  }
2580
2981
  function validateConstraints(v) {
2581
- var _a, _b;
2982
+ var _a;
2582
2983
  for (const t of v.tags) {
2583
2984
  const eff = effectiveConstraints(v, t.id);
2584
2985
  const hasAnyRequired = Object.values(eff).some(
@@ -2587,7 +2988,7 @@ function validateConstraints(v) {
2587
2988
  if (!hasAnyRequired) continue;
2588
2989
  const visible = v.fieldsVisibleUnder(t.id);
2589
2990
  for (const f of visible) {
2590
- for (const o of (_a = f.options) != null ? _a : []) {
2991
+ for (const { option: o } of walkFieldOptions(f)) {
2591
2992
  if (!isServiceIdRef(o.service_id)) continue;
2592
2993
  const svc = getServiceCapability(v.serviceMap, o.service_id);
2593
2994
  if (!svc || typeof svc !== "object") continue;
@@ -2641,7 +3042,7 @@ function validateConstraints(v) {
2641
3042
  if (!row) continue;
2642
3043
  const from = row.from === true;
2643
3044
  const to = row.to === true;
2644
- const origin = String((_b = row.origin) != null ? _b : "");
3045
+ const origin = String((_a = row.origin) != null ? _a : "");
2645
3046
  v.errors.push({
2646
3047
  code: "constraint_overridden",
2647
3048
  severity: "warning",
@@ -2675,14 +3076,14 @@ function validateCustomFields(v) {
2675
3076
 
2676
3077
  // src/core/validate/steps/global-utility-guard.ts
2677
3078
  function validateGlobalUtilityGuard(v) {
2678
- var _a, _b, _c;
3079
+ var _a, _b;
2679
3080
  if (!v.options.globalUtilityGuard) return;
2680
3081
  let hasUtility = false;
2681
3082
  let hasBase = false;
2682
3083
  for (const f of v.fields) {
2683
- for (const o of (_a = f.options) != null ? _a : []) {
3084
+ for (const { option: o } of walkFieldOptions(f)) {
2684
3085
  if (!isServiceIdRef(o.service_id)) continue;
2685
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
3086
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
2686
3087
  if (role === "base") hasBase = true;
2687
3088
  else if (role === "utility") hasUtility = true;
2688
3089
  if (hasUtility && hasBase) break;
@@ -2884,7 +3285,7 @@ function applyFilterAllowLists(tagId, fieldId, filter) {
2884
3285
  return true;
2885
3286
  }
2886
3287
  function collectServiceItems(args) {
2887
- var _a, _b, _c, _d, _e;
3288
+ var _a, _b, _c, _d;
2888
3289
  const filter = args.filter;
2889
3290
  const roleFilter = (_a = filter == null ? void 0 : filter.role) != null ? _a : "both";
2890
3291
  const where = filter == null ? void 0 : filter.where;
@@ -2934,7 +3335,7 @@ function collectServiceItems(args) {
2934
3335
  affectedIds: [`field:${f.id}`, `service:${String(fSid)}`]
2935
3336
  });
2936
3337
  }
2937
- for (const o of (_d = f.options) != null ? _d : []) {
3338
+ for (const { option: o } of walkFieldOptions(f)) {
2938
3339
  const oSid = o.service_id;
2939
3340
  if (!isServiceIdRef2(oSid)) continue;
2940
3341
  const role = fieldRoleOf(f, o);
@@ -3019,7 +3420,7 @@ function collectServiceItems(args) {
3019
3420
  }
3020
3421
  } else if (includeGroupFallbacks) {
3021
3422
  const allowPrimaries = new Set(
3022
- ((_e = args.visiblePrimaries) != null ? _e : []).map((x) => String(x))
3423
+ ((_d = args.visiblePrimaries) != null ? _d : []).map((x) => String(x))
3023
3424
  );
3024
3425
  for (const primaryKey of allowPrimaries) {
3025
3426
  const list = globalFb[primaryKey];
@@ -3100,17 +3501,15 @@ function affectedFromItems(items) {
3100
3501
  return uniq(ids);
3101
3502
  }
3102
3503
  function visibleGroupNodeIds(tag, fields) {
3103
- var _a;
3104
3504
  const ids = [tag.id];
3105
3505
  for (const f of fields) {
3106
- for (const o of (_a = f.options) != null ? _a : []) {
3506
+ for (const { option: o } of walkFieldOptions(f)) {
3107
3507
  ids.push(o.id);
3108
3508
  }
3109
3509
  }
3110
3510
  return uniq(ids);
3111
3511
  }
3112
3512
  function visibleGroupPrimaries(tag, fields) {
3113
- var _a;
3114
3513
  const prim = [];
3115
3514
  const tagSid = tag.service_id;
3116
3515
  if (typeof tagSid === "string" || typeof tagSid === "number" && Number.isFinite(tagSid)) {
@@ -3121,7 +3520,7 @@ function visibleGroupPrimaries(tag, fields) {
3121
3520
  if (typeof fsid === "string" || typeof fsid === "number" && Number.isFinite(fsid)) {
3122
3521
  prim.push(fsid);
3123
3522
  }
3124
- for (const o of (_a = f.options) != null ? _a : []) {
3523
+ for (const { option: o } of walkFieldOptions(f)) {
3125
3524
  const osid = o.service_id;
3126
3525
  if (typeof osid === "string" || typeof osid === "number" && Number.isFinite(osid)) {
3127
3526
  prim.push(osid);
@@ -3345,6 +3744,7 @@ function validate(props, ctx = {}) {
3345
3744
  validateStructure(v);
3346
3745
  validateIdentity(v);
3347
3746
  validateOptionMaps(v);
3747
+ validateVisibilityCycles(v);
3348
3748
  validateOrderKinds(v);
3349
3749
  v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
3350
3750
  const visSim = readVisibilitySimOpts(options);
@@ -3478,14 +3878,14 @@ var BuilderImpl = class {
3478
3878
  const showOptions = showSet.has(f.id);
3479
3879
  if (!showOptions) continue;
3480
3880
  if (!Array.isArray(f.options)) continue;
3481
- for (const o of f.options) {
3881
+ for (const { option: o, parentId } of walkFieldOptions(f)) {
3482
3882
  nodes.push({
3483
3883
  id: o.id,
3484
3884
  kind: "option",
3485
3885
  label: o.label
3486
3886
  });
3487
3887
  const e = {
3488
- from: f.id,
3888
+ from: parentId != null ? parentId : f.id,
3489
3889
  to: o.id,
3490
3890
  kind: "option",
3491
3891
  meta: { ownerField: f.id }
@@ -3532,7 +3932,7 @@ var BuilderImpl = class {
3532
3932
  return { nodes, edges };
3533
3933
  }
3534
3934
  cleanedProps() {
3535
- var _a, _b, _c, _d, _e;
3935
+ var _a, _b, _c, _d, _e, _f;
3536
3936
  const fieldIds = new Set(this.props.fields.map((f) => f.id));
3537
3937
  const optionIds = /* @__PURE__ */ new Set();
3538
3938
  this.optionOwnerById.forEach((_v, oid) => optionIds.add(oid));
@@ -3544,6 +3944,7 @@ var BuilderImpl = class {
3544
3944
  }
3545
3945
  const incMap = (_c = this.props.includes_for_buttons) != null ? _c : {};
3546
3946
  const excMap = (_d = this.props.excludes_for_buttons) != null ? _d : {};
3947
+ const effectMap = (_e = this.props.option_effects_for_buttons) != null ? _e : {};
3547
3948
  const includedByButtons = /* @__PURE__ */ new Set();
3548
3949
  const referencedKeys = /* @__PURE__ */ new Set();
3549
3950
  const referencedOwnerFields = /* @__PURE__ */ new Set();
@@ -3563,6 +3964,14 @@ var BuilderImpl = class {
3563
3964
  void fid;
3564
3965
  }
3565
3966
  }
3967
+ for (const [key, targets] of Object.entries(effectMap)) {
3968
+ referencedKeys.add(key);
3969
+ const owner = this.optionOwnerById.get(key);
3970
+ if (owner) referencedOwnerFields.add(owner.fieldId);
3971
+ for (const [fid, effect] of Object.entries(targets != null ? targets : {})) {
3972
+ if ((effect == null ? void 0 : effect.forceVisible) === true) includedByButtons.add(fid);
3973
+ }
3974
+ }
3566
3975
  const boundIds = /* @__PURE__ */ new Set();
3567
3976
  for (const f of this.props.fields) {
3568
3977
  const b = f.bind_id;
@@ -3580,6 +3989,7 @@ var BuilderImpl = class {
3580
3989
  return bound || included || referenced || !excluded;
3581
3990
  });
3582
3991
  const allowedTargets = new Set(fields.map((f) => f.id));
3992
+ const allowedFieldById = new Map(fields.map((f) => [f.id, f]));
3583
3993
  const pruneButtons = (src) => {
3584
3994
  if (!src) return void 0;
3585
3995
  const out2 = {};
@@ -3599,13 +4009,52 @@ var BuilderImpl = class {
3599
4009
  const excludes_for_buttons = pruneButtons(
3600
4010
  this.props.excludes_for_buttons
3601
4011
  );
4012
+ const pruneOptionEffects = (src) => {
4013
+ var _a2, _b2, _c2, _d2;
4014
+ if (!src) return void 0;
4015
+ const out2 = {};
4016
+ for (const [key, targets] of Object.entries(src)) {
4017
+ const keyIsValid = optionIds.has(key) || fieldIds.has(key);
4018
+ if (!keyIsValid) continue;
4019
+ const cleanedTargets = {};
4020
+ for (const [targetFieldId, effect] of Object.entries(
4021
+ targets != null ? targets : {}
4022
+ )) {
4023
+ const field = allowedFieldById.get(targetFieldId);
4024
+ if (!field || !effect) continue;
4025
+ const validOptionIds = fieldOptionIdSet(field);
4026
+ const include2 = Array.from(
4027
+ new Set((_a2 = effect.include) != null ? _a2 : [])
4028
+ ).filter((optionId) => validOptionIds.has(optionId));
4029
+ const exclude2 = Array.from(
4030
+ new Set((_b2 = effect.exclude) != null ? _b2 : [])
4031
+ ).filter((optionId) => validOptionIds.has(optionId));
4032
+ const next = {
4033
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
4034
+ ...include2.length ? { include: include2 } : {},
4035
+ ...exclude2.length ? { exclude: exclude2 } : {}
4036
+ };
4037
+ if (next.forceVisible === true || ((_c2 = next.include) == null ? void 0 : _c2.length) || ((_d2 = next.exclude) == null ? void 0 : _d2.length)) {
4038
+ cleanedTargets[targetFieldId] = next;
4039
+ }
4040
+ }
4041
+ if (Object.keys(cleanedTargets).length) {
4042
+ out2[key] = cleanedTargets;
4043
+ }
4044
+ }
4045
+ return Object.keys(out2).length ? out2 : void 0;
4046
+ };
4047
+ const option_effects_for_buttons = pruneOptionEffects(
4048
+ this.props.option_effects_for_buttons
4049
+ );
3602
4050
  const out = {
3603
4051
  filters: this.props.filters.slice(),
3604
4052
  fields,
3605
4053
  ...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
3606
4054
  ...includes_for_buttons && { includes_for_buttons },
3607
4055
  ...excludes_for_buttons && { excludes_for_buttons },
3608
- schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
4056
+ ...option_effects_for_buttons && { option_effects_for_buttons },
4057
+ schema_version: (_f = this.props.schema_version) != null ? _f : "1.0",
3609
4058
  // keep fallbacks & other maps as-is
3610
4059
  ...this.props.fallbacks ? { fallbacks: this.props.fallbacks } : {}
3611
4060
  };
@@ -3618,12 +4067,15 @@ var BuilderImpl = class {
3618
4067
  return cloneDeep2(this.options);
3619
4068
  }
3620
4069
  visibleFields(tagId, selectedKeys) {
4070
+ return this.resolveVisibility(tagId, selectedKeys).fieldIds;
4071
+ }
4072
+ resolveVisibility(tagId, selectedKeys) {
3621
4073
  var _a;
3622
- return visibleFieldIdsUnder(this.props, tagId, {
3623
- selectedKeys: new Set(
3624
- (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
3625
- )
3626
- });
4074
+ return resolveVisibility(
4075
+ this.props,
4076
+ tagId,
4077
+ (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
4078
+ );
3627
4079
  }
3628
4080
  getNodeMap() {
3629
4081
  if (!this._nodemap) this._nodemap = buildNodeMap(this.getProps());
@@ -3638,9 +4090,8 @@ var BuilderImpl = class {
3638
4090
  for (const t of this.props.filters) this.tagById.set(t.id, t);
3639
4091
  for (const f of this.props.fields) {
3640
4092
  this.fieldById.set(f.id, f);
3641
- if (Array.isArray(f.options)) {
3642
- for (const o of f.options)
3643
- this.optionOwnerById.set(o.id, { fieldId: f.id });
4093
+ for (const [optionId, owner] of optionOwnerMap([f])) {
4094
+ this.optionOwnerById.set(optionId, { fieldId: owner.fieldId });
3644
4095
  }
3645
4096
  }
3646
4097
  }
@@ -5077,20 +5528,20 @@ function isFiniteNumber2(v) {
5077
5528
 
5078
5529
  // src/utils/build-order-snapshot/selection.ts
5079
5530
  function isOptionBased(f) {
5080
- const hasOptions = Array.isArray(f.options) && f.options.length > 0;
5531
+ const hasOptions = fieldOptionIdSet(f).size > 0;
5081
5532
  return hasOptions || isMultiField(f);
5082
5533
  }
5083
5534
  function toSelectedOptionKeys(byField) {
5084
5535
  const keys = [];
5085
- for (const [fieldId, optionIds] of Object.entries(byField != null ? byField : {})) {
5536
+ for (const optionIds of Object.values(byField != null ? byField : {})) {
5086
5537
  for (const optionId of optionIds != null ? optionIds : []) {
5087
- keys.push(`${fieldId}::${optionId}`);
5538
+ keys.push(optionId);
5088
5539
  }
5089
5540
  }
5090
5541
  return keys;
5091
5542
  }
5092
- function getSelectedOptionsByFieldId(selection, fieldById, mode) {
5093
- var _a, _b;
5543
+ function getSelectedOptionsByFieldId(selection, fieldById, mode, visibleOptionsByFieldId) {
5544
+ var _a;
5094
5545
  const collected = {};
5095
5546
  for (const visit of buildSelectedNodeVisitOrder(selection, fieldById)) {
5096
5547
  if (visit.kind !== "option") continue;
@@ -5101,18 +5552,18 @@ function getSelectedOptionsByFieldId(selection, fieldById, mode) {
5101
5552
  for (const [fieldId, optionIds] of Object.entries(collected)) {
5102
5553
  const field = fieldById.get(fieldId);
5103
5554
  if (!field) continue;
5104
- const validOptionIds = new Set(
5105
- ((_a = field.options) != null ? _a : []).map((option) => option.id)
5106
- );
5555
+ const validOptionIds = fieldOptionIdSet(field);
5556
+ const visibleOptionIds = (visibleOptionsByFieldId == null ? void 0 : visibleOptionsByFieldId[fieldId]) ? new Set(visibleOptionsByFieldId[fieldId]) : void 0;
5107
5557
  const dedupedValid = [];
5108
5558
  const seen = /* @__PURE__ */ new Set();
5109
5559
  for (const optionId of optionIds) {
5110
5560
  if (!validOptionIds.has(optionId)) continue;
5561
+ if (visibleOptionIds && !visibleOptionIds.has(optionId)) continue;
5111
5562
  if (seen.has(optionId)) continue;
5112
5563
  seen.add(optionId);
5113
5564
  dedupedValid.push(optionId);
5114
5565
  }
5115
- const isMulti = ((_b = field.meta) == null ? void 0 : _b.multi) === true;
5566
+ const isMulti = ((_a = field.meta) == null ? void 0 : _a.multi) === true;
5116
5567
  const normalized = mode === "prod" && !isMulti ? dedupedValid.length ? [dedupedValid[dedupedValid.length - 1]] : [] : dedupedValid;
5117
5568
  if (normalized.length) out[fieldId] = normalized;
5118
5569
  }
@@ -5129,57 +5580,49 @@ function buildSelectedNodeVisitOrder(selection, fieldById) {
5129
5580
  out.push({ kind: "field", fieldId });
5130
5581
  }
5131
5582
  function pushOption(fieldId, optionId) {
5132
- const key = `option:${fieldId}::${optionId}`;
5583
+ const key = `option:${optionId}`;
5133
5584
  if (seen.has(key)) return;
5134
5585
  seen.add(key);
5135
5586
  out.push({ kind: "option", fieldId, optionId });
5136
5587
  }
5137
- for (const item of (_a = selection.optionTraversalOrder) != null ? _a : []) {
5138
- pushOption(item.fieldId, item.optionId);
5588
+ for (const optionId of (_a = selection.optionTraversalOrder) != null ? _a : []) {
5589
+ const ownerField = findOptionOwnerField(fieldById.values(), optionId);
5590
+ if (ownerField) pushOption(ownerField.id, optionId);
5139
5591
  }
5140
5592
  for (const rawKey of (_b = selection.selectedKeys) != null ? _b : []) {
5141
5593
  const key = String(rawKey);
5142
- if (key.includes("::")) {
5143
- const [fieldId, optionId] = key.split("::", 2);
5144
- if (fieldId && optionId) pushOption(fieldId, optionId);
5145
- continue;
5146
- }
5147
5594
  const field = fieldById.get(key);
5148
5595
  if (field) {
5149
5596
  pushField(field.id);
5150
5597
  continue;
5151
5598
  }
5152
- const ownerField = findOptionOwnerField(key, fieldById);
5599
+ const ownerField = findOptionOwnerField(fieldById.values(), key);
5153
5600
  if (ownerField) pushOption(ownerField.id, key);
5154
5601
  }
5155
5602
  for (const [fieldId, optionIds] of Object.entries(
5156
5603
  (_c = selection.optionSelectionsByFieldId) != null ? _c : {}
5157
5604
  )) {
5158
- if (!fieldById.has(fieldId)) continue;
5605
+ const hintedField = fieldById.get(fieldId);
5606
+ if (!hintedField) continue;
5159
5607
  for (const optionId of optionIds != null ? optionIds : []) {
5160
- pushOption(fieldId, optionId);
5608
+ const ownerField = findOptionOwnerField(fieldById.values(), optionId);
5609
+ if ((ownerField == null ? void 0 : ownerField.id) === hintedField.id) {
5610
+ pushOption(ownerField.id, optionId);
5611
+ }
5161
5612
  }
5162
5613
  }
5163
5614
  return out;
5164
5615
  }
5165
- function findOptionOwnerField(optionId, fieldById) {
5166
- var _a;
5167
- for (const field of fieldById.values()) {
5168
- if ((_a = field.options) == null ? void 0 : _a.some((option) => option.id === optionId)) return field;
5169
- }
5170
- return void 0;
5171
- }
5172
5616
 
5173
5617
  // src/utils/build-order-snapshot/services.ts
5174
5618
  function isServiceBased(field) {
5175
- var _a;
5176
5619
  if (field.service_id !== void 0 && field.service_id !== null) return true;
5177
- return !!((_a = field.options) == null ? void 0 : _a.some(
5178
- (item) => item.service_id !== void 0 && item.service_id !== null
5179
- ));
5620
+ return walkFieldOptions(field).some(
5621
+ ({ option }) => option.service_id !== void 0 && option.service_id !== null
5622
+ );
5180
5623
  }
5181
5624
  function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById, services) {
5182
- var _a, _b, _c, _d;
5625
+ var _a, _b, _c;
5183
5626
  const serviceMap = {};
5184
5627
  const visible = new Set(visibleFieldIds);
5185
5628
  const selectedBaseServices = [];
@@ -5206,9 +5649,9 @@ function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById,
5206
5649
  }
5207
5650
  continue;
5208
5651
  }
5209
- const option = (_b = field.options) == null ? void 0 : _b.find((item) => item.id === visit.optionId);
5652
+ const option = findFieldOption(field, visit.optionId);
5210
5653
  if (!option) continue;
5211
- const role = (_d = (_c = option.pricing_role) != null ? _c : field.pricing_role) != null ? _d : "base";
5654
+ const role = (_c = (_b = option.pricing_role) != null ? _b : field.pricing_role) != null ? _c : "base";
5212
5655
  if (role === "utility") continue;
5213
5656
  if (option.service_id !== void 0 && option.service_id !== null) {
5214
5657
  addSelectedBaseService(option.id, option.service_id);
@@ -5339,16 +5782,15 @@ function resolveQuantity(visibleFieldIds, fieldById, tagById, selection, tagId,
5339
5782
  return { quantity: hostDefault, source: { kind: "default", defaultedFromHost: true } };
5340
5783
  }
5341
5784
  function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selection, tagId) {
5342
- var _a, _b, _c;
5785
+ var _a, _b;
5343
5786
  const visible = new Set(visibleFieldIds);
5344
5787
  const visits = buildSelectedNodeVisitOrder(selection, fieldById);
5345
5788
  for (const visit of visits) {
5346
5789
  if (visit.kind !== "option") continue;
5347
5790
  if (!visible.has(visit.fieldId)) continue;
5348
5791
  const field = fieldById.get(visit.fieldId);
5349
- if (!((_a = field == null ? void 0 : field.options) == null ? void 0 : _a.length)) continue;
5350
- const option = field.options.find((item) => item.id === visit.optionId);
5351
- const quantity = readPositiveFiniteNumber((_b = option == null ? void 0 : option.meta) == null ? void 0 : _b.quantityDefault);
5792
+ const option = findFieldOption(field, visit.optionId);
5793
+ const quantity = readPositiveFiniteNumber((_a = option == null ? void 0 : option.meta) == null ? void 0 : _a.quantityDefault);
5352
5794
  if (quantity !== void 0) {
5353
5795
  return { quantity, source: { kind: "option", id: option.id } };
5354
5796
  }
@@ -5363,7 +5805,7 @@ function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selecti
5363
5805
  }
5364
5806
  }
5365
5807
  const tag = tagById.get(tagId);
5366
- const tagQuantity = readPositiveFiniteNumber((_c = tag == null ? void 0 : tag.meta) == null ? void 0 : _c.quantityDefault);
5808
+ const tagQuantity = readPositiveFiniteNumber((_b = tag == null ? void 0 : tag.meta) == null ? void 0 : _b.quantityDefault);
5367
5809
  if (tagQuantity !== void 0) {
5368
5810
  return { quantity: tagQuantity, source: { kind: "tag", id: tagId } };
5369
5811
  }
@@ -5466,12 +5908,10 @@ function collectUtilityLineItems(visibleFieldIds, fieldById, selection, selected
5466
5908
  const item = buildUtilityItemFromMarker(field.id, marker, quantity, value);
5467
5909
  if (item) items.push(item);
5468
5910
  }
5469
- if (Array.isArray(field.options) && field.options.length) {
5470
- const selectedOptionIds = (_c = selectedOptionsByFieldId[field.id]) != null ? _c : [];
5471
- if (!selectedOptionIds.length) continue;
5472
- const optById = new Map(field.options.map((o) => [o.id, o]));
5911
+ const selectedOptionIds = (_c = selectedOptionsByFieldId[field.id]) != null ? _c : [];
5912
+ if (selectedOptionIds.length) {
5473
5913
  for (const oid of selectedOptionIds) {
5474
- const option = optById.get(oid);
5914
+ const option = findFieldOption(field, oid);
5475
5915
  if (!option) continue;
5476
5916
  if (((_d = option.pricing_role) != null ? _d : "base") !== "utility") continue;
5477
5917
  const optionMarker = readUtilityMarker((_e = option.meta) == null ? void 0 : _e.utility);
@@ -5587,7 +6027,7 @@ function buildDevWarnings(props, svcMap, originalFallbacks, fieldById, visibleFi
5587
6027
 
5588
6028
  // src/utils/build-order-snapshot/index.ts
5589
6029
  function buildOrderSnapshot(props, builder, selection, services, settings = {}) {
5590
- var _a, _b, _c, _d, _e, _f, _g, _h;
6030
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
5591
6031
  const mode = (_a = settings.mode) != null ? _a : "prod";
5592
6032
  const hostDefaultQty = Number.isFinite((_b = settings.hostDefaultQuantity) != null ? _b : 1) ? settings.hostDefaultQuantity : 1;
5593
6033
  const fbSettings = {
@@ -5600,14 +6040,31 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5600
6040
  const builtAt = (/* @__PURE__ */ new Date()).toISOString();
5601
6041
  const tagId = selection.activeTagId;
5602
6042
  const selectedButtonKeys = (_d = selection.selectedKeys) != null ? _d : toSelectedOptionKeys(selection.optionSelectionsByFieldId);
5603
- const visibleFieldIds = builder.visibleFields(tagId, selectedButtonKeys);
5604
6043
  const tagById = new Map(((_e = props.filters) != null ? _e : []).map((t) => [t.id, t]));
5605
6044
  const fieldById = new Map(((_f = props.fields) != null ? _f : []).map((f) => [f.id, f]));
5606
- const tagConstraints = (_h = (_g = tagById.get(tagId)) == null ? void 0 : _g.constraints) != null ? _h : void 0;
6045
+ const resolve = typeof builder.resolveVisibility === "function" ? builder.resolveVisibility.bind(builder) : void 0;
6046
+ let resolvedVisibility = resolve == null ? void 0 : resolve(tagId, selectedButtonKeys);
6047
+ let visibleFieldIds = (_g = resolvedVisibility == null ? void 0 : resolvedVisibility.fieldIds) != null ? _g : builder.visibleFields(tagId, selectedButtonKeys);
6048
+ const filteredSelectedButtonKeys = filterSelectedKeysByVisibility(
6049
+ selectedButtonKeys,
6050
+ visibleFieldIds,
6051
+ resolvedVisibility == null ? void 0 : resolvedVisibility.optionsByFieldId,
6052
+ fieldById
6053
+ );
6054
+ if (resolve && filteredSelectedButtonKeys.join("\0") !== selectedButtonKeys.join("\0")) {
6055
+ resolvedVisibility = resolve(tagId, filteredSelectedButtonKeys);
6056
+ visibleFieldIds = resolvedVisibility.fieldIds;
6057
+ }
6058
+ const effectiveSelection = {
6059
+ ...selection,
6060
+ selectedKeys: filteredSelectedButtonKeys
6061
+ };
6062
+ const tagConstraints = (_i = (_h = tagById.get(tagId)) == null ? void 0 : _h.constraints) != null ? _i : void 0;
5607
6063
  const selectedOptionsByFieldId = getSelectedOptionsByFieldId(
5608
- selection,
6064
+ effectiveSelection,
5609
6065
  fieldById,
5610
- mode
6066
+ mode,
6067
+ resolvedVisibility == null ? void 0 : resolvedVisibility.optionsByFieldId
5611
6068
  );
5612
6069
  const selectionFields = visibleFieldIds.map((fid) => fieldById.get(fid)).filter((f) => !!f).map((f) => {
5613
6070
  var _a2;
@@ -5621,21 +6078,21 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5621
6078
  const { formValues, selections } = buildInputs(
5622
6079
  visibleFieldIds,
5623
6080
  fieldById,
5624
- selection,
6081
+ effectiveSelection,
5625
6082
  selectedOptionsByFieldId
5626
6083
  );
5627
6084
  const qtyRes = resolveQuantity(
5628
6085
  visibleFieldIds,
5629
6086
  fieldById,
5630
6087
  tagById,
5631
- selection,
6088
+ effectiveSelection,
5632
6089
  tagId,
5633
6090
  hostDefaultQty
5634
6091
  );
5635
6092
  const { serviceMap, servicesList } = resolveServices(
5636
6093
  tagId,
5637
6094
  visibleFieldIds,
5638
- selection,
6095
+ effectiveSelection,
5639
6096
  tagById,
5640
6097
  fieldById,
5641
6098
  services
@@ -5657,7 +6114,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5657
6114
  const utilities = collectUtilityLineItems(
5658
6115
  visibleFieldIds,
5659
6116
  fieldById,
5660
- selection,
6117
+ effectiveSelection,
5661
6118
  selectedOptionsByFieldId,
5662
6119
  qtyRes.quantity
5663
6120
  );
@@ -5667,7 +6124,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5667
6124
  prunedFallbacks.original,
5668
6125
  fieldById,
5669
6126
  visibleFieldIds,
5670
- selection
6127
+ effectiveSelection
5671
6128
  ) : void 0;
5672
6129
  const meta = {
5673
6130
  schema_version: props.schema_version,
@@ -5680,7 +6137,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5680
6137
  tagId,
5681
6138
  visibleFieldIds,
5682
6139
  fieldById,
5683
- selection,
6140
+ effectiveSelection,
5684
6141
  selectedOptionsByFieldId
5685
6142
  ),
5686
6143
  policy: toSnapshotPolicy(fbSettings)
@@ -5692,7 +6149,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5692
6149
  builtAt,
5693
6150
  selection: {
5694
6151
  tag: tagId,
5695
- buttons: selectedButtonKeys,
6152
+ buttons: filteredSelectedButtonKeys,
5696
6153
  fields: selectionFields
5697
6154
  },
5698
6155
  inputs: { form: formValues, selections },
@@ -5710,6 +6167,24 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5710
6167
  meta
5711
6168
  };
5712
6169
  }
6170
+ function filterSelectedKeysByVisibility(selectedKeys, visibleFieldIds, optionsByFieldId, fieldById) {
6171
+ if (!optionsByFieldId) return selectedKeys;
6172
+ const visibleFields = new Set(visibleFieldIds);
6173
+ const out = [];
6174
+ for (const rawKey of selectedKeys) {
6175
+ const key = String(rawKey);
6176
+ if (fieldById.has(key)) {
6177
+ if (visibleFields.has(key)) out.push(key);
6178
+ continue;
6179
+ }
6180
+ const owner = findOptionOwnerField(fieldById.values(), key);
6181
+ if (!owner || !visibleFields.has(owner.id)) continue;
6182
+ const allowed = optionsByFieldId[owner.id];
6183
+ if (allowed && !allowed.includes(key)) continue;
6184
+ out.push(key);
6185
+ }
6186
+ return out;
6187
+ }
5713
6188
 
5714
6189
  // src/core/fallback-editor.ts
5715
6190
  function createFallbackEditor(options = {}) {
@@ -6189,6 +6664,97 @@ function bumpSuffix(old) {
6189
6664
  return `${stem}${parseInt(m[2], 10) + 1}`;
6190
6665
  }
6191
6666
 
6667
+ // src/react/canvas/editor/editor-utils.ts
6668
+ function ownerOfOption(props, optionId) {
6669
+ var _a;
6670
+ for (const f of (_a = props.fields) != null ? _a : []) {
6671
+ const found = findOptionLocationInField(f, optionId);
6672
+ if (found) return { fieldId: f.id, index: found.index };
6673
+ }
6674
+ return null;
6675
+ }
6676
+ function findMutableOption(props, optionId) {
6677
+ var _a;
6678
+ for (const field of (_a = props.fields) != null ? _a : []) {
6679
+ const found = findOptionLocationInField(field, optionId);
6680
+ if (found) return { field, ...found };
6681
+ }
6682
+ return void 0;
6683
+ }
6684
+ function collectFieldOptionIds(field) {
6685
+ const out = [];
6686
+ const visit = (options) => {
6687
+ for (const option of options != null ? options : []) {
6688
+ out.push(String(option.id));
6689
+ visit(option.children);
6690
+ }
6691
+ };
6692
+ visit(field == null ? void 0 : field.options);
6693
+ return out;
6694
+ }
6695
+ function findOptionLocationInField(field, optionId) {
6696
+ const visit = (siblings, parent) => {
6697
+ if (!siblings) return void 0;
6698
+ const index = siblings.findIndex((option) => option.id === optionId);
6699
+ if (index >= 0) {
6700
+ return {
6701
+ option: siblings[index],
6702
+ siblings,
6703
+ index,
6704
+ parent
6705
+ };
6706
+ }
6707
+ for (const option of siblings) {
6708
+ const found = visit(option.children, option);
6709
+ if (found) return found;
6710
+ }
6711
+ return void 0;
6712
+ };
6713
+ return visit(field.options);
6714
+ }
6715
+ function hasFieldOptions(field) {
6716
+ return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
6717
+ }
6718
+ function isActualButtonField(field) {
6719
+ return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
6720
+ }
6721
+ function clearFieldButtonReceiverMaps(props, fieldId) {
6722
+ var _a, _b, _c;
6723
+ if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
6724
+ delete props.includes_for_buttons[fieldId];
6725
+ }
6726
+ if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
6727
+ delete props.excludes_for_buttons[fieldId];
6728
+ }
6729
+ if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
6730
+ delete props.includes_for_buttons;
6731
+ }
6732
+ if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
6733
+ delete props.excludes_for_buttons;
6734
+ }
6735
+ if ((_c = props.option_effects_for_buttons) == null ? void 0 : _c[fieldId]) {
6736
+ delete props.option_effects_for_buttons[fieldId];
6737
+ }
6738
+ if (props.option_effects_for_buttons && Object.keys(props.option_effects_for_buttons).length === 0) {
6739
+ delete props.option_effects_for_buttons;
6740
+ }
6741
+ }
6742
+ function ensureServiceExists(opts, id) {
6743
+ if (typeof opts.serviceExists === "function") {
6744
+ if (!opts.serviceExists(id)) {
6745
+ throw new Error(`service_not_found:${String(id)}`);
6746
+ }
6747
+ return;
6748
+ }
6749
+ if (opts.serviceMap) {
6750
+ if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
6751
+ throw new Error(`service_not_found:${String(id)}`);
6752
+ }
6753
+ return;
6754
+ }
6755
+ throw new Error("service_checker_missing");
6756
+ }
6757
+
6192
6758
  // src/react/canvas/editor/editor-duplicate.ts
6193
6759
  function duplicate(ctx, ref, opts = {}) {
6194
6760
  const snapBefore = ctx.makeSnapshot("duplicate:before");
@@ -6263,14 +6829,66 @@ function duplicateInPlace(ctx, ref, opts = {}) {
6263
6829
  return duplicateOption(ctx, ref.fieldId, ref.id, opts);
6264
6830
  }
6265
6831
  function ownerFieldOfOption(props, optionId) {
6266
- var _a, _b;
6832
+ var _a;
6267
6833
  for (const field of (_a = props.fields) != null ? _a : []) {
6268
- if (((_b = field.options) != null ? _b : []).some((o) => o.id === optionId)) {
6834
+ if (findMutableOption({ ...props, fields: [field] }, optionId)) {
6269
6835
  return { fieldId: field.id };
6270
6836
  }
6271
6837
  }
6272
6838
  return null;
6273
6839
  }
6840
+ function cloneOptionTree(ctx, fieldId, option, opts, optionIdMap) {
6841
+ var _a, _b, _c, _d;
6842
+ const newId = ctx.uniqueOptionId(
6843
+ fieldId,
6844
+ ((_a = opts.optionIdStrategy) != null ? _a : defaultOptionIdStrategy)(option.id)
6845
+ );
6846
+ optionIdMap.set(option.id, newId);
6847
+ const children = (_b = option.children) == null ? void 0 : _b.map(
6848
+ (child) => cloneOptionTree(ctx, fieldId, child, opts, optionIdMap)
6849
+ );
6850
+ return {
6851
+ ...option,
6852
+ id: newId,
6853
+ label: ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = option.label) != null ? _d : option.id),
6854
+ ...(children == null ? void 0 : children.length) ? { children } : {}
6855
+ };
6856
+ }
6857
+ function remapEffect(effect, optionIdMap) {
6858
+ const remapList = (values) => values == null ? void 0 : values.map((value) => {
6859
+ var _a;
6860
+ return (_a = optionIdMap.get(value)) != null ? _a : value;
6861
+ });
6862
+ return {
6863
+ ...effect,
6864
+ ...effect.include ? { include: remapList(effect.include) } : {},
6865
+ ...effect.exclude ? { exclude: remapList(effect.exclude) } : {}
6866
+ };
6867
+ }
6868
+ function copyOptionEffects(props, args) {
6869
+ var _a, _b, _c, _d, _e;
6870
+ const source = props.option_effects_for_buttons;
6871
+ if (!source) return;
6872
+ const next = {
6873
+ ...source
6874
+ };
6875
+ const triggerIdMap = (_a = args.triggerIdMap) != null ? _a : /* @__PURE__ */ new Map();
6876
+ const targetFieldIdMap = (_b = args.targetFieldIdMap) != null ? _b : /* @__PURE__ */ new Map();
6877
+ const optionIdMap = (_c = args.optionIdMap) != null ? _c : /* @__PURE__ */ new Map();
6878
+ for (const [oldTriggerId, targetMap] of Object.entries(source)) {
6879
+ const newTriggerId = triggerIdMap.get(oldTriggerId);
6880
+ if (!newTriggerId) continue;
6881
+ const copiedTargets = {
6882
+ ...(_d = next[newTriggerId]) != null ? _d : {}
6883
+ };
6884
+ for (const [oldTargetFieldId, effect] of Object.entries(targetMap != null ? targetMap : {})) {
6885
+ const newTargetFieldId = (_e = targetFieldIdMap.get(oldTargetFieldId)) != null ? _e : oldTargetFieldId;
6886
+ copiedTargets[newTargetFieldId] = remapEffect(effect, optionIdMap);
6887
+ }
6888
+ next[newTriggerId] = copiedTargets;
6889
+ }
6890
+ props.option_effects_for_buttons = next;
6891
+ }
6274
6892
  function duplicateTag(ctx, tagId, opts) {
6275
6893
  var _a, _b, _c, _d;
6276
6894
  const props = ctx.getProps();
@@ -6326,7 +6944,7 @@ function duplicateTag(ctx, tagId, opts) {
6326
6944
  return id;
6327
6945
  }
6328
6946
  function duplicateField(ctx, fieldId, opts) {
6329
- var _a, _b, _c, _d, _e, _f, _g;
6947
+ var _a, _b, _c, _d, _e, _f;
6330
6948
  const props = ctx.getProps();
6331
6949
  const fields = (_a = props.fields) != null ? _a : [];
6332
6950
  const src = fields.find((f) => f.id === fieldId);
@@ -6334,21 +6952,10 @@ function duplicateField(ctx, fieldId, opts) {
6334
6952
  const id = (_b = opts.id) != null ? _b : ctx.uniqueId(src.id);
6335
6953
  const label = ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = src.label) != null ? _d : id);
6336
6954
  const name = opts.nameStrategy ? opts.nameStrategy(src.name) : nextCopyName(src.name);
6337
- const optId = (old) => {
6338
- var _a2;
6339
- return ctx.uniqueOptionId(
6340
- id,
6341
- ((_a2 = opts.optionIdStrategy) != null ? _a2 : defaultOptionIdStrategy)(old)
6342
- );
6343
- };
6344
- const clonedOptions = ((_e = src.options) != null ? _e : []).map((o) => {
6345
- var _a2, _b2;
6346
- return {
6347
- ...o,
6348
- id: optId(o.id),
6349
- label: ((_a2 = opts.labelStrategy) != null ? _a2 : nextCopyLabel)((_b2 = o.label) != null ? _b2 : o.id)
6350
- };
6351
- });
6955
+ const optionIdMap = /* @__PURE__ */ new Map();
6956
+ const clonedOptions = ((_e = src.options) != null ? _e : []).map(
6957
+ (o) => cloneOptionTree(ctx, id, o, opts, optionIdMap)
6958
+ );
6352
6959
  const cloned = {
6353
6960
  ...src,
6354
6961
  id,
@@ -6357,14 +6964,8 @@ function duplicateField(ctx, fieldId, opts) {
6357
6964
  bind_id: ((_f = opts.copyBindings) != null ? _f : true) ? src.bind_id : void 0,
6358
6965
  options: clonedOptions
6359
6966
  };
6360
- const optionIdMap = /* @__PURE__ */ new Map();
6361
- ((_g = src.options) != null ? _g : []).forEach((o, i) => {
6362
- var _a2, _b2;
6363
- const newOptId = (_b2 = (_a2 = clonedOptions[i]) == null ? void 0 : _a2.id) != null ? _b2 : o.id;
6364
- optionIdMap.set(o.id, newOptId);
6365
- });
6366
6967
  ctx.patchProps((p) => {
6367
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
6968
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g;
6368
6969
  const arr = (_a2 = p.fields) != null ? _a2 : [];
6369
6970
  const idx = arr.findIndex((f) => f.id === fieldId);
6370
6971
  arr.splice(idx + 1, 0, cloned);
@@ -6402,52 +7003,56 @@ function duplicateField(ctx, fieldId, opts) {
6402
7003
  }
6403
7004
  if (optionIdMap.has(key)) {
6404
7005
  const newKey = optionIdMap.get(key);
6405
- const merged = /* @__PURE__ */ new Set([...(_g2 = nextMap[newKey]) != null ? _g2 : [], ...targets]);
7006
+ const merged = /* @__PURE__ */ new Set([...(_g = nextMap[newKey]) != null ? _g : [], ...targets]);
6406
7007
  nextMap[newKey] = Array.from(merged);
6407
7008
  }
6408
7009
  }
6409
7010
  p[mapKey] = nextMap;
6410
7011
  }
7012
+ copyOptionEffects(p, {
7013
+ triggerIdMap: new Map([
7014
+ [fieldId, id],
7015
+ ...Array.from(optionIdMap.entries())
7016
+ ]),
7017
+ targetFieldIdMap: /* @__PURE__ */ new Map([[fieldId, id]]),
7018
+ optionIdMap
7019
+ });
6411
7020
  }
6412
7021
  });
6413
7022
  return id;
6414
7023
  }
6415
7024
  function duplicateOption(ctx, fieldId, optionId, opts) {
6416
- var _a, _b, _c, _d, _e, _f;
6417
7025
  const props = ctx.getProps();
6418
- const fields = (_a = props.fields) != null ? _a : [];
6419
- const f = fields.find((x) => x.id === fieldId);
6420
- if (!f) throw new Error(`Field not found: ${fieldId}`);
6421
- const optIdx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
6422
- if (optIdx < 0) {
6423
- throw new Error(`Option not found: ${fieldId}::${optionId}`);
7026
+ const location = findMutableOption(props, optionId);
7027
+ if (!location || location.field.id !== fieldId) {
7028
+ throw new Error(`Option not found: ${fieldId}/${optionId}`);
6424
7029
  }
6425
- const src = ((_c = f.options) != null ? _c : [])[optIdx];
6426
- const newId = ctx.uniqueOptionId(
6427
- fieldId,
6428
- ((_d = opts.optionIdStrategy) != null ? _d : defaultOptionIdStrategy)(src.id)
6429
- );
6430
- const newLabel = ((_e = opts.labelStrategy) != null ? _e : nextCopyLabel)((_f = src.label) != null ? _f : src.id);
7030
+ const src = location.option;
7031
+ const optionIdMap = /* @__PURE__ */ new Map();
7032
+ const clone2 = cloneOptionTree(ctx, fieldId, src, opts, optionIdMap);
7033
+ const newId = clone2.id;
6431
7034
  ctx.patchProps((p) => {
6432
- var _a2, _b2, _c2;
6433
- const fld = ((_a2 = p.fields) != null ? _a2 : []).find((x) => x.id === fieldId);
6434
- const arr = (_b2 = fld.options) != null ? _b2 : [];
6435
- const clone2 = { ...src, id: newId, label: newLabel };
6436
- arr.splice(optIdx + 1, 0, clone2);
6437
- fld.options = arr;
7035
+ var _a;
7036
+ const current = findMutableOption(p, optionId);
7037
+ if (!current) return;
7038
+ current.siblings.splice(current.index + 1, 0, clone2);
6438
7039
  if (opts.copyOptionMaps) {
6439
- const oldKey = `${fieldId}::${optionId}`;
6440
- const newKey = `${fieldId}::${newId}`;
6441
7040
  for (const mapKey of [
6442
7041
  "includes_for_buttons",
6443
7042
  "excludes_for_buttons"
6444
7043
  ]) {
6445
- const m = (_c2 = p[mapKey]) != null ? _c2 : {};
6446
- if (m[oldKey]) {
6447
- m[newKey] = Array.from(new Set(m[oldKey]));
6448
- p[mapKey] = m;
7044
+ const m = (_a = p[mapKey]) != null ? _a : {};
7045
+ for (const [oldKey, newKey] of optionIdMap.entries()) {
7046
+ if (m[oldKey]) {
7047
+ m[newKey] = Array.from(new Set(m[oldKey]));
7048
+ p[mapKey] = m;
7049
+ }
6449
7050
  }
6450
7051
  }
7052
+ copyOptionEffects(p, {
7053
+ triggerIdMap: optionIdMap,
7054
+ optionIdMap
7055
+ });
6451
7056
  }
6452
7057
  });
6453
7058
  return newId;
@@ -6509,54 +7114,6 @@ function removeNotice(ctx, id) {
6509
7114
 
6510
7115
  // src/react/canvas/editor/editor-nodes.ts
6511
7116
  import { cloneDeep as cloneDeep3 } from "lodash-es";
6512
-
6513
- // src/react/canvas/editor/editor-utils.ts
6514
- function ownerOfOption(props, optionId) {
6515
- var _a, _b;
6516
- for (const f of (_a = props.fields) != null ? _a : []) {
6517
- const idx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
6518
- if (idx >= 0) return { fieldId: f.id, index: idx };
6519
- }
6520
- return null;
6521
- }
6522
- function hasFieldOptions(field) {
6523
- return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
6524
- }
6525
- function isActualButtonField(field) {
6526
- return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
6527
- }
6528
- function clearFieldButtonReceiverMaps(props, fieldId) {
6529
- var _a, _b;
6530
- if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
6531
- delete props.includes_for_buttons[fieldId];
6532
- }
6533
- if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
6534
- delete props.excludes_for_buttons[fieldId];
6535
- }
6536
- if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
6537
- delete props.includes_for_buttons;
6538
- }
6539
- if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
6540
- delete props.excludes_for_buttons;
6541
- }
6542
- }
6543
- function ensureServiceExists(opts, id) {
6544
- if (typeof opts.serviceExists === "function") {
6545
- if (!opts.serviceExists(id)) {
6546
- throw new Error(`service_not_found:${String(id)}`);
6547
- }
6548
- return;
6549
- }
6550
- if (opts.serviceMap) {
6551
- if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
6552
- throw new Error(`service_not_found:${String(id)}`);
6553
- }
6554
- return;
6555
- }
6556
- throw new Error("service_checker_missing");
6557
- }
6558
-
6559
- // src/react/canvas/editor/editor-nodes.ts
6560
7117
  var RELATION_MAP_KEYS = [
6561
7118
  "includes_for_buttons",
6562
7119
  "excludes_for_buttons",
@@ -6607,14 +7164,51 @@ function cleanRelationMapsForDeleted(p, deleted) {
6607
7164
  delete map[mapKey];
6608
7165
  continue;
6609
7166
  }
6610
- const next = ((_a = map[mapKey]) != null ? _a : []).filter(
6611
- (item) => !deleted.has(String(item))
6612
- );
6613
- if (next.length) map[mapKey] = next;
6614
- else delete map[mapKey];
7167
+ const next = ((_a = map[mapKey]) != null ? _a : []).filter(
7168
+ (item) => !deleted.has(String(item))
7169
+ );
7170
+ if (next.length) map[mapKey] = next;
7171
+ else delete map[mapKey];
7172
+ }
7173
+ if (!Object.keys(map).length) delete p[key];
7174
+ }
7175
+ }
7176
+ function cleanOptionEffectsForDeleted(p, deleted) {
7177
+ var _a, _b;
7178
+ const map = p.option_effects_for_buttons;
7179
+ if (!map) return;
7180
+ for (const triggerId of Object.keys(map)) {
7181
+ if (deleted.has(String(triggerId))) {
7182
+ delete map[triggerId];
7183
+ continue;
7184
+ }
7185
+ const targets = map[triggerId];
7186
+ for (const targetFieldId of Object.keys(targets != null ? targets : {})) {
7187
+ if (deleted.has(String(targetFieldId))) {
7188
+ delete targets[targetFieldId];
7189
+ continue;
7190
+ }
7191
+ const effect = targets[targetFieldId];
7192
+ if (!effect) continue;
7193
+ if (effect.include) {
7194
+ effect.include = effect.include.filter(
7195
+ (optionId) => !deleted.has(String(optionId))
7196
+ );
7197
+ if (!effect.include.length) delete effect.include;
7198
+ }
7199
+ if (effect.exclude) {
7200
+ effect.exclude = effect.exclude.filter(
7201
+ (optionId) => !deleted.has(String(optionId))
7202
+ );
7203
+ if (!effect.exclude.length) delete effect.exclude;
7204
+ }
7205
+ if (effect.forceVisible !== true && !((_a = effect.include) == null ? void 0 : _a.length) && !((_b = effect.exclude) == null ? void 0 : _b.length)) {
7206
+ delete targets[targetFieldId];
7207
+ }
6615
7208
  }
6616
- if (!Object.keys(map).length) delete p[key];
7209
+ if (!Object.keys(targets != null ? targets : {}).length) delete map[triggerId];
6617
7210
  }
7211
+ if (!Object.keys(map).length) delete p.option_effects_for_buttons;
6618
7212
  }
6619
7213
  function cleanOrderForTagsForDeleted(p, deleted) {
6620
7214
  var _a, _b;
@@ -6651,28 +7245,37 @@ function applyDeleteCleanup(p, deleted) {
6651
7245
  cleanTagRelationsForDeleted(p, deleted);
6652
7246
  cleanFieldBindsForDeleted(p, deleted);
6653
7247
  cleanRelationMapsForDeleted(p, deleted);
7248
+ cleanOptionEffectsForDeleted(p, deleted);
6654
7249
  cleanOrderForTagsForDeleted(p, deleted);
6655
7250
  cleanNoticesForDeleted(p, deleted);
6656
7251
  }
7252
+ function collectOptionSubtreeIds(option) {
7253
+ var _a;
7254
+ return [
7255
+ String(option.id),
7256
+ ...((_a = option.children) != null ? _a : []).flatMap((child) => collectOptionSubtreeIds(child))
7257
+ ];
7258
+ }
6657
7259
  function removeOptionInPlace(p, optionId) {
6658
7260
  var _a;
6659
- const owner = ownerOfOption(p, optionId);
6660
- if (!owner) return false;
6661
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
6662
- if (!(f == null ? void 0 : f.options)) return false;
6663
- const before = f.options.length;
6664
- f.options = f.options.filter((o) => o.id !== optionId);
6665
- return f.options.length !== before;
7261
+ const found = findMutableOption(p, optionId);
7262
+ if (!found) return [];
7263
+ const deleted = collectOptionSubtreeIds(found.option);
7264
+ found.siblings.splice(found.index, 1);
7265
+ if (found.parent && ((_a = found.parent.children) == null ? void 0 : _a.length) === 0) {
7266
+ delete found.parent.children;
7267
+ }
7268
+ return deleted;
6666
7269
  }
6667
7270
  function removeFieldInPlace(p, fieldId) {
6668
- var _a, _b, _c, _d, _e;
7271
+ var _a, _b, _c, _d;
6669
7272
  const field = ((_a = p.fields) != null ? _a : []).find((f) => f.id === fieldId);
6670
7273
  if (!field) return [];
6671
- const deleted = [fieldId, ...((_b = field.options) != null ? _b : []).map((o) => String(o.id))];
6672
- const before = ((_c = p.fields) != null ? _c : []).length;
6673
- p.fields = ((_d = p.fields) != null ? _d : []).filter((f) => f.id !== fieldId);
7274
+ const deleted = [fieldId, ...collectFieldOptionIds(field)];
7275
+ const before = ((_b = p.fields) != null ? _b : []).length;
7276
+ p.fields = ((_c = p.fields) != null ? _c : []).filter((f) => f.id !== fieldId);
6674
7277
  clearFieldButtonReceiverMaps(p, fieldId);
6675
- return ((_e = p.fields) != null ? _e : []).length !== before ? deleted : [];
7278
+ return ((_d = p.fields) != null ? _d : []).length !== before ? deleted : [];
6676
7279
  }
6677
7280
  function removeTagInPlace(p, tagId) {
6678
7281
  var _a, _b, _c;
@@ -6685,7 +7288,7 @@ function reLabel(ctx, id, nextLabel) {
6685
7288
  ctx.exec({
6686
7289
  name: "reLabel",
6687
7290
  do: () => ctx.patchProps((p) => {
6688
- var _a, _b, _c, _d, _e, _f, _g;
7291
+ var _a, _b, _c, _d, _e, _f;
6689
7292
  if (ctx.isTagId(id)) {
6690
7293
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
6691
7294
  if (!t) return;
@@ -6695,19 +7298,16 @@ function reLabel(ctx, id, nextLabel) {
6695
7298
  return;
6696
7299
  }
6697
7300
  if (ctx.isOptionId(id)) {
6698
- const own = ownerOfOption(p, id);
6699
- if (!own) return;
6700
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
6701
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
7301
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
6702
7302
  if (!o) return;
6703
- if (((_e = o.label) != null ? _e : "") === label) return;
7303
+ if (((_d = o.label) != null ? _d : "") === label) return;
6704
7304
  o.label = label;
6705
7305
  ctx.api.refreshGraph();
6706
7306
  return;
6707
7307
  }
6708
- const fld = ((_f = p.fields) != null ? _f : []).find((x) => x.id === id);
7308
+ const fld = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
6709
7309
  if (!fld) return;
6710
- if (((_g = fld.label) != null ? _g : "") === label) return;
7310
+ if (((_f = fld.label) != null ? _f : "") === label) return;
6711
7311
  fld.label = label;
6712
7312
  ctx.api.refreshGraph();
6713
7313
  }),
@@ -6797,11 +7397,7 @@ function updateOption(ctx, optionId, patch) {
6797
7397
  name: "updateOption",
6798
7398
  do: () => ctx.patchProps((p) => {
6799
7399
  var _a;
6800
- const owner = ownerOfOption(p, optionId);
6801
- if (!owner) return;
6802
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
6803
- if (!(f == null ? void 0 : f.options)) return;
6804
- const o = f.options.find((x) => x.id === optionId);
7400
+ const o = (_a = findMutableOption(p, optionId)) == null ? void 0 : _a.option;
6805
7401
  if (o) Object.assign(o, patch);
6806
7402
  }),
6807
7403
  undo: () => ctx.undo()
@@ -6814,9 +7410,9 @@ function removeOption(ctx, optionId) {
6814
7410
  ctx.exec({
6815
7411
  name: "removeOption",
6816
7412
  do: () => ctx.patchProps((p) => {
6817
- const removed = removeOptionInPlace(p, optionId);
6818
- if (!removed) return;
6819
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([optionId]));
7413
+ const removedIds = removeOptionInPlace(p, optionId);
7414
+ if (!removedIds.length) return;
7415
+ applyDeleteCleanup(p, new Set(removedIds));
6820
7416
  }),
6821
7417
  undo: () => ctx.undo()
6822
7418
  });
@@ -6827,7 +7423,7 @@ function editLabel(ctx, id, label) {
6827
7423
  ctx.exec({
6828
7424
  name: "editLabel",
6829
7425
  do: () => ctx.patchProps((p) => {
6830
- var _a, _b, _c, _d;
7426
+ var _a, _b, _c;
6831
7427
  if (ctx.isTagId(id)) {
6832
7428
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
6833
7429
  if (t) t.label = next;
@@ -6839,10 +7435,7 @@ function editLabel(ctx, id, label) {
6839
7435
  return;
6840
7436
  }
6841
7437
  if (ctx.isOptionId(id)) {
6842
- const own = ownerOfOption(p, id);
6843
- if (!own) return;
6844
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
6845
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
7438
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
6846
7439
  if (o) o.label = next;
6847
7440
  return;
6848
7441
  }
@@ -6867,7 +7460,7 @@ function setService(ctx, id, input) {
6867
7460
  ctx.exec({
6868
7461
  name: "setService",
6869
7462
  do: () => ctx.patchProps((p) => {
6870
- var _a, _b, _c, _d, _e, _f;
7463
+ var _a, _b, _c, _d, _e;
6871
7464
  const hasSidKey = Object.prototype.hasOwnProperty.call(
6872
7465
  input,
6873
7466
  "service_id"
@@ -6885,12 +7478,9 @@ function setService(ctx, id, input) {
6885
7478
  return;
6886
7479
  }
6887
7480
  if (ctx.isOptionId(id)) {
6888
- const own = ownerOfOption(p, id);
6889
- if (!own) return;
6890
- const f2 = ((_b = p.fields) != null ? _b : []).find((x) => x.id === own.fieldId);
6891
- const o = (_c = f2 == null ? void 0 : f2.options) == null ? void 0 : _c.find((x) => x.id === id);
7481
+ const o = (_b = findMutableOption(p, id)) == null ? void 0 : _b.option;
6892
7482
  if (!o) return;
6893
- const currentRole = (_d = o.pricing_role) != null ? _d : "base";
7483
+ const currentRole = (_c = o.pricing_role) != null ? _c : "base";
6894
7484
  const role = nextRole != null ? nextRole : currentRole;
6895
7485
  if (role === "utility") {
6896
7486
  if (hasSidKey && sid !== void 0) {
@@ -6911,7 +7501,7 @@ function setService(ctx, id, input) {
6911
7501
  }
6912
7502
  return;
6913
7503
  }
6914
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
7504
+ const f = ((_d = p.fields) != null ? _d : []).find((x) => x.id === id);
6915
7505
  if (!f) {
6916
7506
  throw new Error(
6917
7507
  'setService only supports tag ("t:*"), option ("o:*"), or field ("f:*") ids'
@@ -6922,7 +7512,7 @@ function setService(ctx, id, input) {
6922
7512
  if (nextRole) {
6923
7513
  f.pricing_role = nextRole;
6924
7514
  }
6925
- const effectiveRole = (_f = f.pricing_role) != null ? _f : "base";
7515
+ const effectiveRole = (_e = f.pricing_role) != null ? _e : "base";
6926
7516
  if (isOptionBased2) {
6927
7517
  if (hasSidKey) {
6928
7518
  ctx.api.emit("error", {
@@ -7034,13 +7624,15 @@ function updateField(ctx, id, patch) {
7034
7624
  let prev;
7035
7625
  let prevIncludesForButton;
7036
7626
  let prevExcludesForButton;
7627
+ let prevOptionEffectsForButton;
7037
7628
  ctx.exec({
7038
7629
  name: "updateField",
7039
7630
  do: () => ctx.patchProps((p) => {
7040
- var _a, _b, _c, _d, _e, _f, _g;
7631
+ var _a, _b, _c, _d, _e, _f, _g, _h;
7041
7632
  prevIncludesForButton = ((_a = p.includes_for_buttons) == null ? void 0 : _a[id]) ? [...(_c = (_b = p.includes_for_buttons) == null ? void 0 : _b[id]) != null ? _c : []] : void 0;
7042
7633
  prevExcludesForButton = ((_d = p.excludes_for_buttons) == null ? void 0 : _d[id]) ? [...(_f = (_e = p.excludes_for_buttons) == null ? void 0 : _e[id]) != null ? _f : []] : void 0;
7043
- p.fields = ((_g = p.fields) != null ? _g : []).map((f) => {
7634
+ prevOptionEffectsForButton = ((_g = p.option_effects_for_buttons) == null ? void 0 : _g[id]) ? cloneDeep3(p.option_effects_for_buttons[id]) : void 0;
7635
+ p.fields = ((_h = p.fields) != null ? _h : []).map((f) => {
7044
7636
  if (f.id !== id) return f;
7045
7637
  prev = cloneDeep3(f);
7046
7638
  const nextField = { ...f, ...patch };
@@ -7051,7 +7643,7 @@ function updateField(ctx, id, patch) {
7051
7643
  });
7052
7644
  }),
7053
7645
  undo: () => ctx.patchProps((p) => {
7054
- var _a, _b, _c;
7646
+ var _a, _b, _c, _d;
7055
7647
  p.fields = ((_a = p.fields) != null ? _a : []).map(
7056
7648
  (f) => f.id === id && prev ? prev : f
7057
7649
  );
@@ -7069,6 +7661,12 @@ function updateField(ctx, id, patch) {
7069
7661
  [id]: [...prevExcludesForButton]
7070
7662
  };
7071
7663
  }
7664
+ if (prevOptionEffectsForButton) {
7665
+ p.option_effects_for_buttons = {
7666
+ ...(_d = p.option_effects_for_buttons) != null ? _d : {},
7667
+ [id]: cloneDeep3(prevOptionEffectsForButton)
7668
+ };
7669
+ }
7072
7670
  })
7073
7671
  });
7074
7672
  }
@@ -7115,9 +7713,9 @@ function remove(ctx, id) {
7115
7713
  ctx.exec({
7116
7714
  name: "removeOption",
7117
7715
  do: () => ctx.patchProps((p) => {
7118
- const removed = removeOptionInPlace(p, key);
7119
- if (!removed) return;
7120
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
7716
+ const removedIds = removeOptionInPlace(p, key);
7717
+ if (!removedIds.length) return;
7718
+ applyDeleteCleanup(p, new Set(removedIds));
7121
7719
  }),
7122
7720
  undo: () => ctx.undo()
7123
7721
  });
@@ -7134,10 +7732,7 @@ function removeMany(ctx, ids) {
7134
7732
  const existingFieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
7135
7733
  const existingTagIds = new Set(((_b = p.filters) != null ? _b : []).map((t) => String(t.id)));
7136
7734
  const existingOptionIds = new Set(
7137
- ((_c = p.fields) != null ? _c : []).flatMap((f) => {
7138
- var _a2;
7139
- return ((_a2 = f.options) != null ? _a2 : []).map((o) => String(o.id));
7140
- })
7735
+ ((_c = p.fields) != null ? _c : []).flatMap((f) => collectFieldOptionIds(f))
7141
7736
  );
7142
7737
  const fieldIds = ordered.filter((id) => ctx.isFieldId(id) && existingFieldIds.has(id));
7143
7738
  const fieldIdSet = new Set(fieldIds);
@@ -7150,7 +7745,9 @@ function removeMany(ctx, ids) {
7150
7745
  });
7151
7746
  const deleted = /* @__PURE__ */ new Set();
7152
7747
  for (const optionId of optionIds) {
7153
- if (removeOptionInPlace(p, optionId)) deleted.add(optionId);
7748
+ for (const removedId of removeOptionInPlace(p, optionId)) {
7749
+ deleted.add(removedId);
7750
+ }
7154
7751
  }
7155
7752
  for (const fieldId of fieldIds) {
7156
7753
  const removedIds = removeFieldInPlace(p, fieldId);
@@ -7165,7 +7762,7 @@ function removeMany(ctx, ids) {
7165
7762
  });
7166
7763
  }
7167
7764
  function getNode(ctx, id) {
7168
- var _a, _b, _c, _d;
7765
+ var _a, _b, _c;
7169
7766
  const props = ctx.getProps();
7170
7767
  if (ctx.isTagId(id)) {
7171
7768
  const t = ((_a = props.filters) != null ? _a : []).find((x) => x.id === id);
@@ -7182,8 +7779,7 @@ function getNode(ctx, id) {
7182
7779
  }
7183
7780
  if (ctx.isOptionId(id)) {
7184
7781
  const own = ownerOfOption(props, id);
7185
- const f = own ? ((_c = props.fields) != null ? _c : []).find((x) => x.id === own.fieldId) : void 0;
7186
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
7782
+ const o = (_c = findMutableOption(props, id)) == null ? void 0 : _c.option;
7187
7783
  return {
7188
7784
  kind: "option",
7189
7785
  data: o,
@@ -7720,7 +8316,7 @@ function connect(ctx, kind, fromId, toId2) {
7720
8316
  ctx.exec({
7721
8317
  name: `connect:${kind}`,
7722
8318
  do: () => ctx.patchProps((p) => {
7723
- var _a, _b, _c, _d, _e, _f, _g, _h;
8319
+ var _a, _b, _c, _d, _e, _f, _g;
7724
8320
  if (kind === "bind") {
7725
8321
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
7726
8322
  if (wouldCreateTagCycle(ctx, p, fromId, toId2)) {
@@ -7800,12 +8396,10 @@ function connect(ctx, kind, fromId, toId2) {
7800
8396
  return;
7801
8397
  }
7802
8398
  if (toId2.startsWith("o:")) {
7803
- for (const f of (_g = p.fields) != null ? _g : []) {
7804
- const o = (_h = f.options) == null ? void 0 : _h.find((x) => x.id === toId2);
7805
- if (o) {
7806
- o.service_id = fromId;
7807
- return;
7808
- }
8399
+ const o = (_g = findMutableOption(p, toId2)) == null ? void 0 : _g.option;
8400
+ if (o) {
8401
+ o.service_id = fromId;
8402
+ return;
7809
8403
  }
7810
8404
  return;
7811
8405
  }
@@ -7822,7 +8416,7 @@ function disconnect(ctx, kind, fromId, toId2) {
7822
8416
  ctx.exec({
7823
8417
  name: `disconnect:${kind}`,
7824
8418
  do: () => ctx.patchProps((p) => {
7825
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
8419
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
7826
8420
  if (kind === "bind") {
7827
8421
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
7828
8422
  const child = ((_a = p.filters) != null ? _a : []).find(
@@ -7898,12 +8492,10 @@ function disconnect(ctx, kind, fromId, toId2) {
7898
8492
  return;
7899
8493
  }
7900
8494
  if (toId2.startsWith("o:")) {
7901
- for (const f of (_i = p.fields) != null ? _i : []) {
7902
- const o = (_j = f.options) == null ? void 0 : _j.find((x) => x.id === toId2);
7903
- if (o) {
7904
- delete o.service_id;
7905
- return;
7906
- }
8495
+ const o = (_i = findMutableOption(p, toId2)) == null ? void 0 : _i.option;
8496
+ if (o) {
8497
+ delete o.service_id;
8498
+ return;
7907
8499
  }
7908
8500
  return;
7909
8501
  }
@@ -7926,6 +8518,250 @@ function addMappedField(p, mapKey, fromId, toId2) {
7926
8518
  p[mapKey] = maps;
7927
8519
  }
7928
8520
 
8521
+ // src/react/canvas/editor/editor-option-effects.ts
8522
+ function assertCanonicalId(id, label) {
8523
+ if (!id || id.includes("::") || id.includes("/")) {
8524
+ throw new Error(
8525
+ `${label}: expected a raw field or option id, not a composite/path id`
8526
+ );
8527
+ }
8528
+ }
8529
+ function assertTrigger(ctx, triggerId) {
8530
+ assertCanonicalId(triggerId, "option effect trigger");
8531
+ const trigger = ctx.getNode(triggerId);
8532
+ if (trigger.kind === "option" && trigger.data) return;
8533
+ if (trigger.kind === "field" && trigger.data && isActualButtonField(trigger.data)) {
8534
+ return;
8535
+ }
8536
+ throw new Error(
8537
+ "option effect trigger must be an option id or button field id"
8538
+ );
8539
+ }
8540
+ function assertTargetField(props, targetFieldId) {
8541
+ var _a;
8542
+ assertCanonicalId(targetFieldId, "option effect target");
8543
+ const field = ((_a = props.fields) != null ? _a : []).find((item) => item.id === targetFieldId);
8544
+ if (!field) {
8545
+ throw new Error(`option effect target field not found: ${targetFieldId}`);
8546
+ }
8547
+ return field;
8548
+ }
8549
+ function dedupe2(values) {
8550
+ if (!values) return void 0;
8551
+ const out = [];
8552
+ for (const value of values) {
8553
+ const id = String(value);
8554
+ if (!id || out.includes(id)) continue;
8555
+ out.push(id);
8556
+ }
8557
+ return out.length ? out : void 0;
8558
+ }
8559
+ function assertTargetOptions(props, targetFieldId, ids, kind) {
8560
+ if (!(ids == null ? void 0 : ids.length)) return;
8561
+ const field = assertTargetField(props, targetFieldId);
8562
+ const valid = fieldOptionIdSet(field);
8563
+ for (const id of ids) {
8564
+ assertCanonicalId(String(id), `option effect ${kind} option`);
8565
+ if (!valid.has(String(id))) {
8566
+ throw new Error(
8567
+ `option effect ${kind} option not found under ${targetFieldId}: ${String(id)}`
8568
+ );
8569
+ }
8570
+ }
8571
+ }
8572
+ function normalizeEffect(effect) {
8573
+ var _a;
8574
+ if (!effect) return void 0;
8575
+ const exclude2 = dedupe2(effect.exclude);
8576
+ const excluded = new Set(exclude2 != null ? exclude2 : []);
8577
+ const include2 = (_a = dedupe2(effect.include)) == null ? void 0 : _a.filter((id) => !excluded.has(id));
8578
+ const out = {};
8579
+ if (effect.forceVisible === true) out.forceVisible = true;
8580
+ if (include2 == null ? void 0 : include2.length) out.include = include2;
8581
+ if (exclude2 == null ? void 0 : exclude2.length) out.exclude = exclude2;
8582
+ return Object.keys(out).length ? out : void 0;
8583
+ }
8584
+ function ensureTargetMap(props, triggerId) {
8585
+ var _a, _b, _c;
8586
+ (_a = props.option_effects_for_buttons) != null ? _a : props.option_effects_for_buttons = {};
8587
+ (_c = (_b = props.option_effects_for_buttons)[triggerId]) != null ? _c : _b[triggerId] = {};
8588
+ return props.option_effects_for_buttons[triggerId];
8589
+ }
8590
+ function pruneEffectMap(props, triggerId) {
8591
+ const map = props.option_effects_for_buttons;
8592
+ if (!map) return;
8593
+ const keys = triggerId ? [triggerId] : Object.keys(map);
8594
+ for (const key of keys) {
8595
+ const targets = map[key];
8596
+ if (!targets || Object.keys(targets).length === 0) delete map[key];
8597
+ }
8598
+ if (Object.keys(map).length === 0) delete props.option_effects_for_buttons;
8599
+ }
8600
+ function validateEffect(ctx, props, triggerId, targetFieldId, effect) {
8601
+ assertTrigger(ctx, triggerId);
8602
+ assertTargetField(props, targetFieldId);
8603
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.include, "include");
8604
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.exclude, "exclude");
8605
+ return normalizeEffect(effect);
8606
+ }
8607
+ function setOptionEffect(ctx, triggerId, targetFieldId, effect) {
8608
+ ctx.exec({
8609
+ name: "setOptionEffect",
8610
+ do: () => ctx.patchProps((props) => {
8611
+ var _a;
8612
+ const normalized = validateEffect(
8613
+ ctx,
8614
+ props,
8615
+ triggerId,
8616
+ targetFieldId,
8617
+ effect
8618
+ );
8619
+ if (!normalized) {
8620
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
8621
+ if (map) delete map[targetFieldId];
8622
+ pruneEffectMap(props, triggerId);
8623
+ return;
8624
+ }
8625
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8626
+ }),
8627
+ undo: () => ctx.undo()
8628
+ });
8629
+ }
8630
+ function patchOptionEffect(ctx, triggerId, targetFieldId, patch) {
8631
+ ctx.exec({
8632
+ name: "patchOptionEffect",
8633
+ do: () => ctx.patchProps((props) => {
8634
+ var _a, _b, _c, _d;
8635
+ const current = (_c = (_b = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
8636
+ const merged = {
8637
+ ...current,
8638
+ ...patch
8639
+ };
8640
+ const normalized = validateEffect(
8641
+ ctx,
8642
+ props,
8643
+ triggerId,
8644
+ targetFieldId,
8645
+ merged
8646
+ );
8647
+ if (!normalized) {
8648
+ const map = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId];
8649
+ if (map) delete map[targetFieldId];
8650
+ pruneEffectMap(props, triggerId);
8651
+ return;
8652
+ }
8653
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8654
+ }),
8655
+ undo: () => ctx.undo()
8656
+ });
8657
+ }
8658
+ function clearOptionEffect(ctx, triggerId, targetFieldId) {
8659
+ ctx.exec({
8660
+ name: "clearOptionEffect",
8661
+ do: () => ctx.patchProps((props) => {
8662
+ var _a;
8663
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
8664
+ if (!map) return;
8665
+ delete map[targetFieldId];
8666
+ pruneEffectMap(props, triggerId);
8667
+ }),
8668
+ undo: () => ctx.undo()
8669
+ });
8670
+ }
8671
+ function clearOptionEffectsForTrigger(ctx, triggerId) {
8672
+ ctx.exec({
8673
+ name: "clearOptionEffectsForTrigger",
8674
+ do: () => ctx.patchProps((props) => {
8675
+ if (!props.option_effects_for_buttons) return;
8676
+ delete props.option_effects_for_buttons[triggerId];
8677
+ pruneEffectMap(props);
8678
+ }),
8679
+ undo: () => ctx.undo()
8680
+ });
8681
+ }
8682
+ function clearOptionEffectsForTarget(ctx, targetFieldId) {
8683
+ ctx.exec({
8684
+ name: "clearOptionEffectsForTarget",
8685
+ do: () => ctx.patchProps((props) => {
8686
+ var _a;
8687
+ const map = props.option_effects_for_buttons;
8688
+ if (!map) return;
8689
+ for (const triggerId of Object.keys(map)) {
8690
+ (_a = map[triggerId]) == null ? true : delete _a[targetFieldId];
8691
+ }
8692
+ pruneEffectMap(props);
8693
+ }),
8694
+ undo: () => ctx.undo()
8695
+ });
8696
+ }
8697
+ function addOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
8698
+ var _a;
8699
+ const additions = (_a = dedupe2(optionIds)) != null ? _a : [];
8700
+ if (!additions.length) return;
8701
+ ctx.exec({
8702
+ name: "addOptionEffectOptions",
8703
+ do: () => ctx.patchProps((props) => {
8704
+ var _a2, _b, _c, _d;
8705
+ const current = (_c = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
8706
+ const nextValues = dedupe2([
8707
+ ...(_d = current[kind]) != null ? _d : [],
8708
+ ...additions
8709
+ ]);
8710
+ const normalized = validateEffect(
8711
+ ctx,
8712
+ props,
8713
+ triggerId,
8714
+ targetFieldId,
8715
+ {
8716
+ ...current,
8717
+ [kind]: nextValues
8718
+ }
8719
+ );
8720
+ if (!normalized) return;
8721
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8722
+ }),
8723
+ undo: () => ctx.undo()
8724
+ });
8725
+ }
8726
+ function removeOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
8727
+ var _a;
8728
+ const removals = new Set((_a = dedupe2(optionIds)) != null ? _a : []);
8729
+ if (!removals.size) return;
8730
+ ctx.exec({
8731
+ name: "removeOptionEffectOptions",
8732
+ do: () => ctx.patchProps((props) => {
8733
+ var _a2, _b, _c, _d, _e;
8734
+ const current = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId];
8735
+ if (!current) return;
8736
+ const next = {
8737
+ ...current,
8738
+ [kind]: ((_c = current[kind]) != null ? _c : []).filter(
8739
+ (optionId) => !removals.has(optionId)
8740
+ )
8741
+ };
8742
+ const normalized = validateEffect(
8743
+ ctx,
8744
+ props,
8745
+ triggerId,
8746
+ targetFieldId,
8747
+ next
8748
+ );
8749
+ if (!normalized) {
8750
+ (_e = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId]) == null ? true : delete _e[targetFieldId];
8751
+ pruneEffectMap(props, triggerId);
8752
+ return;
8753
+ }
8754
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8755
+ }),
8756
+ undo: () => ctx.undo()
8757
+ });
8758
+ }
8759
+ function setOptionEffectForceVisible(ctx, triggerId, targetFieldId, forceVisible) {
8760
+ patchOptionEffect(ctx, triggerId, targetFieldId, {
8761
+ forceVisible: forceVisible === true ? true : void 0
8762
+ });
8763
+ }
8764
+
7929
8765
  // src/react/canvas/editor/editor-service-filter.ts
7930
8766
  function filterServicesForVisibleGroup2(ctx, candidates, input) {
7931
8767
  const coreInput = {
@@ -8512,7 +9348,7 @@ var Editor = class {
8512
9348
  if (!ordered.length) return;
8513
9349
  this.transact("clearServiceMany", () => {
8514
9350
  this.patchProps((p) => {
8515
- var _a, _b, _c, _d;
9351
+ var _a, _b;
8516
9352
  for (const id of ordered) {
8517
9353
  if (this.isTagId(id)) {
8518
9354
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
@@ -8525,10 +9361,8 @@ var Editor = class {
8525
9361
  continue;
8526
9362
  }
8527
9363
  if (this.isOptionId(id)) {
8528
- const own = ownerOfOption(p, id);
8529
- if (!own) continue;
8530
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
8531
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
9364
+ const found = findMutableOption(p, id);
9365
+ const o = found == null ? void 0 : found.option;
8532
9366
  if (o && "service_id" in o) delete o.service_id;
8533
9367
  }
8534
9368
  }
@@ -8577,7 +9411,7 @@ var Editor = class {
8577
9411
  if (!selected.size) return;
8578
9412
  this.transact("clearRelationsMany", () => {
8579
9413
  this.patchProps((p) => {
8580
- var _a, _b, _c;
9414
+ var _a, _b, _c, _d, _e;
8581
9415
  const clearOwned = mode === "owned" || mode === "both";
8582
9416
  const clearIncoming = mode === "incoming" || mode === "both";
8583
9417
  for (const t of (_a = p.filters) != null ? _a : []) {
@@ -8617,6 +9451,44 @@ var Editor = class {
8617
9451
  }
8618
9452
  if (!Object.keys(map).length) delete p[k];
8619
9453
  }
9454
+ const effectMap = p.option_effects_for_buttons;
9455
+ if (effectMap) {
9456
+ for (const triggerId of Object.keys(effectMap)) {
9457
+ if (clearOwned && selected.has(String(triggerId))) {
9458
+ delete effectMap[triggerId];
9459
+ continue;
9460
+ }
9461
+ const targets = effectMap[triggerId];
9462
+ if (!targets || !clearIncoming) continue;
9463
+ for (const targetFieldId of Object.keys(targets)) {
9464
+ if (selected.has(String(targetFieldId))) {
9465
+ delete targets[targetFieldId];
9466
+ continue;
9467
+ }
9468
+ const effect = targets[targetFieldId];
9469
+ if (!effect) continue;
9470
+ if (effect.include) {
9471
+ effect.include = effect.include.filter(
9472
+ (optionId) => !selected.has(String(optionId))
9473
+ );
9474
+ if (!effect.include.length) delete effect.include;
9475
+ }
9476
+ if (effect.exclude) {
9477
+ effect.exclude = effect.exclude.filter(
9478
+ (optionId) => !selected.has(String(optionId))
9479
+ );
9480
+ if (!effect.exclude.length) delete effect.exclude;
9481
+ }
9482
+ if (effect.forceVisible !== true && !((_d = effect.include) == null ? void 0 : _d.length) && !((_e = effect.exclude) == null ? void 0 : _e.length)) {
9483
+ delete targets[targetFieldId];
9484
+ }
9485
+ }
9486
+ if (!Object.keys(targets).length) delete effectMap[triggerId];
9487
+ }
9488
+ if (!Object.keys(effectMap).length) {
9489
+ delete p.option_effects_for_buttons;
9490
+ }
9491
+ }
8620
9492
  });
8621
9493
  });
8622
9494
  }
@@ -8628,7 +9500,7 @@ var Editor = class {
8628
9500
  const suffix = (_b = input.suffix) != null ? _b : "";
8629
9501
  this.transact("renameLabelsMany", () => {
8630
9502
  this.patchProps((p) => {
8631
- var _a2, _b2, _c, _d, _e, _f, _g;
9503
+ var _a2, _b2, _c, _d, _e, _f;
8632
9504
  for (const id of ordered) {
8633
9505
  if (this.isTagId(id)) {
8634
9506
  const t = ((_a2 = p.filters) != null ? _a2 : []).find((x) => x.id === id);
@@ -8641,11 +9513,8 @@ var Editor = class {
8641
9513
  continue;
8642
9514
  }
8643
9515
  if (this.isOptionId(id)) {
8644
- const own = ownerOfOption(p, id);
8645
- if (!own) continue;
8646
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === own.fieldId);
8647
- const o = (_f = f == null ? void 0 : f.options) == null ? void 0 : _f.find((x) => x.id === id);
8648
- if (o) o.label = `${prefix}${(_g = o.label) != null ? _g : ""}${suffix}`.trim();
9516
+ const o = (_e = findMutableOption(p, id)) == null ? void 0 : _e.option;
9517
+ if (o) o.label = `${prefix}${(_f = o.label) != null ? _f : ""}${suffix}`.trim();
8649
9518
  }
8650
9519
  }
8651
9520
  });
@@ -8798,6 +9667,57 @@ var Editor = class {
8798
9667
  exclude(receiverId, idOrIds) {
8799
9668
  return exclude(this.moduleCtx(), receiverId, idOrIds);
8800
9669
  }
9670
+ setOptionEffect(triggerId, targetFieldId, effect) {
9671
+ return setOptionEffect(
9672
+ this.moduleCtx(),
9673
+ triggerId,
9674
+ targetFieldId,
9675
+ effect
9676
+ );
9677
+ }
9678
+ patchOptionEffect(triggerId, targetFieldId, patch) {
9679
+ return patchOptionEffect(
9680
+ this.moduleCtx(),
9681
+ triggerId,
9682
+ targetFieldId,
9683
+ patch
9684
+ );
9685
+ }
9686
+ clearOptionEffect(triggerId, targetFieldId) {
9687
+ return clearOptionEffect(this.moduleCtx(), triggerId, targetFieldId);
9688
+ }
9689
+ clearOptionEffectsForTrigger(triggerId) {
9690
+ return clearOptionEffectsForTrigger(this.moduleCtx(), triggerId);
9691
+ }
9692
+ clearOptionEffectsForTarget(targetFieldId) {
9693
+ return clearOptionEffectsForTarget(this.moduleCtx(), targetFieldId);
9694
+ }
9695
+ addOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
9696
+ return addOptionEffectOptions(
9697
+ this.moduleCtx(),
9698
+ triggerId,
9699
+ targetFieldId,
9700
+ kind,
9701
+ optionIds
9702
+ );
9703
+ }
9704
+ removeOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
9705
+ return removeOptionEffectOptions(
9706
+ this.moduleCtx(),
9707
+ triggerId,
9708
+ targetFieldId,
9709
+ kind,
9710
+ optionIds
9711
+ );
9712
+ }
9713
+ setOptionEffectForceVisible(triggerId, targetFieldId, forceVisible) {
9714
+ return setOptionEffectForceVisible(
9715
+ this.moduleCtx(),
9716
+ triggerId,
9717
+ targetFieldId,
9718
+ forceVisible
9719
+ );
9720
+ }
8801
9721
  connect(kind, fromId, toId2) {
8802
9722
  return connect(this.moduleCtx(), kind, fromId, toId2);
8803
9723
  }
@@ -9166,11 +10086,10 @@ var Selection = class {
9166
10086
  * What counts as a "button selection" (trigger key):
9167
10087
  * - field key where the field has button === true (e.g. "f:dripfeed")
9168
10088
  * - option key (e.g. "o:fast")
9169
- * - composite key "fieldId::optionId" (e.g. "f:speed::o:fast")
9170
10089
  *
9171
10090
  * Grouping:
9172
10091
  * - button-field trigger groups under its own fieldId
9173
- * - option/composite groups under the option's owning fieldId (from nodeMap)
10092
+ * - option trigger groups under the option's owning fieldId (from nodeMap)
9174
10093
  *
9175
10094
  * Deterministic:
9176
10095
  * - preserves selection insertion order
@@ -9187,15 +10106,6 @@ var Selection = class {
9187
10106
  };
9188
10107
  for (const key of this.set) {
9189
10108
  if (!key) continue;
9190
- const idx = key.indexOf("::");
9191
- if (idx !== -1) {
9192
- const optionId = key.slice(idx + 2);
9193
- const optRef = nodeMap.get(optionId);
9194
- if ((optRef == null ? void 0 : optRef.kind) === "option" && typeof optRef.fieldId === "string") {
9195
- push(optRef.fieldId, key);
9196
- }
9197
- continue;
9198
- }
9199
10109
  const ref = nodeMap.get(key);
9200
10110
  if (!ref) continue;
9201
10111
  if (ref.kind === "option" && typeof ref.fieldId === "string") {
@@ -9216,7 +10126,6 @@ var Selection = class {
9216
10126
  * Returns only selection keys that are valid "trigger buttons":
9217
10127
  * - field keys where field.button === true
9218
10128
  * - option keys
9219
- * - composite keys "fieldId::optionId" (validated by optionId)
9220
10129
  * Excludes tags and non-button fields.
9221
10130
  */
9222
10131
  selectedButtons() {
@@ -9232,13 +10141,6 @@ var Selection = class {
9232
10141
  };
9233
10142
  for (const key of this.set) {
9234
10143
  if (!key) continue;
9235
- const idx = key.indexOf("::");
9236
- if (idx !== -1) {
9237
- const optionId = key.slice(idx + 2);
9238
- const optRef = nodeMap.get(optionId);
9239
- if ((optRef == null ? void 0 : optRef.kind) === "option") push(key);
9240
- continue;
9241
- }
9242
10144
  const ref = nodeMap.get(key);
9243
10145
  if (!ref) continue;
9244
10146
  if (ref.kind === "option") {
@@ -9277,17 +10179,7 @@ var Selection = class {
9277
10179
  const direct = fields.find((x) => x.id === id);
9278
10180
  if (direct) return direct;
9279
10181
  if (this.builder.isOptionId(id)) {
9280
- return fields.find(
9281
- (x) => {
9282
- var _a2;
9283
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
9284
- }
9285
- );
9286
- }
9287
- if (id.includes("::")) {
9288
- const [fieldId] = id.split("::");
9289
- if (!fieldId) return void 0;
9290
- return fields.find((x) => x.id === fieldId);
10182
+ return findOptionOwnerField(fields, id);
9291
10183
  }
9292
10184
  return void 0;
9293
10185
  };
@@ -9318,18 +10210,7 @@ var Selection = class {
9318
10210
  }
9319
10211
  for (const id of this.set) {
9320
10212
  if (this.builder.isOptionId(id)) {
9321
- const host = fields.find(
9322
- (x) => {
9323
- var _a2;
9324
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
9325
- }
9326
- );
9327
- if (host == null ? void 0 : host.bind_id)
9328
- return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
9329
- }
9330
- if (id.includes("::")) {
9331
- const [fid] = id.split("::");
9332
- const host = fields.find((x) => x.id === fid);
10213
+ const host = findOptionOwnerField(fields, id);
9333
10214
  if (host == null ? void 0 : host.bind_id)
9334
10215
  return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
9335
10216
  }
@@ -9343,7 +10224,11 @@ var Selection = class {
9343
10224
  const tagById = new Map(tags.map((t) => [t.id, t]));
9344
10225
  const tag = tagById.get(tagId);
9345
10226
  const selectedTriggerIds = this.selectedButtons();
9346
- const fieldIds = this.builder.visibleFields(tagId, selectedTriggerIds);
10227
+ const visibility = this.builder.resolveVisibility(
10228
+ tagId,
10229
+ selectedTriggerIds
10230
+ );
10231
+ const fieldIds = visibility.fieldIds;
9347
10232
  const fieldById = new Map(fields.map((f) => [f.id, f]));
9348
10233
  const visible = fieldIds.map((id) => fieldById.get(id)).filter(Boolean);
9349
10234
  const parentTags = [];
@@ -9369,6 +10254,14 @@ var Selection = class {
9369
10254
  let baseOverridden = false;
9370
10255
  for (const selId of this.set) {
9371
10256
  const opt = this.findOptionById(fields, selId);
10257
+ if (opt && !this.isSelectedOptionVisible(
10258
+ fields,
10259
+ selId,
10260
+ fieldIds,
10261
+ visibility.optionsByFieldId
10262
+ )) {
10263
+ continue;
10264
+ }
9372
10265
  if ((opt == null ? void 0 : opt.service_id) != null) {
9373
10266
  const role = (_d = opt.pricing_role) != null ? _d : "base";
9374
10267
  const cap = (_e = resolve == null ? void 0 : resolve(opt.service_id)) != null ? _e : { id: opt.service_id };
@@ -9399,6 +10292,8 @@ var Selection = class {
9399
10292
  tag,
9400
10293
  fields: visible,
9401
10294
  fieldIds,
10295
+ optionsByFieldId: visibility.optionsByFieldId,
10296
+ forcedFieldIds: visibility.forcedFieldIds,
9402
10297
  parentTags,
9403
10298
  childrenTags,
9404
10299
  services
@@ -9422,21 +10317,19 @@ var Selection = class {
9422
10317
  return baseOverridden;
9423
10318
  }
9424
10319
  findOptionById(fields, selId) {
9425
- var _a, _b;
9426
10320
  if (this.builder.isOptionId(selId)) {
9427
- for (const f of fields) {
9428
- const o = (_a = f.options) == null ? void 0 : _a.find((x) => x.id === selId);
9429
- if (o) return o;
9430
- }
9431
- }
9432
- if (selId.includes("::")) {
9433
- const [fid, oid] = selId.split("::");
9434
- const f = fields.find((x) => x.id === fid);
9435
- const o = (_b = f == null ? void 0 : f.options) == null ? void 0 : _b.find((x) => x.id === oid || x.id === selId);
9436
- if (o) return o;
10321
+ const field = findOptionOwnerField(fields, selId);
10322
+ return findFieldOption(field, selId);
9437
10323
  }
9438
10324
  return void 0;
9439
10325
  }
10326
+ isSelectedOptionVisible(fields, selId, visibleFieldIds, optionsByFieldId) {
10327
+ const visibleFields = new Set(visibleFieldIds);
10328
+ const field = findOptionOwnerField(fields, selId);
10329
+ if (!field || !visibleFields.has(field.id)) return false;
10330
+ const allowed = optionsByFieldId[field.id];
10331
+ return !allowed || allowed.includes(selId);
10332
+ }
9440
10333
  };
9441
10334
 
9442
10335
  // src/react/canvas/api.ts
@@ -10349,11 +11242,83 @@ function useOrderFlow() {
10349
11242
  if (!ready) return void 0;
10350
11243
  return (_c2 = (_b2 = (_a2 = ctx.selection) == null ? void 0 : _a2.currentTag) == null ? void 0 : _b2.call(_a2)) != null ? _c2 : ctx.activeTagId;
10351
11244
  }, [ready, ctx.selection, ctx.activeTagId, selTick]);
11245
+ const visibleOptionsByFieldId = useMemo4(
11246
+ () => {
11247
+ var _a2;
11248
+ return (_a2 = visibleGroup == null ? void 0 : visibleGroup.optionsByFieldId) != null ? _a2 : {};
11249
+ },
11250
+ [visibleGroup]
11251
+ );
11252
+ const forcedFieldIds = useMemo4(
11253
+ () => {
11254
+ var _a2;
11255
+ return (_a2 = visibleGroup == null ? void 0 : visibleGroup.forcedFieldIds) != null ? _a2 : [];
11256
+ },
11257
+ [visibleGroup]
11258
+ );
10352
11259
  const formValuesByFieldId = useMemo4(() => {
10353
11260
  var _a2, _b2, _c2;
10354
11261
  const values = (_c2 = (_b2 = (_a2 = ctx.formApi).snapshot) == null ? void 0 : _b2.call(_a2)) != null ? _c2 : {};
10355
11262
  return values;
10356
11263
  }, [ctx.formApi, formTick]);
11264
+ useEffect3(() => {
11265
+ var _a2, _b2, _c2, _d;
11266
+ if (!ready || !ctx.selection) return;
11267
+ const { builder, selection } = ctx.ensureReady("pruneHiddenOptions");
11268
+ const fields = (_a2 = builder.getProps().fields) != null ? _a2 : [];
11269
+ const visibleOptionEntries = Object.entries(visibleOptionsByFieldId);
11270
+ if (!visibleOptionEntries.length) return;
11271
+ const visibleOptionSets = new Map(
11272
+ visibleOptionEntries.map(([fieldId, optionIds]) => [
11273
+ fieldId,
11274
+ new Set(optionIds)
11275
+ ])
11276
+ );
11277
+ const changedFieldIds = /* @__PURE__ */ new Set();
11278
+ const ownerForToken = (token) => {
11279
+ if (!token) return void 0;
11280
+ const owner = findOptionOwnerField(fields, token);
11281
+ return owner == null ? void 0 : owner.id;
11282
+ };
11283
+ const retained = [];
11284
+ let changed = false;
11285
+ for (const token of selection.all()) {
11286
+ const ownerFieldId = ownerForToken(token);
11287
+ if (!ownerFieldId) {
11288
+ retained.push(token);
11289
+ continue;
11290
+ }
11291
+ const allowed = visibleOptionSets.get(ownerFieldId);
11292
+ if (!allowed) {
11293
+ retained.push(token);
11294
+ continue;
11295
+ }
11296
+ if (allowed.has(token)) {
11297
+ retained.push(token);
11298
+ continue;
11299
+ }
11300
+ changed = true;
11301
+ changedFieldIds.add(ownerFieldId);
11302
+ }
11303
+ if (!changed) return;
11304
+ for (const fieldId of changedFieldIds) {
11305
+ const allowed = visibleOptionSets.get(fieldId);
11306
+ const currentValue = ((_d = (_c2 = (_b2 = ctx.formApi).snapshot) == null ? void 0 : _c2.call(_b2)) != null ? _d : {})[fieldId];
11307
+ if (Array.isArray(currentValue)) {
11308
+ const next = currentValue.filter(
11309
+ (value) => allowed == null ? void 0 : allowed.has(String(value))
11310
+ );
11311
+ if (next.length !== currentValue.length) {
11312
+ ctx.formApi.set(fieldId, next.length ? next : void 0);
11313
+ }
11314
+ continue;
11315
+ }
11316
+ if (currentValue !== void 0 && allowed && !allowed.has(String(currentValue))) {
11317
+ ctx.formApi.set(fieldId, void 0);
11318
+ }
11319
+ }
11320
+ selection.many(retained, retained[retained.length - 1]);
11321
+ }, [ctx, ready, selTick, visibleOptionsByFieldId]);
10357
11322
  const optionSelectionsByFieldId = useMemo4(
10358
11323
  () => ({}),
10359
11324
  []
@@ -10501,7 +11466,7 @@ function useOrderFlow() {
10501
11466
  );
10502
11467
  const setFieldOptions = useCallback3(
10503
11468
  (fieldId, optionIds) => {
10504
- var _a2, _b2, _c2, _d;
11469
+ var _a2, _b2, _c2;
10505
11470
  const { builder, selection, init } = ctx.ensureReady(
10506
11471
  "setFieldOptions"
10507
11472
  );
@@ -10509,7 +11474,9 @@ function useOrderFlow() {
10509
11474
  const field = fields.find((f) => f.id === fieldId);
10510
11475
  if (!field) return;
10511
11476
  const validOptionIds = new Set(
10512
- ((_b2 = field.options) != null ? _b2 : []).map((option) => String(option.id))
11477
+ Array.from(fieldOptionIdSet(field)).map(
11478
+ (optionId) => String(optionId)
11479
+ )
10513
11480
  );
10514
11481
  const dedupedValid = [];
10515
11482
  const seen = /* @__PURE__ */ new Set();
@@ -10520,36 +11487,18 @@ function useOrderFlow() {
10520
11487
  seen.add(optionId);
10521
11488
  dedupedValid.push(optionId);
10522
11489
  }
10523
- const mode = (_c2 = init.mode) != null ? _c2 : "prod";
10524
- const isMulti = ((_d = field.meta) == null ? void 0 : _d.multi) === true;
11490
+ const mode = (_b2 = init.mode) != null ? _b2 : "prod";
11491
+ const isMulti = ((_c2 = field.meta) == null ? void 0 : _c2.multi) === true;
10525
11492
  const normalized = mode === "prod" && !isMulti ? dedupedValid.length ? [dedupedValid[dedupedValid.length - 1]] : [] : dedupedValid;
10526
- const fieldById = new Map(fields.map((f) => [f.id, f]));
10527
11493
  const nodeMap = builder.getNodeMap();
10528
11494
  const resolveOptionOwnerFieldId = (token) => {
10529
11495
  var _a3;
10530
11496
  if (!token) return void 0;
10531
- if (token.includes("::")) {
10532
- const [legacyFieldId, optionId] = token.split("::", 2);
10533
- if (!optionId) return void 0;
10534
- const optionRef2 = nodeMap.get(optionId);
10535
- if ((optionRef2 == null ? void 0 : optionRef2.kind) === "option" && typeof optionRef2.fieldId === "string") {
10536
- return optionRef2.fieldId;
10537
- }
10538
- if (legacyFieldId && fieldById.has(legacyFieldId)) {
10539
- return legacyFieldId;
10540
- }
10541
- return void 0;
10542
- }
10543
11497
  const optionRef = nodeMap.get(token);
10544
11498
  if ((optionRef == null ? void 0 : optionRef.kind) === "option" && typeof optionRef.fieldId === "string") {
10545
11499
  return optionRef.fieldId;
10546
11500
  }
10547
- for (const f of fields) {
10548
- if ((_a3 = f.options) == null ? void 0 : _a3.some((option) => option.id === token)) {
10549
- return f.id;
10550
- }
10551
- }
10552
- return void 0;
11501
+ return (_a3 = findOptionOwnerField(fields, token)) == null ? void 0 : _a3.id;
10553
11502
  };
10554
11503
  const retained = Array.from(selection.all()).filter(
10555
11504
  (token) => resolveOptionOwnerFieldId(token) !== fieldId
@@ -10653,6 +11602,8 @@ function useOrderFlow() {
10653
11602
  raw,
10654
11603
  notices,
10655
11604
  visibleGroup,
11605
+ visibleOptionsByFieldId,
11606
+ forcedFieldIds,
10656
11607
  formValuesByFieldId,
10657
11608
  optionSelectionsByFieldId,
10658
11609
  quantityPreview: previewSnapshot.quantity,
@@ -10759,6 +11710,18 @@ function Wrapper({
10759
11710
  const flow = useOrderFlow();
10760
11711
  const kind = toKind(field);
10761
11712
  const variant = toVariant(field);
11713
+ const renderedField = React4.useMemo(() => {
11714
+ var _a2;
11715
+ const visibleOptionIds = flow.visibleOptionsByFieldId[field.id];
11716
+ if (!visibleOptionIds) return field;
11717
+ return {
11718
+ ...field,
11719
+ options: (_a2 = filterFieldOptionsById(
11720
+ field.options,
11721
+ new Set(visibleOptionIds)
11722
+ )) != null ? _a2 : []
11723
+ };
11724
+ }, [field, flow.visibleOptionsByFieldId]);
10762
11725
  const descriptor = React4.useMemo(
10763
11726
  () => resolveInputDescriptor(registry, kind, variant),
10764
11727
  [kind, registry, variant]
@@ -10772,8 +11735,8 @@ function Wrapper({
10772
11735
  const baseProps = (_b = descriptor.defaultProps) != null ? _b : {};
10773
11736
  const defaultProps = useMemo6(() => {
10774
11737
  var _a2;
10775
- return { ...baseProps, ...(_a2 = field.defaults) != null ? _a2 : {} };
10776
- }, [baseProps, field.defaults]);
11738
+ return { ...baseProps, ...(_a2 = renderedField.defaults) != null ? _a2 : {} };
11739
+ }, [baseProps, renderedField.defaults]);
10777
11740
  const valueProp = (_c = adapter.valueProp) != null ? _c : "value";
10778
11741
  const changeProp = (_d = adapter.changeProp) != null ? _d : "onChange";
10779
11742
  const errorProp = (_e = adapter.errorProp) != null ? _e : "errorText";
@@ -10787,13 +11750,14 @@ function Wrapper({
10787
11750
  disabled: !!disabled
10788
11751
  });
10789
11752
  const optionIds = React4.useMemo(() => {
10790
- var _a2;
10791
11753
  if (!isOptionBased2) return /* @__PURE__ */ new Set();
10792
- return new Set(((_a2 = field.options) != null ? _a2 : []).map((o) => o.id));
10793
- }, [isOptionBased2, field.options]);
11754
+ return new Set(
11755
+ Array.from(fieldOptionIdSet(renderedField)).map(String)
11756
+ );
11757
+ }, [isOptionBased2, renderedField]);
10794
11758
  const adapterCtx = React4.useMemo(
10795
- () => ({ field, props: flow.raw }),
10796
- [field, flow.raw]
11759
+ () => ({ field: renderedField, props: flow.raw }),
11760
+ [renderedField, flow.raw]
10797
11761
  );
10798
11762
  const onHostChange = React4.useCallback(
10799
11763
  (next) => {
@@ -10804,7 +11768,7 @@ function Wrapper({
10804
11768
  if (isOptionBased2) {
10805
11769
  if (!adapter.getSelectedOptions) {
10806
11770
  throw new Error(
10807
- `[Wrapper] Adapter for "${field.id}" (${field.type}) must implement getSelectedOptions() because this field has options.`
11771
+ `[Wrapper] Adapter for "${renderedField.id}" (${renderedField.type}) must implement getSelectedOptions() because this field has options.`
10808
11772
  );
10809
11773
  }
10810
11774
  const rawIds = adapter.getSelectedOptions(
@@ -10817,20 +11781,20 @@ function Wrapper({
10817
11781
  (rawIds != null ? rawIds : []).map(String).filter((id) => optionIds.has(id))
10818
11782
  )
10819
11783
  );
10820
- flow.setFieldOptions(field.id, nextIds);
11784
+ flow.setFieldOptions(renderedField.id, nextIds);
10821
11785
  return;
10822
11786
  }
10823
11787
  if (isActionButton) {
10824
11788
  const isActive = (_e2 = (_d2 = adapter.isActive) == null ? void 0 : _d2.call(adapter, stored, adapterCtx)) != null ? _e2 : Boolean(stored);
10825
- if (isActive) flow.toggleOption(field.id);
10826
- else flow.clearField(field.id);
11789
+ if (isActive) flow.toggleOption(renderedField.id);
11790
+ else flow.clearField(renderedField.id);
10827
11791
  }
10828
11792
  },
10829
11793
  [
10830
11794
  adapter,
10831
11795
  adapterCtx,
10832
- field.id,
10833
- field.type,
11796
+ renderedField.id,
11797
+ renderedField.type,
10834
11798
  flow,
10835
11799
  fp,
10836
11800
  isActionButton,
@@ -10844,12 +11808,12 @@ function Wrapper({
10844
11808
  const ctx = ctxOverrides && typeof ctxOverrides === "object" ? { ...ctxFromInit, ...ctxOverrides } : ctxFromInit;
10845
11809
  return {
10846
11810
  ...ctx,
10847
- field,
11811
+ field: renderedField,
10848
11812
  flow,
10849
11813
  value: fp.value,
10850
11814
  error: fp.error
10851
11815
  };
10852
- }, [ctxOverrides, field, flow, fp.error, fp.value]);
11816
+ }, [ctxOverrides, renderedField, flow, fp.error, fp.value]);
10853
11817
  const templatedDefaultProps = React4.useMemo(() => {
10854
11818
  if (!templateStrings) return defaultProps;
10855
11819
  return templateDeep(defaultProps, templateCtx);
@@ -10859,14 +11823,14 @@ function Wrapper({
10859
11823
  return extraProps != null ? extraProps : {};
10860
11824
  return templateDeep(extraProps != null ? extraProps : {}, templateCtx);
10861
11825
  }, [extraProps, templateCtx, templateStrings]);
10862
- const fieldProps = (_h = (_g = adapter == null ? void 0 : adapter.getInputPropsFromField) == null ? void 0 : _g.call(adapter, { field, props: flow.raw })) != null ? _h : {};
11826
+ const fieldProps = (_h = (_g = adapter == null ? void 0 : adapter.getInputPropsFromField) == null ? void 0 : _g.call(adapter, { field: renderedField, props: flow.raw })) != null ? _h : {};
10863
11827
  const hostProps = {
10864
- id: field.id,
10865
- field,
11828
+ id: renderedField.id,
11829
+ field: renderedField,
10866
11830
  disabled: !!disabled || !!fp.disabled,
10867
11831
  required: field.required,
10868
11832
  // DO NOT pass `name` to InputField/entries
10869
- fieldKey: field.id,
11833
+ fieldKey: renderedField.id,
10870
11834
  ...fieldProps != null ? fieldProps : {},
10871
11835
  // error channel
10872
11836
  error: fp.error,
@@ -11872,7 +12836,10 @@ var treeSelectDescriptor = {
11872
12836
  supported: true,
11873
12837
  autoCreate: true,
11874
12838
  defaultLabel: "Option label",
11875
- defaultValue: "option"
12839
+ defaultValue: "option",
12840
+ children: {
12841
+ supported: true
12842
+ }
11876
12843
  },
11877
12844
  multi: {
11878
12845
  supported: true,
@@ -13063,20 +14030,29 @@ function withInputFieldUi(desc) {
13063
14030
  const fieldNotices = notices.filter(
13064
14031
  (notice) => matchesNotice(field, notice)
13065
14032
  );
14033
+ const mapOptionForInputField = (item) => {
14034
+ var _a3;
14035
+ const optionNotices = notices.filter(
14036
+ (notice) => matchesNotice(item, notice)
14037
+ );
14038
+ return {
14039
+ ...item,
14040
+ tags: optionNotices.map(toTagPill),
14041
+ ...((_a3 = item.children) == null ? void 0 : _a3.length) ? {
14042
+ children: item.children.map(
14043
+ mapOptionForInputField
14044
+ )
14045
+ } : {}
14046
+ };
14047
+ };
13066
14048
  return {
13067
14049
  label: field.label,
13068
14050
  tags: fieldNotices.map(toTagPill),
13069
14051
  required: field.required,
13070
14052
  ...((_b = field.options) == null ? void 0 : _b.length) ? {
13071
- options: field.options.map((item) => {
13072
- const optionNotices = notices.filter(
13073
- (notice) => matchesNotice(item, notice)
13074
- );
13075
- return {
13076
- ...item,
13077
- tags: optionNotices.map(toTagPill)
13078
- };
13079
- })
14053
+ options: field.options.map(
14054
+ mapOptionForInputField
14055
+ )
13080
14056
  } : {}
13081
14057
  };
13082
14058
  },