@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.
@@ -731,6 +731,9 @@ function normalise(input, opts = {}) {
731
731
  const excludes_for_buttons = toStringArrayMap(
732
732
  obj.excludes_for_buttons
733
733
  );
734
+ const option_effects_for_buttons = toOptionEffectMap(
735
+ obj.option_effects_for_buttons
736
+ );
734
737
  const orderKinds = toStringMap(obj.orderKinds);
735
738
  const notices = toNoticeArray(obj.notices);
736
739
  let filters = rawFilters.map((t) => coerceTag(t, constraints));
@@ -746,6 +749,9 @@ function normalise(input, opts = {}) {
746
749
  ...isNonEmpty(orderKinds) && { orderKinds },
747
750
  ...isNonEmpty(includes_for_buttons) && { includes_for_buttons },
748
751
  ...isNonEmpty(excludes_for_buttons) && { excludes_for_buttons },
752
+ ...isNonEmpty(option_effects_for_buttons) && {
753
+ option_effects_for_buttons
754
+ },
749
755
  ...fallbacks && (isNonEmpty(fallbacks.nodes) || isNonEmpty(fallbacks.global)) && {
750
756
  fallbacks
751
757
  },
@@ -900,6 +906,7 @@ function coerceOption(src, inheritRole) {
900
906
  const value = typeof src.value === "string" || typeof src.value === "number" ? src.value : void 0;
901
907
  const pricing_role = src.pricing_role === "utility" || src.pricing_role === "base" ? src.pricing_role : inheritRole;
902
908
  const meta = src.meta && typeof src.meta === "object" ? src.meta : void 0;
909
+ const children = Array.isArray(src.children) ? src.children.map((child) => coerceOption(child, pricing_role)) : void 0;
903
910
  const option = {
904
911
  id: "",
905
912
  label: "",
@@ -908,7 +915,8 @@ function coerceOption(src, inheritRole) {
908
915
  ...value !== void 0 && { value },
909
916
  ...service_id !== void 0 && { service_id },
910
917
  pricing_role,
911
- ...meta && { meta }
918
+ ...meta && { meta },
919
+ ...children && children.length && { children }
912
920
  };
913
921
  return option;
914
922
  }
@@ -973,6 +981,35 @@ function toStringArrayMap(src) {
973
981
  }
974
982
  return Object.keys(out).length ? out : void 0;
975
983
  }
984
+ function toOptionEffectMap(src) {
985
+ var _a, _b;
986
+ if (!src || typeof src !== "object") return void 0;
987
+ const out = {};
988
+ for (const [triggerId, rawTargets] of Object.entries(src)) {
989
+ if (!triggerId || !rawTargets || typeof rawTargets !== "object") {
990
+ continue;
991
+ }
992
+ const targets = {};
993
+ for (const [fieldId, rawEffect] of Object.entries(rawTargets)) {
994
+ if (!fieldId || !rawEffect || typeof rawEffect !== "object") {
995
+ continue;
996
+ }
997
+ const effect = rawEffect;
998
+ const include2 = toStringArray(effect.include);
999
+ const exclude2 = toStringArray(effect.exclude);
1000
+ const next = {
1001
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
1002
+ ...include2.length ? { include: dedupe(include2) } : {},
1003
+ ...exclude2.length ? { exclude: dedupe(exclude2) } : {}
1004
+ };
1005
+ if (next.forceVisible === true || ((_a = next.include) == null ? void 0 : _a.length) || ((_b = next.exclude) == null ? void 0 : _b.length)) {
1006
+ targets[fieldId] = next;
1007
+ }
1008
+ }
1009
+ if (Object.keys(targets).length) out[triggerId] = targets;
1010
+ }
1011
+ return Object.keys(out).length ? out : void 0;
1012
+ }
976
1013
  function toStringArray(v) {
977
1014
  if (!Array.isArray(v)) return [];
978
1015
  return v.map((x) => String(x)).filter((s) => !!s && s.trim().length > 0);
@@ -1047,6 +1084,72 @@ function normalizeFieldValidation(input) {
1047
1084
  return one ? [one] : void 0;
1048
1085
  }
1049
1086
 
1087
+ // src/core/options.ts
1088
+ function walkFieldOptions(field) {
1089
+ const out = [];
1090
+ const visit = (options, depth, parentId) => {
1091
+ for (const option of options != null ? options : []) {
1092
+ out.push({
1093
+ field,
1094
+ fieldId: field.id,
1095
+ option,
1096
+ optionId: option.id,
1097
+ depth,
1098
+ parentId
1099
+ });
1100
+ visit(option.children, depth + 1, option.id);
1101
+ }
1102
+ };
1103
+ visit(field.options, 0);
1104
+ return out;
1105
+ }
1106
+ function fieldOptionIds(field) {
1107
+ return walkFieldOptions(field).map((visit) => visit.optionId);
1108
+ }
1109
+ function fieldOptionIdSet(field) {
1110
+ return new Set(fieldOptionIds(field));
1111
+ }
1112
+ function findFieldOption(field, optionId) {
1113
+ var _a;
1114
+ if (!field) return void 0;
1115
+ return (_a = walkFieldOptions(field).find((visit) => visit.optionId === optionId)) == null ? void 0 : _a.option;
1116
+ }
1117
+ function findOptionOwnerField(fields, optionId) {
1118
+ for (const field of fields) {
1119
+ if (findFieldOption(field, optionId)) return field;
1120
+ }
1121
+ return void 0;
1122
+ }
1123
+ function optionOwnerMap(fields) {
1124
+ const out = /* @__PURE__ */ new Map();
1125
+ for (const field of fields) {
1126
+ for (const visit of walkFieldOptions(field)) {
1127
+ if (!out.has(visit.optionId)) {
1128
+ out.set(visit.optionId, {
1129
+ fieldId: field.id,
1130
+ option: visit.option
1131
+ });
1132
+ }
1133
+ }
1134
+ }
1135
+ return out;
1136
+ }
1137
+ function filterFieldOptionsById(options, allowed) {
1138
+ if (!Array.isArray(options)) return void 0;
1139
+ const out = [];
1140
+ for (const option of options) {
1141
+ const children = filterFieldOptionsById(option.children, allowed);
1142
+ if (!allowed.has(option.id) && (!children || children.length === 0)) {
1143
+ continue;
1144
+ }
1145
+ out.push({
1146
+ ...option,
1147
+ ...children ? { children } : {}
1148
+ });
1149
+ }
1150
+ return out;
1151
+ }
1152
+
1050
1153
  // src/core/validate/shared.ts
1051
1154
  function isFiniteNumber(v) {
1052
1155
  return typeof v === "number" && Number.isFinite(v);
@@ -1055,8 +1158,9 @@ function isServiceIdRef(v) {
1055
1158
  return typeof v === "string" && v.trim().length > 0 || typeof v === "number" && Number.isFinite(v);
1056
1159
  }
1057
1160
  function hasAnyServiceOption(f) {
1058
- var _a;
1059
- return ((_a = f.options) != null ? _a : []).some((o) => isServiceIdRef(o.service_id));
1161
+ return walkFieldOptions(f).some(
1162
+ (visit) => isServiceIdRef(visit.option.service_id)
1163
+ );
1060
1164
  }
1061
1165
  function getByPath(obj, path) {
1062
1166
  if (!path) return void 0;
@@ -1145,14 +1249,14 @@ function withAffected(details, ids) {
1145
1249
 
1146
1250
  // src/core/node-map.ts
1147
1251
  function buildNodeMap(props) {
1148
- var _a, _b, _c;
1252
+ var _a, _b;
1149
1253
  const map = /* @__PURE__ */ new Map();
1150
1254
  for (const t of (_a = props.filters) != null ? _a : []) {
1151
1255
  if (!map.has(t.id)) map.set(t.id, { kind: "tag", id: t.id, node: t });
1152
1256
  }
1153
1257
  for (const f of (_b = props.fields) != null ? _b : []) {
1154
1258
  if (!map.has(f.id)) map.set(f.id, { kind: "field", id: f.id, node: f });
1155
- for (const o of (_c = f.options) != null ? _c : []) {
1259
+ for (const { option: o } of walkFieldOptions(f)) {
1156
1260
  if (!map.has(o.id))
1157
1261
  map.set(o.id, {
1158
1262
  kind: "option",
@@ -1165,12 +1269,6 @@ function buildNodeMap(props) {
1165
1269
  return map;
1166
1270
  }
1167
1271
  function resolveTrigger(trigger, nodeMap) {
1168
- const idx = trigger.indexOf("::");
1169
- if (idx !== -1) {
1170
- const fieldId = trigger.slice(0, idx);
1171
- const optionId = trigger.slice(idx + 2);
1172
- return { kind: "composite", triggerKey: trigger, fieldId, optionId };
1173
- }
1174
1272
  const direct = nodeMap.get(trigger);
1175
1273
  if (!direct) return void 0;
1176
1274
  if (direct.kind === "option") {
@@ -1222,11 +1320,6 @@ function visibleFieldIdsUnder(props, tagId, opts = {}) {
1222
1320
  const ownerDepthForTriggerKey = (triggerKey) => {
1223
1321
  const t = resolveTrigger(triggerKey, nodeMap);
1224
1322
  if (!t) return void 0;
1225
- if (t.kind === "composite") {
1226
- const f = fieldById.get(t.fieldId);
1227
- if (!f) return void 0;
1228
- return ownerDepthForField(f);
1229
- }
1230
1323
  if (t.kind === "field") {
1231
1324
  const f = fieldById.get(t.id);
1232
1325
  if (!f || f.button !== true) return void 0;
@@ -1309,6 +1402,84 @@ function visibleFieldsUnder(props, tagId, opts = {}) {
1309
1402
  const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((f) => [f.id, f]));
1310
1403
  return ids.map((id) => fieldById.get(id)).filter(Boolean);
1311
1404
  }
1405
+ function resolveVisibility(props, tagId, selectedKeys) {
1406
+ var _a, _b, _c, _d;
1407
+ const selected = new Set(selectedKeys != null ? selectedKeys : []);
1408
+ const baseFieldIds = visibleFieldIdsUnder(props, tagId, { selectedKeys: selected });
1409
+ const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((field) => [field.id, field]));
1410
+ const visible = new Set(baseFieldIds);
1411
+ const forced = /* @__PURE__ */ new Set();
1412
+ const optionsByFieldId = {};
1413
+ const optionIdsByFieldId = /* @__PURE__ */ new Map();
1414
+ const getOptionIds = (field) => {
1415
+ let ids = optionIdsByFieldId.get(field.id);
1416
+ if (!ids) {
1417
+ ids = fieldOptionIds(field);
1418
+ optionIdsByFieldId.set(field.id, ids);
1419
+ }
1420
+ return ids;
1421
+ };
1422
+ const ensureOptions = (field) => {
1423
+ const ids = getOptionIds(field);
1424
+ if (!ids.length) return void 0;
1425
+ if (!optionsByFieldId[field.id]) optionsByFieldId[field.id] = [...ids];
1426
+ return optionsByFieldId[field.id];
1427
+ };
1428
+ for (const fieldId of baseFieldIds) {
1429
+ const field = fieldById.get(fieldId);
1430
+ if (field) ensureOptions(field);
1431
+ }
1432
+ const effects = (_b = props.option_effects_for_buttons) != null ? _b : {};
1433
+ for (const triggerId of selected) {
1434
+ const targetRules = effects[triggerId];
1435
+ if (!targetRules) continue;
1436
+ for (const [targetFieldId, rule] of Object.entries(targetRules)) {
1437
+ const field = fieldById.get(targetFieldId);
1438
+ if (!field) continue;
1439
+ const isVisible = visible.has(targetFieldId);
1440
+ if (!isVisible && rule.forceVisible !== true) continue;
1441
+ if (!isVisible && rule.forceVisible === true) {
1442
+ visible.add(targetFieldId);
1443
+ forced.add(targetFieldId);
1444
+ }
1445
+ const orderedOptionIds = getOptionIds(field);
1446
+ if (!orderedOptionIds.length) continue;
1447
+ const known = new Set(orderedOptionIds);
1448
+ let allowed = (_c = optionsByFieldId[targetFieldId]) != null ? _c : [...orderedOptionIds];
1449
+ if (Array.isArray(rule.include) && rule.include.length) {
1450
+ const include2 = new Set(
1451
+ rule.include.filter((optionId) => known.has(optionId))
1452
+ );
1453
+ allowed = orderedOptionIds.filter(
1454
+ (optionId) => include2.has(optionId) && allowed.includes(optionId)
1455
+ );
1456
+ }
1457
+ if (Array.isArray(rule.exclude) && rule.exclude.length) {
1458
+ const exclude2 = new Set(
1459
+ rule.exclude.filter((optionId) => known.has(optionId))
1460
+ );
1461
+ allowed = allowed.filter((optionId) => !exclude2.has(optionId));
1462
+ }
1463
+ optionsByFieldId[targetFieldId] = allowed;
1464
+ }
1465
+ }
1466
+ const visibleFieldIds = baseFieldIds.filter((fieldId) => visible.has(fieldId));
1467
+ const seen = new Set(visibleFieldIds);
1468
+ for (const field of (_d = props.fields) != null ? _d : []) {
1469
+ if (!visible.has(field.id) || seen.has(field.id)) continue;
1470
+ seen.add(field.id);
1471
+ visibleFieldIds.push(field.id);
1472
+ ensureOptions(field);
1473
+ }
1474
+ for (const fieldId of Object.keys(optionsByFieldId)) {
1475
+ if (!visible.has(fieldId)) delete optionsByFieldId[fieldId];
1476
+ }
1477
+ return {
1478
+ fieldIds: visibleFieldIds,
1479
+ optionsByFieldId,
1480
+ forcedFieldIds: visibleFieldIds.filter((fieldId) => forced.has(fieldId))
1481
+ };
1482
+ }
1312
1483
 
1313
1484
  // src/core/validate/steps/visibility.ts
1314
1485
  function createFieldsVisibleUnder(v) {
@@ -1326,7 +1497,6 @@ function resolveRootTags(tags) {
1326
1497
  return roots.length ? roots : tags.slice(0, 1);
1327
1498
  }
1328
1499
  function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
1329
- var _a;
1330
1500
  const visible = visibleFieldsUnder(v.props, tagId, {
1331
1501
  selectedKeys
1332
1502
  });
@@ -1336,7 +1506,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
1336
1506
  const t = f.id;
1337
1507
  if (effectfulKeys.has(t)) triggers.push(t);
1338
1508
  }
1339
- for (const o of (_a = f.options) != null ? _a : []) {
1509
+ for (const { option: o } of walkFieldOptions(f)) {
1340
1510
  const t = o.id;
1341
1511
  if (effectfulKeys.has(t)) triggers.push(t);
1342
1512
  }
@@ -1345,7 +1515,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
1345
1515
  return triggers;
1346
1516
  }
1347
1517
  function runVisibilityRulesOnce(v) {
1348
- var _a, _b, _c, _d, _e;
1518
+ var _a, _b, _c, _d;
1349
1519
  for (const t of v.tags) {
1350
1520
  const visible = v.fieldsVisibleUnder(t.id);
1351
1521
  const seen = /* @__PURE__ */ new Map();
@@ -1395,9 +1565,9 @@ function runVisibilityRulesOnce(v) {
1395
1565
  let hasUtility = false;
1396
1566
  const utilityOptionIds = [];
1397
1567
  for (const f of visible) {
1398
- for (const o of (_c = f.options) != null ? _c : []) {
1568
+ for (const { option: o } of walkFieldOptions(f)) {
1399
1569
  if (!isServiceIdRef(o.service_id)) continue;
1400
- const role = (_e = (_d = o.pricing_role) != null ? _d : f.pricing_role) != null ? _e : "base";
1570
+ const role = (_d = (_c = o.pricing_role) != null ? _c : f.pricing_role) != null ? _d : "base";
1401
1571
  if (role === "base") hasBase = true;
1402
1572
  else if (role === "utility") {
1403
1573
  hasUtility = true;
@@ -1438,7 +1608,7 @@ function dedupeErrorsInPlace(v, startIndex) {
1438
1608
  v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
1439
1609
  }
1440
1610
  function validateVisibility(v, options = {}) {
1441
- var _a, _b, _c, _d, _e;
1611
+ var _a, _b, _c, _d, _e, _f;
1442
1612
  v.simulatedVisibilityContexts = [];
1443
1613
  const simulate = options.simulate === true;
1444
1614
  if (!simulate) {
@@ -1463,10 +1633,13 @@ function validateVisibility(v, options = {}) {
1463
1633
  for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
1464
1634
  effectfulKeys.add(key);
1465
1635
  }
1636
+ for (const key of Object.keys((_e = v.props.option_effects_for_buttons) != null ? _e : {})) {
1637
+ effectfulKeys.add(key);
1638
+ }
1466
1639
  }
1467
1640
  const roots = resolveRootTags(v.tags);
1468
1641
  const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
1469
- const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
1642
+ const originalSelected = new Set((_f = v.selectedKeys) != null ? _f : []);
1470
1643
  const errorsStart = v.errors.length;
1471
1644
  const visited = /* @__PURE__ */ new Set();
1472
1645
  const seenContexts = /* @__PURE__ */ new Set();
@@ -1607,7 +1780,7 @@ function validateStructure(v) {
1607
1780
 
1608
1781
  // src/core/validate/steps/identity.ts
1609
1782
  function validateIdentity(v) {
1610
- var _a, _b;
1783
+ var _a;
1611
1784
  const tags = v.tags;
1612
1785
  const fields = v.fields;
1613
1786
  {
@@ -1707,7 +1880,7 @@ function validateIdentity(v) {
1707
1880
  }
1708
1881
  }
1709
1882
  for (const f of fields) {
1710
- for (const o of (_b = f.options) != null ? _b : []) {
1883
+ for (const { option: o } of walkFieldOptions(f)) {
1711
1884
  if (!o.label || !o.label.trim()) {
1712
1885
  v.errors.push({
1713
1886
  code: "label_missing",
@@ -1722,25 +1895,11 @@ function validateIdentity(v) {
1722
1895
  }
1723
1896
 
1724
1897
  // src/core/validate/steps/option-maps.ts
1725
- function parseFieldOptionKey(key) {
1726
- const idx = key.indexOf("::");
1727
- if (idx === -1) return null;
1728
- const fieldId = key.slice(0, idx).trim();
1729
- const optionId = key.slice(idx + 2).trim();
1730
- if (!fieldId || !optionId) return null;
1731
- return { fieldId, optionId };
1732
- }
1733
- function hasOption(v, fid, oid) {
1734
- var _a;
1735
- const f = v.fieldById.get(fid);
1736
- if (!f) return false;
1737
- return !!((_a = f.options) != null ? _a : []).find((o) => o.id === oid);
1738
- }
1739
1898
  function validateOptionMaps(v) {
1740
- var _a, _b;
1899
+ var _a, _b, _c;
1741
1900
  const incMap = (_a = v.props.includes_for_buttons) != null ? _a : {};
1742
1901
  const excMap = (_b = v.props.excludes_for_buttons) != null ? _b : {};
1743
- 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.`;
1902
+ const badKeyMessage = (key) => `Invalid trigger-map key "${key}". Expected a known option id or button-field id.`;
1744
1903
  const validateTriggerKey = (key) => {
1745
1904
  const ref = v.nodeMap.get(key);
1746
1905
  if (ref) {
@@ -1759,19 +1918,7 @@ function validateOptionMaps(v) {
1759
1918
  }
1760
1919
  return { ok: false, nodeId: ref.id, affected: [ref.id] };
1761
1920
  }
1762
- const p = parseFieldOptionKey(key);
1763
- if (!p) return { ok: false };
1764
- if (!hasOption(v, p.fieldId, p.optionId))
1765
- return {
1766
- ok: false,
1767
- nodeId: p.fieldId,
1768
- affected: [p.fieldId, p.optionId]
1769
- };
1770
- return {
1771
- ok: true,
1772
- nodeId: p.fieldId,
1773
- affected: [p.fieldId, p.optionId]
1774
- };
1921
+ return { ok: false };
1775
1922
  };
1776
1923
  for (const k of Object.keys(incMap)) {
1777
1924
  const r = validateTriggerKey(k);
@@ -1797,6 +1944,57 @@ function validateOptionMaps(v) {
1797
1944
  });
1798
1945
  }
1799
1946
  }
1947
+ const effectMap = (_c = v.props.option_effects_for_buttons) != null ? _c : {};
1948
+ for (const [triggerKey, targets] of Object.entries(effectMap)) {
1949
+ const trigger = validateTriggerKey(triggerKey);
1950
+ if (!trigger.ok) {
1951
+ v.errors.push({
1952
+ code: "bad_option_effect_key",
1953
+ severity: "error",
1954
+ message: badKeyMessage(triggerKey),
1955
+ nodeId: trigger.nodeId,
1956
+ details: withAffected({ key: triggerKey }, trigger.affected)
1957
+ });
1958
+ }
1959
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
1960
+ const field = v.fieldById.get(targetFieldId);
1961
+ if (!field) {
1962
+ v.errors.push({
1963
+ code: "bad_option_effect_target",
1964
+ severity: "error",
1965
+ message: `Option effect trigger "${triggerKey}" targets unknown field "${targetFieldId}".`,
1966
+ details: withAffected(
1967
+ { key: triggerKey, targetFieldId },
1968
+ trigger.affected
1969
+ )
1970
+ });
1971
+ continue;
1972
+ }
1973
+ const validOptionIds = fieldOptionIdSet(field);
1974
+ const checkTargetOptions = (kind, optionIds) => {
1975
+ for (const optionId of optionIds != null ? optionIds : []) {
1976
+ if (validOptionIds.has(optionId)) continue;
1977
+ v.errors.push({
1978
+ code: "bad_option_effect_option",
1979
+ severity: "error",
1980
+ message: `Option effect trigger "${triggerKey}" references unknown ${kind} option "${optionId}" for field "${targetFieldId}".`,
1981
+ nodeId: targetFieldId,
1982
+ details: withAffected(
1983
+ {
1984
+ key: triggerKey,
1985
+ targetFieldId,
1986
+ optionId,
1987
+ kind
1988
+ },
1989
+ [targetFieldId, optionId]
1990
+ )
1991
+ });
1992
+ }
1993
+ };
1994
+ checkTargetOptions("include", effect == null ? void 0 : effect.include);
1995
+ checkTargetOptions("exclude", effect == null ? void 0 : effect.exclude);
1996
+ }
1997
+ }
1800
1998
  for (const k of Object.keys(incMap)) {
1801
1999
  if (!(k in excMap)) continue;
1802
2000
  const r = validateTriggerKey(k);
@@ -1810,27 +2008,231 @@ function validateOptionMaps(v) {
1810
2008
  }
1811
2009
  }
1812
2010
 
1813
- // src/utils/order-kind.ts
1814
- function normalizeSelectedTriggerKey(key, nodeMap) {
1815
- if (!key) return void 0;
1816
- const compositeIdx = key.indexOf("::");
1817
- if (compositeIdx !== -1) {
1818
- const fieldId = key.slice(0, compositeIdx).trim();
1819
- const optionId = key.slice(compositeIdx + 2).trim();
1820
- if (optionId) {
1821
- const optionRef = nodeMap.get(optionId);
1822
- if ((optionRef == null ? void 0 : optionRef.kind) === "option") {
1823
- return { nodeId: optionRef.id, nodeKind: "option" };
2011
+ // src/core/validate/steps/visibility-cycles.ts
2012
+ var MAX_VISIBILITY_CYCLE_DEPTH = 20;
2013
+ function validateVisibilityCycles(v) {
2014
+ const triggerById = buildTriggerIndex(v.fields);
2015
+ if (!triggerById.size) return;
2016
+ const fieldTriggers = buildFieldTriggerIndex(v.fields);
2017
+ const revealTargetsByTrigger = buildRevealIndex(v, triggerById);
2018
+ const reported = /* @__PURE__ */ new Set();
2019
+ for (const rootTriggerId of Array.from(triggerById.keys()).sort()) {
2020
+ const required = makeRequiredState(triggerById, [rootTriggerId]);
2021
+ walkFromTrigger({
2022
+ v,
2023
+ triggerById,
2024
+ fieldTriggers,
2025
+ revealTargetsByTrigger,
2026
+ rootTriggerId,
2027
+ currentTriggerId: rootTriggerId,
2028
+ required,
2029
+ path: [rootTriggerId],
2030
+ visited: /* @__PURE__ */ new Set(),
2031
+ reported,
2032
+ depth: 0
2033
+ });
2034
+ }
2035
+ }
2036
+ function buildTriggerIndex(fields) {
2037
+ const out = /* @__PURE__ */ new Map();
2038
+ const owners = optionOwnerMap(fields);
2039
+ for (const field of fields) {
2040
+ if (field.button === true) {
2041
+ out.set(field.id, {
2042
+ kind: "field",
2043
+ id: field.id,
2044
+ ownerFieldId: field.id
2045
+ });
2046
+ }
2047
+ }
2048
+ for (const [optionId, owner] of owners) {
2049
+ out.set(optionId, {
2050
+ kind: "option",
2051
+ id: optionId,
2052
+ ownerFieldId: owner.fieldId
2053
+ });
2054
+ }
2055
+ return out;
2056
+ }
2057
+ function buildFieldTriggerIndex(fields) {
2058
+ const out = /* @__PURE__ */ new Map();
2059
+ for (const field of fields) {
2060
+ const triggers = [];
2061
+ if (field.button === true) triggers.push(field.id);
2062
+ for (const visit of walkFieldOptions(field)) {
2063
+ triggers.push(visit.optionId);
2064
+ }
2065
+ out.set(field.id, triggers);
2066
+ }
2067
+ return out;
2068
+ }
2069
+ function buildRevealIndex(v, triggerById) {
2070
+ var _a, _b;
2071
+ const out = /* @__PURE__ */ new Map();
2072
+ const addReveal = (triggerId, targetFieldId) => {
2073
+ var _a2;
2074
+ if (!triggerById.has(triggerId)) return;
2075
+ if (!v.fieldById.has(targetFieldId)) return;
2076
+ const set = (_a2 = out.get(triggerId)) != null ? _a2 : /* @__PURE__ */ new Set();
2077
+ set.add(targetFieldId);
2078
+ out.set(triggerId, set);
2079
+ };
2080
+ for (const [triggerId, targetIds] of Object.entries(
2081
+ (_a = v.props.includes_for_buttons) != null ? _a : {}
2082
+ )) {
2083
+ for (const targetId of targetIds != null ? targetIds : []) addReveal(triggerId, targetId);
2084
+ }
2085
+ for (const [triggerId, targets] of Object.entries(
2086
+ (_b = v.props.option_effects_for_buttons) != null ? _b : {}
2087
+ )) {
2088
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
2089
+ if ((effect == null ? void 0 : effect.forceVisible) === true)
2090
+ addReveal(triggerId, targetFieldId);
2091
+ }
2092
+ }
2093
+ return new Map(
2094
+ Array.from(out.entries()).map(([triggerId, fieldIds]) => [
2095
+ triggerId,
2096
+ Array.from(fieldIds).sort()
2097
+ ])
2098
+ );
2099
+ }
2100
+ function walkFromTrigger(args) {
2101
+ var _a, _b, _c;
2102
+ if (args.depth >= MAX_VISIBILITY_CYCLE_DEPTH) return;
2103
+ const visitedKey = `${args.rootTriggerId}::${args.currentTriggerId}::${args.path.join(">")}`;
2104
+ if (args.visited.has(visitedKey)) return;
2105
+ args.visited.add(visitedKey);
2106
+ const revealedFieldIds = (_a = args.revealTargetsByTrigger.get(args.currentTriggerId)) != null ? _a : [];
2107
+ for (const revealedFieldId of revealedFieldIds) {
2108
+ const reachableTriggers = (_c = (_b = args.fieldTriggers.get(revealedFieldId)) == null ? void 0 : _b.slice().sort()) != null ? _c : [];
2109
+ for (const reachableTriggerId of reachableTriggers) {
2110
+ const invalidation = invalidatesRequiredPath(
2111
+ args.v,
2112
+ args.triggerById,
2113
+ reachableTriggerId,
2114
+ args.required
2115
+ );
2116
+ if (invalidation) {
2117
+ emitCycleError({
2118
+ v: args.v,
2119
+ rootTriggerId: args.rootTriggerId,
2120
+ revealedFieldId,
2121
+ conflictingTriggerId: reachableTriggerId,
2122
+ invalidatedId: invalidation.invalidatedId,
2123
+ path: [...args.path, reachableTriggerId],
2124
+ reported: args.reported
2125
+ });
2126
+ }
2127
+ if (args.path.includes(reachableTriggerId)) continue;
2128
+ walkFromTrigger({
2129
+ ...args,
2130
+ currentTriggerId: reachableTriggerId,
2131
+ required: addRequiredTrigger(
2132
+ args.triggerById,
2133
+ args.required,
2134
+ reachableTriggerId
2135
+ ),
2136
+ path: [...args.path, reachableTriggerId],
2137
+ depth: args.depth + 1
2138
+ });
2139
+ }
2140
+ }
2141
+ }
2142
+ function makeRequiredState(triggerById, triggerIds) {
2143
+ let required = {
2144
+ triggers: /* @__PURE__ */ new Set(),
2145
+ ownerFields: /* @__PURE__ */ new Set()
2146
+ };
2147
+ for (const triggerId of triggerIds) {
2148
+ required = addRequiredTrigger(triggerById, required, triggerId);
2149
+ }
2150
+ return required;
2151
+ }
2152
+ function addRequiredTrigger(triggerById, current, triggerId) {
2153
+ const next = {
2154
+ triggers: new Set(current.triggers),
2155
+ ownerFields: new Set(current.ownerFields)
2156
+ };
2157
+ const trigger = triggerById.get(triggerId);
2158
+ if (!trigger) return next;
2159
+ next.triggers.add(triggerId);
2160
+ next.ownerFields.add(trigger.ownerFieldId);
2161
+ return next;
2162
+ }
2163
+ function invalidatesRequiredPath(v, triggerById, conflictingTriggerId, required) {
2164
+ var _a, _b, _c, _d, _e, _f;
2165
+ for (const targetId of (_b = (_a = v.props.excludes_for_buttons) == null ? void 0 : _a[conflictingTriggerId]) != null ? _b : []) {
2166
+ if (required.ownerFields.has(targetId)) {
2167
+ return { invalidatedId: targetId };
2168
+ }
2169
+ const targetTrigger = triggerById.get(targetId);
2170
+ if ((targetTrigger == null ? void 0 : targetTrigger.kind) === "option" && required.triggers.has(targetId)) {
2171
+ return { invalidatedId: targetId };
2172
+ }
2173
+ }
2174
+ const effects = (_d = (_c = v.props.option_effects_for_buttons) == null ? void 0 : _c[conflictingTriggerId]) != null ? _d : {};
2175
+ for (const [targetFieldId, effect] of Object.entries(effects)) {
2176
+ if (!v.fieldById.has(targetFieldId)) continue;
2177
+ if ((_e = effect == null ? void 0 : effect.exclude) == null ? void 0 : _e.length) {
2178
+ const excluded = new Set(effect.exclude);
2179
+ for (const requiredTriggerId of required.triggers) {
2180
+ const requiredTrigger = triggerById.get(requiredTriggerId);
2181
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
2182
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
2183
+ if (excluded.has(requiredTriggerId)) {
2184
+ return { invalidatedId: requiredTriggerId };
2185
+ }
1824
2186
  }
1825
2187
  }
1826
- if (fieldId) {
1827
- const fieldRef = nodeMap.get(fieldId);
1828
- if ((fieldRef == null ? void 0 : fieldRef.kind) === "field") {
1829
- return { nodeId: fieldRef.id, nodeKind: "field" };
2188
+ if ((_f = effect == null ? void 0 : effect.include) == null ? void 0 : _f.length) {
2189
+ const included = new Set(effect.include);
2190
+ for (const requiredTriggerId of required.triggers) {
2191
+ const requiredTrigger = triggerById.get(requiredTriggerId);
2192
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
2193
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
2194
+ if (!included.has(requiredTriggerId)) {
2195
+ return { invalidatedId: requiredTriggerId };
2196
+ }
1830
2197
  }
1831
2198
  }
1832
- return void 0;
1833
2199
  }
2200
+ return void 0;
2201
+ }
2202
+ function emitCycleError(args) {
2203
+ const key = [
2204
+ args.rootTriggerId,
2205
+ args.conflictingTriggerId,
2206
+ args.invalidatedId,
2207
+ args.path.join(">")
2208
+ ].join("::");
2209
+ if (args.reported.has(key)) return;
2210
+ args.reported.add(key);
2211
+ args.v.errors.push({
2212
+ code: "visibility_dependency_cycle",
2213
+ severity: "error",
2214
+ message: `Visibility dependency cycle: trigger "${args.rootTriggerId}" reveals "${args.revealedFieldId}", but reachable trigger "${args.conflictingTriggerId}" can hide or remove "${args.invalidatedId}".`,
2215
+ nodeId: args.conflictingTriggerId,
2216
+ details: withAffected(
2217
+ {
2218
+ rootTriggerId: args.rootTriggerId,
2219
+ conflictingTriggerId: args.conflictingTriggerId,
2220
+ invalidatedId: args.invalidatedId,
2221
+ path: args.path
2222
+ },
2223
+ [
2224
+ args.rootTriggerId,
2225
+ args.revealedFieldId,
2226
+ args.conflictingTriggerId,
2227
+ args.invalidatedId
2228
+ ]
2229
+ )
2230
+ });
2231
+ }
2232
+
2233
+ // src/utils/order-kind.ts
2234
+ function normalizeSelectedTriggerKey(key, nodeMap) {
2235
+ if (!key) return void 0;
1834
2236
  const ref = nodeMap.get(key);
1835
2237
  if (!ref) return void 0;
1836
2238
  if (ref.kind !== "field" && ref.kind !== "option") return void 0;
@@ -1989,8 +2391,7 @@ function validateUtilityMarkers(v) {
1989
2391
  "percent"
1990
2392
  ]);
1991
2393
  for (const f of v.fields) {
1992
- const optsArr = Array.isArray(f.options) ? f.options : [];
1993
- for (const o of optsArr) {
2394
+ for (const { option: o } of walkFieldOptions(f)) {
1994
2395
  const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
1995
2396
  const hasService = isServiceIdRef(o.service_id);
1996
2397
  const util = (_c = o.meta) == null ? void 0 : _c.utility;
@@ -2226,13 +2627,13 @@ function normalizeServiceRef(value) {
2226
2627
 
2227
2628
  // src/core/validate/steps/rates.ts
2228
2629
  function validateRates(v) {
2229
- var _a, _b, _c;
2630
+ var _a, _b;
2230
2631
  const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
2231
2632
  for (const f of v.fields) {
2232
2633
  if (!isMultiField(f)) continue;
2233
2634
  const baseRates = [];
2234
- for (const o of (_a = f.options) != null ? _a : []) {
2235
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
2635
+ for (const { option: o } of walkFieldOptions(f)) {
2636
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
2236
2637
  if (role !== "base") continue;
2237
2638
  const sid = o.service_id;
2238
2639
  if (!isServiceIdRef(sid)) continue;
@@ -2643,7 +3044,7 @@ function effectiveConstraints(v, tagId) {
2643
3044
  return out;
2644
3045
  }
2645
3046
  function validateConstraints(v) {
2646
- var _a, _b;
3047
+ var _a;
2647
3048
  for (const t of v.tags) {
2648
3049
  const eff = effectiveConstraints(v, t.id);
2649
3050
  const hasAnyRequired = Object.values(eff).some(
@@ -2652,7 +3053,7 @@ function validateConstraints(v) {
2652
3053
  if (!hasAnyRequired) continue;
2653
3054
  const visible = v.fieldsVisibleUnder(t.id);
2654
3055
  for (const f of visible) {
2655
- for (const o of (_a = f.options) != null ? _a : []) {
3056
+ for (const { option: o } of walkFieldOptions(f)) {
2656
3057
  if (!isServiceIdRef(o.service_id)) continue;
2657
3058
  const svc = getServiceCapability(v.serviceMap, o.service_id);
2658
3059
  if (!svc || typeof svc !== "object") continue;
@@ -2706,7 +3107,7 @@ function validateConstraints(v) {
2706
3107
  if (!row) continue;
2707
3108
  const from = row.from === true;
2708
3109
  const to = row.to === true;
2709
- const origin = String((_b = row.origin) != null ? _b : "");
3110
+ const origin = String((_a = row.origin) != null ? _a : "");
2710
3111
  v.errors.push({
2711
3112
  code: "constraint_overridden",
2712
3113
  severity: "warning",
@@ -2740,14 +3141,14 @@ function validateCustomFields(v) {
2740
3141
 
2741
3142
  // src/core/validate/steps/global-utility-guard.ts
2742
3143
  function validateGlobalUtilityGuard(v) {
2743
- var _a, _b, _c;
3144
+ var _a, _b;
2744
3145
  if (!v.options.globalUtilityGuard) return;
2745
3146
  let hasUtility = false;
2746
3147
  let hasBase = false;
2747
3148
  for (const f of v.fields) {
2748
- for (const o of (_a = f.options) != null ? _a : []) {
3149
+ for (const { option: o } of walkFieldOptions(f)) {
2749
3150
  if (!isServiceIdRef(o.service_id)) continue;
2750
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
3151
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
2751
3152
  if (role === "base") hasBase = true;
2752
3153
  else if (role === "utility") hasUtility = true;
2753
3154
  if (hasUtility && hasBase) break;
@@ -2949,7 +3350,7 @@ function applyFilterAllowLists(tagId, fieldId, filter) {
2949
3350
  return true;
2950
3351
  }
2951
3352
  function collectServiceItems(args) {
2952
- var _a, _b, _c, _d, _e;
3353
+ var _a, _b, _c, _d;
2953
3354
  const filter = args.filter;
2954
3355
  const roleFilter = (_a = filter == null ? void 0 : filter.role) != null ? _a : "both";
2955
3356
  const where = filter == null ? void 0 : filter.where;
@@ -2999,7 +3400,7 @@ function collectServiceItems(args) {
2999
3400
  affectedIds: [`field:${f.id}`, `service:${String(fSid)}`]
3000
3401
  });
3001
3402
  }
3002
- for (const o of (_d = f.options) != null ? _d : []) {
3403
+ for (const { option: o } of walkFieldOptions(f)) {
3003
3404
  const oSid = o.service_id;
3004
3405
  if (!isServiceIdRef2(oSid)) continue;
3005
3406
  const role = fieldRoleOf(f, o);
@@ -3084,7 +3485,7 @@ function collectServiceItems(args) {
3084
3485
  }
3085
3486
  } else if (includeGroupFallbacks) {
3086
3487
  const allowPrimaries = new Set(
3087
- ((_e = args.visiblePrimaries) != null ? _e : []).map((x) => String(x))
3488
+ ((_d = args.visiblePrimaries) != null ? _d : []).map((x) => String(x))
3088
3489
  );
3089
3490
  for (const primaryKey of allowPrimaries) {
3090
3491
  const list = globalFb[primaryKey];
@@ -3165,17 +3566,15 @@ function affectedFromItems(items) {
3165
3566
  return uniq(ids);
3166
3567
  }
3167
3568
  function visibleGroupNodeIds(tag, fields) {
3168
- var _a;
3169
3569
  const ids = [tag.id];
3170
3570
  for (const f of fields) {
3171
- for (const o of (_a = f.options) != null ? _a : []) {
3571
+ for (const { option: o } of walkFieldOptions(f)) {
3172
3572
  ids.push(o.id);
3173
3573
  }
3174
3574
  }
3175
3575
  return uniq(ids);
3176
3576
  }
3177
3577
  function visibleGroupPrimaries(tag, fields) {
3178
- var _a;
3179
3578
  const prim = [];
3180
3579
  const tagSid = tag.service_id;
3181
3580
  if (typeof tagSid === "string" || typeof tagSid === "number" && Number.isFinite(tagSid)) {
@@ -3186,7 +3585,7 @@ function visibleGroupPrimaries(tag, fields) {
3186
3585
  if (typeof fsid === "string" || typeof fsid === "number" && Number.isFinite(fsid)) {
3187
3586
  prim.push(fsid);
3188
3587
  }
3189
- for (const o of (_a = f.options) != null ? _a : []) {
3588
+ for (const { option: o } of walkFieldOptions(f)) {
3190
3589
  const osid = o.service_id;
3191
3590
  if (typeof osid === "string" || typeof osid === "number" && Number.isFinite(osid)) {
3192
3591
  prim.push(osid);
@@ -3410,6 +3809,7 @@ function validate(props, ctx = {}) {
3410
3809
  validateStructure(v);
3411
3810
  validateIdentity(v);
3412
3811
  validateOptionMaps(v);
3812
+ validateVisibilityCycles(v);
3413
3813
  validateOrderKinds(v);
3414
3814
  v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
3415
3815
  const visSim = readVisibilitySimOpts(options);
@@ -3543,14 +3943,14 @@ var BuilderImpl = class {
3543
3943
  const showOptions = showSet.has(f.id);
3544
3944
  if (!showOptions) continue;
3545
3945
  if (!Array.isArray(f.options)) continue;
3546
- for (const o of f.options) {
3946
+ for (const { option: o, parentId } of walkFieldOptions(f)) {
3547
3947
  nodes.push({
3548
3948
  id: o.id,
3549
3949
  kind: "option",
3550
3950
  label: o.label
3551
3951
  });
3552
3952
  const e = {
3553
- from: f.id,
3953
+ from: parentId != null ? parentId : f.id,
3554
3954
  to: o.id,
3555
3955
  kind: "option",
3556
3956
  meta: { ownerField: f.id }
@@ -3597,7 +3997,7 @@ var BuilderImpl = class {
3597
3997
  return { nodes, edges };
3598
3998
  }
3599
3999
  cleanedProps() {
3600
- var _a, _b, _c, _d, _e;
4000
+ var _a, _b, _c, _d, _e, _f;
3601
4001
  const fieldIds = new Set(this.props.fields.map((f) => f.id));
3602
4002
  const optionIds = /* @__PURE__ */ new Set();
3603
4003
  this.optionOwnerById.forEach((_v, oid) => optionIds.add(oid));
@@ -3609,6 +4009,7 @@ var BuilderImpl = class {
3609
4009
  }
3610
4010
  const incMap = (_c = this.props.includes_for_buttons) != null ? _c : {};
3611
4011
  const excMap = (_d = this.props.excludes_for_buttons) != null ? _d : {};
4012
+ const effectMap = (_e = this.props.option_effects_for_buttons) != null ? _e : {};
3612
4013
  const includedByButtons = /* @__PURE__ */ new Set();
3613
4014
  const referencedKeys = /* @__PURE__ */ new Set();
3614
4015
  const referencedOwnerFields = /* @__PURE__ */ new Set();
@@ -3628,6 +4029,14 @@ var BuilderImpl = class {
3628
4029
  void fid;
3629
4030
  }
3630
4031
  }
4032
+ for (const [key, targets] of Object.entries(effectMap)) {
4033
+ referencedKeys.add(key);
4034
+ const owner = this.optionOwnerById.get(key);
4035
+ if (owner) referencedOwnerFields.add(owner.fieldId);
4036
+ for (const [fid, effect] of Object.entries(targets != null ? targets : {})) {
4037
+ if ((effect == null ? void 0 : effect.forceVisible) === true) includedByButtons.add(fid);
4038
+ }
4039
+ }
3631
4040
  const boundIds = /* @__PURE__ */ new Set();
3632
4041
  for (const f of this.props.fields) {
3633
4042
  const b = f.bind_id;
@@ -3645,6 +4054,7 @@ var BuilderImpl = class {
3645
4054
  return bound || included || referenced || !excluded;
3646
4055
  });
3647
4056
  const allowedTargets = new Set(fields.map((f) => f.id));
4057
+ const allowedFieldById = new Map(fields.map((f) => [f.id, f]));
3648
4058
  const pruneButtons = (src) => {
3649
4059
  if (!src) return void 0;
3650
4060
  const out2 = {};
@@ -3664,13 +4074,52 @@ var BuilderImpl = class {
3664
4074
  const excludes_for_buttons = pruneButtons(
3665
4075
  this.props.excludes_for_buttons
3666
4076
  );
4077
+ const pruneOptionEffects = (src) => {
4078
+ var _a2, _b2, _c2, _d2;
4079
+ if (!src) return void 0;
4080
+ const out2 = {};
4081
+ for (const [key, targets] of Object.entries(src)) {
4082
+ const keyIsValid = optionIds.has(key) || fieldIds.has(key);
4083
+ if (!keyIsValid) continue;
4084
+ const cleanedTargets = {};
4085
+ for (const [targetFieldId, effect] of Object.entries(
4086
+ targets != null ? targets : {}
4087
+ )) {
4088
+ const field = allowedFieldById.get(targetFieldId);
4089
+ if (!field || !effect) continue;
4090
+ const validOptionIds = fieldOptionIdSet(field);
4091
+ const include2 = Array.from(
4092
+ new Set((_a2 = effect.include) != null ? _a2 : [])
4093
+ ).filter((optionId) => validOptionIds.has(optionId));
4094
+ const exclude2 = Array.from(
4095
+ new Set((_b2 = effect.exclude) != null ? _b2 : [])
4096
+ ).filter((optionId) => validOptionIds.has(optionId));
4097
+ const next = {
4098
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
4099
+ ...include2.length ? { include: include2 } : {},
4100
+ ...exclude2.length ? { exclude: exclude2 } : {}
4101
+ };
4102
+ if (next.forceVisible === true || ((_c2 = next.include) == null ? void 0 : _c2.length) || ((_d2 = next.exclude) == null ? void 0 : _d2.length)) {
4103
+ cleanedTargets[targetFieldId] = next;
4104
+ }
4105
+ }
4106
+ if (Object.keys(cleanedTargets).length) {
4107
+ out2[key] = cleanedTargets;
4108
+ }
4109
+ }
4110
+ return Object.keys(out2).length ? out2 : void 0;
4111
+ };
4112
+ const option_effects_for_buttons = pruneOptionEffects(
4113
+ this.props.option_effects_for_buttons
4114
+ );
3667
4115
  const out = {
3668
4116
  filters: this.props.filters.slice(),
3669
4117
  fields,
3670
4118
  ...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
3671
4119
  ...includes_for_buttons && { includes_for_buttons },
3672
4120
  ...excludes_for_buttons && { excludes_for_buttons },
3673
- schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
4121
+ ...option_effects_for_buttons && { option_effects_for_buttons },
4122
+ schema_version: (_f = this.props.schema_version) != null ? _f : "1.0",
3674
4123
  // keep fallbacks & other maps as-is
3675
4124
  ...this.props.fallbacks ? { fallbacks: this.props.fallbacks } : {}
3676
4125
  };
@@ -3683,12 +4132,15 @@ var BuilderImpl = class {
3683
4132
  return (0, import_lodash_es2.cloneDeep)(this.options);
3684
4133
  }
3685
4134
  visibleFields(tagId, selectedKeys) {
4135
+ return this.resolveVisibility(tagId, selectedKeys).fieldIds;
4136
+ }
4137
+ resolveVisibility(tagId, selectedKeys) {
3686
4138
  var _a;
3687
- return visibleFieldIdsUnder(this.props, tagId, {
3688
- selectedKeys: new Set(
3689
- (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
3690
- )
3691
- });
4139
+ return resolveVisibility(
4140
+ this.props,
4141
+ tagId,
4142
+ (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
4143
+ );
3692
4144
  }
3693
4145
  getNodeMap() {
3694
4146
  if (!this._nodemap) this._nodemap = buildNodeMap(this.getProps());
@@ -3703,9 +4155,8 @@ var BuilderImpl = class {
3703
4155
  for (const t of this.props.filters) this.tagById.set(t.id, t);
3704
4156
  for (const f of this.props.fields) {
3705
4157
  this.fieldById.set(f.id, f);
3706
- if (Array.isArray(f.options)) {
3707
- for (const o of f.options)
3708
- this.optionOwnerById.set(o.id, { fieldId: f.id });
4158
+ for (const [optionId, owner] of optionOwnerMap([f])) {
4159
+ this.optionOwnerById.set(optionId, { fieldId: owner.fieldId });
3709
4160
  }
3710
4161
  }
3711
4162
  }
@@ -5142,20 +5593,20 @@ function isFiniteNumber2(v) {
5142
5593
 
5143
5594
  // src/utils/build-order-snapshot/selection.ts
5144
5595
  function isOptionBased(f) {
5145
- const hasOptions = Array.isArray(f.options) && f.options.length > 0;
5596
+ const hasOptions = fieldOptionIdSet(f).size > 0;
5146
5597
  return hasOptions || isMultiField(f);
5147
5598
  }
5148
5599
  function toSelectedOptionKeys(byField) {
5149
5600
  const keys = [];
5150
- for (const [fieldId, optionIds] of Object.entries(byField != null ? byField : {})) {
5601
+ for (const optionIds of Object.values(byField != null ? byField : {})) {
5151
5602
  for (const optionId of optionIds != null ? optionIds : []) {
5152
- keys.push(`${fieldId}::${optionId}`);
5603
+ keys.push(optionId);
5153
5604
  }
5154
5605
  }
5155
5606
  return keys;
5156
5607
  }
5157
- function getSelectedOptionsByFieldId(selection, fieldById, mode) {
5158
- var _a, _b;
5608
+ function getSelectedOptionsByFieldId(selection, fieldById, mode, visibleOptionsByFieldId) {
5609
+ var _a;
5159
5610
  const collected = {};
5160
5611
  for (const visit of buildSelectedNodeVisitOrder(selection, fieldById)) {
5161
5612
  if (visit.kind !== "option") continue;
@@ -5166,18 +5617,18 @@ function getSelectedOptionsByFieldId(selection, fieldById, mode) {
5166
5617
  for (const [fieldId, optionIds] of Object.entries(collected)) {
5167
5618
  const field = fieldById.get(fieldId);
5168
5619
  if (!field) continue;
5169
- const validOptionIds = new Set(
5170
- ((_a = field.options) != null ? _a : []).map((option) => option.id)
5171
- );
5620
+ const validOptionIds = fieldOptionIdSet(field);
5621
+ const visibleOptionIds = (visibleOptionsByFieldId == null ? void 0 : visibleOptionsByFieldId[fieldId]) ? new Set(visibleOptionsByFieldId[fieldId]) : void 0;
5172
5622
  const dedupedValid = [];
5173
5623
  const seen = /* @__PURE__ */ new Set();
5174
5624
  for (const optionId of optionIds) {
5175
5625
  if (!validOptionIds.has(optionId)) continue;
5626
+ if (visibleOptionIds && !visibleOptionIds.has(optionId)) continue;
5176
5627
  if (seen.has(optionId)) continue;
5177
5628
  seen.add(optionId);
5178
5629
  dedupedValid.push(optionId);
5179
5630
  }
5180
- const isMulti = ((_b = field.meta) == null ? void 0 : _b.multi) === true;
5631
+ const isMulti = ((_a = field.meta) == null ? void 0 : _a.multi) === true;
5181
5632
  const normalized = mode === "prod" && !isMulti ? dedupedValid.length ? [dedupedValid[dedupedValid.length - 1]] : [] : dedupedValid;
5182
5633
  if (normalized.length) out[fieldId] = normalized;
5183
5634
  }
@@ -5194,57 +5645,49 @@ function buildSelectedNodeVisitOrder(selection, fieldById) {
5194
5645
  out.push({ kind: "field", fieldId });
5195
5646
  }
5196
5647
  function pushOption(fieldId, optionId) {
5197
- const key = `option:${fieldId}::${optionId}`;
5648
+ const key = `option:${optionId}`;
5198
5649
  if (seen.has(key)) return;
5199
5650
  seen.add(key);
5200
5651
  out.push({ kind: "option", fieldId, optionId });
5201
5652
  }
5202
- for (const item of (_a = selection.optionTraversalOrder) != null ? _a : []) {
5203
- pushOption(item.fieldId, item.optionId);
5653
+ for (const optionId of (_a = selection.optionTraversalOrder) != null ? _a : []) {
5654
+ const ownerField = findOptionOwnerField(fieldById.values(), optionId);
5655
+ if (ownerField) pushOption(ownerField.id, optionId);
5204
5656
  }
5205
5657
  for (const rawKey of (_b = selection.selectedKeys) != null ? _b : []) {
5206
5658
  const key = String(rawKey);
5207
- if (key.includes("::")) {
5208
- const [fieldId, optionId] = key.split("::", 2);
5209
- if (fieldId && optionId) pushOption(fieldId, optionId);
5210
- continue;
5211
- }
5212
5659
  const field = fieldById.get(key);
5213
5660
  if (field) {
5214
5661
  pushField(field.id);
5215
5662
  continue;
5216
5663
  }
5217
- const ownerField = findOptionOwnerField(key, fieldById);
5664
+ const ownerField = findOptionOwnerField(fieldById.values(), key);
5218
5665
  if (ownerField) pushOption(ownerField.id, key);
5219
5666
  }
5220
5667
  for (const [fieldId, optionIds] of Object.entries(
5221
5668
  (_c = selection.optionSelectionsByFieldId) != null ? _c : {}
5222
5669
  )) {
5223
- if (!fieldById.has(fieldId)) continue;
5670
+ const hintedField = fieldById.get(fieldId);
5671
+ if (!hintedField) continue;
5224
5672
  for (const optionId of optionIds != null ? optionIds : []) {
5225
- pushOption(fieldId, optionId);
5673
+ const ownerField = findOptionOwnerField(fieldById.values(), optionId);
5674
+ if ((ownerField == null ? void 0 : ownerField.id) === hintedField.id) {
5675
+ pushOption(ownerField.id, optionId);
5676
+ }
5226
5677
  }
5227
5678
  }
5228
5679
  return out;
5229
5680
  }
5230
- function findOptionOwnerField(optionId, fieldById) {
5231
- var _a;
5232
- for (const field of fieldById.values()) {
5233
- if ((_a = field.options) == null ? void 0 : _a.some((option) => option.id === optionId)) return field;
5234
- }
5235
- return void 0;
5236
- }
5237
5681
 
5238
5682
  // src/utils/build-order-snapshot/services.ts
5239
5683
  function isServiceBased(field) {
5240
- var _a;
5241
5684
  if (field.service_id !== void 0 && field.service_id !== null) return true;
5242
- return !!((_a = field.options) == null ? void 0 : _a.some(
5243
- (item) => item.service_id !== void 0 && item.service_id !== null
5244
- ));
5685
+ return walkFieldOptions(field).some(
5686
+ ({ option }) => option.service_id !== void 0 && option.service_id !== null
5687
+ );
5245
5688
  }
5246
5689
  function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById, services) {
5247
- var _a, _b, _c, _d;
5690
+ var _a, _b, _c;
5248
5691
  const serviceMap = {};
5249
5692
  const visible = new Set(visibleFieldIds);
5250
5693
  const selectedBaseServices = [];
@@ -5271,9 +5714,9 @@ function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById,
5271
5714
  }
5272
5715
  continue;
5273
5716
  }
5274
- const option = (_b = field.options) == null ? void 0 : _b.find((item) => item.id === visit.optionId);
5717
+ const option = findFieldOption(field, visit.optionId);
5275
5718
  if (!option) continue;
5276
- const role = (_d = (_c = option.pricing_role) != null ? _c : field.pricing_role) != null ? _d : "base";
5719
+ const role = (_c = (_b = option.pricing_role) != null ? _b : field.pricing_role) != null ? _c : "base";
5277
5720
  if (role === "utility") continue;
5278
5721
  if (option.service_id !== void 0 && option.service_id !== null) {
5279
5722
  addSelectedBaseService(option.id, option.service_id);
@@ -5404,16 +5847,15 @@ function resolveQuantity(visibleFieldIds, fieldById, tagById, selection, tagId,
5404
5847
  return { quantity: hostDefault, source: { kind: "default", defaultedFromHost: true } };
5405
5848
  }
5406
5849
  function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selection, tagId) {
5407
- var _a, _b, _c;
5850
+ var _a, _b;
5408
5851
  const visible = new Set(visibleFieldIds);
5409
5852
  const visits = buildSelectedNodeVisitOrder(selection, fieldById);
5410
5853
  for (const visit of visits) {
5411
5854
  if (visit.kind !== "option") continue;
5412
5855
  if (!visible.has(visit.fieldId)) continue;
5413
5856
  const field = fieldById.get(visit.fieldId);
5414
- if (!((_a = field == null ? void 0 : field.options) == null ? void 0 : _a.length)) continue;
5415
- const option = field.options.find((item) => item.id === visit.optionId);
5416
- const quantity = readPositiveFiniteNumber((_b = option == null ? void 0 : option.meta) == null ? void 0 : _b.quantityDefault);
5857
+ const option = findFieldOption(field, visit.optionId);
5858
+ const quantity = readPositiveFiniteNumber((_a = option == null ? void 0 : option.meta) == null ? void 0 : _a.quantityDefault);
5417
5859
  if (quantity !== void 0) {
5418
5860
  return { quantity, source: { kind: "option", id: option.id } };
5419
5861
  }
@@ -5428,7 +5870,7 @@ function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selecti
5428
5870
  }
5429
5871
  }
5430
5872
  const tag = tagById.get(tagId);
5431
- const tagQuantity = readPositiveFiniteNumber((_c = tag == null ? void 0 : tag.meta) == null ? void 0 : _c.quantityDefault);
5873
+ const tagQuantity = readPositiveFiniteNumber((_b = tag == null ? void 0 : tag.meta) == null ? void 0 : _b.quantityDefault);
5432
5874
  if (tagQuantity !== void 0) {
5433
5875
  return { quantity: tagQuantity, source: { kind: "tag", id: tagId } };
5434
5876
  }
@@ -5531,12 +5973,10 @@ function collectUtilityLineItems(visibleFieldIds, fieldById, selection, selected
5531
5973
  const item = buildUtilityItemFromMarker(field.id, marker, quantity, value);
5532
5974
  if (item) items.push(item);
5533
5975
  }
5534
- if (Array.isArray(field.options) && field.options.length) {
5535
- const selectedOptionIds = (_c = selectedOptionsByFieldId[field.id]) != null ? _c : [];
5536
- if (!selectedOptionIds.length) continue;
5537
- const optById = new Map(field.options.map((o) => [o.id, o]));
5976
+ const selectedOptionIds = (_c = selectedOptionsByFieldId[field.id]) != null ? _c : [];
5977
+ if (selectedOptionIds.length) {
5538
5978
  for (const oid of selectedOptionIds) {
5539
- const option = optById.get(oid);
5979
+ const option = findFieldOption(field, oid);
5540
5980
  if (!option) continue;
5541
5981
  if (((_d = option.pricing_role) != null ? _d : "base") !== "utility") continue;
5542
5982
  const optionMarker = readUtilityMarker((_e = option.meta) == null ? void 0 : _e.utility);
@@ -5652,7 +6092,7 @@ function buildDevWarnings(props, svcMap, originalFallbacks, fieldById, visibleFi
5652
6092
 
5653
6093
  // src/utils/build-order-snapshot/index.ts
5654
6094
  function buildOrderSnapshot(props, builder, selection, services, settings = {}) {
5655
- var _a, _b, _c, _d, _e, _f, _g, _h;
6095
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
5656
6096
  const mode = (_a = settings.mode) != null ? _a : "prod";
5657
6097
  const hostDefaultQty = Number.isFinite((_b = settings.hostDefaultQuantity) != null ? _b : 1) ? settings.hostDefaultQuantity : 1;
5658
6098
  const fbSettings = {
@@ -5665,14 +6105,31 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5665
6105
  const builtAt = (/* @__PURE__ */ new Date()).toISOString();
5666
6106
  const tagId = selection.activeTagId;
5667
6107
  const selectedButtonKeys = (_d = selection.selectedKeys) != null ? _d : toSelectedOptionKeys(selection.optionSelectionsByFieldId);
5668
- const visibleFieldIds = builder.visibleFields(tagId, selectedButtonKeys);
5669
6108
  const tagById = new Map(((_e = props.filters) != null ? _e : []).map((t) => [t.id, t]));
5670
6109
  const fieldById = new Map(((_f = props.fields) != null ? _f : []).map((f) => [f.id, f]));
5671
- const tagConstraints = (_h = (_g = tagById.get(tagId)) == null ? void 0 : _g.constraints) != null ? _h : void 0;
6110
+ const resolve = typeof builder.resolveVisibility === "function" ? builder.resolveVisibility.bind(builder) : void 0;
6111
+ let resolvedVisibility = resolve == null ? void 0 : resolve(tagId, selectedButtonKeys);
6112
+ let visibleFieldIds = (_g = resolvedVisibility == null ? void 0 : resolvedVisibility.fieldIds) != null ? _g : builder.visibleFields(tagId, selectedButtonKeys);
6113
+ const filteredSelectedButtonKeys = filterSelectedKeysByVisibility(
6114
+ selectedButtonKeys,
6115
+ visibleFieldIds,
6116
+ resolvedVisibility == null ? void 0 : resolvedVisibility.optionsByFieldId,
6117
+ fieldById
6118
+ );
6119
+ if (resolve && filteredSelectedButtonKeys.join("\0") !== selectedButtonKeys.join("\0")) {
6120
+ resolvedVisibility = resolve(tagId, filteredSelectedButtonKeys);
6121
+ visibleFieldIds = resolvedVisibility.fieldIds;
6122
+ }
6123
+ const effectiveSelection = {
6124
+ ...selection,
6125
+ selectedKeys: filteredSelectedButtonKeys
6126
+ };
6127
+ const tagConstraints = (_i = (_h = tagById.get(tagId)) == null ? void 0 : _h.constraints) != null ? _i : void 0;
5672
6128
  const selectedOptionsByFieldId = getSelectedOptionsByFieldId(
5673
- selection,
6129
+ effectiveSelection,
5674
6130
  fieldById,
5675
- mode
6131
+ mode,
6132
+ resolvedVisibility == null ? void 0 : resolvedVisibility.optionsByFieldId
5676
6133
  );
5677
6134
  const selectionFields = visibleFieldIds.map((fid) => fieldById.get(fid)).filter((f) => !!f).map((f) => {
5678
6135
  var _a2;
@@ -5686,21 +6143,21 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5686
6143
  const { formValues, selections } = buildInputs(
5687
6144
  visibleFieldIds,
5688
6145
  fieldById,
5689
- selection,
6146
+ effectiveSelection,
5690
6147
  selectedOptionsByFieldId
5691
6148
  );
5692
6149
  const qtyRes = resolveQuantity(
5693
6150
  visibleFieldIds,
5694
6151
  fieldById,
5695
6152
  tagById,
5696
- selection,
6153
+ effectiveSelection,
5697
6154
  tagId,
5698
6155
  hostDefaultQty
5699
6156
  );
5700
6157
  const { serviceMap, servicesList } = resolveServices(
5701
6158
  tagId,
5702
6159
  visibleFieldIds,
5703
- selection,
6160
+ effectiveSelection,
5704
6161
  tagById,
5705
6162
  fieldById,
5706
6163
  services
@@ -5722,7 +6179,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5722
6179
  const utilities = collectUtilityLineItems(
5723
6180
  visibleFieldIds,
5724
6181
  fieldById,
5725
- selection,
6182
+ effectiveSelection,
5726
6183
  selectedOptionsByFieldId,
5727
6184
  qtyRes.quantity
5728
6185
  );
@@ -5732,7 +6189,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5732
6189
  prunedFallbacks.original,
5733
6190
  fieldById,
5734
6191
  visibleFieldIds,
5735
- selection
6192
+ effectiveSelection
5736
6193
  ) : void 0;
5737
6194
  const meta = {
5738
6195
  schema_version: props.schema_version,
@@ -5745,7 +6202,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5745
6202
  tagId,
5746
6203
  visibleFieldIds,
5747
6204
  fieldById,
5748
- selection,
6205
+ effectiveSelection,
5749
6206
  selectedOptionsByFieldId
5750
6207
  ),
5751
6208
  policy: toSnapshotPolicy(fbSettings)
@@ -5757,7 +6214,7 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5757
6214
  builtAt,
5758
6215
  selection: {
5759
6216
  tag: tagId,
5760
- buttons: selectedButtonKeys,
6217
+ buttons: filteredSelectedButtonKeys,
5761
6218
  fields: selectionFields
5762
6219
  },
5763
6220
  inputs: { form: formValues, selections },
@@ -5775,6 +6232,24 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
5775
6232
  meta
5776
6233
  };
5777
6234
  }
6235
+ function filterSelectedKeysByVisibility(selectedKeys, visibleFieldIds, optionsByFieldId, fieldById) {
6236
+ if (!optionsByFieldId) return selectedKeys;
6237
+ const visibleFields = new Set(visibleFieldIds);
6238
+ const out = [];
6239
+ for (const rawKey of selectedKeys) {
6240
+ const key = String(rawKey);
6241
+ if (fieldById.has(key)) {
6242
+ if (visibleFields.has(key)) out.push(key);
6243
+ continue;
6244
+ }
6245
+ const owner = findOptionOwnerField(fieldById.values(), key);
6246
+ if (!owner || !visibleFields.has(owner.id)) continue;
6247
+ const allowed = optionsByFieldId[owner.id];
6248
+ if (allowed && !allowed.includes(key)) continue;
6249
+ out.push(key);
6250
+ }
6251
+ return out;
6252
+ }
5778
6253
 
5779
6254
  // src/core/fallback-editor.ts
5780
6255
  function createFallbackEditor(options = {}) {
@@ -6254,6 +6729,97 @@ function bumpSuffix(old) {
6254
6729
  return `${stem}${parseInt(m[2], 10) + 1}`;
6255
6730
  }
6256
6731
 
6732
+ // src/react/canvas/editor/editor-utils.ts
6733
+ function ownerOfOption(props, optionId) {
6734
+ var _a;
6735
+ for (const f of (_a = props.fields) != null ? _a : []) {
6736
+ const found = findOptionLocationInField(f, optionId);
6737
+ if (found) return { fieldId: f.id, index: found.index };
6738
+ }
6739
+ return null;
6740
+ }
6741
+ function findMutableOption(props, optionId) {
6742
+ var _a;
6743
+ for (const field of (_a = props.fields) != null ? _a : []) {
6744
+ const found = findOptionLocationInField(field, optionId);
6745
+ if (found) return { field, ...found };
6746
+ }
6747
+ return void 0;
6748
+ }
6749
+ function collectFieldOptionIds(field) {
6750
+ const out = [];
6751
+ const visit = (options) => {
6752
+ for (const option of options != null ? options : []) {
6753
+ out.push(String(option.id));
6754
+ visit(option.children);
6755
+ }
6756
+ };
6757
+ visit(field == null ? void 0 : field.options);
6758
+ return out;
6759
+ }
6760
+ function findOptionLocationInField(field, optionId) {
6761
+ const visit = (siblings, parent) => {
6762
+ if (!siblings) return void 0;
6763
+ const index = siblings.findIndex((option) => option.id === optionId);
6764
+ if (index >= 0) {
6765
+ return {
6766
+ option: siblings[index],
6767
+ siblings,
6768
+ index,
6769
+ parent
6770
+ };
6771
+ }
6772
+ for (const option of siblings) {
6773
+ const found = visit(option.children, option);
6774
+ if (found) return found;
6775
+ }
6776
+ return void 0;
6777
+ };
6778
+ return visit(field.options);
6779
+ }
6780
+ function hasFieldOptions(field) {
6781
+ return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
6782
+ }
6783
+ function isActualButtonField(field) {
6784
+ return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
6785
+ }
6786
+ function clearFieldButtonReceiverMaps(props, fieldId) {
6787
+ var _a, _b, _c;
6788
+ if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
6789
+ delete props.includes_for_buttons[fieldId];
6790
+ }
6791
+ if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
6792
+ delete props.excludes_for_buttons[fieldId];
6793
+ }
6794
+ if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
6795
+ delete props.includes_for_buttons;
6796
+ }
6797
+ if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
6798
+ delete props.excludes_for_buttons;
6799
+ }
6800
+ if ((_c = props.option_effects_for_buttons) == null ? void 0 : _c[fieldId]) {
6801
+ delete props.option_effects_for_buttons[fieldId];
6802
+ }
6803
+ if (props.option_effects_for_buttons && Object.keys(props.option_effects_for_buttons).length === 0) {
6804
+ delete props.option_effects_for_buttons;
6805
+ }
6806
+ }
6807
+ function ensureServiceExists(opts, id) {
6808
+ if (typeof opts.serviceExists === "function") {
6809
+ if (!opts.serviceExists(id)) {
6810
+ throw new Error(`service_not_found:${String(id)}`);
6811
+ }
6812
+ return;
6813
+ }
6814
+ if (opts.serviceMap) {
6815
+ if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
6816
+ throw new Error(`service_not_found:${String(id)}`);
6817
+ }
6818
+ return;
6819
+ }
6820
+ throw new Error("service_checker_missing");
6821
+ }
6822
+
6257
6823
  // src/react/canvas/editor/editor-duplicate.ts
6258
6824
  function duplicate(ctx, ref, opts = {}) {
6259
6825
  const snapBefore = ctx.makeSnapshot("duplicate:before");
@@ -6328,14 +6894,66 @@ function duplicateInPlace(ctx, ref, opts = {}) {
6328
6894
  return duplicateOption(ctx, ref.fieldId, ref.id, opts);
6329
6895
  }
6330
6896
  function ownerFieldOfOption(props, optionId) {
6331
- var _a, _b;
6897
+ var _a;
6332
6898
  for (const field of (_a = props.fields) != null ? _a : []) {
6333
- if (((_b = field.options) != null ? _b : []).some((o) => o.id === optionId)) {
6899
+ if (findMutableOption({ ...props, fields: [field] }, optionId)) {
6334
6900
  return { fieldId: field.id };
6335
6901
  }
6336
6902
  }
6337
6903
  return null;
6338
6904
  }
6905
+ function cloneOptionTree(ctx, fieldId, option, opts, optionIdMap) {
6906
+ var _a, _b, _c, _d;
6907
+ const newId = ctx.uniqueOptionId(
6908
+ fieldId,
6909
+ ((_a = opts.optionIdStrategy) != null ? _a : defaultOptionIdStrategy)(option.id)
6910
+ );
6911
+ optionIdMap.set(option.id, newId);
6912
+ const children = (_b = option.children) == null ? void 0 : _b.map(
6913
+ (child) => cloneOptionTree(ctx, fieldId, child, opts, optionIdMap)
6914
+ );
6915
+ return {
6916
+ ...option,
6917
+ id: newId,
6918
+ label: ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = option.label) != null ? _d : option.id),
6919
+ ...(children == null ? void 0 : children.length) ? { children } : {}
6920
+ };
6921
+ }
6922
+ function remapEffect(effect, optionIdMap) {
6923
+ const remapList = (values) => values == null ? void 0 : values.map((value) => {
6924
+ var _a;
6925
+ return (_a = optionIdMap.get(value)) != null ? _a : value;
6926
+ });
6927
+ return {
6928
+ ...effect,
6929
+ ...effect.include ? { include: remapList(effect.include) } : {},
6930
+ ...effect.exclude ? { exclude: remapList(effect.exclude) } : {}
6931
+ };
6932
+ }
6933
+ function copyOptionEffects(props, args) {
6934
+ var _a, _b, _c, _d, _e;
6935
+ const source = props.option_effects_for_buttons;
6936
+ if (!source) return;
6937
+ const next = {
6938
+ ...source
6939
+ };
6940
+ const triggerIdMap = (_a = args.triggerIdMap) != null ? _a : /* @__PURE__ */ new Map();
6941
+ const targetFieldIdMap = (_b = args.targetFieldIdMap) != null ? _b : /* @__PURE__ */ new Map();
6942
+ const optionIdMap = (_c = args.optionIdMap) != null ? _c : /* @__PURE__ */ new Map();
6943
+ for (const [oldTriggerId, targetMap] of Object.entries(source)) {
6944
+ const newTriggerId = triggerIdMap.get(oldTriggerId);
6945
+ if (!newTriggerId) continue;
6946
+ const copiedTargets = {
6947
+ ...(_d = next[newTriggerId]) != null ? _d : {}
6948
+ };
6949
+ for (const [oldTargetFieldId, effect] of Object.entries(targetMap != null ? targetMap : {})) {
6950
+ const newTargetFieldId = (_e = targetFieldIdMap.get(oldTargetFieldId)) != null ? _e : oldTargetFieldId;
6951
+ copiedTargets[newTargetFieldId] = remapEffect(effect, optionIdMap);
6952
+ }
6953
+ next[newTriggerId] = copiedTargets;
6954
+ }
6955
+ props.option_effects_for_buttons = next;
6956
+ }
6339
6957
  function duplicateTag(ctx, tagId, opts) {
6340
6958
  var _a, _b, _c, _d;
6341
6959
  const props = ctx.getProps();
@@ -6391,7 +7009,7 @@ function duplicateTag(ctx, tagId, opts) {
6391
7009
  return id;
6392
7010
  }
6393
7011
  function duplicateField(ctx, fieldId, opts) {
6394
- var _a, _b, _c, _d, _e, _f, _g;
7012
+ var _a, _b, _c, _d, _e, _f;
6395
7013
  const props = ctx.getProps();
6396
7014
  const fields = (_a = props.fields) != null ? _a : [];
6397
7015
  const src = fields.find((f) => f.id === fieldId);
@@ -6399,21 +7017,10 @@ function duplicateField(ctx, fieldId, opts) {
6399
7017
  const id = (_b = opts.id) != null ? _b : ctx.uniqueId(src.id);
6400
7018
  const label = ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = src.label) != null ? _d : id);
6401
7019
  const name = opts.nameStrategy ? opts.nameStrategy(src.name) : nextCopyName(src.name);
6402
- const optId = (old) => {
6403
- var _a2;
6404
- return ctx.uniqueOptionId(
6405
- id,
6406
- ((_a2 = opts.optionIdStrategy) != null ? _a2 : defaultOptionIdStrategy)(old)
6407
- );
6408
- };
6409
- const clonedOptions = ((_e = src.options) != null ? _e : []).map((o) => {
6410
- var _a2, _b2;
6411
- return {
6412
- ...o,
6413
- id: optId(o.id),
6414
- label: ((_a2 = opts.labelStrategy) != null ? _a2 : nextCopyLabel)((_b2 = o.label) != null ? _b2 : o.id)
6415
- };
6416
- });
7020
+ const optionIdMap = /* @__PURE__ */ new Map();
7021
+ const clonedOptions = ((_e = src.options) != null ? _e : []).map(
7022
+ (o) => cloneOptionTree(ctx, id, o, opts, optionIdMap)
7023
+ );
6417
7024
  const cloned = {
6418
7025
  ...src,
6419
7026
  id,
@@ -6422,14 +7029,8 @@ function duplicateField(ctx, fieldId, opts) {
6422
7029
  bind_id: ((_f = opts.copyBindings) != null ? _f : true) ? src.bind_id : void 0,
6423
7030
  options: clonedOptions
6424
7031
  };
6425
- const optionIdMap = /* @__PURE__ */ new Map();
6426
- ((_g = src.options) != null ? _g : []).forEach((o, i) => {
6427
- var _a2, _b2;
6428
- const newOptId = (_b2 = (_a2 = clonedOptions[i]) == null ? void 0 : _a2.id) != null ? _b2 : o.id;
6429
- optionIdMap.set(o.id, newOptId);
6430
- });
6431
7032
  ctx.patchProps((p) => {
6432
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
7033
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g;
6433
7034
  const arr = (_a2 = p.fields) != null ? _a2 : [];
6434
7035
  const idx = arr.findIndex((f) => f.id === fieldId);
6435
7036
  arr.splice(idx + 1, 0, cloned);
@@ -6467,52 +7068,56 @@ function duplicateField(ctx, fieldId, opts) {
6467
7068
  }
6468
7069
  if (optionIdMap.has(key)) {
6469
7070
  const newKey = optionIdMap.get(key);
6470
- const merged = /* @__PURE__ */ new Set([...(_g2 = nextMap[newKey]) != null ? _g2 : [], ...targets]);
7071
+ const merged = /* @__PURE__ */ new Set([...(_g = nextMap[newKey]) != null ? _g : [], ...targets]);
6471
7072
  nextMap[newKey] = Array.from(merged);
6472
7073
  }
6473
7074
  }
6474
7075
  p[mapKey] = nextMap;
6475
7076
  }
7077
+ copyOptionEffects(p, {
7078
+ triggerIdMap: new Map([
7079
+ [fieldId, id],
7080
+ ...Array.from(optionIdMap.entries())
7081
+ ]),
7082
+ targetFieldIdMap: /* @__PURE__ */ new Map([[fieldId, id]]),
7083
+ optionIdMap
7084
+ });
6476
7085
  }
6477
7086
  });
6478
7087
  return id;
6479
7088
  }
6480
7089
  function duplicateOption(ctx, fieldId, optionId, opts) {
6481
- var _a, _b, _c, _d, _e, _f;
6482
7090
  const props = ctx.getProps();
6483
- const fields = (_a = props.fields) != null ? _a : [];
6484
- const f = fields.find((x) => x.id === fieldId);
6485
- if (!f) throw new Error(`Field not found: ${fieldId}`);
6486
- const optIdx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
6487
- if (optIdx < 0) {
6488
- throw new Error(`Option not found: ${fieldId}::${optionId}`);
7091
+ const location = findMutableOption(props, optionId);
7092
+ if (!location || location.field.id !== fieldId) {
7093
+ throw new Error(`Option not found: ${fieldId}/${optionId}`);
6489
7094
  }
6490
- const src = ((_c = f.options) != null ? _c : [])[optIdx];
6491
- const newId = ctx.uniqueOptionId(
6492
- fieldId,
6493
- ((_d = opts.optionIdStrategy) != null ? _d : defaultOptionIdStrategy)(src.id)
6494
- );
6495
- const newLabel = ((_e = opts.labelStrategy) != null ? _e : nextCopyLabel)((_f = src.label) != null ? _f : src.id);
7095
+ const src = location.option;
7096
+ const optionIdMap = /* @__PURE__ */ new Map();
7097
+ const clone2 = cloneOptionTree(ctx, fieldId, src, opts, optionIdMap);
7098
+ const newId = clone2.id;
6496
7099
  ctx.patchProps((p) => {
6497
- var _a2, _b2, _c2;
6498
- const fld = ((_a2 = p.fields) != null ? _a2 : []).find((x) => x.id === fieldId);
6499
- const arr = (_b2 = fld.options) != null ? _b2 : [];
6500
- const clone2 = { ...src, id: newId, label: newLabel };
6501
- arr.splice(optIdx + 1, 0, clone2);
6502
- fld.options = arr;
7100
+ var _a;
7101
+ const current = findMutableOption(p, optionId);
7102
+ if (!current) return;
7103
+ current.siblings.splice(current.index + 1, 0, clone2);
6503
7104
  if (opts.copyOptionMaps) {
6504
- const oldKey = `${fieldId}::${optionId}`;
6505
- const newKey = `${fieldId}::${newId}`;
6506
7105
  for (const mapKey of [
6507
7106
  "includes_for_buttons",
6508
7107
  "excludes_for_buttons"
6509
7108
  ]) {
6510
- const m = (_c2 = p[mapKey]) != null ? _c2 : {};
6511
- if (m[oldKey]) {
6512
- m[newKey] = Array.from(new Set(m[oldKey]));
6513
- p[mapKey] = m;
7109
+ const m = (_a = p[mapKey]) != null ? _a : {};
7110
+ for (const [oldKey, newKey] of optionIdMap.entries()) {
7111
+ if (m[oldKey]) {
7112
+ m[newKey] = Array.from(new Set(m[oldKey]));
7113
+ p[mapKey] = m;
7114
+ }
6514
7115
  }
6515
7116
  }
7117
+ copyOptionEffects(p, {
7118
+ triggerIdMap: optionIdMap,
7119
+ optionIdMap
7120
+ });
6516
7121
  }
6517
7122
  });
6518
7123
  return newId;
@@ -6574,54 +7179,6 @@ function removeNotice(ctx, id) {
6574
7179
 
6575
7180
  // src/react/canvas/editor/editor-nodes.ts
6576
7181
  var import_lodash_es3 = require("lodash-es");
6577
-
6578
- // src/react/canvas/editor/editor-utils.ts
6579
- function ownerOfOption(props, optionId) {
6580
- var _a, _b;
6581
- for (const f of (_a = props.fields) != null ? _a : []) {
6582
- const idx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
6583
- if (idx >= 0) return { fieldId: f.id, index: idx };
6584
- }
6585
- return null;
6586
- }
6587
- function hasFieldOptions(field) {
6588
- return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
6589
- }
6590
- function isActualButtonField(field) {
6591
- return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
6592
- }
6593
- function clearFieldButtonReceiverMaps(props, fieldId) {
6594
- var _a, _b;
6595
- if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
6596
- delete props.includes_for_buttons[fieldId];
6597
- }
6598
- if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
6599
- delete props.excludes_for_buttons[fieldId];
6600
- }
6601
- if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
6602
- delete props.includes_for_buttons;
6603
- }
6604
- if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
6605
- delete props.excludes_for_buttons;
6606
- }
6607
- }
6608
- function ensureServiceExists(opts, id) {
6609
- if (typeof opts.serviceExists === "function") {
6610
- if (!opts.serviceExists(id)) {
6611
- throw new Error(`service_not_found:${String(id)}`);
6612
- }
6613
- return;
6614
- }
6615
- if (opts.serviceMap) {
6616
- if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
6617
- throw new Error(`service_not_found:${String(id)}`);
6618
- }
6619
- return;
6620
- }
6621
- throw new Error("service_checker_missing");
6622
- }
6623
-
6624
- // src/react/canvas/editor/editor-nodes.ts
6625
7182
  var RELATION_MAP_KEYS = [
6626
7183
  "includes_for_buttons",
6627
7184
  "excludes_for_buttons",
@@ -6672,14 +7229,51 @@ function cleanRelationMapsForDeleted(p, deleted) {
6672
7229
  delete map[mapKey];
6673
7230
  continue;
6674
7231
  }
6675
- const next = ((_a = map[mapKey]) != null ? _a : []).filter(
6676
- (item) => !deleted.has(String(item))
6677
- );
6678
- if (next.length) map[mapKey] = next;
6679
- else delete map[mapKey];
7232
+ const next = ((_a = map[mapKey]) != null ? _a : []).filter(
7233
+ (item) => !deleted.has(String(item))
7234
+ );
7235
+ if (next.length) map[mapKey] = next;
7236
+ else delete map[mapKey];
7237
+ }
7238
+ if (!Object.keys(map).length) delete p[key];
7239
+ }
7240
+ }
7241
+ function cleanOptionEffectsForDeleted(p, deleted) {
7242
+ var _a, _b;
7243
+ const map = p.option_effects_for_buttons;
7244
+ if (!map) return;
7245
+ for (const triggerId of Object.keys(map)) {
7246
+ if (deleted.has(String(triggerId))) {
7247
+ delete map[triggerId];
7248
+ continue;
7249
+ }
7250
+ const targets = map[triggerId];
7251
+ for (const targetFieldId of Object.keys(targets != null ? targets : {})) {
7252
+ if (deleted.has(String(targetFieldId))) {
7253
+ delete targets[targetFieldId];
7254
+ continue;
7255
+ }
7256
+ const effect = targets[targetFieldId];
7257
+ if (!effect) continue;
7258
+ if (effect.include) {
7259
+ effect.include = effect.include.filter(
7260
+ (optionId) => !deleted.has(String(optionId))
7261
+ );
7262
+ if (!effect.include.length) delete effect.include;
7263
+ }
7264
+ if (effect.exclude) {
7265
+ effect.exclude = effect.exclude.filter(
7266
+ (optionId) => !deleted.has(String(optionId))
7267
+ );
7268
+ if (!effect.exclude.length) delete effect.exclude;
7269
+ }
7270
+ if (effect.forceVisible !== true && !((_a = effect.include) == null ? void 0 : _a.length) && !((_b = effect.exclude) == null ? void 0 : _b.length)) {
7271
+ delete targets[targetFieldId];
7272
+ }
6680
7273
  }
6681
- if (!Object.keys(map).length) delete p[key];
7274
+ if (!Object.keys(targets != null ? targets : {}).length) delete map[triggerId];
6682
7275
  }
7276
+ if (!Object.keys(map).length) delete p.option_effects_for_buttons;
6683
7277
  }
6684
7278
  function cleanOrderForTagsForDeleted(p, deleted) {
6685
7279
  var _a, _b;
@@ -6716,28 +7310,37 @@ function applyDeleteCleanup(p, deleted) {
6716
7310
  cleanTagRelationsForDeleted(p, deleted);
6717
7311
  cleanFieldBindsForDeleted(p, deleted);
6718
7312
  cleanRelationMapsForDeleted(p, deleted);
7313
+ cleanOptionEffectsForDeleted(p, deleted);
6719
7314
  cleanOrderForTagsForDeleted(p, deleted);
6720
7315
  cleanNoticesForDeleted(p, deleted);
6721
7316
  }
7317
+ function collectOptionSubtreeIds(option) {
7318
+ var _a;
7319
+ return [
7320
+ String(option.id),
7321
+ ...((_a = option.children) != null ? _a : []).flatMap((child) => collectOptionSubtreeIds(child))
7322
+ ];
7323
+ }
6722
7324
  function removeOptionInPlace(p, optionId) {
6723
7325
  var _a;
6724
- const owner = ownerOfOption(p, optionId);
6725
- if (!owner) return false;
6726
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
6727
- if (!(f == null ? void 0 : f.options)) return false;
6728
- const before = f.options.length;
6729
- f.options = f.options.filter((o) => o.id !== optionId);
6730
- return f.options.length !== before;
7326
+ const found = findMutableOption(p, optionId);
7327
+ if (!found) return [];
7328
+ const deleted = collectOptionSubtreeIds(found.option);
7329
+ found.siblings.splice(found.index, 1);
7330
+ if (found.parent && ((_a = found.parent.children) == null ? void 0 : _a.length) === 0) {
7331
+ delete found.parent.children;
7332
+ }
7333
+ return deleted;
6731
7334
  }
6732
7335
  function removeFieldInPlace(p, fieldId) {
6733
- var _a, _b, _c, _d, _e;
7336
+ var _a, _b, _c, _d;
6734
7337
  const field = ((_a = p.fields) != null ? _a : []).find((f) => f.id === fieldId);
6735
7338
  if (!field) return [];
6736
- const deleted = [fieldId, ...((_b = field.options) != null ? _b : []).map((o) => String(o.id))];
6737
- const before = ((_c = p.fields) != null ? _c : []).length;
6738
- p.fields = ((_d = p.fields) != null ? _d : []).filter((f) => f.id !== fieldId);
7339
+ const deleted = [fieldId, ...collectFieldOptionIds(field)];
7340
+ const before = ((_b = p.fields) != null ? _b : []).length;
7341
+ p.fields = ((_c = p.fields) != null ? _c : []).filter((f) => f.id !== fieldId);
6739
7342
  clearFieldButtonReceiverMaps(p, fieldId);
6740
- return ((_e = p.fields) != null ? _e : []).length !== before ? deleted : [];
7343
+ return ((_d = p.fields) != null ? _d : []).length !== before ? deleted : [];
6741
7344
  }
6742
7345
  function removeTagInPlace(p, tagId) {
6743
7346
  var _a, _b, _c;
@@ -6750,7 +7353,7 @@ function reLabel(ctx, id, nextLabel) {
6750
7353
  ctx.exec({
6751
7354
  name: "reLabel",
6752
7355
  do: () => ctx.patchProps((p) => {
6753
- var _a, _b, _c, _d, _e, _f, _g;
7356
+ var _a, _b, _c, _d, _e, _f;
6754
7357
  if (ctx.isTagId(id)) {
6755
7358
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
6756
7359
  if (!t) return;
@@ -6760,19 +7363,16 @@ function reLabel(ctx, id, nextLabel) {
6760
7363
  return;
6761
7364
  }
6762
7365
  if (ctx.isOptionId(id)) {
6763
- const own = ownerOfOption(p, id);
6764
- if (!own) return;
6765
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
6766
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
7366
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
6767
7367
  if (!o) return;
6768
- if (((_e = o.label) != null ? _e : "") === label) return;
7368
+ if (((_d = o.label) != null ? _d : "") === label) return;
6769
7369
  o.label = label;
6770
7370
  ctx.api.refreshGraph();
6771
7371
  return;
6772
7372
  }
6773
- const fld = ((_f = p.fields) != null ? _f : []).find((x) => x.id === id);
7373
+ const fld = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
6774
7374
  if (!fld) return;
6775
- if (((_g = fld.label) != null ? _g : "") === label) return;
7375
+ if (((_f = fld.label) != null ? _f : "") === label) return;
6776
7376
  fld.label = label;
6777
7377
  ctx.api.refreshGraph();
6778
7378
  }),
@@ -6862,11 +7462,7 @@ function updateOption(ctx, optionId, patch) {
6862
7462
  name: "updateOption",
6863
7463
  do: () => ctx.patchProps((p) => {
6864
7464
  var _a;
6865
- const owner = ownerOfOption(p, optionId);
6866
- if (!owner) return;
6867
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
6868
- if (!(f == null ? void 0 : f.options)) return;
6869
- const o = f.options.find((x) => x.id === optionId);
7465
+ const o = (_a = findMutableOption(p, optionId)) == null ? void 0 : _a.option;
6870
7466
  if (o) Object.assign(o, patch);
6871
7467
  }),
6872
7468
  undo: () => ctx.undo()
@@ -6879,9 +7475,9 @@ function removeOption(ctx, optionId) {
6879
7475
  ctx.exec({
6880
7476
  name: "removeOption",
6881
7477
  do: () => ctx.patchProps((p) => {
6882
- const removed = removeOptionInPlace(p, optionId);
6883
- if (!removed) return;
6884
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([optionId]));
7478
+ const removedIds = removeOptionInPlace(p, optionId);
7479
+ if (!removedIds.length) return;
7480
+ applyDeleteCleanup(p, new Set(removedIds));
6885
7481
  }),
6886
7482
  undo: () => ctx.undo()
6887
7483
  });
@@ -6892,7 +7488,7 @@ function editLabel(ctx, id, label) {
6892
7488
  ctx.exec({
6893
7489
  name: "editLabel",
6894
7490
  do: () => ctx.patchProps((p) => {
6895
- var _a, _b, _c, _d;
7491
+ var _a, _b, _c;
6896
7492
  if (ctx.isTagId(id)) {
6897
7493
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
6898
7494
  if (t) t.label = next;
@@ -6904,10 +7500,7 @@ function editLabel(ctx, id, label) {
6904
7500
  return;
6905
7501
  }
6906
7502
  if (ctx.isOptionId(id)) {
6907
- const own = ownerOfOption(p, id);
6908
- if (!own) return;
6909
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
6910
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
7503
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
6911
7504
  if (o) o.label = next;
6912
7505
  return;
6913
7506
  }
@@ -6932,7 +7525,7 @@ function setService(ctx, id, input) {
6932
7525
  ctx.exec({
6933
7526
  name: "setService",
6934
7527
  do: () => ctx.patchProps((p) => {
6935
- var _a, _b, _c, _d, _e, _f;
7528
+ var _a, _b, _c, _d, _e;
6936
7529
  const hasSidKey = Object.prototype.hasOwnProperty.call(
6937
7530
  input,
6938
7531
  "service_id"
@@ -6950,12 +7543,9 @@ function setService(ctx, id, input) {
6950
7543
  return;
6951
7544
  }
6952
7545
  if (ctx.isOptionId(id)) {
6953
- const own = ownerOfOption(p, id);
6954
- if (!own) return;
6955
- const f2 = ((_b = p.fields) != null ? _b : []).find((x) => x.id === own.fieldId);
6956
- const o = (_c = f2 == null ? void 0 : f2.options) == null ? void 0 : _c.find((x) => x.id === id);
7546
+ const o = (_b = findMutableOption(p, id)) == null ? void 0 : _b.option;
6957
7547
  if (!o) return;
6958
- const currentRole = (_d = o.pricing_role) != null ? _d : "base";
7548
+ const currentRole = (_c = o.pricing_role) != null ? _c : "base";
6959
7549
  const role = nextRole != null ? nextRole : currentRole;
6960
7550
  if (role === "utility") {
6961
7551
  if (hasSidKey && sid !== void 0) {
@@ -6976,7 +7566,7 @@ function setService(ctx, id, input) {
6976
7566
  }
6977
7567
  return;
6978
7568
  }
6979
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
7569
+ const f = ((_d = p.fields) != null ? _d : []).find((x) => x.id === id);
6980
7570
  if (!f) {
6981
7571
  throw new Error(
6982
7572
  'setService only supports tag ("t:*"), option ("o:*"), or field ("f:*") ids'
@@ -6987,7 +7577,7 @@ function setService(ctx, id, input) {
6987
7577
  if (nextRole) {
6988
7578
  f.pricing_role = nextRole;
6989
7579
  }
6990
- const effectiveRole = (_f = f.pricing_role) != null ? _f : "base";
7580
+ const effectiveRole = (_e = f.pricing_role) != null ? _e : "base";
6991
7581
  if (isOptionBased2) {
6992
7582
  if (hasSidKey) {
6993
7583
  ctx.api.emit("error", {
@@ -7099,13 +7689,15 @@ function updateField(ctx, id, patch) {
7099
7689
  let prev;
7100
7690
  let prevIncludesForButton;
7101
7691
  let prevExcludesForButton;
7692
+ let prevOptionEffectsForButton;
7102
7693
  ctx.exec({
7103
7694
  name: "updateField",
7104
7695
  do: () => ctx.patchProps((p) => {
7105
- var _a, _b, _c, _d, _e, _f, _g;
7696
+ var _a, _b, _c, _d, _e, _f, _g, _h;
7106
7697
  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;
7107
7698
  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;
7108
- p.fields = ((_g = p.fields) != null ? _g : []).map((f) => {
7699
+ prevOptionEffectsForButton = ((_g = p.option_effects_for_buttons) == null ? void 0 : _g[id]) ? (0, import_lodash_es3.cloneDeep)(p.option_effects_for_buttons[id]) : void 0;
7700
+ p.fields = ((_h = p.fields) != null ? _h : []).map((f) => {
7109
7701
  if (f.id !== id) return f;
7110
7702
  prev = (0, import_lodash_es3.cloneDeep)(f);
7111
7703
  const nextField = { ...f, ...patch };
@@ -7116,7 +7708,7 @@ function updateField(ctx, id, patch) {
7116
7708
  });
7117
7709
  }),
7118
7710
  undo: () => ctx.patchProps((p) => {
7119
- var _a, _b, _c;
7711
+ var _a, _b, _c, _d;
7120
7712
  p.fields = ((_a = p.fields) != null ? _a : []).map(
7121
7713
  (f) => f.id === id && prev ? prev : f
7122
7714
  );
@@ -7134,6 +7726,12 @@ function updateField(ctx, id, patch) {
7134
7726
  [id]: [...prevExcludesForButton]
7135
7727
  };
7136
7728
  }
7729
+ if (prevOptionEffectsForButton) {
7730
+ p.option_effects_for_buttons = {
7731
+ ...(_d = p.option_effects_for_buttons) != null ? _d : {},
7732
+ [id]: (0, import_lodash_es3.cloneDeep)(prevOptionEffectsForButton)
7733
+ };
7734
+ }
7137
7735
  })
7138
7736
  });
7139
7737
  }
@@ -7180,9 +7778,9 @@ function remove(ctx, id) {
7180
7778
  ctx.exec({
7181
7779
  name: "removeOption",
7182
7780
  do: () => ctx.patchProps((p) => {
7183
- const removed = removeOptionInPlace(p, key);
7184
- if (!removed) return;
7185
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
7781
+ const removedIds = removeOptionInPlace(p, key);
7782
+ if (!removedIds.length) return;
7783
+ applyDeleteCleanup(p, new Set(removedIds));
7186
7784
  }),
7187
7785
  undo: () => ctx.undo()
7188
7786
  });
@@ -7199,10 +7797,7 @@ function removeMany(ctx, ids) {
7199
7797
  const existingFieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
7200
7798
  const existingTagIds = new Set(((_b = p.filters) != null ? _b : []).map((t) => String(t.id)));
7201
7799
  const existingOptionIds = new Set(
7202
- ((_c = p.fields) != null ? _c : []).flatMap((f) => {
7203
- var _a2;
7204
- return ((_a2 = f.options) != null ? _a2 : []).map((o) => String(o.id));
7205
- })
7800
+ ((_c = p.fields) != null ? _c : []).flatMap((f) => collectFieldOptionIds(f))
7206
7801
  );
7207
7802
  const fieldIds = ordered.filter((id) => ctx.isFieldId(id) && existingFieldIds.has(id));
7208
7803
  const fieldIdSet = new Set(fieldIds);
@@ -7215,7 +7810,9 @@ function removeMany(ctx, ids) {
7215
7810
  });
7216
7811
  const deleted = /* @__PURE__ */ new Set();
7217
7812
  for (const optionId of optionIds) {
7218
- if (removeOptionInPlace(p, optionId)) deleted.add(optionId);
7813
+ for (const removedId of removeOptionInPlace(p, optionId)) {
7814
+ deleted.add(removedId);
7815
+ }
7219
7816
  }
7220
7817
  for (const fieldId of fieldIds) {
7221
7818
  const removedIds = removeFieldInPlace(p, fieldId);
@@ -7230,7 +7827,7 @@ function removeMany(ctx, ids) {
7230
7827
  });
7231
7828
  }
7232
7829
  function getNode(ctx, id) {
7233
- var _a, _b, _c, _d;
7830
+ var _a, _b, _c;
7234
7831
  const props = ctx.getProps();
7235
7832
  if (ctx.isTagId(id)) {
7236
7833
  const t = ((_a = props.filters) != null ? _a : []).find((x) => x.id === id);
@@ -7247,8 +7844,7 @@ function getNode(ctx, id) {
7247
7844
  }
7248
7845
  if (ctx.isOptionId(id)) {
7249
7846
  const own = ownerOfOption(props, id);
7250
- const f = own ? ((_c = props.fields) != null ? _c : []).find((x) => x.id === own.fieldId) : void 0;
7251
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
7847
+ const o = (_c = findMutableOption(props, id)) == null ? void 0 : _c.option;
7252
7848
  return {
7253
7849
  kind: "option",
7254
7850
  data: o,
@@ -7785,7 +8381,7 @@ function connect(ctx, kind, fromId, toId2) {
7785
8381
  ctx.exec({
7786
8382
  name: `connect:${kind}`,
7787
8383
  do: () => ctx.patchProps((p) => {
7788
- var _a, _b, _c, _d, _e, _f, _g, _h;
8384
+ var _a, _b, _c, _d, _e, _f, _g;
7789
8385
  if (kind === "bind") {
7790
8386
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
7791
8387
  if (wouldCreateTagCycle(ctx, p, fromId, toId2)) {
@@ -7865,12 +8461,10 @@ function connect(ctx, kind, fromId, toId2) {
7865
8461
  return;
7866
8462
  }
7867
8463
  if (toId2.startsWith("o:")) {
7868
- for (const f of (_g = p.fields) != null ? _g : []) {
7869
- const o = (_h = f.options) == null ? void 0 : _h.find((x) => x.id === toId2);
7870
- if (o) {
7871
- o.service_id = fromId;
7872
- return;
7873
- }
8464
+ const o = (_g = findMutableOption(p, toId2)) == null ? void 0 : _g.option;
8465
+ if (o) {
8466
+ o.service_id = fromId;
8467
+ return;
7874
8468
  }
7875
8469
  return;
7876
8470
  }
@@ -7887,7 +8481,7 @@ function disconnect(ctx, kind, fromId, toId2) {
7887
8481
  ctx.exec({
7888
8482
  name: `disconnect:${kind}`,
7889
8483
  do: () => ctx.patchProps((p) => {
7890
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
8484
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
7891
8485
  if (kind === "bind") {
7892
8486
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
7893
8487
  const child = ((_a = p.filters) != null ? _a : []).find(
@@ -7963,12 +8557,10 @@ function disconnect(ctx, kind, fromId, toId2) {
7963
8557
  return;
7964
8558
  }
7965
8559
  if (toId2.startsWith("o:")) {
7966
- for (const f of (_i = p.fields) != null ? _i : []) {
7967
- const o = (_j = f.options) == null ? void 0 : _j.find((x) => x.id === toId2);
7968
- if (o) {
7969
- delete o.service_id;
7970
- return;
7971
- }
8560
+ const o = (_i = findMutableOption(p, toId2)) == null ? void 0 : _i.option;
8561
+ if (o) {
8562
+ delete o.service_id;
8563
+ return;
7972
8564
  }
7973
8565
  return;
7974
8566
  }
@@ -7991,6 +8583,250 @@ function addMappedField(p, mapKey, fromId, toId2) {
7991
8583
  p[mapKey] = maps;
7992
8584
  }
7993
8585
 
8586
+ // src/react/canvas/editor/editor-option-effects.ts
8587
+ function assertCanonicalId(id, label) {
8588
+ if (!id || id.includes("::") || id.includes("/")) {
8589
+ throw new Error(
8590
+ `${label}: expected a raw field or option id, not a composite/path id`
8591
+ );
8592
+ }
8593
+ }
8594
+ function assertTrigger(ctx, triggerId) {
8595
+ assertCanonicalId(triggerId, "option effect trigger");
8596
+ const trigger = ctx.getNode(triggerId);
8597
+ if (trigger.kind === "option" && trigger.data) return;
8598
+ if (trigger.kind === "field" && trigger.data && isActualButtonField(trigger.data)) {
8599
+ return;
8600
+ }
8601
+ throw new Error(
8602
+ "option effect trigger must be an option id or button field id"
8603
+ );
8604
+ }
8605
+ function assertTargetField(props, targetFieldId) {
8606
+ var _a;
8607
+ assertCanonicalId(targetFieldId, "option effect target");
8608
+ const field = ((_a = props.fields) != null ? _a : []).find((item) => item.id === targetFieldId);
8609
+ if (!field) {
8610
+ throw new Error(`option effect target field not found: ${targetFieldId}`);
8611
+ }
8612
+ return field;
8613
+ }
8614
+ function dedupe2(values) {
8615
+ if (!values) return void 0;
8616
+ const out = [];
8617
+ for (const value of values) {
8618
+ const id = String(value);
8619
+ if (!id || out.includes(id)) continue;
8620
+ out.push(id);
8621
+ }
8622
+ return out.length ? out : void 0;
8623
+ }
8624
+ function assertTargetOptions(props, targetFieldId, ids, kind) {
8625
+ if (!(ids == null ? void 0 : ids.length)) return;
8626
+ const field = assertTargetField(props, targetFieldId);
8627
+ const valid = fieldOptionIdSet(field);
8628
+ for (const id of ids) {
8629
+ assertCanonicalId(String(id), `option effect ${kind} option`);
8630
+ if (!valid.has(String(id))) {
8631
+ throw new Error(
8632
+ `option effect ${kind} option not found under ${targetFieldId}: ${String(id)}`
8633
+ );
8634
+ }
8635
+ }
8636
+ }
8637
+ function normalizeEffect(effect) {
8638
+ var _a;
8639
+ if (!effect) return void 0;
8640
+ const exclude2 = dedupe2(effect.exclude);
8641
+ const excluded = new Set(exclude2 != null ? exclude2 : []);
8642
+ const include2 = (_a = dedupe2(effect.include)) == null ? void 0 : _a.filter((id) => !excluded.has(id));
8643
+ const out = {};
8644
+ if (effect.forceVisible === true) out.forceVisible = true;
8645
+ if (include2 == null ? void 0 : include2.length) out.include = include2;
8646
+ if (exclude2 == null ? void 0 : exclude2.length) out.exclude = exclude2;
8647
+ return Object.keys(out).length ? out : void 0;
8648
+ }
8649
+ function ensureTargetMap(props, triggerId) {
8650
+ var _a, _b, _c;
8651
+ (_a = props.option_effects_for_buttons) != null ? _a : props.option_effects_for_buttons = {};
8652
+ (_c = (_b = props.option_effects_for_buttons)[triggerId]) != null ? _c : _b[triggerId] = {};
8653
+ return props.option_effects_for_buttons[triggerId];
8654
+ }
8655
+ function pruneEffectMap(props, triggerId) {
8656
+ const map = props.option_effects_for_buttons;
8657
+ if (!map) return;
8658
+ const keys = triggerId ? [triggerId] : Object.keys(map);
8659
+ for (const key of keys) {
8660
+ const targets = map[key];
8661
+ if (!targets || Object.keys(targets).length === 0) delete map[key];
8662
+ }
8663
+ if (Object.keys(map).length === 0) delete props.option_effects_for_buttons;
8664
+ }
8665
+ function validateEffect(ctx, props, triggerId, targetFieldId, effect) {
8666
+ assertTrigger(ctx, triggerId);
8667
+ assertTargetField(props, targetFieldId);
8668
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.include, "include");
8669
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.exclude, "exclude");
8670
+ return normalizeEffect(effect);
8671
+ }
8672
+ function setOptionEffect(ctx, triggerId, targetFieldId, effect) {
8673
+ ctx.exec({
8674
+ name: "setOptionEffect",
8675
+ do: () => ctx.patchProps((props) => {
8676
+ var _a;
8677
+ const normalized = validateEffect(
8678
+ ctx,
8679
+ props,
8680
+ triggerId,
8681
+ targetFieldId,
8682
+ effect
8683
+ );
8684
+ if (!normalized) {
8685
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
8686
+ if (map) delete map[targetFieldId];
8687
+ pruneEffectMap(props, triggerId);
8688
+ return;
8689
+ }
8690
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8691
+ }),
8692
+ undo: () => ctx.undo()
8693
+ });
8694
+ }
8695
+ function patchOptionEffect(ctx, triggerId, targetFieldId, patch) {
8696
+ ctx.exec({
8697
+ name: "patchOptionEffect",
8698
+ do: () => ctx.patchProps((props) => {
8699
+ var _a, _b, _c, _d;
8700
+ const current = (_c = (_b = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
8701
+ const merged = {
8702
+ ...current,
8703
+ ...patch
8704
+ };
8705
+ const normalized = validateEffect(
8706
+ ctx,
8707
+ props,
8708
+ triggerId,
8709
+ targetFieldId,
8710
+ merged
8711
+ );
8712
+ if (!normalized) {
8713
+ const map = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId];
8714
+ if (map) delete map[targetFieldId];
8715
+ pruneEffectMap(props, triggerId);
8716
+ return;
8717
+ }
8718
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8719
+ }),
8720
+ undo: () => ctx.undo()
8721
+ });
8722
+ }
8723
+ function clearOptionEffect(ctx, triggerId, targetFieldId) {
8724
+ ctx.exec({
8725
+ name: "clearOptionEffect",
8726
+ do: () => ctx.patchProps((props) => {
8727
+ var _a;
8728
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
8729
+ if (!map) return;
8730
+ delete map[targetFieldId];
8731
+ pruneEffectMap(props, triggerId);
8732
+ }),
8733
+ undo: () => ctx.undo()
8734
+ });
8735
+ }
8736
+ function clearOptionEffectsForTrigger(ctx, triggerId) {
8737
+ ctx.exec({
8738
+ name: "clearOptionEffectsForTrigger",
8739
+ do: () => ctx.patchProps((props) => {
8740
+ if (!props.option_effects_for_buttons) return;
8741
+ delete props.option_effects_for_buttons[triggerId];
8742
+ pruneEffectMap(props);
8743
+ }),
8744
+ undo: () => ctx.undo()
8745
+ });
8746
+ }
8747
+ function clearOptionEffectsForTarget(ctx, targetFieldId) {
8748
+ ctx.exec({
8749
+ name: "clearOptionEffectsForTarget",
8750
+ do: () => ctx.patchProps((props) => {
8751
+ var _a;
8752
+ const map = props.option_effects_for_buttons;
8753
+ if (!map) return;
8754
+ for (const triggerId of Object.keys(map)) {
8755
+ (_a = map[triggerId]) == null ? true : delete _a[targetFieldId];
8756
+ }
8757
+ pruneEffectMap(props);
8758
+ }),
8759
+ undo: () => ctx.undo()
8760
+ });
8761
+ }
8762
+ function addOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
8763
+ var _a;
8764
+ const additions = (_a = dedupe2(optionIds)) != null ? _a : [];
8765
+ if (!additions.length) return;
8766
+ ctx.exec({
8767
+ name: "addOptionEffectOptions",
8768
+ do: () => ctx.patchProps((props) => {
8769
+ var _a2, _b, _c, _d;
8770
+ const current = (_c = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
8771
+ const nextValues = dedupe2([
8772
+ ...(_d = current[kind]) != null ? _d : [],
8773
+ ...additions
8774
+ ]);
8775
+ const normalized = validateEffect(
8776
+ ctx,
8777
+ props,
8778
+ triggerId,
8779
+ targetFieldId,
8780
+ {
8781
+ ...current,
8782
+ [kind]: nextValues
8783
+ }
8784
+ );
8785
+ if (!normalized) return;
8786
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8787
+ }),
8788
+ undo: () => ctx.undo()
8789
+ });
8790
+ }
8791
+ function removeOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
8792
+ var _a;
8793
+ const removals = new Set((_a = dedupe2(optionIds)) != null ? _a : []);
8794
+ if (!removals.size) return;
8795
+ ctx.exec({
8796
+ name: "removeOptionEffectOptions",
8797
+ do: () => ctx.patchProps((props) => {
8798
+ var _a2, _b, _c, _d, _e;
8799
+ const current = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId];
8800
+ if (!current) return;
8801
+ const next = {
8802
+ ...current,
8803
+ [kind]: ((_c = current[kind]) != null ? _c : []).filter(
8804
+ (optionId) => !removals.has(optionId)
8805
+ )
8806
+ };
8807
+ const normalized = validateEffect(
8808
+ ctx,
8809
+ props,
8810
+ triggerId,
8811
+ targetFieldId,
8812
+ next
8813
+ );
8814
+ if (!normalized) {
8815
+ (_e = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId]) == null ? true : delete _e[targetFieldId];
8816
+ pruneEffectMap(props, triggerId);
8817
+ return;
8818
+ }
8819
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
8820
+ }),
8821
+ undo: () => ctx.undo()
8822
+ });
8823
+ }
8824
+ function setOptionEffectForceVisible(ctx, triggerId, targetFieldId, forceVisible) {
8825
+ patchOptionEffect(ctx, triggerId, targetFieldId, {
8826
+ forceVisible: forceVisible === true ? true : void 0
8827
+ });
8828
+ }
8829
+
7994
8830
  // src/react/canvas/editor/editor-service-filter.ts
7995
8831
  function filterServicesForVisibleGroup2(ctx, candidates, input) {
7996
8832
  const coreInput = {
@@ -8577,7 +9413,7 @@ var Editor = class {
8577
9413
  if (!ordered.length) return;
8578
9414
  this.transact("clearServiceMany", () => {
8579
9415
  this.patchProps((p) => {
8580
- var _a, _b, _c, _d;
9416
+ var _a, _b;
8581
9417
  for (const id of ordered) {
8582
9418
  if (this.isTagId(id)) {
8583
9419
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
@@ -8590,10 +9426,8 @@ var Editor = class {
8590
9426
  continue;
8591
9427
  }
8592
9428
  if (this.isOptionId(id)) {
8593
- const own = ownerOfOption(p, id);
8594
- if (!own) continue;
8595
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
8596
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
9429
+ const found = findMutableOption(p, id);
9430
+ const o = found == null ? void 0 : found.option;
8597
9431
  if (o && "service_id" in o) delete o.service_id;
8598
9432
  }
8599
9433
  }
@@ -8642,7 +9476,7 @@ var Editor = class {
8642
9476
  if (!selected.size) return;
8643
9477
  this.transact("clearRelationsMany", () => {
8644
9478
  this.patchProps((p) => {
8645
- var _a, _b, _c;
9479
+ var _a, _b, _c, _d, _e;
8646
9480
  const clearOwned = mode === "owned" || mode === "both";
8647
9481
  const clearIncoming = mode === "incoming" || mode === "both";
8648
9482
  for (const t of (_a = p.filters) != null ? _a : []) {
@@ -8682,6 +9516,44 @@ var Editor = class {
8682
9516
  }
8683
9517
  if (!Object.keys(map).length) delete p[k];
8684
9518
  }
9519
+ const effectMap = p.option_effects_for_buttons;
9520
+ if (effectMap) {
9521
+ for (const triggerId of Object.keys(effectMap)) {
9522
+ if (clearOwned && selected.has(String(triggerId))) {
9523
+ delete effectMap[triggerId];
9524
+ continue;
9525
+ }
9526
+ const targets = effectMap[triggerId];
9527
+ if (!targets || !clearIncoming) continue;
9528
+ for (const targetFieldId of Object.keys(targets)) {
9529
+ if (selected.has(String(targetFieldId))) {
9530
+ delete targets[targetFieldId];
9531
+ continue;
9532
+ }
9533
+ const effect = targets[targetFieldId];
9534
+ if (!effect) continue;
9535
+ if (effect.include) {
9536
+ effect.include = effect.include.filter(
9537
+ (optionId) => !selected.has(String(optionId))
9538
+ );
9539
+ if (!effect.include.length) delete effect.include;
9540
+ }
9541
+ if (effect.exclude) {
9542
+ effect.exclude = effect.exclude.filter(
9543
+ (optionId) => !selected.has(String(optionId))
9544
+ );
9545
+ if (!effect.exclude.length) delete effect.exclude;
9546
+ }
9547
+ if (effect.forceVisible !== true && !((_d = effect.include) == null ? void 0 : _d.length) && !((_e = effect.exclude) == null ? void 0 : _e.length)) {
9548
+ delete targets[targetFieldId];
9549
+ }
9550
+ }
9551
+ if (!Object.keys(targets).length) delete effectMap[triggerId];
9552
+ }
9553
+ if (!Object.keys(effectMap).length) {
9554
+ delete p.option_effects_for_buttons;
9555
+ }
9556
+ }
8685
9557
  });
8686
9558
  });
8687
9559
  }
@@ -8693,7 +9565,7 @@ var Editor = class {
8693
9565
  const suffix = (_b = input.suffix) != null ? _b : "";
8694
9566
  this.transact("renameLabelsMany", () => {
8695
9567
  this.patchProps((p) => {
8696
- var _a2, _b2, _c, _d, _e, _f, _g;
9568
+ var _a2, _b2, _c, _d, _e, _f;
8697
9569
  for (const id of ordered) {
8698
9570
  if (this.isTagId(id)) {
8699
9571
  const t = ((_a2 = p.filters) != null ? _a2 : []).find((x) => x.id === id);
@@ -8706,11 +9578,8 @@ var Editor = class {
8706
9578
  continue;
8707
9579
  }
8708
9580
  if (this.isOptionId(id)) {
8709
- const own = ownerOfOption(p, id);
8710
- if (!own) continue;
8711
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === own.fieldId);
8712
- const o = (_f = f == null ? void 0 : f.options) == null ? void 0 : _f.find((x) => x.id === id);
8713
- if (o) o.label = `${prefix}${(_g = o.label) != null ? _g : ""}${suffix}`.trim();
9581
+ const o = (_e = findMutableOption(p, id)) == null ? void 0 : _e.option;
9582
+ if (o) o.label = `${prefix}${(_f = o.label) != null ? _f : ""}${suffix}`.trim();
8714
9583
  }
8715
9584
  }
8716
9585
  });
@@ -8863,6 +9732,57 @@ var Editor = class {
8863
9732
  exclude(receiverId, idOrIds) {
8864
9733
  return exclude(this.moduleCtx(), receiverId, idOrIds);
8865
9734
  }
9735
+ setOptionEffect(triggerId, targetFieldId, effect) {
9736
+ return setOptionEffect(
9737
+ this.moduleCtx(),
9738
+ triggerId,
9739
+ targetFieldId,
9740
+ effect
9741
+ );
9742
+ }
9743
+ patchOptionEffect(triggerId, targetFieldId, patch) {
9744
+ return patchOptionEffect(
9745
+ this.moduleCtx(),
9746
+ triggerId,
9747
+ targetFieldId,
9748
+ patch
9749
+ );
9750
+ }
9751
+ clearOptionEffect(triggerId, targetFieldId) {
9752
+ return clearOptionEffect(this.moduleCtx(), triggerId, targetFieldId);
9753
+ }
9754
+ clearOptionEffectsForTrigger(triggerId) {
9755
+ return clearOptionEffectsForTrigger(this.moduleCtx(), triggerId);
9756
+ }
9757
+ clearOptionEffectsForTarget(targetFieldId) {
9758
+ return clearOptionEffectsForTarget(this.moduleCtx(), targetFieldId);
9759
+ }
9760
+ addOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
9761
+ return addOptionEffectOptions(
9762
+ this.moduleCtx(),
9763
+ triggerId,
9764
+ targetFieldId,
9765
+ kind,
9766
+ optionIds
9767
+ );
9768
+ }
9769
+ removeOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
9770
+ return removeOptionEffectOptions(
9771
+ this.moduleCtx(),
9772
+ triggerId,
9773
+ targetFieldId,
9774
+ kind,
9775
+ optionIds
9776
+ );
9777
+ }
9778
+ setOptionEffectForceVisible(triggerId, targetFieldId, forceVisible) {
9779
+ return setOptionEffectForceVisible(
9780
+ this.moduleCtx(),
9781
+ triggerId,
9782
+ targetFieldId,
9783
+ forceVisible
9784
+ );
9785
+ }
8866
9786
  connect(kind, fromId, toId2) {
8867
9787
  return connect(this.moduleCtx(), kind, fromId, toId2);
8868
9788
  }
@@ -9231,11 +10151,10 @@ var Selection = class {
9231
10151
  * What counts as a "button selection" (trigger key):
9232
10152
  * - field key where the field has button === true (e.g. "f:dripfeed")
9233
10153
  * - option key (e.g. "o:fast")
9234
- * - composite key "fieldId::optionId" (e.g. "f:speed::o:fast")
9235
10154
  *
9236
10155
  * Grouping:
9237
10156
  * - button-field trigger groups under its own fieldId
9238
- * - option/composite groups under the option's owning fieldId (from nodeMap)
10157
+ * - option trigger groups under the option's owning fieldId (from nodeMap)
9239
10158
  *
9240
10159
  * Deterministic:
9241
10160
  * - preserves selection insertion order
@@ -9252,15 +10171,6 @@ var Selection = class {
9252
10171
  };
9253
10172
  for (const key of this.set) {
9254
10173
  if (!key) continue;
9255
- const idx = key.indexOf("::");
9256
- if (idx !== -1) {
9257
- const optionId = key.slice(idx + 2);
9258
- const optRef = nodeMap.get(optionId);
9259
- if ((optRef == null ? void 0 : optRef.kind) === "option" && typeof optRef.fieldId === "string") {
9260
- push(optRef.fieldId, key);
9261
- }
9262
- continue;
9263
- }
9264
10174
  const ref = nodeMap.get(key);
9265
10175
  if (!ref) continue;
9266
10176
  if (ref.kind === "option" && typeof ref.fieldId === "string") {
@@ -9281,7 +10191,6 @@ var Selection = class {
9281
10191
  * Returns only selection keys that are valid "trigger buttons":
9282
10192
  * - field keys where field.button === true
9283
10193
  * - option keys
9284
- * - composite keys "fieldId::optionId" (validated by optionId)
9285
10194
  * Excludes tags and non-button fields.
9286
10195
  */
9287
10196
  selectedButtons() {
@@ -9297,13 +10206,6 @@ var Selection = class {
9297
10206
  };
9298
10207
  for (const key of this.set) {
9299
10208
  if (!key) continue;
9300
- const idx = key.indexOf("::");
9301
- if (idx !== -1) {
9302
- const optionId = key.slice(idx + 2);
9303
- const optRef = nodeMap.get(optionId);
9304
- if ((optRef == null ? void 0 : optRef.kind) === "option") push(key);
9305
- continue;
9306
- }
9307
10209
  const ref = nodeMap.get(key);
9308
10210
  if (!ref) continue;
9309
10211
  if (ref.kind === "option") {
@@ -9342,17 +10244,7 @@ var Selection = class {
9342
10244
  const direct = fields.find((x) => x.id === id);
9343
10245
  if (direct) return direct;
9344
10246
  if (this.builder.isOptionId(id)) {
9345
- return fields.find(
9346
- (x) => {
9347
- var _a2;
9348
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
9349
- }
9350
- );
9351
- }
9352
- if (id.includes("::")) {
9353
- const [fieldId] = id.split("::");
9354
- if (!fieldId) return void 0;
9355
- return fields.find((x) => x.id === fieldId);
10247
+ return findOptionOwnerField(fields, id);
9356
10248
  }
9357
10249
  return void 0;
9358
10250
  };
@@ -9383,18 +10275,7 @@ var Selection = class {
9383
10275
  }
9384
10276
  for (const id of this.set) {
9385
10277
  if (this.builder.isOptionId(id)) {
9386
- const host = fields.find(
9387
- (x) => {
9388
- var _a2;
9389
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
9390
- }
9391
- );
9392
- if (host == null ? void 0 : host.bind_id)
9393
- return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
9394
- }
9395
- if (id.includes("::")) {
9396
- const [fid] = id.split("::");
9397
- const host = fields.find((x) => x.id === fid);
10278
+ const host = findOptionOwnerField(fields, id);
9398
10279
  if (host == null ? void 0 : host.bind_id)
9399
10280
  return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
9400
10281
  }
@@ -9408,7 +10289,11 @@ var Selection = class {
9408
10289
  const tagById = new Map(tags.map((t) => [t.id, t]));
9409
10290
  const tag = tagById.get(tagId);
9410
10291
  const selectedTriggerIds = this.selectedButtons();
9411
- const fieldIds = this.builder.visibleFields(tagId, selectedTriggerIds);
10292
+ const visibility = this.builder.resolveVisibility(
10293
+ tagId,
10294
+ selectedTriggerIds
10295
+ );
10296
+ const fieldIds = visibility.fieldIds;
9412
10297
  const fieldById = new Map(fields.map((f) => [f.id, f]));
9413
10298
  const visible = fieldIds.map((id) => fieldById.get(id)).filter(Boolean);
9414
10299
  const parentTags = [];
@@ -9434,6 +10319,14 @@ var Selection = class {
9434
10319
  let baseOverridden = false;
9435
10320
  for (const selId of this.set) {
9436
10321
  const opt = this.findOptionById(fields, selId);
10322
+ if (opt && !this.isSelectedOptionVisible(
10323
+ fields,
10324
+ selId,
10325
+ fieldIds,
10326
+ visibility.optionsByFieldId
10327
+ )) {
10328
+ continue;
10329
+ }
9437
10330
  if ((opt == null ? void 0 : opt.service_id) != null) {
9438
10331
  const role = (_d = opt.pricing_role) != null ? _d : "base";
9439
10332
  const cap = (_e = resolve == null ? void 0 : resolve(opt.service_id)) != null ? _e : { id: opt.service_id };
@@ -9464,6 +10357,8 @@ var Selection = class {
9464
10357
  tag,
9465
10358
  fields: visible,
9466
10359
  fieldIds,
10360
+ optionsByFieldId: visibility.optionsByFieldId,
10361
+ forcedFieldIds: visibility.forcedFieldIds,
9467
10362
  parentTags,
9468
10363
  childrenTags,
9469
10364
  services
@@ -9487,21 +10382,19 @@ var Selection = class {
9487
10382
  return baseOverridden;
9488
10383
  }
9489
10384
  findOptionById(fields, selId) {
9490
- var _a, _b;
9491
10385
  if (this.builder.isOptionId(selId)) {
9492
- for (const f of fields) {
9493
- const o = (_a = f.options) == null ? void 0 : _a.find((x) => x.id === selId);
9494
- if (o) return o;
9495
- }
9496
- }
9497
- if (selId.includes("::")) {
9498
- const [fid, oid] = selId.split("::");
9499
- const f = fields.find((x) => x.id === fid);
9500
- const o = (_b = f == null ? void 0 : f.options) == null ? void 0 : _b.find((x) => x.id === oid || x.id === selId);
9501
- if (o) return o;
10386
+ const field = findOptionOwnerField(fields, selId);
10387
+ return findFieldOption(field, selId);
9502
10388
  }
9503
10389
  return void 0;
9504
10390
  }
10391
+ isSelectedOptionVisible(fields, selId, visibleFieldIds, optionsByFieldId) {
10392
+ const visibleFields = new Set(visibleFieldIds);
10393
+ const field = findOptionOwnerField(fields, selId);
10394
+ if (!field || !visibleFields.has(field.id)) return false;
10395
+ const allowed = optionsByFieldId[field.id];
10396
+ return !allowed || allowed.includes(selId);
10397
+ }
9505
10398
  };
9506
10399
 
9507
10400
  // src/react/canvas/api.ts
@@ -10404,11 +11297,83 @@ function useOrderFlow() {
10404
11297
  if (!ready) return void 0;
10405
11298
  return (_c2 = (_b2 = (_a2 = ctx.selection) == null ? void 0 : _a2.currentTag) == null ? void 0 : _b2.call(_a2)) != null ? _c2 : ctx.activeTagId;
10406
11299
  }, [ready, ctx.selection, ctx.activeTagId, selTick]);
11300
+ const visibleOptionsByFieldId = (0, import_react6.useMemo)(
11301
+ () => {
11302
+ var _a2;
11303
+ return (_a2 = visibleGroup == null ? void 0 : visibleGroup.optionsByFieldId) != null ? _a2 : {};
11304
+ },
11305
+ [visibleGroup]
11306
+ );
11307
+ const forcedFieldIds = (0, import_react6.useMemo)(
11308
+ () => {
11309
+ var _a2;
11310
+ return (_a2 = visibleGroup == null ? void 0 : visibleGroup.forcedFieldIds) != null ? _a2 : [];
11311
+ },
11312
+ [visibleGroup]
11313
+ );
10407
11314
  const formValuesByFieldId = (0, import_react6.useMemo)(() => {
10408
11315
  var _a2, _b2, _c2;
10409
11316
  const values = (_c2 = (_b2 = (_a2 = ctx.formApi).snapshot) == null ? void 0 : _b2.call(_a2)) != null ? _c2 : {};
10410
11317
  return values;
10411
11318
  }, [ctx.formApi, formTick]);
11319
+ (0, import_react6.useEffect)(() => {
11320
+ var _a2, _b2, _c2, _d;
11321
+ if (!ready || !ctx.selection) return;
11322
+ const { builder, selection } = ctx.ensureReady("pruneHiddenOptions");
11323
+ const fields = (_a2 = builder.getProps().fields) != null ? _a2 : [];
11324
+ const visibleOptionEntries = Object.entries(visibleOptionsByFieldId);
11325
+ if (!visibleOptionEntries.length) return;
11326
+ const visibleOptionSets = new Map(
11327
+ visibleOptionEntries.map(([fieldId, optionIds]) => [
11328
+ fieldId,
11329
+ new Set(optionIds)
11330
+ ])
11331
+ );
11332
+ const changedFieldIds = /* @__PURE__ */ new Set();
11333
+ const ownerForToken = (token) => {
11334
+ if (!token) return void 0;
11335
+ const owner = findOptionOwnerField(fields, token);
11336
+ return owner == null ? void 0 : owner.id;
11337
+ };
11338
+ const retained = [];
11339
+ let changed = false;
11340
+ for (const token of selection.all()) {
11341
+ const ownerFieldId = ownerForToken(token);
11342
+ if (!ownerFieldId) {
11343
+ retained.push(token);
11344
+ continue;
11345
+ }
11346
+ const allowed = visibleOptionSets.get(ownerFieldId);
11347
+ if (!allowed) {
11348
+ retained.push(token);
11349
+ continue;
11350
+ }
11351
+ if (allowed.has(token)) {
11352
+ retained.push(token);
11353
+ continue;
11354
+ }
11355
+ changed = true;
11356
+ changedFieldIds.add(ownerFieldId);
11357
+ }
11358
+ if (!changed) return;
11359
+ for (const fieldId of changedFieldIds) {
11360
+ const allowed = visibleOptionSets.get(fieldId);
11361
+ const currentValue = ((_d = (_c2 = (_b2 = ctx.formApi).snapshot) == null ? void 0 : _c2.call(_b2)) != null ? _d : {})[fieldId];
11362
+ if (Array.isArray(currentValue)) {
11363
+ const next = currentValue.filter(
11364
+ (value) => allowed == null ? void 0 : allowed.has(String(value))
11365
+ );
11366
+ if (next.length !== currentValue.length) {
11367
+ ctx.formApi.set(fieldId, next.length ? next : void 0);
11368
+ }
11369
+ continue;
11370
+ }
11371
+ if (currentValue !== void 0 && allowed && !allowed.has(String(currentValue))) {
11372
+ ctx.formApi.set(fieldId, void 0);
11373
+ }
11374
+ }
11375
+ selection.many(retained, retained[retained.length - 1]);
11376
+ }, [ctx, ready, selTick, visibleOptionsByFieldId]);
10412
11377
  const optionSelectionsByFieldId = (0, import_react6.useMemo)(
10413
11378
  () => ({}),
10414
11379
  []
@@ -10556,7 +11521,7 @@ function useOrderFlow() {
10556
11521
  );
10557
11522
  const setFieldOptions = (0, import_react6.useCallback)(
10558
11523
  (fieldId, optionIds) => {
10559
- var _a2, _b2, _c2, _d;
11524
+ var _a2, _b2, _c2;
10560
11525
  const { builder, selection, init } = ctx.ensureReady(
10561
11526
  "setFieldOptions"
10562
11527
  );
@@ -10564,7 +11529,9 @@ function useOrderFlow() {
10564
11529
  const field = fields.find((f) => f.id === fieldId);
10565
11530
  if (!field) return;
10566
11531
  const validOptionIds = new Set(
10567
- ((_b2 = field.options) != null ? _b2 : []).map((option) => String(option.id))
11532
+ Array.from(fieldOptionIdSet(field)).map(
11533
+ (optionId) => String(optionId)
11534
+ )
10568
11535
  );
10569
11536
  const dedupedValid = [];
10570
11537
  const seen = /* @__PURE__ */ new Set();
@@ -10575,36 +11542,18 @@ function useOrderFlow() {
10575
11542
  seen.add(optionId);
10576
11543
  dedupedValid.push(optionId);
10577
11544
  }
10578
- const mode = (_c2 = init.mode) != null ? _c2 : "prod";
10579
- const isMulti = ((_d = field.meta) == null ? void 0 : _d.multi) === true;
11545
+ const mode = (_b2 = init.mode) != null ? _b2 : "prod";
11546
+ const isMulti = ((_c2 = field.meta) == null ? void 0 : _c2.multi) === true;
10580
11547
  const normalized = mode === "prod" && !isMulti ? dedupedValid.length ? [dedupedValid[dedupedValid.length - 1]] : [] : dedupedValid;
10581
- const fieldById = new Map(fields.map((f) => [f.id, f]));
10582
11548
  const nodeMap = builder.getNodeMap();
10583
11549
  const resolveOptionOwnerFieldId = (token) => {
10584
11550
  var _a3;
10585
11551
  if (!token) return void 0;
10586
- if (token.includes("::")) {
10587
- const [legacyFieldId, optionId] = token.split("::", 2);
10588
- if (!optionId) return void 0;
10589
- const optionRef2 = nodeMap.get(optionId);
10590
- if ((optionRef2 == null ? void 0 : optionRef2.kind) === "option" && typeof optionRef2.fieldId === "string") {
10591
- return optionRef2.fieldId;
10592
- }
10593
- if (legacyFieldId && fieldById.has(legacyFieldId)) {
10594
- return legacyFieldId;
10595
- }
10596
- return void 0;
10597
- }
10598
11552
  const optionRef = nodeMap.get(token);
10599
11553
  if ((optionRef == null ? void 0 : optionRef.kind) === "option" && typeof optionRef.fieldId === "string") {
10600
11554
  return optionRef.fieldId;
10601
11555
  }
10602
- for (const f of fields) {
10603
- if ((_a3 = f.options) == null ? void 0 : _a3.some((option) => option.id === token)) {
10604
- return f.id;
10605
- }
10606
- }
10607
- return void 0;
11556
+ return (_a3 = findOptionOwnerField(fields, token)) == null ? void 0 : _a3.id;
10608
11557
  };
10609
11558
  const retained = Array.from(selection.all()).filter(
10610
11559
  (token) => resolveOptionOwnerFieldId(token) !== fieldId
@@ -10708,6 +11657,8 @@ function useOrderFlow() {
10708
11657
  raw,
10709
11658
  notices,
10710
11659
  visibleGroup,
11660
+ visibleOptionsByFieldId,
11661
+ forcedFieldIds,
10711
11662
  formValuesByFieldId,
10712
11663
  optionSelectionsByFieldId,
10713
11664
  quantityPreview: previewSnapshot.quantity,
@@ -10814,6 +11765,18 @@ function Wrapper({
10814
11765
  const flow = useOrderFlow();
10815
11766
  const kind = toKind(field);
10816
11767
  const variant = toVariant(field);
11768
+ const renderedField = React4.useMemo(() => {
11769
+ var _a2;
11770
+ const visibleOptionIds = flow.visibleOptionsByFieldId[field.id];
11771
+ if (!visibleOptionIds) return field;
11772
+ return {
11773
+ ...field,
11774
+ options: (_a2 = filterFieldOptionsById(
11775
+ field.options,
11776
+ new Set(visibleOptionIds)
11777
+ )) != null ? _a2 : []
11778
+ };
11779
+ }, [field, flow.visibleOptionsByFieldId]);
10817
11780
  const descriptor = React4.useMemo(
10818
11781
  () => resolveInputDescriptor(registry, kind, variant),
10819
11782
  [kind, registry, variant]
@@ -10827,8 +11790,8 @@ function Wrapper({
10827
11790
  const baseProps = (_b = descriptor.defaultProps) != null ? _b : {};
10828
11791
  const defaultProps = (0, import_react7.useMemo)(() => {
10829
11792
  var _a2;
10830
- return { ...baseProps, ...(_a2 = field.defaults) != null ? _a2 : {} };
10831
- }, [baseProps, field.defaults]);
11793
+ return { ...baseProps, ...(_a2 = renderedField.defaults) != null ? _a2 : {} };
11794
+ }, [baseProps, renderedField.defaults]);
10832
11795
  const valueProp = (_c = adapter.valueProp) != null ? _c : "value";
10833
11796
  const changeProp = (_d = adapter.changeProp) != null ? _d : "onChange";
10834
11797
  const errorProp = (_e = adapter.errorProp) != null ? _e : "errorText";
@@ -10842,13 +11805,14 @@ function Wrapper({
10842
11805
  disabled: !!disabled
10843
11806
  });
10844
11807
  const optionIds = React4.useMemo(() => {
10845
- var _a2;
10846
11808
  if (!isOptionBased2) return /* @__PURE__ */ new Set();
10847
- return new Set(((_a2 = field.options) != null ? _a2 : []).map((o) => o.id));
10848
- }, [isOptionBased2, field.options]);
11809
+ return new Set(
11810
+ Array.from(fieldOptionIdSet(renderedField)).map(String)
11811
+ );
11812
+ }, [isOptionBased2, renderedField]);
10849
11813
  const adapterCtx = React4.useMemo(
10850
- () => ({ field, props: flow.raw }),
10851
- [field, flow.raw]
11814
+ () => ({ field: renderedField, props: flow.raw }),
11815
+ [renderedField, flow.raw]
10852
11816
  );
10853
11817
  const onHostChange = React4.useCallback(
10854
11818
  (next) => {
@@ -10859,7 +11823,7 @@ function Wrapper({
10859
11823
  if (isOptionBased2) {
10860
11824
  if (!adapter.getSelectedOptions) {
10861
11825
  throw new Error(
10862
- `[Wrapper] Adapter for "${field.id}" (${field.type}) must implement getSelectedOptions() because this field has options.`
11826
+ `[Wrapper] Adapter for "${renderedField.id}" (${renderedField.type}) must implement getSelectedOptions() because this field has options.`
10863
11827
  );
10864
11828
  }
10865
11829
  const rawIds = adapter.getSelectedOptions(
@@ -10872,20 +11836,20 @@ function Wrapper({
10872
11836
  (rawIds != null ? rawIds : []).map(String).filter((id) => optionIds.has(id))
10873
11837
  )
10874
11838
  );
10875
- flow.setFieldOptions(field.id, nextIds);
11839
+ flow.setFieldOptions(renderedField.id, nextIds);
10876
11840
  return;
10877
11841
  }
10878
11842
  if (isActionButton) {
10879
11843
  const isActive = (_e2 = (_d2 = adapter.isActive) == null ? void 0 : _d2.call(adapter, stored, adapterCtx)) != null ? _e2 : Boolean(stored);
10880
- if (isActive) flow.toggleOption(field.id);
10881
- else flow.clearField(field.id);
11844
+ if (isActive) flow.toggleOption(renderedField.id);
11845
+ else flow.clearField(renderedField.id);
10882
11846
  }
10883
11847
  },
10884
11848
  [
10885
11849
  adapter,
10886
11850
  adapterCtx,
10887
- field.id,
10888
- field.type,
11851
+ renderedField.id,
11852
+ renderedField.type,
10889
11853
  flow,
10890
11854
  fp,
10891
11855
  isActionButton,
@@ -10899,12 +11863,12 @@ function Wrapper({
10899
11863
  const ctx = ctxOverrides && typeof ctxOverrides === "object" ? { ...ctxFromInit, ...ctxOverrides } : ctxFromInit;
10900
11864
  return {
10901
11865
  ...ctx,
10902
- field,
11866
+ field: renderedField,
10903
11867
  flow,
10904
11868
  value: fp.value,
10905
11869
  error: fp.error
10906
11870
  };
10907
- }, [ctxOverrides, field, flow, fp.error, fp.value]);
11871
+ }, [ctxOverrides, renderedField, flow, fp.error, fp.value]);
10908
11872
  const templatedDefaultProps = React4.useMemo(() => {
10909
11873
  if (!templateStrings) return defaultProps;
10910
11874
  return templateDeep(defaultProps, templateCtx);
@@ -10914,14 +11878,14 @@ function Wrapper({
10914
11878
  return extraProps != null ? extraProps : {};
10915
11879
  return templateDeep(extraProps != null ? extraProps : {}, templateCtx);
10916
11880
  }, [extraProps, templateCtx, templateStrings]);
10917
- const fieldProps = (_h = (_g = adapter == null ? void 0 : adapter.getInputPropsFromField) == null ? void 0 : _g.call(adapter, { field, props: flow.raw })) != null ? _h : {};
11881
+ const fieldProps = (_h = (_g = adapter == null ? void 0 : adapter.getInputPropsFromField) == null ? void 0 : _g.call(adapter, { field: renderedField, props: flow.raw })) != null ? _h : {};
10918
11882
  const hostProps = {
10919
- id: field.id,
10920
- field,
11883
+ id: renderedField.id,
11884
+ field: renderedField,
10921
11885
  disabled: !!disabled || !!fp.disabled,
10922
11886
  required: field.required,
10923
11887
  // DO NOT pass `name` to InputField/entries
10924
- fieldKey: field.id,
11888
+ fieldKey: renderedField.id,
10925
11889
  ...fieldProps != null ? fieldProps : {},
10926
11890
  // error channel
10927
11891
  error: fp.error,
@@ -11927,7 +12891,10 @@ var treeSelectDescriptor = {
11927
12891
  supported: true,
11928
12892
  autoCreate: true,
11929
12893
  defaultLabel: "Option label",
11930
- defaultValue: "option"
12894
+ defaultValue: "option",
12895
+ children: {
12896
+ supported: true
12897
+ }
11931
12898
  },
11932
12899
  multi: {
11933
12900
  supported: true,
@@ -13118,20 +14085,29 @@ function withInputFieldUi(desc) {
13118
14085
  const fieldNotices = notices.filter(
13119
14086
  (notice) => matchesNotice(field, notice)
13120
14087
  );
14088
+ const mapOptionForInputField = (item) => {
14089
+ var _a3;
14090
+ const optionNotices = notices.filter(
14091
+ (notice) => matchesNotice(item, notice)
14092
+ );
14093
+ return {
14094
+ ...item,
14095
+ tags: optionNotices.map(toTagPill),
14096
+ ...((_a3 = item.children) == null ? void 0 : _a3.length) ? {
14097
+ children: item.children.map(
14098
+ mapOptionForInputField
14099
+ )
14100
+ } : {}
14101
+ };
14102
+ };
13121
14103
  return {
13122
14104
  label: field.label,
13123
14105
  tags: fieldNotices.map(toTagPill),
13124
14106
  required: field.required,
13125
14107
  ...((_b = field.options) == null ? void 0 : _b.length) ? {
13126
- options: field.options.map((item) => {
13127
- const optionNotices = notices.filter(
13128
- (notice) => matchesNotice(item, notice)
13129
- );
13130
- return {
13131
- ...item,
13132
- tags: optionNotices.map(toTagPill)
13133
- };
13134
- })
14108
+ options: field.options.map(
14109
+ mapOptionForInputField
14110
+ )
13135
14111
  } : {}
13136
14112
  };
13137
14113
  },