@timeax/digital-service-engine 0.3.4 → 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.
@@ -384,6 +384,7 @@ function templateTime(template) {
384
384
  return (_a = parseTimestamp(template.updatedAt)) != null ? _a : parseTimestamp(template.createdAt);
385
385
  }
386
386
  function shouldReplaceTemplates(params) {
387
+ var _a;
387
388
  if (!params.requestedSince) return true;
388
389
  if (!params.lastUpdatedAt) return false;
389
390
  const requested = parseTimestamp(params.requestedSince);
@@ -391,7 +392,9 @@ function shouldReplaceTemplates(params) {
391
392
  if (requested === void 0 || last === void 0) {
392
393
  return false;
393
394
  }
394
- return requested < last;
395
+ const currentTimes = ((_a = params.current) != null ? _a : []).map((template) => templateTime(template)).filter((time) => time !== void 0);
396
+ if (!currentTimes.length) return requested < last;
397
+ return requested < Math.min(...currentTimes);
395
398
  }
396
399
  function pickNewestTemplate(current, incoming) {
397
400
  const currentTime = templateTime(current);
@@ -399,7 +402,8 @@ function pickNewestTemplate(current, incoming) {
399
402
  if (currentTime !== void 0 && incomingTime !== void 0) {
400
403
  return incomingTime >= currentTime ? incoming : current;
401
404
  }
402
- if (currentTime === void 0 && incomingTime !== void 0) return incoming;
405
+ if (currentTime === void 0 && incomingTime !== void 0)
406
+ return incoming;
403
407
  if (currentTime !== void 0 && incomingTime === void 0) return current;
404
408
  return incoming;
405
409
  }
@@ -472,7 +476,8 @@ function useTemplatesSlice(params) {
472
476
  setTemplates((current) => {
473
477
  const replace = shouldReplaceTemplates({
474
478
  requestedSince,
475
- lastUpdatedAt: current.updatedAt
479
+ lastUpdatedAt: current.updatedAt,
480
+ current: current.data
476
481
  });
477
482
  return {
478
483
  data: replace ? res.value : mergeTemplates(current.data, res.value, {
@@ -571,7 +576,9 @@ function useTemplatesSlice(params) {
571
576
  var _a, _b;
572
577
  return {
573
578
  ...current,
574
- data: (_b = (_a = current.data) == null ? void 0 : _a.filter((template) => template.id !== id)) != null ? _b : current.data,
579
+ data: (_b = (_a = current.data) == null ? void 0 : _a.filter(
580
+ (template) => template.id !== id
581
+ )) != null ? _b : current.data,
575
582
  updatedAt: deleteRefreshSince
576
583
  };
577
584
  });
@@ -3634,6 +3641,9 @@ function normalise(input, opts = {}) {
3634
3641
  const excludes_for_buttons = toStringArrayMap(
3635
3642
  obj.excludes_for_buttons
3636
3643
  );
3644
+ const option_effects_for_buttons = toOptionEffectMap(
3645
+ obj.option_effects_for_buttons
3646
+ );
3637
3647
  const orderKinds = toStringMap(obj.orderKinds);
3638
3648
  const notices = toNoticeArray(obj.notices);
3639
3649
  let filters = rawFilters.map((t) => coerceTag(t, constraints));
@@ -3649,6 +3659,9 @@ function normalise(input, opts = {}) {
3649
3659
  ...isNonEmpty(orderKinds) && { orderKinds },
3650
3660
  ...isNonEmpty(includes_for_buttons) && { includes_for_buttons },
3651
3661
  ...isNonEmpty(excludes_for_buttons) && { excludes_for_buttons },
3662
+ ...isNonEmpty(option_effects_for_buttons) && {
3663
+ option_effects_for_buttons
3664
+ },
3652
3665
  ...fallbacks && (isNonEmpty(fallbacks.nodes) || isNonEmpty(fallbacks.global)) && {
3653
3666
  fallbacks
3654
3667
  },
@@ -3803,6 +3816,7 @@ function coerceOption(src, inheritRole) {
3803
3816
  const value = typeof src.value === "string" || typeof src.value === "number" ? src.value : void 0;
3804
3817
  const pricing_role = src.pricing_role === "utility" || src.pricing_role === "base" ? src.pricing_role : inheritRole;
3805
3818
  const meta = src.meta && typeof src.meta === "object" ? src.meta : void 0;
3819
+ const children = Array.isArray(src.children) ? src.children.map((child) => coerceOption(child, pricing_role)) : void 0;
3806
3820
  const option = {
3807
3821
  id: "",
3808
3822
  label: "",
@@ -3811,7 +3825,8 @@ function coerceOption(src, inheritRole) {
3811
3825
  ...value !== void 0 && { value },
3812
3826
  ...service_id !== void 0 && { service_id },
3813
3827
  pricing_role,
3814
- ...meta && { meta }
3828
+ ...meta && { meta },
3829
+ ...children && children.length && { children }
3815
3830
  };
3816
3831
  return option;
3817
3832
  }
@@ -3876,6 +3891,35 @@ function toStringArrayMap(src) {
3876
3891
  }
3877
3892
  return Object.keys(out).length ? out : void 0;
3878
3893
  }
3894
+ function toOptionEffectMap(src) {
3895
+ var _a, _b;
3896
+ if (!src || typeof src !== "object") return void 0;
3897
+ const out = {};
3898
+ for (const [triggerId, rawTargets] of Object.entries(src)) {
3899
+ if (!triggerId || !rawTargets || typeof rawTargets !== "object") {
3900
+ continue;
3901
+ }
3902
+ const targets = {};
3903
+ for (const [fieldId, rawEffect] of Object.entries(rawTargets)) {
3904
+ if (!fieldId || !rawEffect || typeof rawEffect !== "object") {
3905
+ continue;
3906
+ }
3907
+ const effect = rawEffect;
3908
+ const include2 = toStringArray(effect.include);
3909
+ const exclude2 = toStringArray(effect.exclude);
3910
+ const next = {
3911
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
3912
+ ...include2.length ? { include: dedupe(include2) } : {},
3913
+ ...exclude2.length ? { exclude: dedupe(exclude2) } : {}
3914
+ };
3915
+ if (next.forceVisible === true || ((_a = next.include) == null ? void 0 : _a.length) || ((_b = next.exclude) == null ? void 0 : _b.length)) {
3916
+ targets[fieldId] = next;
3917
+ }
3918
+ }
3919
+ if (Object.keys(targets).length) out[triggerId] = targets;
3920
+ }
3921
+ return Object.keys(out).length ? out : void 0;
3922
+ }
3879
3923
  function toStringArray(v) {
3880
3924
  if (!Array.isArray(v)) return [];
3881
3925
  return v.map((x) => String(x)).filter((s) => !!s && s.trim().length > 0);
@@ -3950,6 +3994,57 @@ function normalizeFieldValidation(input) {
3950
3994
  return one ? [one] : void 0;
3951
3995
  }
3952
3996
 
3997
+ // src/core/options.ts
3998
+ function walkFieldOptions(field) {
3999
+ const out = [];
4000
+ const visit = (options, depth, parentId) => {
4001
+ for (const option of options != null ? options : []) {
4002
+ out.push({
4003
+ field,
4004
+ fieldId: field.id,
4005
+ option,
4006
+ optionId: option.id,
4007
+ depth,
4008
+ parentId
4009
+ });
4010
+ visit(option.children, depth + 1, option.id);
4011
+ }
4012
+ };
4013
+ visit(field.options, 0);
4014
+ return out;
4015
+ }
4016
+ function fieldOptionIds(field) {
4017
+ return walkFieldOptions(field).map((visit) => visit.optionId);
4018
+ }
4019
+ function fieldOptionIdSet(field) {
4020
+ return new Set(fieldOptionIds(field));
4021
+ }
4022
+ function findFieldOption(field, optionId) {
4023
+ var _a;
4024
+ if (!field) return void 0;
4025
+ return (_a = walkFieldOptions(field).find((visit) => visit.optionId === optionId)) == null ? void 0 : _a.option;
4026
+ }
4027
+ function findOptionOwnerField(fields, optionId) {
4028
+ for (const field of fields) {
4029
+ if (findFieldOption(field, optionId)) return field;
4030
+ }
4031
+ return void 0;
4032
+ }
4033
+ function optionOwnerMap(fields) {
4034
+ const out = /* @__PURE__ */ new Map();
4035
+ for (const field of fields) {
4036
+ for (const visit of walkFieldOptions(field)) {
4037
+ if (!out.has(visit.optionId)) {
4038
+ out.set(visit.optionId, {
4039
+ fieldId: field.id,
4040
+ option: visit.option
4041
+ });
4042
+ }
4043
+ }
4044
+ }
4045
+ return out;
4046
+ }
4047
+
3953
4048
  // src/core/validate/shared.ts
3954
4049
  function isFiniteNumber(v) {
3955
4050
  return typeof v === "number" && Number.isFinite(v);
@@ -3958,8 +4053,9 @@ function isServiceIdRef(v) {
3958
4053
  return typeof v === "string" && v.trim().length > 0 || typeof v === "number" && Number.isFinite(v);
3959
4054
  }
3960
4055
  function hasAnyServiceOption(f) {
3961
- var _a;
3962
- return ((_a = f.options) != null ? _a : []).some((o) => isServiceIdRef(o.service_id));
4056
+ return walkFieldOptions(f).some(
4057
+ (visit) => isServiceIdRef(visit.option.service_id)
4058
+ );
3963
4059
  }
3964
4060
  function getByPath(obj, path) {
3965
4061
  if (!path) return void 0;
@@ -4048,14 +4144,14 @@ function withAffected(details, ids) {
4048
4144
 
4049
4145
  // src/core/node-map.ts
4050
4146
  function buildNodeMap(props) {
4051
- var _a, _b, _c;
4147
+ var _a, _b;
4052
4148
  const map = /* @__PURE__ */ new Map();
4053
4149
  for (const t of (_a = props.filters) != null ? _a : []) {
4054
4150
  if (!map.has(t.id)) map.set(t.id, { kind: "tag", id: t.id, node: t });
4055
4151
  }
4056
4152
  for (const f of (_b = props.fields) != null ? _b : []) {
4057
4153
  if (!map.has(f.id)) map.set(f.id, { kind: "field", id: f.id, node: f });
4058
- for (const o of (_c = f.options) != null ? _c : []) {
4154
+ for (const { option: o } of walkFieldOptions(f)) {
4059
4155
  if (!map.has(o.id))
4060
4156
  map.set(o.id, {
4061
4157
  kind: "option",
@@ -4068,12 +4164,6 @@ function buildNodeMap(props) {
4068
4164
  return map;
4069
4165
  }
4070
4166
  function resolveTrigger(trigger, nodeMap) {
4071
- const idx = trigger.indexOf("::");
4072
- if (idx !== -1) {
4073
- const fieldId = trigger.slice(0, idx);
4074
- const optionId = trigger.slice(idx + 2);
4075
- return { kind: "composite", triggerKey: trigger, fieldId, optionId };
4076
- }
4077
4167
  const direct = nodeMap.get(trigger);
4078
4168
  if (!direct) return void 0;
4079
4169
  if (direct.kind === "option") {
@@ -4125,11 +4215,6 @@ function visibleFieldIdsUnder(props, tagId, opts = {}) {
4125
4215
  const ownerDepthForTriggerKey = (triggerKey) => {
4126
4216
  const t = resolveTrigger(triggerKey, nodeMap);
4127
4217
  if (!t) return void 0;
4128
- if (t.kind === "composite") {
4129
- const f = fieldById.get(t.fieldId);
4130
- if (!f) return void 0;
4131
- return ownerDepthForField(f);
4132
- }
4133
4218
  if (t.kind === "field") {
4134
4219
  const f = fieldById.get(t.id);
4135
4220
  if (!f || f.button !== true) return void 0;
@@ -4212,6 +4297,84 @@ function visibleFieldsUnder(props, tagId, opts = {}) {
4212
4297
  const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((f) => [f.id, f]));
4213
4298
  return ids.map((id) => fieldById.get(id)).filter(Boolean);
4214
4299
  }
4300
+ function resolveVisibility(props, tagId, selectedKeys) {
4301
+ var _a, _b, _c, _d;
4302
+ const selected = new Set(selectedKeys != null ? selectedKeys : []);
4303
+ const baseFieldIds = visibleFieldIdsUnder(props, tagId, { selectedKeys: selected });
4304
+ const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((field) => [field.id, field]));
4305
+ const visible = new Set(baseFieldIds);
4306
+ const forced = /* @__PURE__ */ new Set();
4307
+ const optionsByFieldId = {};
4308
+ const optionIdsByFieldId = /* @__PURE__ */ new Map();
4309
+ const getOptionIds = (field) => {
4310
+ let ids = optionIdsByFieldId.get(field.id);
4311
+ if (!ids) {
4312
+ ids = fieldOptionIds(field);
4313
+ optionIdsByFieldId.set(field.id, ids);
4314
+ }
4315
+ return ids;
4316
+ };
4317
+ const ensureOptions = (field) => {
4318
+ const ids = getOptionIds(field);
4319
+ if (!ids.length) return void 0;
4320
+ if (!optionsByFieldId[field.id]) optionsByFieldId[field.id] = [...ids];
4321
+ return optionsByFieldId[field.id];
4322
+ };
4323
+ for (const fieldId of baseFieldIds) {
4324
+ const field = fieldById.get(fieldId);
4325
+ if (field) ensureOptions(field);
4326
+ }
4327
+ const effects = (_b = props.option_effects_for_buttons) != null ? _b : {};
4328
+ for (const triggerId of selected) {
4329
+ const targetRules = effects[triggerId];
4330
+ if (!targetRules) continue;
4331
+ for (const [targetFieldId, rule] of Object.entries(targetRules)) {
4332
+ const field = fieldById.get(targetFieldId);
4333
+ if (!field) continue;
4334
+ const isVisible = visible.has(targetFieldId);
4335
+ if (!isVisible && rule.forceVisible !== true) continue;
4336
+ if (!isVisible && rule.forceVisible === true) {
4337
+ visible.add(targetFieldId);
4338
+ forced.add(targetFieldId);
4339
+ }
4340
+ const orderedOptionIds = getOptionIds(field);
4341
+ if (!orderedOptionIds.length) continue;
4342
+ const known = new Set(orderedOptionIds);
4343
+ let allowed = (_c = optionsByFieldId[targetFieldId]) != null ? _c : [...orderedOptionIds];
4344
+ if (Array.isArray(rule.include) && rule.include.length) {
4345
+ const include2 = new Set(
4346
+ rule.include.filter((optionId) => known.has(optionId))
4347
+ );
4348
+ allowed = orderedOptionIds.filter(
4349
+ (optionId) => include2.has(optionId) && allowed.includes(optionId)
4350
+ );
4351
+ }
4352
+ if (Array.isArray(rule.exclude) && rule.exclude.length) {
4353
+ const exclude2 = new Set(
4354
+ rule.exclude.filter((optionId) => known.has(optionId))
4355
+ );
4356
+ allowed = allowed.filter((optionId) => !exclude2.has(optionId));
4357
+ }
4358
+ optionsByFieldId[targetFieldId] = allowed;
4359
+ }
4360
+ }
4361
+ const visibleFieldIds = baseFieldIds.filter((fieldId) => visible.has(fieldId));
4362
+ const seen = new Set(visibleFieldIds);
4363
+ for (const field of (_d = props.fields) != null ? _d : []) {
4364
+ if (!visible.has(field.id) || seen.has(field.id)) continue;
4365
+ seen.add(field.id);
4366
+ visibleFieldIds.push(field.id);
4367
+ ensureOptions(field);
4368
+ }
4369
+ for (const fieldId of Object.keys(optionsByFieldId)) {
4370
+ if (!visible.has(fieldId)) delete optionsByFieldId[fieldId];
4371
+ }
4372
+ return {
4373
+ fieldIds: visibleFieldIds,
4374
+ optionsByFieldId,
4375
+ forcedFieldIds: visibleFieldIds.filter((fieldId) => forced.has(fieldId))
4376
+ };
4377
+ }
4215
4378
 
4216
4379
  // src/core/validate/steps/visibility.ts
4217
4380
  function createFieldsVisibleUnder(v) {
@@ -4229,7 +4392,6 @@ function resolveRootTags(tags) {
4229
4392
  return roots.length ? roots : tags.slice(0, 1);
4230
4393
  }
4231
4394
  function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
4232
- var _a;
4233
4395
  const visible = visibleFieldsUnder(v.props, tagId, {
4234
4396
  selectedKeys
4235
4397
  });
@@ -4239,7 +4401,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
4239
4401
  const t = f.id;
4240
4402
  if (effectfulKeys.has(t)) triggers.push(t);
4241
4403
  }
4242
- for (const o of (_a = f.options) != null ? _a : []) {
4404
+ for (const { option: o } of walkFieldOptions(f)) {
4243
4405
  const t = o.id;
4244
4406
  if (effectfulKeys.has(t)) triggers.push(t);
4245
4407
  }
@@ -4248,7 +4410,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
4248
4410
  return triggers;
4249
4411
  }
4250
4412
  function runVisibilityRulesOnce(v) {
4251
- var _a, _b, _c, _d, _e;
4413
+ var _a, _b, _c, _d;
4252
4414
  for (const t of v.tags) {
4253
4415
  const visible = v.fieldsVisibleUnder(t.id);
4254
4416
  const seen = /* @__PURE__ */ new Map();
@@ -4298,9 +4460,9 @@ function runVisibilityRulesOnce(v) {
4298
4460
  let hasUtility = false;
4299
4461
  const utilityOptionIds = [];
4300
4462
  for (const f of visible) {
4301
- for (const o of (_c = f.options) != null ? _c : []) {
4463
+ for (const { option: o } of walkFieldOptions(f)) {
4302
4464
  if (!isServiceIdRef(o.service_id)) continue;
4303
- const role = (_e = (_d = o.pricing_role) != null ? _d : f.pricing_role) != null ? _e : "base";
4465
+ const role = (_d = (_c = o.pricing_role) != null ? _c : f.pricing_role) != null ? _d : "base";
4304
4466
  if (role === "base") hasBase = true;
4305
4467
  else if (role === "utility") {
4306
4468
  hasUtility = true;
@@ -4341,7 +4503,7 @@ function dedupeErrorsInPlace(v, startIndex) {
4341
4503
  v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
4342
4504
  }
4343
4505
  function validateVisibility(v, options = {}) {
4344
- var _a, _b, _c, _d, _e;
4506
+ var _a, _b, _c, _d, _e, _f;
4345
4507
  v.simulatedVisibilityContexts = [];
4346
4508
  const simulate = options.simulate === true;
4347
4509
  if (!simulate) {
@@ -4366,10 +4528,13 @@ function validateVisibility(v, options = {}) {
4366
4528
  for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
4367
4529
  effectfulKeys.add(key);
4368
4530
  }
4531
+ for (const key of Object.keys((_e = v.props.option_effects_for_buttons) != null ? _e : {})) {
4532
+ effectfulKeys.add(key);
4533
+ }
4369
4534
  }
4370
4535
  const roots = resolveRootTags(v.tags);
4371
4536
  const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
4372
- const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
4537
+ const originalSelected = new Set((_f = v.selectedKeys) != null ? _f : []);
4373
4538
  const errorsStart = v.errors.length;
4374
4539
  const visited = /* @__PURE__ */ new Set();
4375
4540
  const seenContexts = /* @__PURE__ */ new Set();
@@ -4510,7 +4675,7 @@ function validateStructure(v) {
4510
4675
 
4511
4676
  // src/core/validate/steps/identity.ts
4512
4677
  function validateIdentity(v) {
4513
- var _a, _b;
4678
+ var _a;
4514
4679
  const tags = v.tags;
4515
4680
  const fields = v.fields;
4516
4681
  {
@@ -4610,7 +4775,7 @@ function validateIdentity(v) {
4610
4775
  }
4611
4776
  }
4612
4777
  for (const f of fields) {
4613
- for (const o of (_b = f.options) != null ? _b : []) {
4778
+ for (const { option: o } of walkFieldOptions(f)) {
4614
4779
  if (!o.label || !o.label.trim()) {
4615
4780
  v.errors.push({
4616
4781
  code: "label_missing",
@@ -4625,25 +4790,11 @@ function validateIdentity(v) {
4625
4790
  }
4626
4791
 
4627
4792
  // src/core/validate/steps/option-maps.ts
4628
- function parseFieldOptionKey(key) {
4629
- const idx = key.indexOf("::");
4630
- if (idx === -1) return null;
4631
- const fieldId = key.slice(0, idx).trim();
4632
- const optionId = key.slice(idx + 2).trim();
4633
- if (!fieldId || !optionId) return null;
4634
- return { fieldId, optionId };
4635
- }
4636
- function hasOption(v, fid, oid) {
4637
- var _a;
4638
- const f = v.fieldById.get(fid);
4639
- if (!f) return false;
4640
- return !!((_a = f.options) != null ? _a : []).find((o) => o.id === oid);
4641
- }
4642
4793
  function validateOptionMaps(v) {
4643
- var _a, _b;
4794
+ var _a, _b, _c;
4644
4795
  const incMap = (_a = v.props.includes_for_buttons) != null ? _a : {};
4645
4796
  const excMap = (_b = v.props.excludes_for_buttons) != null ? _b : {};
4646
- 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.`;
4797
+ const badKeyMessage = (key) => `Invalid trigger-map key "${key}". Expected a known option id or button-field id.`;
4647
4798
  const validateTriggerKey = (key) => {
4648
4799
  const ref = v.nodeMap.get(key);
4649
4800
  if (ref) {
@@ -4662,19 +4813,7 @@ function validateOptionMaps(v) {
4662
4813
  }
4663
4814
  return { ok: false, nodeId: ref.id, affected: [ref.id] };
4664
4815
  }
4665
- const p = parseFieldOptionKey(key);
4666
- if (!p) return { ok: false };
4667
- if (!hasOption(v, p.fieldId, p.optionId))
4668
- return {
4669
- ok: false,
4670
- nodeId: p.fieldId,
4671
- affected: [p.fieldId, p.optionId]
4672
- };
4673
- return {
4674
- ok: true,
4675
- nodeId: p.fieldId,
4676
- affected: [p.fieldId, p.optionId]
4677
- };
4816
+ return { ok: false };
4678
4817
  };
4679
4818
  for (const k of Object.keys(incMap)) {
4680
4819
  const r = validateTriggerKey(k);
@@ -4700,6 +4839,57 @@ function validateOptionMaps(v) {
4700
4839
  });
4701
4840
  }
4702
4841
  }
4842
+ const effectMap = (_c = v.props.option_effects_for_buttons) != null ? _c : {};
4843
+ for (const [triggerKey, targets] of Object.entries(effectMap)) {
4844
+ const trigger = validateTriggerKey(triggerKey);
4845
+ if (!trigger.ok) {
4846
+ v.errors.push({
4847
+ code: "bad_option_effect_key",
4848
+ severity: "error",
4849
+ message: badKeyMessage(triggerKey),
4850
+ nodeId: trigger.nodeId,
4851
+ details: withAffected({ key: triggerKey }, trigger.affected)
4852
+ });
4853
+ }
4854
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
4855
+ const field = v.fieldById.get(targetFieldId);
4856
+ if (!field) {
4857
+ v.errors.push({
4858
+ code: "bad_option_effect_target",
4859
+ severity: "error",
4860
+ message: `Option effect trigger "${triggerKey}" targets unknown field "${targetFieldId}".`,
4861
+ details: withAffected(
4862
+ { key: triggerKey, targetFieldId },
4863
+ trigger.affected
4864
+ )
4865
+ });
4866
+ continue;
4867
+ }
4868
+ const validOptionIds = fieldOptionIdSet(field);
4869
+ const checkTargetOptions = (kind, optionIds) => {
4870
+ for (const optionId of optionIds != null ? optionIds : []) {
4871
+ if (validOptionIds.has(optionId)) continue;
4872
+ v.errors.push({
4873
+ code: "bad_option_effect_option",
4874
+ severity: "error",
4875
+ message: `Option effect trigger "${triggerKey}" references unknown ${kind} option "${optionId}" for field "${targetFieldId}".`,
4876
+ nodeId: targetFieldId,
4877
+ details: withAffected(
4878
+ {
4879
+ key: triggerKey,
4880
+ targetFieldId,
4881
+ optionId,
4882
+ kind
4883
+ },
4884
+ [targetFieldId, optionId]
4885
+ )
4886
+ });
4887
+ }
4888
+ };
4889
+ checkTargetOptions("include", effect == null ? void 0 : effect.include);
4890
+ checkTargetOptions("exclude", effect == null ? void 0 : effect.exclude);
4891
+ }
4892
+ }
4703
4893
  for (const k of Object.keys(incMap)) {
4704
4894
  if (!(k in excMap)) continue;
4705
4895
  const r = validateTriggerKey(k);
@@ -4713,27 +4903,231 @@ function validateOptionMaps(v) {
4713
4903
  }
4714
4904
  }
4715
4905
 
4716
- // src/utils/order-kind.ts
4717
- function normalizeSelectedTriggerKey(key, nodeMap) {
4718
- if (!key) return void 0;
4719
- const compositeIdx = key.indexOf("::");
4720
- if (compositeIdx !== -1) {
4721
- const fieldId = key.slice(0, compositeIdx).trim();
4722
- const optionId = key.slice(compositeIdx + 2).trim();
4723
- if (optionId) {
4724
- const optionRef = nodeMap.get(optionId);
4725
- if ((optionRef == null ? void 0 : optionRef.kind) === "option") {
4726
- return { nodeId: optionRef.id, nodeKind: "option" };
4906
+ // src/core/validate/steps/visibility-cycles.ts
4907
+ var MAX_VISIBILITY_CYCLE_DEPTH = 20;
4908
+ function validateVisibilityCycles(v) {
4909
+ const triggerById = buildTriggerIndex(v.fields);
4910
+ if (!triggerById.size) return;
4911
+ const fieldTriggers = buildFieldTriggerIndex(v.fields);
4912
+ const revealTargetsByTrigger = buildRevealIndex(v, triggerById);
4913
+ const reported = /* @__PURE__ */ new Set();
4914
+ for (const rootTriggerId of Array.from(triggerById.keys()).sort()) {
4915
+ const required = makeRequiredState(triggerById, [rootTriggerId]);
4916
+ walkFromTrigger({
4917
+ v,
4918
+ triggerById,
4919
+ fieldTriggers,
4920
+ revealTargetsByTrigger,
4921
+ rootTriggerId,
4922
+ currentTriggerId: rootTriggerId,
4923
+ required,
4924
+ path: [rootTriggerId],
4925
+ visited: /* @__PURE__ */ new Set(),
4926
+ reported,
4927
+ depth: 0
4928
+ });
4929
+ }
4930
+ }
4931
+ function buildTriggerIndex(fields) {
4932
+ const out = /* @__PURE__ */ new Map();
4933
+ const owners = optionOwnerMap(fields);
4934
+ for (const field of fields) {
4935
+ if (field.button === true) {
4936
+ out.set(field.id, {
4937
+ kind: "field",
4938
+ id: field.id,
4939
+ ownerFieldId: field.id
4940
+ });
4941
+ }
4942
+ }
4943
+ for (const [optionId, owner] of owners) {
4944
+ out.set(optionId, {
4945
+ kind: "option",
4946
+ id: optionId,
4947
+ ownerFieldId: owner.fieldId
4948
+ });
4949
+ }
4950
+ return out;
4951
+ }
4952
+ function buildFieldTriggerIndex(fields) {
4953
+ const out = /* @__PURE__ */ new Map();
4954
+ for (const field of fields) {
4955
+ const triggers = [];
4956
+ if (field.button === true) triggers.push(field.id);
4957
+ for (const visit of walkFieldOptions(field)) {
4958
+ triggers.push(visit.optionId);
4959
+ }
4960
+ out.set(field.id, triggers);
4961
+ }
4962
+ return out;
4963
+ }
4964
+ function buildRevealIndex(v, triggerById) {
4965
+ var _a, _b;
4966
+ const out = /* @__PURE__ */ new Map();
4967
+ const addReveal = (triggerId, targetFieldId) => {
4968
+ var _a2;
4969
+ if (!triggerById.has(triggerId)) return;
4970
+ if (!v.fieldById.has(targetFieldId)) return;
4971
+ const set = (_a2 = out.get(triggerId)) != null ? _a2 : /* @__PURE__ */ new Set();
4972
+ set.add(targetFieldId);
4973
+ out.set(triggerId, set);
4974
+ };
4975
+ for (const [triggerId, targetIds] of Object.entries(
4976
+ (_a = v.props.includes_for_buttons) != null ? _a : {}
4977
+ )) {
4978
+ for (const targetId of targetIds != null ? targetIds : []) addReveal(triggerId, targetId);
4979
+ }
4980
+ for (const [triggerId, targets] of Object.entries(
4981
+ (_b = v.props.option_effects_for_buttons) != null ? _b : {}
4982
+ )) {
4983
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
4984
+ if ((effect == null ? void 0 : effect.forceVisible) === true)
4985
+ addReveal(triggerId, targetFieldId);
4986
+ }
4987
+ }
4988
+ return new Map(
4989
+ Array.from(out.entries()).map(([triggerId, fieldIds]) => [
4990
+ triggerId,
4991
+ Array.from(fieldIds).sort()
4992
+ ])
4993
+ );
4994
+ }
4995
+ function walkFromTrigger(args) {
4996
+ var _a, _b, _c;
4997
+ if (args.depth >= MAX_VISIBILITY_CYCLE_DEPTH) return;
4998
+ const visitedKey = `${args.rootTriggerId}::${args.currentTriggerId}::${args.path.join(">")}`;
4999
+ if (args.visited.has(visitedKey)) return;
5000
+ args.visited.add(visitedKey);
5001
+ const revealedFieldIds = (_a = args.revealTargetsByTrigger.get(args.currentTriggerId)) != null ? _a : [];
5002
+ for (const revealedFieldId of revealedFieldIds) {
5003
+ const reachableTriggers = (_c = (_b = args.fieldTriggers.get(revealedFieldId)) == null ? void 0 : _b.slice().sort()) != null ? _c : [];
5004
+ for (const reachableTriggerId of reachableTriggers) {
5005
+ const invalidation = invalidatesRequiredPath(
5006
+ args.v,
5007
+ args.triggerById,
5008
+ reachableTriggerId,
5009
+ args.required
5010
+ );
5011
+ if (invalidation) {
5012
+ emitCycleError({
5013
+ v: args.v,
5014
+ rootTriggerId: args.rootTriggerId,
5015
+ revealedFieldId,
5016
+ conflictingTriggerId: reachableTriggerId,
5017
+ invalidatedId: invalidation.invalidatedId,
5018
+ path: [...args.path, reachableTriggerId],
5019
+ reported: args.reported
5020
+ });
4727
5021
  }
5022
+ if (args.path.includes(reachableTriggerId)) continue;
5023
+ walkFromTrigger({
5024
+ ...args,
5025
+ currentTriggerId: reachableTriggerId,
5026
+ required: addRequiredTrigger(
5027
+ args.triggerById,
5028
+ args.required,
5029
+ reachableTriggerId
5030
+ ),
5031
+ path: [...args.path, reachableTriggerId],
5032
+ depth: args.depth + 1
5033
+ });
4728
5034
  }
4729
- if (fieldId) {
4730
- const fieldRef = nodeMap.get(fieldId);
4731
- if ((fieldRef == null ? void 0 : fieldRef.kind) === "field") {
4732
- return { nodeId: fieldRef.id, nodeKind: "field" };
5035
+ }
5036
+ }
5037
+ function makeRequiredState(triggerById, triggerIds) {
5038
+ let required = {
5039
+ triggers: /* @__PURE__ */ new Set(),
5040
+ ownerFields: /* @__PURE__ */ new Set()
5041
+ };
5042
+ for (const triggerId of triggerIds) {
5043
+ required = addRequiredTrigger(triggerById, required, triggerId);
5044
+ }
5045
+ return required;
5046
+ }
5047
+ function addRequiredTrigger(triggerById, current, triggerId) {
5048
+ const next = {
5049
+ triggers: new Set(current.triggers),
5050
+ ownerFields: new Set(current.ownerFields)
5051
+ };
5052
+ const trigger = triggerById.get(triggerId);
5053
+ if (!trigger) return next;
5054
+ next.triggers.add(triggerId);
5055
+ next.ownerFields.add(trigger.ownerFieldId);
5056
+ return next;
5057
+ }
5058
+ function invalidatesRequiredPath(v, triggerById, conflictingTriggerId, required) {
5059
+ var _a, _b, _c, _d, _e, _f;
5060
+ for (const targetId of (_b = (_a = v.props.excludes_for_buttons) == null ? void 0 : _a[conflictingTriggerId]) != null ? _b : []) {
5061
+ if (required.ownerFields.has(targetId)) {
5062
+ return { invalidatedId: targetId };
5063
+ }
5064
+ const targetTrigger = triggerById.get(targetId);
5065
+ if ((targetTrigger == null ? void 0 : targetTrigger.kind) === "option" && required.triggers.has(targetId)) {
5066
+ return { invalidatedId: targetId };
5067
+ }
5068
+ }
5069
+ const effects = (_d = (_c = v.props.option_effects_for_buttons) == null ? void 0 : _c[conflictingTriggerId]) != null ? _d : {};
5070
+ for (const [targetFieldId, effect] of Object.entries(effects)) {
5071
+ if (!v.fieldById.has(targetFieldId)) continue;
5072
+ if ((_e = effect == null ? void 0 : effect.exclude) == null ? void 0 : _e.length) {
5073
+ const excluded = new Set(effect.exclude);
5074
+ for (const requiredTriggerId of required.triggers) {
5075
+ const requiredTrigger = triggerById.get(requiredTriggerId);
5076
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
5077
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
5078
+ if (excluded.has(requiredTriggerId)) {
5079
+ return { invalidatedId: requiredTriggerId };
5080
+ }
5081
+ }
5082
+ }
5083
+ if ((_f = effect == null ? void 0 : effect.include) == null ? void 0 : _f.length) {
5084
+ const included = new Set(effect.include);
5085
+ for (const requiredTriggerId of required.triggers) {
5086
+ const requiredTrigger = triggerById.get(requiredTriggerId);
5087
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
5088
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
5089
+ if (!included.has(requiredTriggerId)) {
5090
+ return { invalidatedId: requiredTriggerId };
5091
+ }
4733
5092
  }
4734
5093
  }
4735
- return void 0;
4736
5094
  }
5095
+ return void 0;
5096
+ }
5097
+ function emitCycleError(args) {
5098
+ const key = [
5099
+ args.rootTriggerId,
5100
+ args.conflictingTriggerId,
5101
+ args.invalidatedId,
5102
+ args.path.join(">")
5103
+ ].join("::");
5104
+ if (args.reported.has(key)) return;
5105
+ args.reported.add(key);
5106
+ args.v.errors.push({
5107
+ code: "visibility_dependency_cycle",
5108
+ severity: "error",
5109
+ message: `Visibility dependency cycle: trigger "${args.rootTriggerId}" reveals "${args.revealedFieldId}", but reachable trigger "${args.conflictingTriggerId}" can hide or remove "${args.invalidatedId}".`,
5110
+ nodeId: args.conflictingTriggerId,
5111
+ details: withAffected(
5112
+ {
5113
+ rootTriggerId: args.rootTriggerId,
5114
+ conflictingTriggerId: args.conflictingTriggerId,
5115
+ invalidatedId: args.invalidatedId,
5116
+ path: args.path
5117
+ },
5118
+ [
5119
+ args.rootTriggerId,
5120
+ args.revealedFieldId,
5121
+ args.conflictingTriggerId,
5122
+ args.invalidatedId
5123
+ ]
5124
+ )
5125
+ });
5126
+ }
5127
+
5128
+ // src/utils/order-kind.ts
5129
+ function normalizeSelectedTriggerKey(key, nodeMap) {
5130
+ if (!key) return void 0;
4737
5131
  const ref = nodeMap.get(key);
4738
5132
  if (!ref) return void 0;
4739
5133
  if (ref.kind !== "field" && ref.kind !== "option") return void 0;
@@ -4892,8 +5286,7 @@ function validateUtilityMarkers(v) {
4892
5286
  "percent"
4893
5287
  ]);
4894
5288
  for (const f of v.fields) {
4895
- const optsArr = Array.isArray(f.options) ? f.options : [];
4896
- for (const o of optsArr) {
5289
+ for (const { option: o } of walkFieldOptions(f)) {
4897
5290
  const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
4898
5291
  const hasService = isServiceIdRef(o.service_id);
4899
5292
  const util = (_c = o.meta) == null ? void 0 : _c.utility;
@@ -5115,13 +5508,13 @@ function normalizeServiceRef(value) {
5115
5508
 
5116
5509
  // src/core/validate/steps/rates.ts
5117
5510
  function validateRates(v) {
5118
- var _a, _b, _c;
5511
+ var _a, _b;
5119
5512
  const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
5120
5513
  for (const f of v.fields) {
5121
5514
  if (!isMultiField(f)) continue;
5122
5515
  const baseRates = [];
5123
- for (const o of (_a = f.options) != null ? _a : []) {
5124
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
5516
+ for (const { option: o } of walkFieldOptions(f)) {
5517
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
5125
5518
  if (role !== "base") continue;
5126
5519
  const sid = o.service_id;
5127
5520
  if (!isServiceIdRef(sid)) continue;
@@ -5532,7 +5925,7 @@ function effectiveConstraints(v, tagId) {
5532
5925
  return out;
5533
5926
  }
5534
5927
  function validateConstraints(v) {
5535
- var _a, _b;
5928
+ var _a;
5536
5929
  for (const t of v.tags) {
5537
5930
  const eff = effectiveConstraints(v, t.id);
5538
5931
  const hasAnyRequired = Object.values(eff).some(
@@ -5541,7 +5934,7 @@ function validateConstraints(v) {
5541
5934
  if (!hasAnyRequired) continue;
5542
5935
  const visible = v.fieldsVisibleUnder(t.id);
5543
5936
  for (const f of visible) {
5544
- for (const o of (_a = f.options) != null ? _a : []) {
5937
+ for (const { option: o } of walkFieldOptions(f)) {
5545
5938
  if (!isServiceIdRef(o.service_id)) continue;
5546
5939
  const svc = getServiceCapability(v.serviceMap, o.service_id);
5547
5940
  if (!svc || typeof svc !== "object") continue;
@@ -5595,7 +5988,7 @@ function validateConstraints(v) {
5595
5988
  if (!row) continue;
5596
5989
  const from = row.from === true;
5597
5990
  const to = row.to === true;
5598
- const origin = String((_b = row.origin) != null ? _b : "");
5991
+ const origin = String((_a = row.origin) != null ? _a : "");
5599
5992
  v.errors.push({
5600
5993
  code: "constraint_overridden",
5601
5994
  severity: "warning",
@@ -5629,14 +6022,14 @@ function validateCustomFields(v) {
5629
6022
 
5630
6023
  // src/core/validate/steps/global-utility-guard.ts
5631
6024
  function validateGlobalUtilityGuard(v) {
5632
- var _a, _b, _c;
6025
+ var _a, _b;
5633
6026
  if (!v.options.globalUtilityGuard) return;
5634
6027
  let hasUtility = false;
5635
6028
  let hasBase = false;
5636
6029
  for (const f of v.fields) {
5637
- for (const o of (_a = f.options) != null ? _a : []) {
6030
+ for (const { option: o } of walkFieldOptions(f)) {
5638
6031
  if (!isServiceIdRef(o.service_id)) continue;
5639
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
6032
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
5640
6033
  if (role === "base") hasBase = true;
5641
6034
  else if (role === "utility") hasUtility = true;
5642
6035
  if (hasUtility && hasBase) break;
@@ -5838,7 +6231,7 @@ function applyFilterAllowLists(tagId, fieldId, filter) {
5838
6231
  return true;
5839
6232
  }
5840
6233
  function collectServiceItems(args) {
5841
- var _a, _b, _c, _d, _e;
6234
+ var _a, _b, _c, _d;
5842
6235
  const filter = args.filter;
5843
6236
  const roleFilter = (_a = filter == null ? void 0 : filter.role) != null ? _a : "both";
5844
6237
  const where = filter == null ? void 0 : filter.where;
@@ -5888,7 +6281,7 @@ function collectServiceItems(args) {
5888
6281
  affectedIds: [`field:${f.id}`, `service:${String(fSid)}`]
5889
6282
  });
5890
6283
  }
5891
- for (const o of (_d = f.options) != null ? _d : []) {
6284
+ for (const { option: o } of walkFieldOptions(f)) {
5892
6285
  const oSid = o.service_id;
5893
6286
  if (!isServiceIdRef2(oSid)) continue;
5894
6287
  const role = fieldRoleOf(f, o);
@@ -5973,7 +6366,7 @@ function collectServiceItems(args) {
5973
6366
  }
5974
6367
  } else if (includeGroupFallbacks) {
5975
6368
  const allowPrimaries = new Set(
5976
- ((_e = args.visiblePrimaries) != null ? _e : []).map((x) => String(x))
6369
+ ((_d = args.visiblePrimaries) != null ? _d : []).map((x) => String(x))
5977
6370
  );
5978
6371
  for (const primaryKey of allowPrimaries) {
5979
6372
  const list = globalFb[primaryKey];
@@ -6054,17 +6447,15 @@ function affectedFromItems(items) {
6054
6447
  return uniq(ids);
6055
6448
  }
6056
6449
  function visibleGroupNodeIds(tag, fields) {
6057
- var _a;
6058
6450
  const ids = [tag.id];
6059
6451
  for (const f of fields) {
6060
- for (const o of (_a = f.options) != null ? _a : []) {
6452
+ for (const { option: o } of walkFieldOptions(f)) {
6061
6453
  ids.push(o.id);
6062
6454
  }
6063
6455
  }
6064
6456
  return uniq(ids);
6065
6457
  }
6066
6458
  function visibleGroupPrimaries(tag, fields) {
6067
- var _a;
6068
6459
  const prim = [];
6069
6460
  const tagSid = tag.service_id;
6070
6461
  if (typeof tagSid === "string" || typeof tagSid === "number" && Number.isFinite(tagSid)) {
@@ -6075,7 +6466,7 @@ function visibleGroupPrimaries(tag, fields) {
6075
6466
  if (typeof fsid === "string" || typeof fsid === "number" && Number.isFinite(fsid)) {
6076
6467
  prim.push(fsid);
6077
6468
  }
6078
- for (const o of (_a = f.options) != null ? _a : []) {
6469
+ for (const { option: o } of walkFieldOptions(f)) {
6079
6470
  const osid = o.service_id;
6080
6471
  if (typeof osid === "string" || typeof osid === "number" && Number.isFinite(osid)) {
6081
6472
  prim.push(osid);
@@ -6299,6 +6690,7 @@ function validate(props, ctx = {}) {
6299
6690
  validateStructure(v);
6300
6691
  validateIdentity(v);
6301
6692
  validateOptionMaps(v);
6693
+ validateVisibilityCycles(v);
6302
6694
  validateOrderKinds(v);
6303
6695
  v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
6304
6696
  const visSim = readVisibilitySimOpts(options);
@@ -6432,14 +6824,14 @@ var BuilderImpl = class {
6432
6824
  const showOptions = showSet.has(f.id);
6433
6825
  if (!showOptions) continue;
6434
6826
  if (!Array.isArray(f.options)) continue;
6435
- for (const o of f.options) {
6827
+ for (const { option: o, parentId } of walkFieldOptions(f)) {
6436
6828
  nodes.push({
6437
6829
  id: o.id,
6438
6830
  kind: "option",
6439
6831
  label: o.label
6440
6832
  });
6441
6833
  const e = {
6442
- from: f.id,
6834
+ from: parentId != null ? parentId : f.id,
6443
6835
  to: o.id,
6444
6836
  kind: "option",
6445
6837
  meta: { ownerField: f.id }
@@ -6486,7 +6878,7 @@ var BuilderImpl = class {
6486
6878
  return { nodes, edges };
6487
6879
  }
6488
6880
  cleanedProps() {
6489
- var _a, _b, _c, _d, _e;
6881
+ var _a, _b, _c, _d, _e, _f;
6490
6882
  const fieldIds = new Set(this.props.fields.map((f) => f.id));
6491
6883
  const optionIds = /* @__PURE__ */ new Set();
6492
6884
  this.optionOwnerById.forEach((_v, oid) => optionIds.add(oid));
@@ -6498,6 +6890,7 @@ var BuilderImpl = class {
6498
6890
  }
6499
6891
  const incMap = (_c = this.props.includes_for_buttons) != null ? _c : {};
6500
6892
  const excMap = (_d = this.props.excludes_for_buttons) != null ? _d : {};
6893
+ const effectMap = (_e = this.props.option_effects_for_buttons) != null ? _e : {};
6501
6894
  const includedByButtons = /* @__PURE__ */ new Set();
6502
6895
  const referencedKeys = /* @__PURE__ */ new Set();
6503
6896
  const referencedOwnerFields = /* @__PURE__ */ new Set();
@@ -6517,6 +6910,14 @@ var BuilderImpl = class {
6517
6910
  void fid;
6518
6911
  }
6519
6912
  }
6913
+ for (const [key, targets] of Object.entries(effectMap)) {
6914
+ referencedKeys.add(key);
6915
+ const owner = this.optionOwnerById.get(key);
6916
+ if (owner) referencedOwnerFields.add(owner.fieldId);
6917
+ for (const [fid, effect] of Object.entries(targets != null ? targets : {})) {
6918
+ if ((effect == null ? void 0 : effect.forceVisible) === true) includedByButtons.add(fid);
6919
+ }
6920
+ }
6520
6921
  const boundIds = /* @__PURE__ */ new Set();
6521
6922
  for (const f of this.props.fields) {
6522
6923
  const b = f.bind_id;
@@ -6534,6 +6935,7 @@ var BuilderImpl = class {
6534
6935
  return bound || included || referenced || !excluded;
6535
6936
  });
6536
6937
  const allowedTargets = new Set(fields.map((f) => f.id));
6938
+ const allowedFieldById = new Map(fields.map((f) => [f.id, f]));
6537
6939
  const pruneButtons = (src) => {
6538
6940
  if (!src) return void 0;
6539
6941
  const out2 = {};
@@ -6553,13 +6955,52 @@ var BuilderImpl = class {
6553
6955
  const excludes_for_buttons = pruneButtons(
6554
6956
  this.props.excludes_for_buttons
6555
6957
  );
6958
+ const pruneOptionEffects = (src) => {
6959
+ var _a2, _b2, _c2, _d2;
6960
+ if (!src) return void 0;
6961
+ const out2 = {};
6962
+ for (const [key, targets] of Object.entries(src)) {
6963
+ const keyIsValid = optionIds.has(key) || fieldIds.has(key);
6964
+ if (!keyIsValid) continue;
6965
+ const cleanedTargets = {};
6966
+ for (const [targetFieldId, effect] of Object.entries(
6967
+ targets != null ? targets : {}
6968
+ )) {
6969
+ const field = allowedFieldById.get(targetFieldId);
6970
+ if (!field || !effect) continue;
6971
+ const validOptionIds = fieldOptionIdSet(field);
6972
+ const include2 = Array.from(
6973
+ new Set((_a2 = effect.include) != null ? _a2 : [])
6974
+ ).filter((optionId) => validOptionIds.has(optionId));
6975
+ const exclude2 = Array.from(
6976
+ new Set((_b2 = effect.exclude) != null ? _b2 : [])
6977
+ ).filter((optionId) => validOptionIds.has(optionId));
6978
+ const next = {
6979
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
6980
+ ...include2.length ? { include: include2 } : {},
6981
+ ...exclude2.length ? { exclude: exclude2 } : {}
6982
+ };
6983
+ if (next.forceVisible === true || ((_c2 = next.include) == null ? void 0 : _c2.length) || ((_d2 = next.exclude) == null ? void 0 : _d2.length)) {
6984
+ cleanedTargets[targetFieldId] = next;
6985
+ }
6986
+ }
6987
+ if (Object.keys(cleanedTargets).length) {
6988
+ out2[key] = cleanedTargets;
6989
+ }
6990
+ }
6991
+ return Object.keys(out2).length ? out2 : void 0;
6992
+ };
6993
+ const option_effects_for_buttons = pruneOptionEffects(
6994
+ this.props.option_effects_for_buttons
6995
+ );
6556
6996
  const out = {
6557
6997
  filters: this.props.filters.slice(),
6558
6998
  fields,
6559
6999
  ...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
6560
7000
  ...includes_for_buttons && { includes_for_buttons },
6561
7001
  ...excludes_for_buttons && { excludes_for_buttons },
6562
- schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
7002
+ ...option_effects_for_buttons && { option_effects_for_buttons },
7003
+ schema_version: (_f = this.props.schema_version) != null ? _f : "1.0",
6563
7004
  // keep fallbacks & other maps as-is
6564
7005
  ...this.props.fallbacks ? { fallbacks: this.props.fallbacks } : {}
6565
7006
  };
@@ -6572,12 +7013,15 @@ var BuilderImpl = class {
6572
7013
  return (0, import_lodash_es2.cloneDeep)(this.options);
6573
7014
  }
6574
7015
  visibleFields(tagId, selectedKeys) {
7016
+ return this.resolveVisibility(tagId, selectedKeys).fieldIds;
7017
+ }
7018
+ resolveVisibility(tagId, selectedKeys) {
6575
7019
  var _a;
6576
- return visibleFieldIdsUnder(this.props, tagId, {
6577
- selectedKeys: new Set(
6578
- (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
6579
- )
6580
- });
7020
+ return resolveVisibility(
7021
+ this.props,
7022
+ tagId,
7023
+ (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
7024
+ );
6581
7025
  }
6582
7026
  getNodeMap() {
6583
7027
  if (!this._nodemap) this._nodemap = buildNodeMap(this.getProps());
@@ -6592,9 +7036,8 @@ var BuilderImpl = class {
6592
7036
  for (const t of this.props.filters) this.tagById.set(t.id, t);
6593
7037
  for (const f of this.props.fields) {
6594
7038
  this.fieldById.set(f.id, f);
6595
- if (Array.isArray(f.options)) {
6596
- for (const o of f.options)
6597
- this.optionOwnerById.set(o.id, { fieldId: f.id });
7039
+ for (const [optionId, owner] of optionOwnerMap([f])) {
7040
+ this.optionOwnerById.set(optionId, { fieldId: owner.fieldId });
6598
7041
  }
6599
7042
  }
6600
7043
  }
@@ -7466,6 +7909,11 @@ function rateIssueAffectsCandidate(error, candidateId, candidateFieldId, primary
7466
7909
  });
7467
7910
  }
7468
7911
 
7912
+ // src/react/inputs/registry.ts
7913
+ function resolveInputDescriptor(registry, kind, variant) {
7914
+ return registry.get(kind, variant);
7915
+ }
7916
+
7469
7917
  // src/react/canvas/editor/editor-ids.ts
7470
7918
  function uniqueId(ctx, base) {
7471
7919
  var _a, _b;
@@ -7537,42 +7985,133 @@ function bumpSuffix(old) {
7537
7985
  return `${stem}${parseInt(m[2], 10) + 1}`;
7538
7986
  }
7539
7987
 
7540
- // src/react/canvas/editor/editor-duplicate.ts
7541
- function duplicate(ctx, ref, opts = {}) {
7542
- const snapBefore = ctx.makeSnapshot("duplicate:before");
7543
- try {
7544
- let newId2 = "";
7545
- ctx.transact("duplicate", () => {
7546
- newId2 = duplicateInPlace(ctx, ref, opts);
7547
- });
7548
- return newId2;
7549
- } catch (err) {
7550
- ctx.loadSnapshot(snapBefore, "undo");
7551
- throw err;
7988
+ // src/react/canvas/editor/editor-utils.ts
7989
+ function ownerOfOption(props, optionId) {
7990
+ var _a;
7991
+ for (const f of (_a = props.fields) != null ? _a : []) {
7992
+ const found = findOptionLocationInField(f, optionId);
7993
+ if (found) return { fieldId: f.id, index: found.index };
7552
7994
  }
7995
+ return null;
7553
7996
  }
7554
- function duplicateMany(ctx, ids, opts = {}) {
7555
- const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
7556
- if (!ordered.length) return [];
7557
- const snapBefore = ctx.makeSnapshot("duplicateMany:before");
7558
- try {
7559
- const created = [];
7560
- ctx.transact("duplicateMany", () => {
7561
- var _a, _b, _c;
7562
- const props = ctx.getProps();
7563
- const selectedFields = /* @__PURE__ */ new Set();
7564
- for (const id of ordered) {
7565
- if (ctx.isFieldId(id) && ((_a = props.fields) != null ? _a : []).some((f) => f.id === id)) {
7566
- selectedFields.add(id);
7567
- }
7568
- }
7569
- for (const id of ordered) {
7570
- if (ctx.isTagId(id)) {
7571
- if (!((_b = ctx.getProps().filters) != null ? _b : []).some((t) => t.id === id)) continue;
7572
- created.push(
7573
- duplicateInPlace(ctx, { kind: "tag", id }, opts)
7574
- );
7575
- continue;
7997
+ function findMutableOption(props, optionId) {
7998
+ var _a;
7999
+ for (const field of (_a = props.fields) != null ? _a : []) {
8000
+ const found = findOptionLocationInField(field, optionId);
8001
+ if (found) return { field, ...found };
8002
+ }
8003
+ return void 0;
8004
+ }
8005
+ function collectFieldOptionIds(field) {
8006
+ const out = [];
8007
+ const visit = (options) => {
8008
+ for (const option of options != null ? options : []) {
8009
+ out.push(String(option.id));
8010
+ visit(option.children);
8011
+ }
8012
+ };
8013
+ visit(field == null ? void 0 : field.options);
8014
+ return out;
8015
+ }
8016
+ function findOptionLocationInField(field, optionId) {
8017
+ const visit = (siblings, parent) => {
8018
+ if (!siblings) return void 0;
8019
+ const index = siblings.findIndex((option) => option.id === optionId);
8020
+ if (index >= 0) {
8021
+ return {
8022
+ option: siblings[index],
8023
+ siblings,
8024
+ index,
8025
+ parent
8026
+ };
8027
+ }
8028
+ for (const option of siblings) {
8029
+ const found = visit(option.children, option);
8030
+ if (found) return found;
8031
+ }
8032
+ return void 0;
8033
+ };
8034
+ return visit(field.options);
8035
+ }
8036
+ function hasFieldOptions(field) {
8037
+ return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
8038
+ }
8039
+ function isActualButtonField(field) {
8040
+ return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
8041
+ }
8042
+ function clearFieldButtonReceiverMaps(props, fieldId) {
8043
+ var _a, _b, _c;
8044
+ if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
8045
+ delete props.includes_for_buttons[fieldId];
8046
+ }
8047
+ if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
8048
+ delete props.excludes_for_buttons[fieldId];
8049
+ }
8050
+ if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
8051
+ delete props.includes_for_buttons;
8052
+ }
8053
+ if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
8054
+ delete props.excludes_for_buttons;
8055
+ }
8056
+ if ((_c = props.option_effects_for_buttons) == null ? void 0 : _c[fieldId]) {
8057
+ delete props.option_effects_for_buttons[fieldId];
8058
+ }
8059
+ if (props.option_effects_for_buttons && Object.keys(props.option_effects_for_buttons).length === 0) {
8060
+ delete props.option_effects_for_buttons;
8061
+ }
8062
+ }
8063
+ function ensureServiceExists(opts, id) {
8064
+ if (typeof opts.serviceExists === "function") {
8065
+ if (!opts.serviceExists(id)) {
8066
+ throw new Error(`service_not_found:${String(id)}`);
8067
+ }
8068
+ return;
8069
+ }
8070
+ if (opts.serviceMap) {
8071
+ if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
8072
+ throw new Error(`service_not_found:${String(id)}`);
8073
+ }
8074
+ return;
8075
+ }
8076
+ throw new Error("service_checker_missing");
8077
+ }
8078
+
8079
+ // src/react/canvas/editor/editor-duplicate.ts
8080
+ function duplicate(ctx, ref, opts = {}) {
8081
+ const snapBefore = ctx.makeSnapshot("duplicate:before");
8082
+ try {
8083
+ let newId2 = "";
8084
+ ctx.transact("duplicate", () => {
8085
+ newId2 = duplicateInPlace(ctx, ref, opts);
8086
+ });
8087
+ return newId2;
8088
+ } catch (err) {
8089
+ ctx.loadSnapshot(snapBefore, "undo");
8090
+ throw err;
8091
+ }
8092
+ }
8093
+ function duplicateMany(ctx, ids, opts = {}) {
8094
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
8095
+ if (!ordered.length) return [];
8096
+ const snapBefore = ctx.makeSnapshot("duplicateMany:before");
8097
+ try {
8098
+ const created = [];
8099
+ ctx.transact("duplicateMany", () => {
8100
+ var _a, _b, _c;
8101
+ const props = ctx.getProps();
8102
+ const selectedFields = /* @__PURE__ */ new Set();
8103
+ for (const id of ordered) {
8104
+ if (ctx.isFieldId(id) && ((_a = props.fields) != null ? _a : []).some((f) => f.id === id)) {
8105
+ selectedFields.add(id);
8106
+ }
8107
+ }
8108
+ for (const id of ordered) {
8109
+ if (ctx.isTagId(id)) {
8110
+ if (!((_b = ctx.getProps().filters) != null ? _b : []).some((t) => t.id === id)) continue;
8111
+ created.push(
8112
+ duplicateInPlace(ctx, { kind: "tag", id }, opts)
8113
+ );
8114
+ continue;
7576
8115
  }
7577
8116
  if (ctx.isFieldId(id)) {
7578
8117
  if (!((_c = ctx.getProps().fields) != null ? _c : []).some((f) => f.id === id)) continue;
@@ -7611,14 +8150,66 @@ function duplicateInPlace(ctx, ref, opts = {}) {
7611
8150
  return duplicateOption(ctx, ref.fieldId, ref.id, opts);
7612
8151
  }
7613
8152
  function ownerFieldOfOption(props, optionId) {
7614
- var _a, _b;
8153
+ var _a;
7615
8154
  for (const field of (_a = props.fields) != null ? _a : []) {
7616
- if (((_b = field.options) != null ? _b : []).some((o) => o.id === optionId)) {
8155
+ if (findMutableOption({ ...props, fields: [field] }, optionId)) {
7617
8156
  return { fieldId: field.id };
7618
8157
  }
7619
8158
  }
7620
8159
  return null;
7621
8160
  }
8161
+ function cloneOptionTree(ctx, fieldId, option, opts, optionIdMap) {
8162
+ var _a, _b, _c, _d;
8163
+ const newId2 = ctx.uniqueOptionId(
8164
+ fieldId,
8165
+ ((_a = opts.optionIdStrategy) != null ? _a : defaultOptionIdStrategy)(option.id)
8166
+ );
8167
+ optionIdMap.set(option.id, newId2);
8168
+ const children = (_b = option.children) == null ? void 0 : _b.map(
8169
+ (child) => cloneOptionTree(ctx, fieldId, child, opts, optionIdMap)
8170
+ );
8171
+ return {
8172
+ ...option,
8173
+ id: newId2,
8174
+ label: ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = option.label) != null ? _d : option.id),
8175
+ ...(children == null ? void 0 : children.length) ? { children } : {}
8176
+ };
8177
+ }
8178
+ function remapEffect(effect, optionIdMap) {
8179
+ const remapList = (values) => values == null ? void 0 : values.map((value) => {
8180
+ var _a;
8181
+ return (_a = optionIdMap.get(value)) != null ? _a : value;
8182
+ });
8183
+ return {
8184
+ ...effect,
8185
+ ...effect.include ? { include: remapList(effect.include) } : {},
8186
+ ...effect.exclude ? { exclude: remapList(effect.exclude) } : {}
8187
+ };
8188
+ }
8189
+ function copyOptionEffects(props, args) {
8190
+ var _a, _b, _c, _d, _e;
8191
+ const source = props.option_effects_for_buttons;
8192
+ if (!source) return;
8193
+ const next = {
8194
+ ...source
8195
+ };
8196
+ const triggerIdMap = (_a = args.triggerIdMap) != null ? _a : /* @__PURE__ */ new Map();
8197
+ const targetFieldIdMap = (_b = args.targetFieldIdMap) != null ? _b : /* @__PURE__ */ new Map();
8198
+ const optionIdMap = (_c = args.optionIdMap) != null ? _c : /* @__PURE__ */ new Map();
8199
+ for (const [oldTriggerId, targetMap] of Object.entries(source)) {
8200
+ const newTriggerId = triggerIdMap.get(oldTriggerId);
8201
+ if (!newTriggerId) continue;
8202
+ const copiedTargets = {
8203
+ ...(_d = next[newTriggerId]) != null ? _d : {}
8204
+ };
8205
+ for (const [oldTargetFieldId, effect] of Object.entries(targetMap != null ? targetMap : {})) {
8206
+ const newTargetFieldId = (_e = targetFieldIdMap.get(oldTargetFieldId)) != null ? _e : oldTargetFieldId;
8207
+ copiedTargets[newTargetFieldId] = remapEffect(effect, optionIdMap);
8208
+ }
8209
+ next[newTriggerId] = copiedTargets;
8210
+ }
8211
+ props.option_effects_for_buttons = next;
8212
+ }
7622
8213
  function duplicateTag(ctx, tagId, opts) {
7623
8214
  var _a, _b, _c, _d;
7624
8215
  const props = ctx.getProps();
@@ -7674,7 +8265,7 @@ function duplicateTag(ctx, tagId, opts) {
7674
8265
  return id;
7675
8266
  }
7676
8267
  function duplicateField(ctx, fieldId, opts) {
7677
- var _a, _b, _c, _d, _e, _f, _g;
8268
+ var _a, _b, _c, _d, _e, _f;
7678
8269
  const props = ctx.getProps();
7679
8270
  const fields = (_a = props.fields) != null ? _a : [];
7680
8271
  const src = fields.find((f) => f.id === fieldId);
@@ -7682,21 +8273,10 @@ function duplicateField(ctx, fieldId, opts) {
7682
8273
  const id = (_b = opts.id) != null ? _b : ctx.uniqueId(src.id);
7683
8274
  const label = ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = src.label) != null ? _d : id);
7684
8275
  const name = opts.nameStrategy ? opts.nameStrategy(src.name) : nextCopyName(src.name);
7685
- const optId = (old) => {
7686
- var _a2;
7687
- return ctx.uniqueOptionId(
7688
- id,
7689
- ((_a2 = opts.optionIdStrategy) != null ? _a2 : defaultOptionIdStrategy)(old)
7690
- );
7691
- };
7692
- const clonedOptions = ((_e = src.options) != null ? _e : []).map((o) => {
7693
- var _a2, _b2;
7694
- return {
7695
- ...o,
7696
- id: optId(o.id),
7697
- label: ((_a2 = opts.labelStrategy) != null ? _a2 : nextCopyLabel)((_b2 = o.label) != null ? _b2 : o.id)
7698
- };
7699
- });
8276
+ const optionIdMap = /* @__PURE__ */ new Map();
8277
+ const clonedOptions = ((_e = src.options) != null ? _e : []).map(
8278
+ (o) => cloneOptionTree(ctx, id, o, opts, optionIdMap)
8279
+ );
7700
8280
  const cloned = {
7701
8281
  ...src,
7702
8282
  id,
@@ -7705,14 +8285,8 @@ function duplicateField(ctx, fieldId, opts) {
7705
8285
  bind_id: ((_f = opts.copyBindings) != null ? _f : true) ? src.bind_id : void 0,
7706
8286
  options: clonedOptions
7707
8287
  };
7708
- const optionIdMap = /* @__PURE__ */ new Map();
7709
- ((_g = src.options) != null ? _g : []).forEach((o, i) => {
7710
- var _a2, _b2;
7711
- const newOptId = (_b2 = (_a2 = clonedOptions[i]) == null ? void 0 : _a2.id) != null ? _b2 : o.id;
7712
- optionIdMap.set(o.id, newOptId);
7713
- });
7714
8288
  ctx.patchProps((p) => {
7715
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
8289
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g;
7716
8290
  const arr = (_a2 = p.fields) != null ? _a2 : [];
7717
8291
  const idx = arr.findIndex((f) => f.id === fieldId);
7718
8292
  arr.splice(idx + 1, 0, cloned);
@@ -7750,52 +8324,56 @@ function duplicateField(ctx, fieldId, opts) {
7750
8324
  }
7751
8325
  if (optionIdMap.has(key)) {
7752
8326
  const newKey = optionIdMap.get(key);
7753
- const merged = /* @__PURE__ */ new Set([...(_g2 = nextMap[newKey]) != null ? _g2 : [], ...targets]);
8327
+ const merged = /* @__PURE__ */ new Set([...(_g = nextMap[newKey]) != null ? _g : [], ...targets]);
7754
8328
  nextMap[newKey] = Array.from(merged);
7755
8329
  }
7756
8330
  }
7757
8331
  p[mapKey] = nextMap;
7758
8332
  }
8333
+ copyOptionEffects(p, {
8334
+ triggerIdMap: new Map([
8335
+ [fieldId, id],
8336
+ ...Array.from(optionIdMap.entries())
8337
+ ]),
8338
+ targetFieldIdMap: /* @__PURE__ */ new Map([[fieldId, id]]),
8339
+ optionIdMap
8340
+ });
7759
8341
  }
7760
8342
  });
7761
8343
  return id;
7762
8344
  }
7763
8345
  function duplicateOption(ctx, fieldId, optionId, opts) {
7764
- var _a, _b, _c, _d, _e, _f;
7765
8346
  const props = ctx.getProps();
7766
- const fields = (_a = props.fields) != null ? _a : [];
7767
- const f = fields.find((x) => x.id === fieldId);
7768
- if (!f) throw new Error(`Field not found: ${fieldId}`);
7769
- const optIdx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
7770
- if (optIdx < 0) {
7771
- throw new Error(`Option not found: ${fieldId}::${optionId}`);
8347
+ const location = findMutableOption(props, optionId);
8348
+ if (!location || location.field.id !== fieldId) {
8349
+ throw new Error(`Option not found: ${fieldId}/${optionId}`);
7772
8350
  }
7773
- const src = ((_c = f.options) != null ? _c : [])[optIdx];
7774
- const newId2 = ctx.uniqueOptionId(
7775
- fieldId,
7776
- ((_d = opts.optionIdStrategy) != null ? _d : defaultOptionIdStrategy)(src.id)
7777
- );
7778
- const newLabel = ((_e = opts.labelStrategy) != null ? _e : nextCopyLabel)((_f = src.label) != null ? _f : src.id);
8351
+ const src = location.option;
8352
+ const optionIdMap = /* @__PURE__ */ new Map();
8353
+ const clone2 = cloneOptionTree(ctx, fieldId, src, opts, optionIdMap);
8354
+ const newId2 = clone2.id;
7779
8355
  ctx.patchProps((p) => {
7780
- var _a2, _b2, _c2;
7781
- const fld = ((_a2 = p.fields) != null ? _a2 : []).find((x) => x.id === fieldId);
7782
- const arr = (_b2 = fld.options) != null ? _b2 : [];
7783
- const clone2 = { ...src, id: newId2, label: newLabel };
7784
- arr.splice(optIdx + 1, 0, clone2);
7785
- fld.options = arr;
8356
+ var _a;
8357
+ const current = findMutableOption(p, optionId);
8358
+ if (!current) return;
8359
+ current.siblings.splice(current.index + 1, 0, clone2);
7786
8360
  if (opts.copyOptionMaps) {
7787
- const oldKey = `${fieldId}::${optionId}`;
7788
- const newKey = `${fieldId}::${newId2}`;
7789
8361
  for (const mapKey of [
7790
8362
  "includes_for_buttons",
7791
8363
  "excludes_for_buttons"
7792
8364
  ]) {
7793
- const m = (_c2 = p[mapKey]) != null ? _c2 : {};
7794
- if (m[oldKey]) {
7795
- m[newKey] = Array.from(new Set(m[oldKey]));
7796
- p[mapKey] = m;
8365
+ const m = (_a = p[mapKey]) != null ? _a : {};
8366
+ for (const [oldKey, newKey] of optionIdMap.entries()) {
8367
+ if (m[oldKey]) {
8368
+ m[newKey] = Array.from(new Set(m[oldKey]));
8369
+ p[mapKey] = m;
8370
+ }
7797
8371
  }
7798
8372
  }
8373
+ copyOptionEffects(p, {
8374
+ triggerIdMap: optionIdMap,
8375
+ optionIdMap
8376
+ });
7799
8377
  }
7800
8378
  });
7801
8379
  return newId2;
@@ -7857,54 +8435,6 @@ function removeNotice(ctx, id) {
7857
8435
 
7858
8436
  // src/react/canvas/editor/editor-nodes.ts
7859
8437
  var import_lodash_es3 = require("lodash-es");
7860
-
7861
- // src/react/canvas/editor/editor-utils.ts
7862
- function ownerOfOption(props, optionId) {
7863
- var _a, _b;
7864
- for (const f of (_a = props.fields) != null ? _a : []) {
7865
- const idx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
7866
- if (idx >= 0) return { fieldId: f.id, index: idx };
7867
- }
7868
- return null;
7869
- }
7870
- function hasFieldOptions(field) {
7871
- return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
7872
- }
7873
- function isActualButtonField(field) {
7874
- return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
7875
- }
7876
- function clearFieldButtonReceiverMaps(props, fieldId) {
7877
- var _a, _b;
7878
- if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
7879
- delete props.includes_for_buttons[fieldId];
7880
- }
7881
- if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
7882
- delete props.excludes_for_buttons[fieldId];
7883
- }
7884
- if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
7885
- delete props.includes_for_buttons;
7886
- }
7887
- if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
7888
- delete props.excludes_for_buttons;
7889
- }
7890
- }
7891
- function ensureServiceExists(opts, id) {
7892
- if (typeof opts.serviceExists === "function") {
7893
- if (!opts.serviceExists(id)) {
7894
- throw new Error(`service_not_found:${String(id)}`);
7895
- }
7896
- return;
7897
- }
7898
- if (opts.serviceMap) {
7899
- if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
7900
- throw new Error(`service_not_found:${String(id)}`);
7901
- }
7902
- return;
7903
- }
7904
- throw new Error("service_checker_missing");
7905
- }
7906
-
7907
- // src/react/canvas/editor/editor-nodes.ts
7908
8438
  var RELATION_MAP_KEYS = [
7909
8439
  "includes_for_buttons",
7910
8440
  "excludes_for_buttons",
@@ -7964,6 +8494,43 @@ function cleanRelationMapsForDeleted(p, deleted) {
7964
8494
  if (!Object.keys(map).length) delete p[key];
7965
8495
  }
7966
8496
  }
8497
+ function cleanOptionEffectsForDeleted(p, deleted) {
8498
+ var _a, _b;
8499
+ const map = p.option_effects_for_buttons;
8500
+ if (!map) return;
8501
+ for (const triggerId of Object.keys(map)) {
8502
+ if (deleted.has(String(triggerId))) {
8503
+ delete map[triggerId];
8504
+ continue;
8505
+ }
8506
+ const targets = map[triggerId];
8507
+ for (const targetFieldId of Object.keys(targets != null ? targets : {})) {
8508
+ if (deleted.has(String(targetFieldId))) {
8509
+ delete targets[targetFieldId];
8510
+ continue;
8511
+ }
8512
+ const effect = targets[targetFieldId];
8513
+ if (!effect) continue;
8514
+ if (effect.include) {
8515
+ effect.include = effect.include.filter(
8516
+ (optionId) => !deleted.has(String(optionId))
8517
+ );
8518
+ if (!effect.include.length) delete effect.include;
8519
+ }
8520
+ if (effect.exclude) {
8521
+ effect.exclude = effect.exclude.filter(
8522
+ (optionId) => !deleted.has(String(optionId))
8523
+ );
8524
+ if (!effect.exclude.length) delete effect.exclude;
8525
+ }
8526
+ if (effect.forceVisible !== true && !((_a = effect.include) == null ? void 0 : _a.length) && !((_b = effect.exclude) == null ? void 0 : _b.length)) {
8527
+ delete targets[targetFieldId];
8528
+ }
8529
+ }
8530
+ if (!Object.keys(targets != null ? targets : {}).length) delete map[triggerId];
8531
+ }
8532
+ if (!Object.keys(map).length) delete p.option_effects_for_buttons;
8533
+ }
7967
8534
  function cleanOrderForTagsForDeleted(p, deleted) {
7968
8535
  var _a, _b;
7969
8536
  const map = p.order_for_tags;
@@ -7999,28 +8566,37 @@ function applyDeleteCleanup(p, deleted) {
7999
8566
  cleanTagRelationsForDeleted(p, deleted);
8000
8567
  cleanFieldBindsForDeleted(p, deleted);
8001
8568
  cleanRelationMapsForDeleted(p, deleted);
8569
+ cleanOptionEffectsForDeleted(p, deleted);
8002
8570
  cleanOrderForTagsForDeleted(p, deleted);
8003
8571
  cleanNoticesForDeleted(p, deleted);
8004
8572
  }
8573
+ function collectOptionSubtreeIds(option) {
8574
+ var _a;
8575
+ return [
8576
+ String(option.id),
8577
+ ...((_a = option.children) != null ? _a : []).flatMap((child) => collectOptionSubtreeIds(child))
8578
+ ];
8579
+ }
8005
8580
  function removeOptionInPlace(p, optionId) {
8006
8581
  var _a;
8007
- const owner = ownerOfOption(p, optionId);
8008
- if (!owner) return false;
8009
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
8010
- if (!(f == null ? void 0 : f.options)) return false;
8011
- const before = f.options.length;
8012
- f.options = f.options.filter((o) => o.id !== optionId);
8013
- return f.options.length !== before;
8582
+ const found = findMutableOption(p, optionId);
8583
+ if (!found) return [];
8584
+ const deleted = collectOptionSubtreeIds(found.option);
8585
+ found.siblings.splice(found.index, 1);
8586
+ if (found.parent && ((_a = found.parent.children) == null ? void 0 : _a.length) === 0) {
8587
+ delete found.parent.children;
8588
+ }
8589
+ return deleted;
8014
8590
  }
8015
8591
  function removeFieldInPlace(p, fieldId) {
8016
- var _a, _b, _c, _d, _e;
8592
+ var _a, _b, _c, _d;
8017
8593
  const field = ((_a = p.fields) != null ? _a : []).find((f) => f.id === fieldId);
8018
8594
  if (!field) return [];
8019
- const deleted = [fieldId, ...((_b = field.options) != null ? _b : []).map((o) => String(o.id))];
8020
- const before = ((_c = p.fields) != null ? _c : []).length;
8021
- p.fields = ((_d = p.fields) != null ? _d : []).filter((f) => f.id !== fieldId);
8595
+ const deleted = [fieldId, ...collectFieldOptionIds(field)];
8596
+ const before = ((_b = p.fields) != null ? _b : []).length;
8597
+ p.fields = ((_c = p.fields) != null ? _c : []).filter((f) => f.id !== fieldId);
8022
8598
  clearFieldButtonReceiverMaps(p, fieldId);
8023
- return ((_e = p.fields) != null ? _e : []).length !== before ? deleted : [];
8599
+ return ((_d = p.fields) != null ? _d : []).length !== before ? deleted : [];
8024
8600
  }
8025
8601
  function removeTagInPlace(p, tagId) {
8026
8602
  var _a, _b, _c;
@@ -8033,7 +8609,7 @@ function reLabel(ctx, id, nextLabel) {
8033
8609
  ctx.exec({
8034
8610
  name: "reLabel",
8035
8611
  do: () => ctx.patchProps((p) => {
8036
- var _a, _b, _c, _d, _e, _f, _g;
8612
+ var _a, _b, _c, _d, _e, _f;
8037
8613
  if (ctx.isTagId(id)) {
8038
8614
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
8039
8615
  if (!t) return;
@@ -8043,19 +8619,16 @@ function reLabel(ctx, id, nextLabel) {
8043
8619
  return;
8044
8620
  }
8045
8621
  if (ctx.isOptionId(id)) {
8046
- const own = ownerOfOption(p, id);
8047
- if (!own) return;
8048
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
8049
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
8622
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
8050
8623
  if (!o) return;
8051
- if (((_e = o.label) != null ? _e : "") === label) return;
8624
+ if (((_d = o.label) != null ? _d : "") === label) return;
8052
8625
  o.label = label;
8053
8626
  ctx.api.refreshGraph();
8054
8627
  return;
8055
8628
  }
8056
- const fld = ((_f = p.fields) != null ? _f : []).find((x) => x.id === id);
8629
+ const fld = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
8057
8630
  if (!fld) return;
8058
- if (((_g = fld.label) != null ? _g : "") === label) return;
8631
+ if (((_f = fld.label) != null ? _f : "") === label) return;
8059
8632
  fld.label = label;
8060
8633
  ctx.api.refreshGraph();
8061
8634
  }),
@@ -8145,11 +8718,7 @@ function updateOption(ctx, optionId, patch) {
8145
8718
  name: "updateOption",
8146
8719
  do: () => ctx.patchProps((p) => {
8147
8720
  var _a;
8148
- const owner = ownerOfOption(p, optionId);
8149
- if (!owner) return;
8150
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
8151
- if (!(f == null ? void 0 : f.options)) return;
8152
- const o = f.options.find((x) => x.id === optionId);
8721
+ const o = (_a = findMutableOption(p, optionId)) == null ? void 0 : _a.option;
8153
8722
  if (o) Object.assign(o, patch);
8154
8723
  }),
8155
8724
  undo: () => ctx.undo()
@@ -8162,9 +8731,9 @@ function removeOption(ctx, optionId) {
8162
8731
  ctx.exec({
8163
8732
  name: "removeOption",
8164
8733
  do: () => ctx.patchProps((p) => {
8165
- const removed = removeOptionInPlace(p, optionId);
8166
- if (!removed) return;
8167
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([optionId]));
8734
+ const removedIds = removeOptionInPlace(p, optionId);
8735
+ if (!removedIds.length) return;
8736
+ applyDeleteCleanup(p, new Set(removedIds));
8168
8737
  }),
8169
8738
  undo: () => ctx.undo()
8170
8739
  });
@@ -8175,7 +8744,7 @@ function editLabel(ctx, id, label) {
8175
8744
  ctx.exec({
8176
8745
  name: "editLabel",
8177
8746
  do: () => ctx.patchProps((p) => {
8178
- var _a, _b, _c, _d;
8747
+ var _a, _b, _c;
8179
8748
  if (ctx.isTagId(id)) {
8180
8749
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
8181
8750
  if (t) t.label = next;
@@ -8187,10 +8756,7 @@ function editLabel(ctx, id, label) {
8187
8756
  return;
8188
8757
  }
8189
8758
  if (ctx.isOptionId(id)) {
8190
- const own = ownerOfOption(p, id);
8191
- if (!own) return;
8192
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
8193
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
8759
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
8194
8760
  if (o) o.label = next;
8195
8761
  return;
8196
8762
  }
@@ -8215,7 +8781,7 @@ function setService(ctx, id, input) {
8215
8781
  ctx.exec({
8216
8782
  name: "setService",
8217
8783
  do: () => ctx.patchProps((p) => {
8218
- var _a, _b, _c, _d, _e, _f;
8784
+ var _a, _b, _c, _d, _e;
8219
8785
  const hasSidKey = Object.prototype.hasOwnProperty.call(
8220
8786
  input,
8221
8787
  "service_id"
@@ -8233,12 +8799,9 @@ function setService(ctx, id, input) {
8233
8799
  return;
8234
8800
  }
8235
8801
  if (ctx.isOptionId(id)) {
8236
- const own = ownerOfOption(p, id);
8237
- if (!own) return;
8238
- const f2 = ((_b = p.fields) != null ? _b : []).find((x) => x.id === own.fieldId);
8239
- const o = (_c = f2 == null ? void 0 : f2.options) == null ? void 0 : _c.find((x) => x.id === id);
8802
+ const o = (_b = findMutableOption(p, id)) == null ? void 0 : _b.option;
8240
8803
  if (!o) return;
8241
- const currentRole = (_d = o.pricing_role) != null ? _d : "base";
8804
+ const currentRole = (_c = o.pricing_role) != null ? _c : "base";
8242
8805
  const role = nextRole != null ? nextRole : currentRole;
8243
8806
  if (role === "utility") {
8244
8807
  if (hasSidKey && sid !== void 0) {
@@ -8259,7 +8822,7 @@ function setService(ctx, id, input) {
8259
8822
  }
8260
8823
  return;
8261
8824
  }
8262
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
8825
+ const f = ((_d = p.fields) != null ? _d : []).find((x) => x.id === id);
8263
8826
  if (!f) {
8264
8827
  throw new Error(
8265
8828
  'setService only supports tag ("t:*"), option ("o:*"), or field ("f:*") ids'
@@ -8270,7 +8833,7 @@ function setService(ctx, id, input) {
8270
8833
  if (nextRole) {
8271
8834
  f.pricing_role = nextRole;
8272
8835
  }
8273
- const effectiveRole = (_f = f.pricing_role) != null ? _f : "base";
8836
+ const effectiveRole = (_e = f.pricing_role) != null ? _e : "base";
8274
8837
  if (isOptionBased) {
8275
8838
  if (hasSidKey) {
8276
8839
  ctx.api.emit("error", {
@@ -8376,18 +8939,21 @@ function addField(ctx, partial) {
8376
8939
  p.fields = ((_a2 = p.fields) != null ? _a2 : []).filter((f) => f.id !== id);
8377
8940
  })
8378
8941
  });
8942
+ return id;
8379
8943
  }
8380
8944
  function updateField(ctx, id, patch) {
8381
8945
  let prev;
8382
8946
  let prevIncludesForButton;
8383
8947
  let prevExcludesForButton;
8948
+ let prevOptionEffectsForButton;
8384
8949
  ctx.exec({
8385
8950
  name: "updateField",
8386
8951
  do: () => ctx.patchProps((p) => {
8387
- var _a, _b, _c, _d, _e, _f, _g;
8952
+ var _a, _b, _c, _d, _e, _f, _g, _h;
8388
8953
  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;
8389
8954
  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;
8390
- p.fields = ((_g = p.fields) != null ? _g : []).map((f) => {
8955
+ 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;
8956
+ p.fields = ((_h = p.fields) != null ? _h : []).map((f) => {
8391
8957
  if (f.id !== id) return f;
8392
8958
  prev = (0, import_lodash_es3.cloneDeep)(f);
8393
8959
  const nextField = { ...f, ...patch };
@@ -8398,7 +8964,7 @@ function updateField(ctx, id, patch) {
8398
8964
  });
8399
8965
  }),
8400
8966
  undo: () => ctx.patchProps((p) => {
8401
- var _a, _b, _c;
8967
+ var _a, _b, _c, _d;
8402
8968
  p.fields = ((_a = p.fields) != null ? _a : []).map(
8403
8969
  (f) => f.id === id && prev ? prev : f
8404
8970
  );
@@ -8416,6 +8982,12 @@ function updateField(ctx, id, patch) {
8416
8982
  [id]: [...prevExcludesForButton]
8417
8983
  };
8418
8984
  }
8985
+ if (prevOptionEffectsForButton) {
8986
+ p.option_effects_for_buttons = {
8987
+ ...(_d = p.option_effects_for_buttons) != null ? _d : {},
8988
+ [id]: (0, import_lodash_es3.cloneDeep)(prevOptionEffectsForButton)
8989
+ };
8990
+ }
8419
8991
  })
8420
8992
  });
8421
8993
  }
@@ -8462,9 +9034,9 @@ function remove(ctx, id) {
8462
9034
  ctx.exec({
8463
9035
  name: "removeOption",
8464
9036
  do: () => ctx.patchProps((p) => {
8465
- const removed = removeOptionInPlace(p, key);
8466
- if (!removed) return;
8467
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
9037
+ const removedIds = removeOptionInPlace(p, key);
9038
+ if (!removedIds.length) return;
9039
+ applyDeleteCleanup(p, new Set(removedIds));
8468
9040
  }),
8469
9041
  undo: () => ctx.undo()
8470
9042
  });
@@ -8481,10 +9053,7 @@ function removeMany(ctx, ids) {
8481
9053
  const existingFieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
8482
9054
  const existingTagIds = new Set(((_b = p.filters) != null ? _b : []).map((t) => String(t.id)));
8483
9055
  const existingOptionIds = new Set(
8484
- ((_c = p.fields) != null ? _c : []).flatMap((f) => {
8485
- var _a2;
8486
- return ((_a2 = f.options) != null ? _a2 : []).map((o) => String(o.id));
8487
- })
9056
+ ((_c = p.fields) != null ? _c : []).flatMap((f) => collectFieldOptionIds(f))
8488
9057
  );
8489
9058
  const fieldIds = ordered.filter((id) => ctx.isFieldId(id) && existingFieldIds.has(id));
8490
9059
  const fieldIdSet = new Set(fieldIds);
@@ -8497,7 +9066,9 @@ function removeMany(ctx, ids) {
8497
9066
  });
8498
9067
  const deleted = /* @__PURE__ */ new Set();
8499
9068
  for (const optionId of optionIds) {
8500
- if (removeOptionInPlace(p, optionId)) deleted.add(optionId);
9069
+ for (const removedId of removeOptionInPlace(p, optionId)) {
9070
+ deleted.add(removedId);
9071
+ }
8501
9072
  }
8502
9073
  for (const fieldId of fieldIds) {
8503
9074
  const removedIds = removeFieldInPlace(p, fieldId);
@@ -8512,7 +9083,7 @@ function removeMany(ctx, ids) {
8512
9083
  });
8513
9084
  }
8514
9085
  function getNode(ctx, id) {
8515
- var _a, _b, _c, _d;
9086
+ var _a, _b, _c;
8516
9087
  const props = ctx.getProps();
8517
9088
  if (ctx.isTagId(id)) {
8518
9089
  const t = ((_a = props.filters) != null ? _a : []).find((x) => x.id === id);
@@ -8529,8 +9100,7 @@ function getNode(ctx, id) {
8529
9100
  }
8530
9101
  if (ctx.isOptionId(id)) {
8531
9102
  const own = ownerOfOption(props, id);
8532
- const f = own ? ((_c = props.fields) != null ? _c : []).find((x) => x.id === own.fieldId) : void 0;
8533
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
9103
+ const o = (_c = findMutableOption(props, id)) == null ? void 0 : _c.option;
8534
9104
  return {
8535
9105
  kind: "option",
8536
9106
  data: o,
@@ -9067,7 +9637,7 @@ function connect(ctx, kind, fromId, toId2) {
9067
9637
  ctx.exec({
9068
9638
  name: `connect:${kind}`,
9069
9639
  do: () => ctx.patchProps((p) => {
9070
- var _a, _b, _c, _d, _e, _f, _g, _h;
9640
+ var _a, _b, _c, _d, _e, _f, _g;
9071
9641
  if (kind === "bind") {
9072
9642
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
9073
9643
  if (wouldCreateTagCycle(ctx, p, fromId, toId2)) {
@@ -9147,12 +9717,10 @@ function connect(ctx, kind, fromId, toId2) {
9147
9717
  return;
9148
9718
  }
9149
9719
  if (toId2.startsWith("o:")) {
9150
- for (const f of (_g = p.fields) != null ? _g : []) {
9151
- const o = (_h = f.options) == null ? void 0 : _h.find((x) => x.id === toId2);
9152
- if (o) {
9153
- o.service_id = fromId;
9154
- return;
9155
- }
9720
+ const o = (_g = findMutableOption(p, toId2)) == null ? void 0 : _g.option;
9721
+ if (o) {
9722
+ o.service_id = fromId;
9723
+ return;
9156
9724
  }
9157
9725
  return;
9158
9726
  }
@@ -9169,7 +9737,7 @@ function disconnect(ctx, kind, fromId, toId2) {
9169
9737
  ctx.exec({
9170
9738
  name: `disconnect:${kind}`,
9171
9739
  do: () => ctx.patchProps((p) => {
9172
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
9740
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
9173
9741
  if (kind === "bind") {
9174
9742
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
9175
9743
  const child = ((_a = p.filters) != null ? _a : []).find(
@@ -9245,12 +9813,10 @@ function disconnect(ctx, kind, fromId, toId2) {
9245
9813
  return;
9246
9814
  }
9247
9815
  if (toId2.startsWith("o:")) {
9248
- for (const f of (_i = p.fields) != null ? _i : []) {
9249
- const o = (_j = f.options) == null ? void 0 : _j.find((x) => x.id === toId2);
9250
- if (o) {
9251
- delete o.service_id;
9252
- return;
9253
- }
9816
+ const o = (_i = findMutableOption(p, toId2)) == null ? void 0 : _i.option;
9817
+ if (o) {
9818
+ delete o.service_id;
9819
+ return;
9254
9820
  }
9255
9821
  return;
9256
9822
  }
@@ -9273,6 +9839,250 @@ function addMappedField(p, mapKey, fromId, toId2) {
9273
9839
  p[mapKey] = maps;
9274
9840
  }
9275
9841
 
9842
+ // src/react/canvas/editor/editor-option-effects.ts
9843
+ function assertCanonicalId(id, label) {
9844
+ if (!id || id.includes("::") || id.includes("/")) {
9845
+ throw new Error(
9846
+ `${label}: expected a raw field or option id, not a composite/path id`
9847
+ );
9848
+ }
9849
+ }
9850
+ function assertTrigger(ctx, triggerId) {
9851
+ assertCanonicalId(triggerId, "option effect trigger");
9852
+ const trigger = ctx.getNode(triggerId);
9853
+ if (trigger.kind === "option" && trigger.data) return;
9854
+ if (trigger.kind === "field" && trigger.data && isActualButtonField(trigger.data)) {
9855
+ return;
9856
+ }
9857
+ throw new Error(
9858
+ "option effect trigger must be an option id or button field id"
9859
+ );
9860
+ }
9861
+ function assertTargetField(props, targetFieldId) {
9862
+ var _a;
9863
+ assertCanonicalId(targetFieldId, "option effect target");
9864
+ const field = ((_a = props.fields) != null ? _a : []).find((item) => item.id === targetFieldId);
9865
+ if (!field) {
9866
+ throw new Error(`option effect target field not found: ${targetFieldId}`);
9867
+ }
9868
+ return field;
9869
+ }
9870
+ function dedupe2(values) {
9871
+ if (!values) return void 0;
9872
+ const out = [];
9873
+ for (const value of values) {
9874
+ const id = String(value);
9875
+ if (!id || out.includes(id)) continue;
9876
+ out.push(id);
9877
+ }
9878
+ return out.length ? out : void 0;
9879
+ }
9880
+ function assertTargetOptions(props, targetFieldId, ids, kind) {
9881
+ if (!(ids == null ? void 0 : ids.length)) return;
9882
+ const field = assertTargetField(props, targetFieldId);
9883
+ const valid = fieldOptionIdSet(field);
9884
+ for (const id of ids) {
9885
+ assertCanonicalId(String(id), `option effect ${kind} option`);
9886
+ if (!valid.has(String(id))) {
9887
+ throw new Error(
9888
+ `option effect ${kind} option not found under ${targetFieldId}: ${String(id)}`
9889
+ );
9890
+ }
9891
+ }
9892
+ }
9893
+ function normalizeEffect(effect) {
9894
+ var _a;
9895
+ if (!effect) return void 0;
9896
+ const exclude2 = dedupe2(effect.exclude);
9897
+ const excluded = new Set(exclude2 != null ? exclude2 : []);
9898
+ const include2 = (_a = dedupe2(effect.include)) == null ? void 0 : _a.filter((id) => !excluded.has(id));
9899
+ const out = {};
9900
+ if (effect.forceVisible === true) out.forceVisible = true;
9901
+ if (include2 == null ? void 0 : include2.length) out.include = include2;
9902
+ if (exclude2 == null ? void 0 : exclude2.length) out.exclude = exclude2;
9903
+ return Object.keys(out).length ? out : void 0;
9904
+ }
9905
+ function ensureTargetMap(props, triggerId) {
9906
+ var _a, _b, _c;
9907
+ (_a = props.option_effects_for_buttons) != null ? _a : props.option_effects_for_buttons = {};
9908
+ (_c = (_b = props.option_effects_for_buttons)[triggerId]) != null ? _c : _b[triggerId] = {};
9909
+ return props.option_effects_for_buttons[triggerId];
9910
+ }
9911
+ function pruneEffectMap(props, triggerId) {
9912
+ const map = props.option_effects_for_buttons;
9913
+ if (!map) return;
9914
+ const keys = triggerId ? [triggerId] : Object.keys(map);
9915
+ for (const key of keys) {
9916
+ const targets = map[key];
9917
+ if (!targets || Object.keys(targets).length === 0) delete map[key];
9918
+ }
9919
+ if (Object.keys(map).length === 0) delete props.option_effects_for_buttons;
9920
+ }
9921
+ function validateEffect(ctx, props, triggerId, targetFieldId, effect) {
9922
+ assertTrigger(ctx, triggerId);
9923
+ assertTargetField(props, targetFieldId);
9924
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.include, "include");
9925
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.exclude, "exclude");
9926
+ return normalizeEffect(effect);
9927
+ }
9928
+ function setOptionEffect(ctx, triggerId, targetFieldId, effect) {
9929
+ ctx.exec({
9930
+ name: "setOptionEffect",
9931
+ do: () => ctx.patchProps((props) => {
9932
+ var _a;
9933
+ const normalized = validateEffect(
9934
+ ctx,
9935
+ props,
9936
+ triggerId,
9937
+ targetFieldId,
9938
+ effect
9939
+ );
9940
+ if (!normalized) {
9941
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
9942
+ if (map) delete map[targetFieldId];
9943
+ pruneEffectMap(props, triggerId);
9944
+ return;
9945
+ }
9946
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
9947
+ }),
9948
+ undo: () => ctx.undo()
9949
+ });
9950
+ }
9951
+ function patchOptionEffect(ctx, triggerId, targetFieldId, patch) {
9952
+ ctx.exec({
9953
+ name: "patchOptionEffect",
9954
+ do: () => ctx.patchProps((props) => {
9955
+ var _a, _b, _c, _d;
9956
+ const current = (_c = (_b = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
9957
+ const merged = {
9958
+ ...current,
9959
+ ...patch
9960
+ };
9961
+ const normalized = validateEffect(
9962
+ ctx,
9963
+ props,
9964
+ triggerId,
9965
+ targetFieldId,
9966
+ merged
9967
+ );
9968
+ if (!normalized) {
9969
+ const map = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId];
9970
+ if (map) delete map[targetFieldId];
9971
+ pruneEffectMap(props, triggerId);
9972
+ return;
9973
+ }
9974
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
9975
+ }),
9976
+ undo: () => ctx.undo()
9977
+ });
9978
+ }
9979
+ function clearOptionEffect(ctx, triggerId, targetFieldId) {
9980
+ ctx.exec({
9981
+ name: "clearOptionEffect",
9982
+ do: () => ctx.patchProps((props) => {
9983
+ var _a;
9984
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
9985
+ if (!map) return;
9986
+ delete map[targetFieldId];
9987
+ pruneEffectMap(props, triggerId);
9988
+ }),
9989
+ undo: () => ctx.undo()
9990
+ });
9991
+ }
9992
+ function clearOptionEffectsForTrigger(ctx, triggerId) {
9993
+ ctx.exec({
9994
+ name: "clearOptionEffectsForTrigger",
9995
+ do: () => ctx.patchProps((props) => {
9996
+ if (!props.option_effects_for_buttons) return;
9997
+ delete props.option_effects_for_buttons[triggerId];
9998
+ pruneEffectMap(props);
9999
+ }),
10000
+ undo: () => ctx.undo()
10001
+ });
10002
+ }
10003
+ function clearOptionEffectsForTarget(ctx, targetFieldId) {
10004
+ ctx.exec({
10005
+ name: "clearOptionEffectsForTarget",
10006
+ do: () => ctx.patchProps((props) => {
10007
+ var _a;
10008
+ const map = props.option_effects_for_buttons;
10009
+ if (!map) return;
10010
+ for (const triggerId of Object.keys(map)) {
10011
+ (_a = map[triggerId]) == null ? true : delete _a[targetFieldId];
10012
+ }
10013
+ pruneEffectMap(props);
10014
+ }),
10015
+ undo: () => ctx.undo()
10016
+ });
10017
+ }
10018
+ function addOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
10019
+ var _a;
10020
+ const additions = (_a = dedupe2(optionIds)) != null ? _a : [];
10021
+ if (!additions.length) return;
10022
+ ctx.exec({
10023
+ name: "addOptionEffectOptions",
10024
+ do: () => ctx.patchProps((props) => {
10025
+ var _a2, _b, _c, _d;
10026
+ const current = (_c = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
10027
+ const nextValues = dedupe2([
10028
+ ...(_d = current[kind]) != null ? _d : [],
10029
+ ...additions
10030
+ ]);
10031
+ const normalized = validateEffect(
10032
+ ctx,
10033
+ props,
10034
+ triggerId,
10035
+ targetFieldId,
10036
+ {
10037
+ ...current,
10038
+ [kind]: nextValues
10039
+ }
10040
+ );
10041
+ if (!normalized) return;
10042
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
10043
+ }),
10044
+ undo: () => ctx.undo()
10045
+ });
10046
+ }
10047
+ function removeOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
10048
+ var _a;
10049
+ const removals = new Set((_a = dedupe2(optionIds)) != null ? _a : []);
10050
+ if (!removals.size) return;
10051
+ ctx.exec({
10052
+ name: "removeOptionEffectOptions",
10053
+ do: () => ctx.patchProps((props) => {
10054
+ var _a2, _b, _c, _d, _e;
10055
+ const current = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId];
10056
+ if (!current) return;
10057
+ const next = {
10058
+ ...current,
10059
+ [kind]: ((_c = current[kind]) != null ? _c : []).filter(
10060
+ (optionId) => !removals.has(optionId)
10061
+ )
10062
+ };
10063
+ const normalized = validateEffect(
10064
+ ctx,
10065
+ props,
10066
+ triggerId,
10067
+ targetFieldId,
10068
+ next
10069
+ );
10070
+ if (!normalized) {
10071
+ (_e = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId]) == null ? true : delete _e[targetFieldId];
10072
+ pruneEffectMap(props, triggerId);
10073
+ return;
10074
+ }
10075
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
10076
+ }),
10077
+ undo: () => ctx.undo()
10078
+ });
10079
+ }
10080
+ function setOptionEffectForceVisible(ctx, triggerId, targetFieldId, forceVisible) {
10081
+ patchOptionEffect(ctx, triggerId, targetFieldId, {
10082
+ forceVisible: forceVisible === true ? true : void 0
10083
+ });
10084
+ }
10085
+
9276
10086
  // src/react/canvas/editor/editor-service-filter.ts
9277
10087
  function filterServicesForVisibleGroup2(ctx, candidates, input) {
9278
10088
  const coreInput = {
@@ -9812,6 +10622,36 @@ var Editor = class {
9812
10622
  addField(partial) {
9813
10623
  return addField(this.moduleCtx(), partial);
9814
10624
  }
10625
+ addFieldFromDescriptor(registry, partial, opts) {
10626
+ var _a, _b, _c, _d, _e;
10627
+ const variant = (_b = opts == null ? void 0 : opts.variant) != null ? _b : typeof ((_a = partial == null ? void 0 : partial.meta) == null ? void 0 : _a.variant) === "string" ? partial.meta.variant : void 0;
10628
+ const descriptor = resolveInputDescriptor(
10629
+ registry,
10630
+ String(partial.type),
10631
+ variant
10632
+ );
10633
+ const nextMeta = {
10634
+ ...(_c = partial.meta) != null ? _c : {}
10635
+ };
10636
+ if (((_d = descriptor == null ? void 0 : descriptor.multi) == null ? void 0 : _d.autoEnable) === true) {
10637
+ nextMeta.multi = true;
10638
+ }
10639
+ const fieldInput = {
10640
+ ...partial,
10641
+ ...Object.keys(nextMeta).length ? { meta: nextMeta } : {}
10642
+ };
10643
+ const fieldId = this.addField(fieldInput);
10644
+ if (((_e = descriptor == null ? void 0 : descriptor.options) == null ? void 0 : _e.autoCreate) === true) {
10645
+ this.autoCreateOptionsMany([fieldId], () => {
10646
+ var _a2, _b2, _c2, _d2;
10647
+ return {
10648
+ label: (_b2 = (_a2 = descriptor.options) == null ? void 0 : _a2.defaultLabel) != null ? _b2 : "Option label",
10649
+ value: (_d2 = (_c2 = descriptor.options) == null ? void 0 : _c2.defaultValue) != null ? _d2 : "option"
10650
+ };
10651
+ });
10652
+ }
10653
+ return fieldId;
10654
+ }
9815
10655
  updateField(id, patch) {
9816
10656
  return updateField(this.moduleCtx(), id, patch);
9817
10657
  }
@@ -9829,7 +10669,7 @@ var Editor = class {
9829
10669
  if (!ordered.length) return;
9830
10670
  this.transact("clearServiceMany", () => {
9831
10671
  this.patchProps((p) => {
9832
- var _a, _b, _c, _d;
10672
+ var _a, _b;
9833
10673
  for (const id of ordered) {
9834
10674
  if (this.isTagId(id)) {
9835
10675
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
@@ -9842,10 +10682,8 @@ var Editor = class {
9842
10682
  continue;
9843
10683
  }
9844
10684
  if (this.isOptionId(id)) {
9845
- const own = ownerOfOption(p, id);
9846
- if (!own) continue;
9847
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
9848
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
10685
+ const found = findMutableOption(p, id);
10686
+ const o = found == null ? void 0 : found.option;
9849
10687
  if (o && "service_id" in o) delete o.service_id;
9850
10688
  }
9851
10689
  }
@@ -9894,7 +10732,7 @@ var Editor = class {
9894
10732
  if (!selected.size) return;
9895
10733
  this.transact("clearRelationsMany", () => {
9896
10734
  this.patchProps((p) => {
9897
- var _a, _b, _c;
10735
+ var _a, _b, _c, _d, _e;
9898
10736
  const clearOwned = mode === "owned" || mode === "both";
9899
10737
  const clearIncoming = mode === "incoming" || mode === "both";
9900
10738
  for (const t of (_a = p.filters) != null ? _a : []) {
@@ -9934,6 +10772,44 @@ var Editor = class {
9934
10772
  }
9935
10773
  if (!Object.keys(map).length) delete p[k];
9936
10774
  }
10775
+ const effectMap = p.option_effects_for_buttons;
10776
+ if (effectMap) {
10777
+ for (const triggerId of Object.keys(effectMap)) {
10778
+ if (clearOwned && selected.has(String(triggerId))) {
10779
+ delete effectMap[triggerId];
10780
+ continue;
10781
+ }
10782
+ const targets = effectMap[triggerId];
10783
+ if (!targets || !clearIncoming) continue;
10784
+ for (const targetFieldId of Object.keys(targets)) {
10785
+ if (selected.has(String(targetFieldId))) {
10786
+ delete targets[targetFieldId];
10787
+ continue;
10788
+ }
10789
+ const effect = targets[targetFieldId];
10790
+ if (!effect) continue;
10791
+ if (effect.include) {
10792
+ effect.include = effect.include.filter(
10793
+ (optionId) => !selected.has(String(optionId))
10794
+ );
10795
+ if (!effect.include.length) delete effect.include;
10796
+ }
10797
+ if (effect.exclude) {
10798
+ effect.exclude = effect.exclude.filter(
10799
+ (optionId) => !selected.has(String(optionId))
10800
+ );
10801
+ if (!effect.exclude.length) delete effect.exclude;
10802
+ }
10803
+ if (effect.forceVisible !== true && !((_d = effect.include) == null ? void 0 : _d.length) && !((_e = effect.exclude) == null ? void 0 : _e.length)) {
10804
+ delete targets[targetFieldId];
10805
+ }
10806
+ }
10807
+ if (!Object.keys(targets).length) delete effectMap[triggerId];
10808
+ }
10809
+ if (!Object.keys(effectMap).length) {
10810
+ delete p.option_effects_for_buttons;
10811
+ }
10812
+ }
9937
10813
  });
9938
10814
  });
9939
10815
  }
@@ -9945,7 +10821,7 @@ var Editor = class {
9945
10821
  const suffix = (_b = input.suffix) != null ? _b : "";
9946
10822
  this.transact("renameLabelsMany", () => {
9947
10823
  this.patchProps((p) => {
9948
- var _a2, _b2, _c, _d, _e, _f, _g;
10824
+ var _a2, _b2, _c, _d, _e, _f;
9949
10825
  for (const id of ordered) {
9950
10826
  if (this.isTagId(id)) {
9951
10827
  const t = ((_a2 = p.filters) != null ? _a2 : []).find((x) => x.id === id);
@@ -9958,11 +10834,8 @@ var Editor = class {
9958
10834
  continue;
9959
10835
  }
9960
10836
  if (this.isOptionId(id)) {
9961
- const own = ownerOfOption(p, id);
9962
- if (!own) continue;
9963
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === own.fieldId);
9964
- const o = (_f = f == null ? void 0 : f.options) == null ? void 0 : _f.find((x) => x.id === id);
9965
- if (o) o.label = `${prefix}${(_g = o.label) != null ? _g : ""}${suffix}`.trim();
10837
+ const o = (_e = findMutableOption(p, id)) == null ? void 0 : _e.option;
10838
+ if (o) o.label = `${prefix}${(_f = o.label) != null ? _f : ""}${suffix}`.trim();
9966
10839
  }
9967
10840
  }
9968
10841
  });
@@ -10007,6 +10880,28 @@ var Editor = class {
10007
10880
  });
10008
10881
  });
10009
10882
  }
10883
+ setFieldMulti(fieldId, enabled) {
10884
+ const flag = enabled === true;
10885
+ this.transact("setFieldMulti", () => {
10886
+ this.patchProps((p) => {
10887
+ var _a, _b;
10888
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === fieldId);
10889
+ if (!f) return;
10890
+ const currentMeta = (_b = f.meta) != null ? _b : {};
10891
+ const nextMeta = { ...currentMeta };
10892
+ if (flag) {
10893
+ nextMeta.multi = true;
10894
+ } else {
10895
+ delete nextMeta.multi;
10896
+ }
10897
+ if (Object.keys(nextMeta).length === 0) {
10898
+ delete f.meta;
10899
+ } else {
10900
+ f.meta = nextMeta;
10901
+ }
10902
+ });
10903
+ });
10904
+ }
10010
10905
  autoCreateOptionsMany(ids, makeOption) {
10011
10906
  const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
10012
10907
  if (!ordered.length) return;
@@ -10093,6 +10988,57 @@ var Editor = class {
10093
10988
  exclude(receiverId, idOrIds) {
10094
10989
  return exclude(this.moduleCtx(), receiverId, idOrIds);
10095
10990
  }
10991
+ setOptionEffect(triggerId, targetFieldId, effect) {
10992
+ return setOptionEffect(
10993
+ this.moduleCtx(),
10994
+ triggerId,
10995
+ targetFieldId,
10996
+ effect
10997
+ );
10998
+ }
10999
+ patchOptionEffect(triggerId, targetFieldId, patch) {
11000
+ return patchOptionEffect(
11001
+ this.moduleCtx(),
11002
+ triggerId,
11003
+ targetFieldId,
11004
+ patch
11005
+ );
11006
+ }
11007
+ clearOptionEffect(triggerId, targetFieldId) {
11008
+ return clearOptionEffect(this.moduleCtx(), triggerId, targetFieldId);
11009
+ }
11010
+ clearOptionEffectsForTrigger(triggerId) {
11011
+ return clearOptionEffectsForTrigger(this.moduleCtx(), triggerId);
11012
+ }
11013
+ clearOptionEffectsForTarget(targetFieldId) {
11014
+ return clearOptionEffectsForTarget(this.moduleCtx(), targetFieldId);
11015
+ }
11016
+ addOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
11017
+ return addOptionEffectOptions(
11018
+ this.moduleCtx(),
11019
+ triggerId,
11020
+ targetFieldId,
11021
+ kind,
11022
+ optionIds
11023
+ );
11024
+ }
11025
+ removeOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
11026
+ return removeOptionEffectOptions(
11027
+ this.moduleCtx(),
11028
+ triggerId,
11029
+ targetFieldId,
11030
+ kind,
11031
+ optionIds
11032
+ );
11033
+ }
11034
+ setOptionEffectForceVisible(triggerId, targetFieldId, forceVisible) {
11035
+ return setOptionEffectForceVisible(
11036
+ this.moduleCtx(),
11037
+ triggerId,
11038
+ targetFieldId,
11039
+ forceVisible
11040
+ );
11041
+ }
10096
11042
  connect(kind, fromId, toId2) {
10097
11043
  return connect(this.moduleCtx(), kind, fromId, toId2);
10098
11044
  }
@@ -10461,11 +11407,10 @@ var Selection = class {
10461
11407
  * What counts as a "button selection" (trigger key):
10462
11408
  * - field key where the field has button === true (e.g. "f:dripfeed")
10463
11409
  * - option key (e.g. "o:fast")
10464
- * - composite key "fieldId::optionId" (e.g. "f:speed::o:fast")
10465
11410
  *
10466
11411
  * Grouping:
10467
11412
  * - button-field trigger groups under its own fieldId
10468
- * - option/composite groups under the option's owning fieldId (from nodeMap)
11413
+ * - option trigger groups under the option's owning fieldId (from nodeMap)
10469
11414
  *
10470
11415
  * Deterministic:
10471
11416
  * - preserves selection insertion order
@@ -10482,15 +11427,6 @@ var Selection = class {
10482
11427
  };
10483
11428
  for (const key of this.set) {
10484
11429
  if (!key) continue;
10485
- const idx = key.indexOf("::");
10486
- if (idx !== -1) {
10487
- const optionId = key.slice(idx + 2);
10488
- const optRef = nodeMap.get(optionId);
10489
- if ((optRef == null ? void 0 : optRef.kind) === "option" && typeof optRef.fieldId === "string") {
10490
- push(optRef.fieldId, key);
10491
- }
10492
- continue;
10493
- }
10494
11430
  const ref = nodeMap.get(key);
10495
11431
  if (!ref) continue;
10496
11432
  if (ref.kind === "option" && typeof ref.fieldId === "string") {
@@ -10511,7 +11447,6 @@ var Selection = class {
10511
11447
  * Returns only selection keys that are valid "trigger buttons":
10512
11448
  * - field keys where field.button === true
10513
11449
  * - option keys
10514
- * - composite keys "fieldId::optionId" (validated by optionId)
10515
11450
  * Excludes tags and non-button fields.
10516
11451
  */
10517
11452
  selectedButtons() {
@@ -10527,13 +11462,6 @@ var Selection = class {
10527
11462
  };
10528
11463
  for (const key of this.set) {
10529
11464
  if (!key) continue;
10530
- const idx = key.indexOf("::");
10531
- if (idx !== -1) {
10532
- const optionId = key.slice(idx + 2);
10533
- const optRef = nodeMap.get(optionId);
10534
- if ((optRef == null ? void 0 : optRef.kind) === "option") push(key);
10535
- continue;
10536
- }
10537
11465
  const ref = nodeMap.get(key);
10538
11466
  if (!ref) continue;
10539
11467
  if (ref.kind === "option") {
@@ -10572,17 +11500,7 @@ var Selection = class {
10572
11500
  const direct = fields.find((x) => x.id === id);
10573
11501
  if (direct) return direct;
10574
11502
  if (this.builder.isOptionId(id)) {
10575
- return fields.find(
10576
- (x) => {
10577
- var _a2;
10578
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
10579
- }
10580
- );
10581
- }
10582
- if (id.includes("::")) {
10583
- const [fieldId] = id.split("::");
10584
- if (!fieldId) return void 0;
10585
- return fields.find((x) => x.id === fieldId);
11503
+ return findOptionOwnerField(fields, id);
10586
11504
  }
10587
11505
  return void 0;
10588
11506
  };
@@ -10613,18 +11531,7 @@ var Selection = class {
10613
11531
  }
10614
11532
  for (const id of this.set) {
10615
11533
  if (this.builder.isOptionId(id)) {
10616
- const host = fields.find(
10617
- (x) => {
10618
- var _a2;
10619
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
10620
- }
10621
- );
10622
- if (host == null ? void 0 : host.bind_id)
10623
- return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
10624
- }
10625
- if (id.includes("::")) {
10626
- const [fid] = id.split("::");
10627
- const host = fields.find((x) => x.id === fid);
11534
+ const host = findOptionOwnerField(fields, id);
10628
11535
  if (host == null ? void 0 : host.bind_id)
10629
11536
  return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
10630
11537
  }
@@ -10638,7 +11545,11 @@ var Selection = class {
10638
11545
  const tagById = new Map(tags.map((t) => [t.id, t]));
10639
11546
  const tag = tagById.get(tagId);
10640
11547
  const selectedTriggerIds = this.selectedButtons();
10641
- const fieldIds = this.builder.visibleFields(tagId, selectedTriggerIds);
11548
+ const visibility = this.builder.resolveVisibility(
11549
+ tagId,
11550
+ selectedTriggerIds
11551
+ );
11552
+ const fieldIds = visibility.fieldIds;
10642
11553
  const fieldById = new Map(fields.map((f) => [f.id, f]));
10643
11554
  const visible = fieldIds.map((id) => fieldById.get(id)).filter(Boolean);
10644
11555
  const parentTags = [];
@@ -10664,6 +11575,14 @@ var Selection = class {
10664
11575
  let baseOverridden = false;
10665
11576
  for (const selId of this.set) {
10666
11577
  const opt = this.findOptionById(fields, selId);
11578
+ if (opt && !this.isSelectedOptionVisible(
11579
+ fields,
11580
+ selId,
11581
+ fieldIds,
11582
+ visibility.optionsByFieldId
11583
+ )) {
11584
+ continue;
11585
+ }
10667
11586
  if ((opt == null ? void 0 : opt.service_id) != null) {
10668
11587
  const role = (_d = opt.pricing_role) != null ? _d : "base";
10669
11588
  const cap = (_e = resolve == null ? void 0 : resolve(opt.service_id)) != null ? _e : { id: opt.service_id };
@@ -10694,6 +11613,8 @@ var Selection = class {
10694
11613
  tag,
10695
11614
  fields: visible,
10696
11615
  fieldIds,
11616
+ optionsByFieldId: visibility.optionsByFieldId,
11617
+ forcedFieldIds: visibility.forcedFieldIds,
10697
11618
  parentTags,
10698
11619
  childrenTags,
10699
11620
  services
@@ -10717,21 +11638,19 @@ var Selection = class {
10717
11638
  return baseOverridden;
10718
11639
  }
10719
11640
  findOptionById(fields, selId) {
10720
- var _a, _b;
10721
11641
  if (this.builder.isOptionId(selId)) {
10722
- for (const f of fields) {
10723
- const o = (_a = f.options) == null ? void 0 : _a.find((x) => x.id === selId);
10724
- if (o) return o;
10725
- }
10726
- }
10727
- if (selId.includes("::")) {
10728
- const [fid, oid] = selId.split("::");
10729
- const f = fields.find((x) => x.id === fid);
10730
- const o = (_b = f == null ? void 0 : f.options) == null ? void 0 : _b.find((x) => x.id === oid || x.id === selId);
10731
- if (o) return o;
11642
+ const field = findOptionOwnerField(fields, selId);
11643
+ return findFieldOption(field, selId);
10732
11644
  }
10733
11645
  return void 0;
10734
11646
  }
11647
+ isSelectedOptionVisible(fields, selId, visibleFieldIds, optionsByFieldId) {
11648
+ const visibleFields = new Set(visibleFieldIds);
11649
+ const field = findOptionOwnerField(fields, selId);
11650
+ if (!field || !visibleFields.has(field.id)) return false;
11651
+ const allowed = optionsByFieldId[field.id];
11652
+ return !allowed || allowed.includes(selId);
11653
+ }
10735
11654
  };
10736
11655
 
10737
11656
  // src/react/canvas/api.ts