@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.
@@ -332,6 +332,7 @@ function templateTime(template) {
332
332
  return (_a = parseTimestamp(template.updatedAt)) != null ? _a : parseTimestamp(template.createdAt);
333
333
  }
334
334
  function shouldReplaceTemplates(params) {
335
+ var _a;
335
336
  if (!params.requestedSince) return true;
336
337
  if (!params.lastUpdatedAt) return false;
337
338
  const requested = parseTimestamp(params.requestedSince);
@@ -339,7 +340,9 @@ function shouldReplaceTemplates(params) {
339
340
  if (requested === void 0 || last === void 0) {
340
341
  return false;
341
342
  }
342
- return requested < last;
343
+ const currentTimes = ((_a = params.current) != null ? _a : []).map((template) => templateTime(template)).filter((time) => time !== void 0);
344
+ if (!currentTimes.length) return requested < last;
345
+ return requested < Math.min(...currentTimes);
343
346
  }
344
347
  function pickNewestTemplate(current, incoming) {
345
348
  const currentTime = templateTime(current);
@@ -347,7 +350,8 @@ function pickNewestTemplate(current, incoming) {
347
350
  if (currentTime !== void 0 && incomingTime !== void 0) {
348
351
  return incomingTime >= currentTime ? incoming : current;
349
352
  }
350
- if (currentTime === void 0 && incomingTime !== void 0) return incoming;
353
+ if (currentTime === void 0 && incomingTime !== void 0)
354
+ return incoming;
351
355
  if (currentTime !== void 0 && incomingTime === void 0) return current;
352
356
  return incoming;
353
357
  }
@@ -420,7 +424,8 @@ function useTemplatesSlice(params) {
420
424
  setTemplates((current) => {
421
425
  const replace = shouldReplaceTemplates({
422
426
  requestedSince,
423
- lastUpdatedAt: current.updatedAt
427
+ lastUpdatedAt: current.updatedAt,
428
+ current: current.data
424
429
  });
425
430
  return {
426
431
  data: replace ? res.value : mergeTemplates(current.data, res.value, {
@@ -519,7 +524,9 @@ function useTemplatesSlice(params) {
519
524
  var _a, _b;
520
525
  return {
521
526
  ...current,
522
- data: (_b = (_a = current.data) == null ? void 0 : _a.filter((template) => template.id !== id)) != null ? _b : current.data,
527
+ data: (_b = (_a = current.data) == null ? void 0 : _a.filter(
528
+ (template) => template.id !== id
529
+ )) != null ? _b : current.data,
523
530
  updatedAt: deleteRefreshSince
524
531
  };
525
532
  });
@@ -3590,6 +3597,9 @@ function normalise(input, opts = {}) {
3590
3597
  const excludes_for_buttons = toStringArrayMap(
3591
3598
  obj.excludes_for_buttons
3592
3599
  );
3600
+ const option_effects_for_buttons = toOptionEffectMap(
3601
+ obj.option_effects_for_buttons
3602
+ );
3593
3603
  const orderKinds = toStringMap(obj.orderKinds);
3594
3604
  const notices = toNoticeArray(obj.notices);
3595
3605
  let filters = rawFilters.map((t) => coerceTag(t, constraints));
@@ -3605,6 +3615,9 @@ function normalise(input, opts = {}) {
3605
3615
  ...isNonEmpty(orderKinds) && { orderKinds },
3606
3616
  ...isNonEmpty(includes_for_buttons) && { includes_for_buttons },
3607
3617
  ...isNonEmpty(excludes_for_buttons) && { excludes_for_buttons },
3618
+ ...isNonEmpty(option_effects_for_buttons) && {
3619
+ option_effects_for_buttons
3620
+ },
3608
3621
  ...fallbacks && (isNonEmpty(fallbacks.nodes) || isNonEmpty(fallbacks.global)) && {
3609
3622
  fallbacks
3610
3623
  },
@@ -3759,6 +3772,7 @@ function coerceOption(src, inheritRole) {
3759
3772
  const value = typeof src.value === "string" || typeof src.value === "number" ? src.value : void 0;
3760
3773
  const pricing_role = src.pricing_role === "utility" || src.pricing_role === "base" ? src.pricing_role : inheritRole;
3761
3774
  const meta = src.meta && typeof src.meta === "object" ? src.meta : void 0;
3775
+ const children = Array.isArray(src.children) ? src.children.map((child) => coerceOption(child, pricing_role)) : void 0;
3762
3776
  const option = {
3763
3777
  id: "",
3764
3778
  label: "",
@@ -3767,7 +3781,8 @@ function coerceOption(src, inheritRole) {
3767
3781
  ...value !== void 0 && { value },
3768
3782
  ...service_id !== void 0 && { service_id },
3769
3783
  pricing_role,
3770
- ...meta && { meta }
3784
+ ...meta && { meta },
3785
+ ...children && children.length && { children }
3771
3786
  };
3772
3787
  return option;
3773
3788
  }
@@ -3832,6 +3847,35 @@ function toStringArrayMap(src) {
3832
3847
  }
3833
3848
  return Object.keys(out).length ? out : void 0;
3834
3849
  }
3850
+ function toOptionEffectMap(src) {
3851
+ var _a, _b;
3852
+ if (!src || typeof src !== "object") return void 0;
3853
+ const out = {};
3854
+ for (const [triggerId, rawTargets] of Object.entries(src)) {
3855
+ if (!triggerId || !rawTargets || typeof rawTargets !== "object") {
3856
+ continue;
3857
+ }
3858
+ const targets = {};
3859
+ for (const [fieldId, rawEffect] of Object.entries(rawTargets)) {
3860
+ if (!fieldId || !rawEffect || typeof rawEffect !== "object") {
3861
+ continue;
3862
+ }
3863
+ const effect = rawEffect;
3864
+ const include2 = toStringArray(effect.include);
3865
+ const exclude2 = toStringArray(effect.exclude);
3866
+ const next = {
3867
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
3868
+ ...include2.length ? { include: dedupe(include2) } : {},
3869
+ ...exclude2.length ? { exclude: dedupe(exclude2) } : {}
3870
+ };
3871
+ if (next.forceVisible === true || ((_a = next.include) == null ? void 0 : _a.length) || ((_b = next.exclude) == null ? void 0 : _b.length)) {
3872
+ targets[fieldId] = next;
3873
+ }
3874
+ }
3875
+ if (Object.keys(targets).length) out[triggerId] = targets;
3876
+ }
3877
+ return Object.keys(out).length ? out : void 0;
3878
+ }
3835
3879
  function toStringArray(v) {
3836
3880
  if (!Array.isArray(v)) return [];
3837
3881
  return v.map((x) => String(x)).filter((s) => !!s && s.trim().length > 0);
@@ -3906,6 +3950,57 @@ function normalizeFieldValidation(input) {
3906
3950
  return one ? [one] : void 0;
3907
3951
  }
3908
3952
 
3953
+ // src/core/options.ts
3954
+ function walkFieldOptions(field) {
3955
+ const out = [];
3956
+ const visit = (options, depth, parentId) => {
3957
+ for (const option of options != null ? options : []) {
3958
+ out.push({
3959
+ field,
3960
+ fieldId: field.id,
3961
+ option,
3962
+ optionId: option.id,
3963
+ depth,
3964
+ parentId
3965
+ });
3966
+ visit(option.children, depth + 1, option.id);
3967
+ }
3968
+ };
3969
+ visit(field.options, 0);
3970
+ return out;
3971
+ }
3972
+ function fieldOptionIds(field) {
3973
+ return walkFieldOptions(field).map((visit) => visit.optionId);
3974
+ }
3975
+ function fieldOptionIdSet(field) {
3976
+ return new Set(fieldOptionIds(field));
3977
+ }
3978
+ function findFieldOption(field, optionId) {
3979
+ var _a;
3980
+ if (!field) return void 0;
3981
+ return (_a = walkFieldOptions(field).find((visit) => visit.optionId === optionId)) == null ? void 0 : _a.option;
3982
+ }
3983
+ function findOptionOwnerField(fields, optionId) {
3984
+ for (const field of fields) {
3985
+ if (findFieldOption(field, optionId)) return field;
3986
+ }
3987
+ return void 0;
3988
+ }
3989
+ function optionOwnerMap(fields) {
3990
+ const out = /* @__PURE__ */ new Map();
3991
+ for (const field of fields) {
3992
+ for (const visit of walkFieldOptions(field)) {
3993
+ if (!out.has(visit.optionId)) {
3994
+ out.set(visit.optionId, {
3995
+ fieldId: field.id,
3996
+ option: visit.option
3997
+ });
3998
+ }
3999
+ }
4000
+ }
4001
+ return out;
4002
+ }
4003
+
3909
4004
  // src/core/validate/shared.ts
3910
4005
  function isFiniteNumber(v) {
3911
4006
  return typeof v === "number" && Number.isFinite(v);
@@ -3914,8 +4009,9 @@ function isServiceIdRef(v) {
3914
4009
  return typeof v === "string" && v.trim().length > 0 || typeof v === "number" && Number.isFinite(v);
3915
4010
  }
3916
4011
  function hasAnyServiceOption(f) {
3917
- var _a;
3918
- return ((_a = f.options) != null ? _a : []).some((o) => isServiceIdRef(o.service_id));
4012
+ return walkFieldOptions(f).some(
4013
+ (visit) => isServiceIdRef(visit.option.service_id)
4014
+ );
3919
4015
  }
3920
4016
  function getByPath(obj, path) {
3921
4017
  if (!path) return void 0;
@@ -4004,14 +4100,14 @@ function withAffected(details, ids) {
4004
4100
 
4005
4101
  // src/core/node-map.ts
4006
4102
  function buildNodeMap(props) {
4007
- var _a, _b, _c;
4103
+ var _a, _b;
4008
4104
  const map = /* @__PURE__ */ new Map();
4009
4105
  for (const t of (_a = props.filters) != null ? _a : []) {
4010
4106
  if (!map.has(t.id)) map.set(t.id, { kind: "tag", id: t.id, node: t });
4011
4107
  }
4012
4108
  for (const f of (_b = props.fields) != null ? _b : []) {
4013
4109
  if (!map.has(f.id)) map.set(f.id, { kind: "field", id: f.id, node: f });
4014
- for (const o of (_c = f.options) != null ? _c : []) {
4110
+ for (const { option: o } of walkFieldOptions(f)) {
4015
4111
  if (!map.has(o.id))
4016
4112
  map.set(o.id, {
4017
4113
  kind: "option",
@@ -4024,12 +4120,6 @@ function buildNodeMap(props) {
4024
4120
  return map;
4025
4121
  }
4026
4122
  function resolveTrigger(trigger, nodeMap) {
4027
- const idx = trigger.indexOf("::");
4028
- if (idx !== -1) {
4029
- const fieldId = trigger.slice(0, idx);
4030
- const optionId = trigger.slice(idx + 2);
4031
- return { kind: "composite", triggerKey: trigger, fieldId, optionId };
4032
- }
4033
4123
  const direct = nodeMap.get(trigger);
4034
4124
  if (!direct) return void 0;
4035
4125
  if (direct.kind === "option") {
@@ -4081,11 +4171,6 @@ function visibleFieldIdsUnder(props, tagId, opts = {}) {
4081
4171
  const ownerDepthForTriggerKey = (triggerKey) => {
4082
4172
  const t = resolveTrigger(triggerKey, nodeMap);
4083
4173
  if (!t) return void 0;
4084
- if (t.kind === "composite") {
4085
- const f = fieldById.get(t.fieldId);
4086
- if (!f) return void 0;
4087
- return ownerDepthForField(f);
4088
- }
4089
4174
  if (t.kind === "field") {
4090
4175
  const f = fieldById.get(t.id);
4091
4176
  if (!f || f.button !== true) return void 0;
@@ -4168,6 +4253,84 @@ function visibleFieldsUnder(props, tagId, opts = {}) {
4168
4253
  const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((f) => [f.id, f]));
4169
4254
  return ids.map((id) => fieldById.get(id)).filter(Boolean);
4170
4255
  }
4256
+ function resolveVisibility(props, tagId, selectedKeys) {
4257
+ var _a, _b, _c, _d;
4258
+ const selected = new Set(selectedKeys != null ? selectedKeys : []);
4259
+ const baseFieldIds = visibleFieldIdsUnder(props, tagId, { selectedKeys: selected });
4260
+ const fieldById = new Map(((_a = props.fields) != null ? _a : []).map((field) => [field.id, field]));
4261
+ const visible = new Set(baseFieldIds);
4262
+ const forced = /* @__PURE__ */ new Set();
4263
+ const optionsByFieldId = {};
4264
+ const optionIdsByFieldId = /* @__PURE__ */ new Map();
4265
+ const getOptionIds = (field) => {
4266
+ let ids = optionIdsByFieldId.get(field.id);
4267
+ if (!ids) {
4268
+ ids = fieldOptionIds(field);
4269
+ optionIdsByFieldId.set(field.id, ids);
4270
+ }
4271
+ return ids;
4272
+ };
4273
+ const ensureOptions = (field) => {
4274
+ const ids = getOptionIds(field);
4275
+ if (!ids.length) return void 0;
4276
+ if (!optionsByFieldId[field.id]) optionsByFieldId[field.id] = [...ids];
4277
+ return optionsByFieldId[field.id];
4278
+ };
4279
+ for (const fieldId of baseFieldIds) {
4280
+ const field = fieldById.get(fieldId);
4281
+ if (field) ensureOptions(field);
4282
+ }
4283
+ const effects = (_b = props.option_effects_for_buttons) != null ? _b : {};
4284
+ for (const triggerId of selected) {
4285
+ const targetRules = effects[triggerId];
4286
+ if (!targetRules) continue;
4287
+ for (const [targetFieldId, rule] of Object.entries(targetRules)) {
4288
+ const field = fieldById.get(targetFieldId);
4289
+ if (!field) continue;
4290
+ const isVisible = visible.has(targetFieldId);
4291
+ if (!isVisible && rule.forceVisible !== true) continue;
4292
+ if (!isVisible && rule.forceVisible === true) {
4293
+ visible.add(targetFieldId);
4294
+ forced.add(targetFieldId);
4295
+ }
4296
+ const orderedOptionIds = getOptionIds(field);
4297
+ if (!orderedOptionIds.length) continue;
4298
+ const known = new Set(orderedOptionIds);
4299
+ let allowed = (_c = optionsByFieldId[targetFieldId]) != null ? _c : [...orderedOptionIds];
4300
+ if (Array.isArray(rule.include) && rule.include.length) {
4301
+ const include2 = new Set(
4302
+ rule.include.filter((optionId) => known.has(optionId))
4303
+ );
4304
+ allowed = orderedOptionIds.filter(
4305
+ (optionId) => include2.has(optionId) && allowed.includes(optionId)
4306
+ );
4307
+ }
4308
+ if (Array.isArray(rule.exclude) && rule.exclude.length) {
4309
+ const exclude2 = new Set(
4310
+ rule.exclude.filter((optionId) => known.has(optionId))
4311
+ );
4312
+ allowed = allowed.filter((optionId) => !exclude2.has(optionId));
4313
+ }
4314
+ optionsByFieldId[targetFieldId] = allowed;
4315
+ }
4316
+ }
4317
+ const visibleFieldIds = baseFieldIds.filter((fieldId) => visible.has(fieldId));
4318
+ const seen = new Set(visibleFieldIds);
4319
+ for (const field of (_d = props.fields) != null ? _d : []) {
4320
+ if (!visible.has(field.id) || seen.has(field.id)) continue;
4321
+ seen.add(field.id);
4322
+ visibleFieldIds.push(field.id);
4323
+ ensureOptions(field);
4324
+ }
4325
+ for (const fieldId of Object.keys(optionsByFieldId)) {
4326
+ if (!visible.has(fieldId)) delete optionsByFieldId[fieldId];
4327
+ }
4328
+ return {
4329
+ fieldIds: visibleFieldIds,
4330
+ optionsByFieldId,
4331
+ forcedFieldIds: visibleFieldIds.filter((fieldId) => forced.has(fieldId))
4332
+ };
4333
+ }
4171
4334
 
4172
4335
  // src/core/validate/steps/visibility.ts
4173
4336
  function createFieldsVisibleUnder(v) {
@@ -4185,7 +4348,6 @@ function resolveRootTags(tags) {
4185
4348
  return roots.length ? roots : tags.slice(0, 1);
4186
4349
  }
4187
4350
  function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
4188
- var _a;
4189
4351
  const visible = visibleFieldsUnder(v.props, tagId, {
4190
4352
  selectedKeys
4191
4353
  });
@@ -4195,7 +4357,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
4195
4357
  const t = f.id;
4196
4358
  if (effectfulKeys.has(t)) triggers.push(t);
4197
4359
  }
4198
- for (const o of (_a = f.options) != null ? _a : []) {
4360
+ for (const { option: o } of walkFieldOptions(f)) {
4199
4361
  const t = o.id;
4200
4362
  if (effectfulKeys.has(t)) triggers.push(t);
4201
4363
  }
@@ -4204,7 +4366,7 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKey
4204
4366
  return triggers;
4205
4367
  }
4206
4368
  function runVisibilityRulesOnce(v) {
4207
- var _a, _b, _c, _d, _e;
4369
+ var _a, _b, _c, _d;
4208
4370
  for (const t of v.tags) {
4209
4371
  const visible = v.fieldsVisibleUnder(t.id);
4210
4372
  const seen = /* @__PURE__ */ new Map();
@@ -4254,9 +4416,9 @@ function runVisibilityRulesOnce(v) {
4254
4416
  let hasUtility = false;
4255
4417
  const utilityOptionIds = [];
4256
4418
  for (const f of visible) {
4257
- for (const o of (_c = f.options) != null ? _c : []) {
4419
+ for (const { option: o } of walkFieldOptions(f)) {
4258
4420
  if (!isServiceIdRef(o.service_id)) continue;
4259
- const role = (_e = (_d = o.pricing_role) != null ? _d : f.pricing_role) != null ? _e : "base";
4421
+ const role = (_d = (_c = o.pricing_role) != null ? _c : f.pricing_role) != null ? _d : "base";
4260
4422
  if (role === "base") hasBase = true;
4261
4423
  else if (role === "utility") {
4262
4424
  hasUtility = true;
@@ -4297,7 +4459,7 @@ function dedupeErrorsInPlace(v, startIndex) {
4297
4459
  v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
4298
4460
  }
4299
4461
  function validateVisibility(v, options = {}) {
4300
- var _a, _b, _c, _d, _e;
4462
+ var _a, _b, _c, _d, _e, _f;
4301
4463
  v.simulatedVisibilityContexts = [];
4302
4464
  const simulate = options.simulate === true;
4303
4465
  if (!simulate) {
@@ -4322,10 +4484,13 @@ function validateVisibility(v, options = {}) {
4322
4484
  for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
4323
4485
  effectfulKeys.add(key);
4324
4486
  }
4487
+ for (const key of Object.keys((_e = v.props.option_effects_for_buttons) != null ? _e : {})) {
4488
+ effectfulKeys.add(key);
4489
+ }
4325
4490
  }
4326
4491
  const roots = resolveRootTags(v.tags);
4327
4492
  const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
4328
- const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
4493
+ const originalSelected = new Set((_f = v.selectedKeys) != null ? _f : []);
4329
4494
  const errorsStart = v.errors.length;
4330
4495
  const visited = /* @__PURE__ */ new Set();
4331
4496
  const seenContexts = /* @__PURE__ */ new Set();
@@ -4466,7 +4631,7 @@ function validateStructure(v) {
4466
4631
 
4467
4632
  // src/core/validate/steps/identity.ts
4468
4633
  function validateIdentity(v) {
4469
- var _a, _b;
4634
+ var _a;
4470
4635
  const tags = v.tags;
4471
4636
  const fields = v.fields;
4472
4637
  {
@@ -4566,7 +4731,7 @@ function validateIdentity(v) {
4566
4731
  }
4567
4732
  }
4568
4733
  for (const f of fields) {
4569
- for (const o of (_b = f.options) != null ? _b : []) {
4734
+ for (const { option: o } of walkFieldOptions(f)) {
4570
4735
  if (!o.label || !o.label.trim()) {
4571
4736
  v.errors.push({
4572
4737
  code: "label_missing",
@@ -4581,25 +4746,11 @@ function validateIdentity(v) {
4581
4746
  }
4582
4747
 
4583
4748
  // src/core/validate/steps/option-maps.ts
4584
- function parseFieldOptionKey(key) {
4585
- const idx = key.indexOf("::");
4586
- if (idx === -1) return null;
4587
- const fieldId = key.slice(0, idx).trim();
4588
- const optionId = key.slice(idx + 2).trim();
4589
- if (!fieldId || !optionId) return null;
4590
- return { fieldId, optionId };
4591
- }
4592
- function hasOption(v, fid, oid) {
4593
- var _a;
4594
- const f = v.fieldById.get(fid);
4595
- if (!f) return false;
4596
- return !!((_a = f.options) != null ? _a : []).find((o) => o.id === oid);
4597
- }
4598
4749
  function validateOptionMaps(v) {
4599
- var _a, _b;
4750
+ var _a, _b, _c;
4600
4751
  const incMap = (_a = v.props.includes_for_buttons) != null ? _a : {};
4601
4752
  const excMap = (_b = v.props.excludes_for_buttons) != null ? _b : {};
4602
- 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.`;
4753
+ const badKeyMessage = (key) => `Invalid trigger-map key "${key}". Expected a known option id or button-field id.`;
4603
4754
  const validateTriggerKey = (key) => {
4604
4755
  const ref = v.nodeMap.get(key);
4605
4756
  if (ref) {
@@ -4618,19 +4769,7 @@ function validateOptionMaps(v) {
4618
4769
  }
4619
4770
  return { ok: false, nodeId: ref.id, affected: [ref.id] };
4620
4771
  }
4621
- const p = parseFieldOptionKey(key);
4622
- if (!p) return { ok: false };
4623
- if (!hasOption(v, p.fieldId, p.optionId))
4624
- return {
4625
- ok: false,
4626
- nodeId: p.fieldId,
4627
- affected: [p.fieldId, p.optionId]
4628
- };
4629
- return {
4630
- ok: true,
4631
- nodeId: p.fieldId,
4632
- affected: [p.fieldId, p.optionId]
4633
- };
4772
+ return { ok: false };
4634
4773
  };
4635
4774
  for (const k of Object.keys(incMap)) {
4636
4775
  const r = validateTriggerKey(k);
@@ -4656,6 +4795,57 @@ function validateOptionMaps(v) {
4656
4795
  });
4657
4796
  }
4658
4797
  }
4798
+ const effectMap = (_c = v.props.option_effects_for_buttons) != null ? _c : {};
4799
+ for (const [triggerKey, targets] of Object.entries(effectMap)) {
4800
+ const trigger = validateTriggerKey(triggerKey);
4801
+ if (!trigger.ok) {
4802
+ v.errors.push({
4803
+ code: "bad_option_effect_key",
4804
+ severity: "error",
4805
+ message: badKeyMessage(triggerKey),
4806
+ nodeId: trigger.nodeId,
4807
+ details: withAffected({ key: triggerKey }, trigger.affected)
4808
+ });
4809
+ }
4810
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
4811
+ const field = v.fieldById.get(targetFieldId);
4812
+ if (!field) {
4813
+ v.errors.push({
4814
+ code: "bad_option_effect_target",
4815
+ severity: "error",
4816
+ message: `Option effect trigger "${triggerKey}" targets unknown field "${targetFieldId}".`,
4817
+ details: withAffected(
4818
+ { key: triggerKey, targetFieldId },
4819
+ trigger.affected
4820
+ )
4821
+ });
4822
+ continue;
4823
+ }
4824
+ const validOptionIds = fieldOptionIdSet(field);
4825
+ const checkTargetOptions = (kind, optionIds) => {
4826
+ for (const optionId of optionIds != null ? optionIds : []) {
4827
+ if (validOptionIds.has(optionId)) continue;
4828
+ v.errors.push({
4829
+ code: "bad_option_effect_option",
4830
+ severity: "error",
4831
+ message: `Option effect trigger "${triggerKey}" references unknown ${kind} option "${optionId}" for field "${targetFieldId}".`,
4832
+ nodeId: targetFieldId,
4833
+ details: withAffected(
4834
+ {
4835
+ key: triggerKey,
4836
+ targetFieldId,
4837
+ optionId,
4838
+ kind
4839
+ },
4840
+ [targetFieldId, optionId]
4841
+ )
4842
+ });
4843
+ }
4844
+ };
4845
+ checkTargetOptions("include", effect == null ? void 0 : effect.include);
4846
+ checkTargetOptions("exclude", effect == null ? void 0 : effect.exclude);
4847
+ }
4848
+ }
4659
4849
  for (const k of Object.keys(incMap)) {
4660
4850
  if (!(k in excMap)) continue;
4661
4851
  const r = validateTriggerKey(k);
@@ -4669,27 +4859,231 @@ function validateOptionMaps(v) {
4669
4859
  }
4670
4860
  }
4671
4861
 
4672
- // src/utils/order-kind.ts
4673
- function normalizeSelectedTriggerKey(key, nodeMap) {
4674
- if (!key) return void 0;
4675
- const compositeIdx = key.indexOf("::");
4676
- if (compositeIdx !== -1) {
4677
- const fieldId = key.slice(0, compositeIdx).trim();
4678
- const optionId = key.slice(compositeIdx + 2).trim();
4679
- if (optionId) {
4680
- const optionRef = nodeMap.get(optionId);
4681
- if ((optionRef == null ? void 0 : optionRef.kind) === "option") {
4682
- return { nodeId: optionRef.id, nodeKind: "option" };
4862
+ // src/core/validate/steps/visibility-cycles.ts
4863
+ var MAX_VISIBILITY_CYCLE_DEPTH = 20;
4864
+ function validateVisibilityCycles(v) {
4865
+ const triggerById = buildTriggerIndex(v.fields);
4866
+ if (!triggerById.size) return;
4867
+ const fieldTriggers = buildFieldTriggerIndex(v.fields);
4868
+ const revealTargetsByTrigger = buildRevealIndex(v, triggerById);
4869
+ const reported = /* @__PURE__ */ new Set();
4870
+ for (const rootTriggerId of Array.from(triggerById.keys()).sort()) {
4871
+ const required = makeRequiredState(triggerById, [rootTriggerId]);
4872
+ walkFromTrigger({
4873
+ v,
4874
+ triggerById,
4875
+ fieldTriggers,
4876
+ revealTargetsByTrigger,
4877
+ rootTriggerId,
4878
+ currentTriggerId: rootTriggerId,
4879
+ required,
4880
+ path: [rootTriggerId],
4881
+ visited: /* @__PURE__ */ new Set(),
4882
+ reported,
4883
+ depth: 0
4884
+ });
4885
+ }
4886
+ }
4887
+ function buildTriggerIndex(fields) {
4888
+ const out = /* @__PURE__ */ new Map();
4889
+ const owners = optionOwnerMap(fields);
4890
+ for (const field of fields) {
4891
+ if (field.button === true) {
4892
+ out.set(field.id, {
4893
+ kind: "field",
4894
+ id: field.id,
4895
+ ownerFieldId: field.id
4896
+ });
4897
+ }
4898
+ }
4899
+ for (const [optionId, owner] of owners) {
4900
+ out.set(optionId, {
4901
+ kind: "option",
4902
+ id: optionId,
4903
+ ownerFieldId: owner.fieldId
4904
+ });
4905
+ }
4906
+ return out;
4907
+ }
4908
+ function buildFieldTriggerIndex(fields) {
4909
+ const out = /* @__PURE__ */ new Map();
4910
+ for (const field of fields) {
4911
+ const triggers = [];
4912
+ if (field.button === true) triggers.push(field.id);
4913
+ for (const visit of walkFieldOptions(field)) {
4914
+ triggers.push(visit.optionId);
4915
+ }
4916
+ out.set(field.id, triggers);
4917
+ }
4918
+ return out;
4919
+ }
4920
+ function buildRevealIndex(v, triggerById) {
4921
+ var _a, _b;
4922
+ const out = /* @__PURE__ */ new Map();
4923
+ const addReveal = (triggerId, targetFieldId) => {
4924
+ var _a2;
4925
+ if (!triggerById.has(triggerId)) return;
4926
+ if (!v.fieldById.has(targetFieldId)) return;
4927
+ const set = (_a2 = out.get(triggerId)) != null ? _a2 : /* @__PURE__ */ new Set();
4928
+ set.add(targetFieldId);
4929
+ out.set(triggerId, set);
4930
+ };
4931
+ for (const [triggerId, targetIds] of Object.entries(
4932
+ (_a = v.props.includes_for_buttons) != null ? _a : {}
4933
+ )) {
4934
+ for (const targetId of targetIds != null ? targetIds : []) addReveal(triggerId, targetId);
4935
+ }
4936
+ for (const [triggerId, targets] of Object.entries(
4937
+ (_b = v.props.option_effects_for_buttons) != null ? _b : {}
4938
+ )) {
4939
+ for (const [targetFieldId, effect] of Object.entries(targets != null ? targets : {})) {
4940
+ if ((effect == null ? void 0 : effect.forceVisible) === true)
4941
+ addReveal(triggerId, targetFieldId);
4942
+ }
4943
+ }
4944
+ return new Map(
4945
+ Array.from(out.entries()).map(([triggerId, fieldIds]) => [
4946
+ triggerId,
4947
+ Array.from(fieldIds).sort()
4948
+ ])
4949
+ );
4950
+ }
4951
+ function walkFromTrigger(args) {
4952
+ var _a, _b, _c;
4953
+ if (args.depth >= MAX_VISIBILITY_CYCLE_DEPTH) return;
4954
+ const visitedKey = `${args.rootTriggerId}::${args.currentTriggerId}::${args.path.join(">")}`;
4955
+ if (args.visited.has(visitedKey)) return;
4956
+ args.visited.add(visitedKey);
4957
+ const revealedFieldIds = (_a = args.revealTargetsByTrigger.get(args.currentTriggerId)) != null ? _a : [];
4958
+ for (const revealedFieldId of revealedFieldIds) {
4959
+ const reachableTriggers = (_c = (_b = args.fieldTriggers.get(revealedFieldId)) == null ? void 0 : _b.slice().sort()) != null ? _c : [];
4960
+ for (const reachableTriggerId of reachableTriggers) {
4961
+ const invalidation = invalidatesRequiredPath(
4962
+ args.v,
4963
+ args.triggerById,
4964
+ reachableTriggerId,
4965
+ args.required
4966
+ );
4967
+ if (invalidation) {
4968
+ emitCycleError({
4969
+ v: args.v,
4970
+ rootTriggerId: args.rootTriggerId,
4971
+ revealedFieldId,
4972
+ conflictingTriggerId: reachableTriggerId,
4973
+ invalidatedId: invalidation.invalidatedId,
4974
+ path: [...args.path, reachableTriggerId],
4975
+ reported: args.reported
4976
+ });
4683
4977
  }
4978
+ if (args.path.includes(reachableTriggerId)) continue;
4979
+ walkFromTrigger({
4980
+ ...args,
4981
+ currentTriggerId: reachableTriggerId,
4982
+ required: addRequiredTrigger(
4983
+ args.triggerById,
4984
+ args.required,
4985
+ reachableTriggerId
4986
+ ),
4987
+ path: [...args.path, reachableTriggerId],
4988
+ depth: args.depth + 1
4989
+ });
4684
4990
  }
4685
- if (fieldId) {
4686
- const fieldRef = nodeMap.get(fieldId);
4687
- if ((fieldRef == null ? void 0 : fieldRef.kind) === "field") {
4688
- return { nodeId: fieldRef.id, nodeKind: "field" };
4991
+ }
4992
+ }
4993
+ function makeRequiredState(triggerById, triggerIds) {
4994
+ let required = {
4995
+ triggers: /* @__PURE__ */ new Set(),
4996
+ ownerFields: /* @__PURE__ */ new Set()
4997
+ };
4998
+ for (const triggerId of triggerIds) {
4999
+ required = addRequiredTrigger(triggerById, required, triggerId);
5000
+ }
5001
+ return required;
5002
+ }
5003
+ function addRequiredTrigger(triggerById, current, triggerId) {
5004
+ const next = {
5005
+ triggers: new Set(current.triggers),
5006
+ ownerFields: new Set(current.ownerFields)
5007
+ };
5008
+ const trigger = triggerById.get(triggerId);
5009
+ if (!trigger) return next;
5010
+ next.triggers.add(triggerId);
5011
+ next.ownerFields.add(trigger.ownerFieldId);
5012
+ return next;
5013
+ }
5014
+ function invalidatesRequiredPath(v, triggerById, conflictingTriggerId, required) {
5015
+ var _a, _b, _c, _d, _e, _f;
5016
+ for (const targetId of (_b = (_a = v.props.excludes_for_buttons) == null ? void 0 : _a[conflictingTriggerId]) != null ? _b : []) {
5017
+ if (required.ownerFields.has(targetId)) {
5018
+ return { invalidatedId: targetId };
5019
+ }
5020
+ const targetTrigger = triggerById.get(targetId);
5021
+ if ((targetTrigger == null ? void 0 : targetTrigger.kind) === "option" && required.triggers.has(targetId)) {
5022
+ return { invalidatedId: targetId };
5023
+ }
5024
+ }
5025
+ const effects = (_d = (_c = v.props.option_effects_for_buttons) == null ? void 0 : _c[conflictingTriggerId]) != null ? _d : {};
5026
+ for (const [targetFieldId, effect] of Object.entries(effects)) {
5027
+ if (!v.fieldById.has(targetFieldId)) continue;
5028
+ if ((_e = effect == null ? void 0 : effect.exclude) == null ? void 0 : _e.length) {
5029
+ const excluded = new Set(effect.exclude);
5030
+ for (const requiredTriggerId of required.triggers) {
5031
+ const requiredTrigger = triggerById.get(requiredTriggerId);
5032
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
5033
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
5034
+ if (excluded.has(requiredTriggerId)) {
5035
+ return { invalidatedId: requiredTriggerId };
5036
+ }
5037
+ }
5038
+ }
5039
+ if ((_f = effect == null ? void 0 : effect.include) == null ? void 0 : _f.length) {
5040
+ const included = new Set(effect.include);
5041
+ for (const requiredTriggerId of required.triggers) {
5042
+ const requiredTrigger = triggerById.get(requiredTriggerId);
5043
+ if ((requiredTrigger == null ? void 0 : requiredTrigger.kind) !== "option") continue;
5044
+ if (requiredTrigger.ownerFieldId !== targetFieldId) continue;
5045
+ if (!included.has(requiredTriggerId)) {
5046
+ return { invalidatedId: requiredTriggerId };
5047
+ }
4689
5048
  }
4690
5049
  }
4691
- return void 0;
4692
5050
  }
5051
+ return void 0;
5052
+ }
5053
+ function emitCycleError(args) {
5054
+ const key = [
5055
+ args.rootTriggerId,
5056
+ args.conflictingTriggerId,
5057
+ args.invalidatedId,
5058
+ args.path.join(">")
5059
+ ].join("::");
5060
+ if (args.reported.has(key)) return;
5061
+ args.reported.add(key);
5062
+ args.v.errors.push({
5063
+ code: "visibility_dependency_cycle",
5064
+ severity: "error",
5065
+ message: `Visibility dependency cycle: trigger "${args.rootTriggerId}" reveals "${args.revealedFieldId}", but reachable trigger "${args.conflictingTriggerId}" can hide or remove "${args.invalidatedId}".`,
5066
+ nodeId: args.conflictingTriggerId,
5067
+ details: withAffected(
5068
+ {
5069
+ rootTriggerId: args.rootTriggerId,
5070
+ conflictingTriggerId: args.conflictingTriggerId,
5071
+ invalidatedId: args.invalidatedId,
5072
+ path: args.path
5073
+ },
5074
+ [
5075
+ args.rootTriggerId,
5076
+ args.revealedFieldId,
5077
+ args.conflictingTriggerId,
5078
+ args.invalidatedId
5079
+ ]
5080
+ )
5081
+ });
5082
+ }
5083
+
5084
+ // src/utils/order-kind.ts
5085
+ function normalizeSelectedTriggerKey(key, nodeMap) {
5086
+ if (!key) return void 0;
4693
5087
  const ref = nodeMap.get(key);
4694
5088
  if (!ref) return void 0;
4695
5089
  if (ref.kind !== "field" && ref.kind !== "option") return void 0;
@@ -4848,8 +5242,7 @@ function validateUtilityMarkers(v) {
4848
5242
  "percent"
4849
5243
  ]);
4850
5244
  for (const f of v.fields) {
4851
- const optsArr = Array.isArray(f.options) ? f.options : [];
4852
- for (const o of optsArr) {
5245
+ for (const { option: o } of walkFieldOptions(f)) {
4853
5246
  const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
4854
5247
  const hasService = isServiceIdRef(o.service_id);
4855
5248
  const util = (_c = o.meta) == null ? void 0 : _c.utility;
@@ -5071,13 +5464,13 @@ function normalizeServiceRef(value) {
5071
5464
 
5072
5465
  // src/core/validate/steps/rates.ts
5073
5466
  function validateRates(v) {
5074
- var _a, _b, _c;
5467
+ var _a, _b;
5075
5468
  const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
5076
5469
  for (const f of v.fields) {
5077
5470
  if (!isMultiField(f)) continue;
5078
5471
  const baseRates = [];
5079
- for (const o of (_a = f.options) != null ? _a : []) {
5080
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
5472
+ for (const { option: o } of walkFieldOptions(f)) {
5473
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
5081
5474
  if (role !== "base") continue;
5082
5475
  const sid = o.service_id;
5083
5476
  if (!isServiceIdRef(sid)) continue;
@@ -5488,7 +5881,7 @@ function effectiveConstraints(v, tagId) {
5488
5881
  return out;
5489
5882
  }
5490
5883
  function validateConstraints(v) {
5491
- var _a, _b;
5884
+ var _a;
5492
5885
  for (const t of v.tags) {
5493
5886
  const eff = effectiveConstraints(v, t.id);
5494
5887
  const hasAnyRequired = Object.values(eff).some(
@@ -5497,7 +5890,7 @@ function validateConstraints(v) {
5497
5890
  if (!hasAnyRequired) continue;
5498
5891
  const visible = v.fieldsVisibleUnder(t.id);
5499
5892
  for (const f of visible) {
5500
- for (const o of (_a = f.options) != null ? _a : []) {
5893
+ for (const { option: o } of walkFieldOptions(f)) {
5501
5894
  if (!isServiceIdRef(o.service_id)) continue;
5502
5895
  const svc = getServiceCapability(v.serviceMap, o.service_id);
5503
5896
  if (!svc || typeof svc !== "object") continue;
@@ -5551,7 +5944,7 @@ function validateConstraints(v) {
5551
5944
  if (!row) continue;
5552
5945
  const from = row.from === true;
5553
5946
  const to = row.to === true;
5554
- const origin = String((_b = row.origin) != null ? _b : "");
5947
+ const origin = String((_a = row.origin) != null ? _a : "");
5555
5948
  v.errors.push({
5556
5949
  code: "constraint_overridden",
5557
5950
  severity: "warning",
@@ -5585,14 +5978,14 @@ function validateCustomFields(v) {
5585
5978
 
5586
5979
  // src/core/validate/steps/global-utility-guard.ts
5587
5980
  function validateGlobalUtilityGuard(v) {
5588
- var _a, _b, _c;
5981
+ var _a, _b;
5589
5982
  if (!v.options.globalUtilityGuard) return;
5590
5983
  let hasUtility = false;
5591
5984
  let hasBase = false;
5592
5985
  for (const f of v.fields) {
5593
- for (const o of (_a = f.options) != null ? _a : []) {
5986
+ for (const { option: o } of walkFieldOptions(f)) {
5594
5987
  if (!isServiceIdRef(o.service_id)) continue;
5595
- const role = (_c = (_b = o.pricing_role) != null ? _b : f.pricing_role) != null ? _c : "base";
5988
+ const role = (_b = (_a = o.pricing_role) != null ? _a : f.pricing_role) != null ? _b : "base";
5596
5989
  if (role === "base") hasBase = true;
5597
5990
  else if (role === "utility") hasUtility = true;
5598
5991
  if (hasUtility && hasBase) break;
@@ -5794,7 +6187,7 @@ function applyFilterAllowLists(tagId, fieldId, filter) {
5794
6187
  return true;
5795
6188
  }
5796
6189
  function collectServiceItems(args) {
5797
- var _a, _b, _c, _d, _e;
6190
+ var _a, _b, _c, _d;
5798
6191
  const filter = args.filter;
5799
6192
  const roleFilter = (_a = filter == null ? void 0 : filter.role) != null ? _a : "both";
5800
6193
  const where = filter == null ? void 0 : filter.where;
@@ -5844,7 +6237,7 @@ function collectServiceItems(args) {
5844
6237
  affectedIds: [`field:${f.id}`, `service:${String(fSid)}`]
5845
6238
  });
5846
6239
  }
5847
- for (const o of (_d = f.options) != null ? _d : []) {
6240
+ for (const { option: o } of walkFieldOptions(f)) {
5848
6241
  const oSid = o.service_id;
5849
6242
  if (!isServiceIdRef2(oSid)) continue;
5850
6243
  const role = fieldRoleOf(f, o);
@@ -5929,7 +6322,7 @@ function collectServiceItems(args) {
5929
6322
  }
5930
6323
  } else if (includeGroupFallbacks) {
5931
6324
  const allowPrimaries = new Set(
5932
- ((_e = args.visiblePrimaries) != null ? _e : []).map((x) => String(x))
6325
+ ((_d = args.visiblePrimaries) != null ? _d : []).map((x) => String(x))
5933
6326
  );
5934
6327
  for (const primaryKey of allowPrimaries) {
5935
6328
  const list = globalFb[primaryKey];
@@ -6010,17 +6403,15 @@ function affectedFromItems(items) {
6010
6403
  return uniq(ids);
6011
6404
  }
6012
6405
  function visibleGroupNodeIds(tag, fields) {
6013
- var _a;
6014
6406
  const ids = [tag.id];
6015
6407
  for (const f of fields) {
6016
- for (const o of (_a = f.options) != null ? _a : []) {
6408
+ for (const { option: o } of walkFieldOptions(f)) {
6017
6409
  ids.push(o.id);
6018
6410
  }
6019
6411
  }
6020
6412
  return uniq(ids);
6021
6413
  }
6022
6414
  function visibleGroupPrimaries(tag, fields) {
6023
- var _a;
6024
6415
  const prim = [];
6025
6416
  const tagSid = tag.service_id;
6026
6417
  if (typeof tagSid === "string" || typeof tagSid === "number" && Number.isFinite(tagSid)) {
@@ -6031,7 +6422,7 @@ function visibleGroupPrimaries(tag, fields) {
6031
6422
  if (typeof fsid === "string" || typeof fsid === "number" && Number.isFinite(fsid)) {
6032
6423
  prim.push(fsid);
6033
6424
  }
6034
- for (const o of (_a = f.options) != null ? _a : []) {
6425
+ for (const { option: o } of walkFieldOptions(f)) {
6035
6426
  const osid = o.service_id;
6036
6427
  if (typeof osid === "string" || typeof osid === "number" && Number.isFinite(osid)) {
6037
6428
  prim.push(osid);
@@ -6255,6 +6646,7 @@ function validate(props, ctx = {}) {
6255
6646
  validateStructure(v);
6256
6647
  validateIdentity(v);
6257
6648
  validateOptionMaps(v);
6649
+ validateVisibilityCycles(v);
6258
6650
  validateOrderKinds(v);
6259
6651
  v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
6260
6652
  const visSim = readVisibilitySimOpts(options);
@@ -6388,14 +6780,14 @@ var BuilderImpl = class {
6388
6780
  const showOptions = showSet.has(f.id);
6389
6781
  if (!showOptions) continue;
6390
6782
  if (!Array.isArray(f.options)) continue;
6391
- for (const o of f.options) {
6783
+ for (const { option: o, parentId } of walkFieldOptions(f)) {
6392
6784
  nodes.push({
6393
6785
  id: o.id,
6394
6786
  kind: "option",
6395
6787
  label: o.label
6396
6788
  });
6397
6789
  const e = {
6398
- from: f.id,
6790
+ from: parentId != null ? parentId : f.id,
6399
6791
  to: o.id,
6400
6792
  kind: "option",
6401
6793
  meta: { ownerField: f.id }
@@ -6442,7 +6834,7 @@ var BuilderImpl = class {
6442
6834
  return { nodes, edges };
6443
6835
  }
6444
6836
  cleanedProps() {
6445
- var _a, _b, _c, _d, _e;
6837
+ var _a, _b, _c, _d, _e, _f;
6446
6838
  const fieldIds = new Set(this.props.fields.map((f) => f.id));
6447
6839
  const optionIds = /* @__PURE__ */ new Set();
6448
6840
  this.optionOwnerById.forEach((_v, oid) => optionIds.add(oid));
@@ -6454,6 +6846,7 @@ var BuilderImpl = class {
6454
6846
  }
6455
6847
  const incMap = (_c = this.props.includes_for_buttons) != null ? _c : {};
6456
6848
  const excMap = (_d = this.props.excludes_for_buttons) != null ? _d : {};
6849
+ const effectMap = (_e = this.props.option_effects_for_buttons) != null ? _e : {};
6457
6850
  const includedByButtons = /* @__PURE__ */ new Set();
6458
6851
  const referencedKeys = /* @__PURE__ */ new Set();
6459
6852
  const referencedOwnerFields = /* @__PURE__ */ new Set();
@@ -6473,6 +6866,14 @@ var BuilderImpl = class {
6473
6866
  void fid;
6474
6867
  }
6475
6868
  }
6869
+ for (const [key, targets] of Object.entries(effectMap)) {
6870
+ referencedKeys.add(key);
6871
+ const owner = this.optionOwnerById.get(key);
6872
+ if (owner) referencedOwnerFields.add(owner.fieldId);
6873
+ for (const [fid, effect] of Object.entries(targets != null ? targets : {})) {
6874
+ if ((effect == null ? void 0 : effect.forceVisible) === true) includedByButtons.add(fid);
6875
+ }
6876
+ }
6476
6877
  const boundIds = /* @__PURE__ */ new Set();
6477
6878
  for (const f of this.props.fields) {
6478
6879
  const b = f.bind_id;
@@ -6490,6 +6891,7 @@ var BuilderImpl = class {
6490
6891
  return bound || included || referenced || !excluded;
6491
6892
  });
6492
6893
  const allowedTargets = new Set(fields.map((f) => f.id));
6894
+ const allowedFieldById = new Map(fields.map((f) => [f.id, f]));
6493
6895
  const pruneButtons = (src) => {
6494
6896
  if (!src) return void 0;
6495
6897
  const out2 = {};
@@ -6509,13 +6911,52 @@ var BuilderImpl = class {
6509
6911
  const excludes_for_buttons = pruneButtons(
6510
6912
  this.props.excludes_for_buttons
6511
6913
  );
6914
+ const pruneOptionEffects = (src) => {
6915
+ var _a2, _b2, _c2, _d2;
6916
+ if (!src) return void 0;
6917
+ const out2 = {};
6918
+ for (const [key, targets] of Object.entries(src)) {
6919
+ const keyIsValid = optionIds.has(key) || fieldIds.has(key);
6920
+ if (!keyIsValid) continue;
6921
+ const cleanedTargets = {};
6922
+ for (const [targetFieldId, effect] of Object.entries(
6923
+ targets != null ? targets : {}
6924
+ )) {
6925
+ const field = allowedFieldById.get(targetFieldId);
6926
+ if (!field || !effect) continue;
6927
+ const validOptionIds = fieldOptionIdSet(field);
6928
+ const include2 = Array.from(
6929
+ new Set((_a2 = effect.include) != null ? _a2 : [])
6930
+ ).filter((optionId) => validOptionIds.has(optionId));
6931
+ const exclude2 = Array.from(
6932
+ new Set((_b2 = effect.exclude) != null ? _b2 : [])
6933
+ ).filter((optionId) => validOptionIds.has(optionId));
6934
+ const next = {
6935
+ ...effect.forceVisible === true ? { forceVisible: true } : {},
6936
+ ...include2.length ? { include: include2 } : {},
6937
+ ...exclude2.length ? { exclude: exclude2 } : {}
6938
+ };
6939
+ if (next.forceVisible === true || ((_c2 = next.include) == null ? void 0 : _c2.length) || ((_d2 = next.exclude) == null ? void 0 : _d2.length)) {
6940
+ cleanedTargets[targetFieldId] = next;
6941
+ }
6942
+ }
6943
+ if (Object.keys(cleanedTargets).length) {
6944
+ out2[key] = cleanedTargets;
6945
+ }
6946
+ }
6947
+ return Object.keys(out2).length ? out2 : void 0;
6948
+ };
6949
+ const option_effects_for_buttons = pruneOptionEffects(
6950
+ this.props.option_effects_for_buttons
6951
+ );
6512
6952
  const out = {
6513
6953
  filters: this.props.filters.slice(),
6514
6954
  fields,
6515
6955
  ...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
6516
6956
  ...includes_for_buttons && { includes_for_buttons },
6517
6957
  ...excludes_for_buttons && { excludes_for_buttons },
6518
- schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
6958
+ ...option_effects_for_buttons && { option_effects_for_buttons },
6959
+ schema_version: (_f = this.props.schema_version) != null ? _f : "1.0",
6519
6960
  // keep fallbacks & other maps as-is
6520
6961
  ...this.props.fallbacks ? { fallbacks: this.props.fallbacks } : {}
6521
6962
  };
@@ -6528,12 +6969,15 @@ var BuilderImpl = class {
6528
6969
  return cloneDeep2(this.options);
6529
6970
  }
6530
6971
  visibleFields(tagId, selectedKeys) {
6972
+ return this.resolveVisibility(tagId, selectedKeys).fieldIds;
6973
+ }
6974
+ resolveVisibility(tagId, selectedKeys) {
6531
6975
  var _a;
6532
- return visibleFieldIdsUnder(this.props, tagId, {
6533
- selectedKeys: new Set(
6534
- (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
6535
- )
6536
- });
6976
+ return resolveVisibility(
6977
+ this.props,
6978
+ tagId,
6979
+ (_a = selectedKeys != null ? selectedKeys : this.options.selectedOptionKeys) != null ? _a : []
6980
+ );
6537
6981
  }
6538
6982
  getNodeMap() {
6539
6983
  if (!this._nodemap) this._nodemap = buildNodeMap(this.getProps());
@@ -6548,9 +6992,8 @@ var BuilderImpl = class {
6548
6992
  for (const t of this.props.filters) this.tagById.set(t.id, t);
6549
6993
  for (const f of this.props.fields) {
6550
6994
  this.fieldById.set(f.id, f);
6551
- if (Array.isArray(f.options)) {
6552
- for (const o of f.options)
6553
- this.optionOwnerById.set(o.id, { fieldId: f.id });
6995
+ for (const [optionId, owner] of optionOwnerMap([f])) {
6996
+ this.optionOwnerById.set(optionId, { fieldId: owner.fieldId });
6554
6997
  }
6555
6998
  }
6556
6999
  }
@@ -7422,6 +7865,11 @@ function rateIssueAffectsCandidate(error, candidateId, candidateFieldId, primary
7422
7865
  });
7423
7866
  }
7424
7867
 
7868
+ // src/react/inputs/registry.ts
7869
+ function resolveInputDescriptor(registry, kind, variant) {
7870
+ return registry.get(kind, variant);
7871
+ }
7872
+
7425
7873
  // src/react/canvas/editor/editor-ids.ts
7426
7874
  function uniqueId(ctx, base) {
7427
7875
  var _a, _b;
@@ -7493,42 +7941,133 @@ function bumpSuffix(old) {
7493
7941
  return `${stem}${parseInt(m[2], 10) + 1}`;
7494
7942
  }
7495
7943
 
7496
- // src/react/canvas/editor/editor-duplicate.ts
7497
- function duplicate(ctx, ref, opts = {}) {
7498
- const snapBefore = ctx.makeSnapshot("duplicate:before");
7499
- try {
7500
- let newId2 = "";
7501
- ctx.transact("duplicate", () => {
7502
- newId2 = duplicateInPlace(ctx, ref, opts);
7503
- });
7504
- return newId2;
7505
- } catch (err) {
7506
- ctx.loadSnapshot(snapBefore, "undo");
7507
- throw err;
7944
+ // src/react/canvas/editor/editor-utils.ts
7945
+ function ownerOfOption(props, optionId) {
7946
+ var _a;
7947
+ for (const f of (_a = props.fields) != null ? _a : []) {
7948
+ const found = findOptionLocationInField(f, optionId);
7949
+ if (found) return { fieldId: f.id, index: found.index };
7508
7950
  }
7951
+ return null;
7509
7952
  }
7510
- function duplicateMany(ctx, ids, opts = {}) {
7511
- const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
7512
- if (!ordered.length) return [];
7513
- const snapBefore = ctx.makeSnapshot("duplicateMany:before");
7514
- try {
7515
- const created = [];
7516
- ctx.transact("duplicateMany", () => {
7517
- var _a, _b, _c;
7518
- const props = ctx.getProps();
7519
- const selectedFields = /* @__PURE__ */ new Set();
7520
- for (const id of ordered) {
7521
- if (ctx.isFieldId(id) && ((_a = props.fields) != null ? _a : []).some((f) => f.id === id)) {
7522
- selectedFields.add(id);
7523
- }
7524
- }
7525
- for (const id of ordered) {
7526
- if (ctx.isTagId(id)) {
7527
- if (!((_b = ctx.getProps().filters) != null ? _b : []).some((t) => t.id === id)) continue;
7528
- created.push(
7529
- duplicateInPlace(ctx, { kind: "tag", id }, opts)
7530
- );
7531
- continue;
7953
+ function findMutableOption(props, optionId) {
7954
+ var _a;
7955
+ for (const field of (_a = props.fields) != null ? _a : []) {
7956
+ const found = findOptionLocationInField(field, optionId);
7957
+ if (found) return { field, ...found };
7958
+ }
7959
+ return void 0;
7960
+ }
7961
+ function collectFieldOptionIds(field) {
7962
+ const out = [];
7963
+ const visit = (options) => {
7964
+ for (const option of options != null ? options : []) {
7965
+ out.push(String(option.id));
7966
+ visit(option.children);
7967
+ }
7968
+ };
7969
+ visit(field == null ? void 0 : field.options);
7970
+ return out;
7971
+ }
7972
+ function findOptionLocationInField(field, optionId) {
7973
+ const visit = (siblings, parent) => {
7974
+ if (!siblings) return void 0;
7975
+ const index = siblings.findIndex((option) => option.id === optionId);
7976
+ if (index >= 0) {
7977
+ return {
7978
+ option: siblings[index],
7979
+ siblings,
7980
+ index,
7981
+ parent
7982
+ };
7983
+ }
7984
+ for (const option of siblings) {
7985
+ const found = visit(option.children, option);
7986
+ if (found) return found;
7987
+ }
7988
+ return void 0;
7989
+ };
7990
+ return visit(field.options);
7991
+ }
7992
+ function hasFieldOptions(field) {
7993
+ return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
7994
+ }
7995
+ function isActualButtonField(field) {
7996
+ return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
7997
+ }
7998
+ function clearFieldButtonReceiverMaps(props, fieldId) {
7999
+ var _a, _b, _c;
8000
+ if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
8001
+ delete props.includes_for_buttons[fieldId];
8002
+ }
8003
+ if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
8004
+ delete props.excludes_for_buttons[fieldId];
8005
+ }
8006
+ if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
8007
+ delete props.includes_for_buttons;
8008
+ }
8009
+ if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
8010
+ delete props.excludes_for_buttons;
8011
+ }
8012
+ if ((_c = props.option_effects_for_buttons) == null ? void 0 : _c[fieldId]) {
8013
+ delete props.option_effects_for_buttons[fieldId];
8014
+ }
8015
+ if (props.option_effects_for_buttons && Object.keys(props.option_effects_for_buttons).length === 0) {
8016
+ delete props.option_effects_for_buttons;
8017
+ }
8018
+ }
8019
+ function ensureServiceExists(opts, id) {
8020
+ if (typeof opts.serviceExists === "function") {
8021
+ if (!opts.serviceExists(id)) {
8022
+ throw new Error(`service_not_found:${String(id)}`);
8023
+ }
8024
+ return;
8025
+ }
8026
+ if (opts.serviceMap) {
8027
+ if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
8028
+ throw new Error(`service_not_found:${String(id)}`);
8029
+ }
8030
+ return;
8031
+ }
8032
+ throw new Error("service_checker_missing");
8033
+ }
8034
+
8035
+ // src/react/canvas/editor/editor-duplicate.ts
8036
+ function duplicate(ctx, ref, opts = {}) {
8037
+ const snapBefore = ctx.makeSnapshot("duplicate:before");
8038
+ try {
8039
+ let newId2 = "";
8040
+ ctx.transact("duplicate", () => {
8041
+ newId2 = duplicateInPlace(ctx, ref, opts);
8042
+ });
8043
+ return newId2;
8044
+ } catch (err) {
8045
+ ctx.loadSnapshot(snapBefore, "undo");
8046
+ throw err;
8047
+ }
8048
+ }
8049
+ function duplicateMany(ctx, ids, opts = {}) {
8050
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
8051
+ if (!ordered.length) return [];
8052
+ const snapBefore = ctx.makeSnapshot("duplicateMany:before");
8053
+ try {
8054
+ const created = [];
8055
+ ctx.transact("duplicateMany", () => {
8056
+ var _a, _b, _c;
8057
+ const props = ctx.getProps();
8058
+ const selectedFields = /* @__PURE__ */ new Set();
8059
+ for (const id of ordered) {
8060
+ if (ctx.isFieldId(id) && ((_a = props.fields) != null ? _a : []).some((f) => f.id === id)) {
8061
+ selectedFields.add(id);
8062
+ }
8063
+ }
8064
+ for (const id of ordered) {
8065
+ if (ctx.isTagId(id)) {
8066
+ if (!((_b = ctx.getProps().filters) != null ? _b : []).some((t) => t.id === id)) continue;
8067
+ created.push(
8068
+ duplicateInPlace(ctx, { kind: "tag", id }, opts)
8069
+ );
8070
+ continue;
7532
8071
  }
7533
8072
  if (ctx.isFieldId(id)) {
7534
8073
  if (!((_c = ctx.getProps().fields) != null ? _c : []).some((f) => f.id === id)) continue;
@@ -7567,14 +8106,66 @@ function duplicateInPlace(ctx, ref, opts = {}) {
7567
8106
  return duplicateOption(ctx, ref.fieldId, ref.id, opts);
7568
8107
  }
7569
8108
  function ownerFieldOfOption(props, optionId) {
7570
- var _a, _b;
8109
+ var _a;
7571
8110
  for (const field of (_a = props.fields) != null ? _a : []) {
7572
- if (((_b = field.options) != null ? _b : []).some((o) => o.id === optionId)) {
8111
+ if (findMutableOption({ ...props, fields: [field] }, optionId)) {
7573
8112
  return { fieldId: field.id };
7574
8113
  }
7575
8114
  }
7576
8115
  return null;
7577
8116
  }
8117
+ function cloneOptionTree(ctx, fieldId, option, opts, optionIdMap) {
8118
+ var _a, _b, _c, _d;
8119
+ const newId2 = ctx.uniqueOptionId(
8120
+ fieldId,
8121
+ ((_a = opts.optionIdStrategy) != null ? _a : defaultOptionIdStrategy)(option.id)
8122
+ );
8123
+ optionIdMap.set(option.id, newId2);
8124
+ const children = (_b = option.children) == null ? void 0 : _b.map(
8125
+ (child) => cloneOptionTree(ctx, fieldId, child, opts, optionIdMap)
8126
+ );
8127
+ return {
8128
+ ...option,
8129
+ id: newId2,
8130
+ label: ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = option.label) != null ? _d : option.id),
8131
+ ...(children == null ? void 0 : children.length) ? { children } : {}
8132
+ };
8133
+ }
8134
+ function remapEffect(effect, optionIdMap) {
8135
+ const remapList = (values) => values == null ? void 0 : values.map((value) => {
8136
+ var _a;
8137
+ return (_a = optionIdMap.get(value)) != null ? _a : value;
8138
+ });
8139
+ return {
8140
+ ...effect,
8141
+ ...effect.include ? { include: remapList(effect.include) } : {},
8142
+ ...effect.exclude ? { exclude: remapList(effect.exclude) } : {}
8143
+ };
8144
+ }
8145
+ function copyOptionEffects(props, args) {
8146
+ var _a, _b, _c, _d, _e;
8147
+ const source = props.option_effects_for_buttons;
8148
+ if (!source) return;
8149
+ const next = {
8150
+ ...source
8151
+ };
8152
+ const triggerIdMap = (_a = args.triggerIdMap) != null ? _a : /* @__PURE__ */ new Map();
8153
+ const targetFieldIdMap = (_b = args.targetFieldIdMap) != null ? _b : /* @__PURE__ */ new Map();
8154
+ const optionIdMap = (_c = args.optionIdMap) != null ? _c : /* @__PURE__ */ new Map();
8155
+ for (const [oldTriggerId, targetMap] of Object.entries(source)) {
8156
+ const newTriggerId = triggerIdMap.get(oldTriggerId);
8157
+ if (!newTriggerId) continue;
8158
+ const copiedTargets = {
8159
+ ...(_d = next[newTriggerId]) != null ? _d : {}
8160
+ };
8161
+ for (const [oldTargetFieldId, effect] of Object.entries(targetMap != null ? targetMap : {})) {
8162
+ const newTargetFieldId = (_e = targetFieldIdMap.get(oldTargetFieldId)) != null ? _e : oldTargetFieldId;
8163
+ copiedTargets[newTargetFieldId] = remapEffect(effect, optionIdMap);
8164
+ }
8165
+ next[newTriggerId] = copiedTargets;
8166
+ }
8167
+ props.option_effects_for_buttons = next;
8168
+ }
7578
8169
  function duplicateTag(ctx, tagId, opts) {
7579
8170
  var _a, _b, _c, _d;
7580
8171
  const props = ctx.getProps();
@@ -7630,7 +8221,7 @@ function duplicateTag(ctx, tagId, opts) {
7630
8221
  return id;
7631
8222
  }
7632
8223
  function duplicateField(ctx, fieldId, opts) {
7633
- var _a, _b, _c, _d, _e, _f, _g;
8224
+ var _a, _b, _c, _d, _e, _f;
7634
8225
  const props = ctx.getProps();
7635
8226
  const fields = (_a = props.fields) != null ? _a : [];
7636
8227
  const src = fields.find((f) => f.id === fieldId);
@@ -7638,21 +8229,10 @@ function duplicateField(ctx, fieldId, opts) {
7638
8229
  const id = (_b = opts.id) != null ? _b : ctx.uniqueId(src.id);
7639
8230
  const label = ((_c = opts.labelStrategy) != null ? _c : nextCopyLabel)((_d = src.label) != null ? _d : id);
7640
8231
  const name = opts.nameStrategy ? opts.nameStrategy(src.name) : nextCopyName(src.name);
7641
- const optId = (old) => {
7642
- var _a2;
7643
- return ctx.uniqueOptionId(
7644
- id,
7645
- ((_a2 = opts.optionIdStrategy) != null ? _a2 : defaultOptionIdStrategy)(old)
7646
- );
7647
- };
7648
- const clonedOptions = ((_e = src.options) != null ? _e : []).map((o) => {
7649
- var _a2, _b2;
7650
- return {
7651
- ...o,
7652
- id: optId(o.id),
7653
- label: ((_a2 = opts.labelStrategy) != null ? _a2 : nextCopyLabel)((_b2 = o.label) != null ? _b2 : o.id)
7654
- };
7655
- });
8232
+ const optionIdMap = /* @__PURE__ */ new Map();
8233
+ const clonedOptions = ((_e = src.options) != null ? _e : []).map(
8234
+ (o) => cloneOptionTree(ctx, id, o, opts, optionIdMap)
8235
+ );
7656
8236
  const cloned = {
7657
8237
  ...src,
7658
8238
  id,
@@ -7661,14 +8241,8 @@ function duplicateField(ctx, fieldId, opts) {
7661
8241
  bind_id: ((_f = opts.copyBindings) != null ? _f : true) ? src.bind_id : void 0,
7662
8242
  options: clonedOptions
7663
8243
  };
7664
- const optionIdMap = /* @__PURE__ */ new Map();
7665
- ((_g = src.options) != null ? _g : []).forEach((o, i) => {
7666
- var _a2, _b2;
7667
- const newOptId = (_b2 = (_a2 = clonedOptions[i]) == null ? void 0 : _a2.id) != null ? _b2 : o.id;
7668
- optionIdMap.set(o.id, newOptId);
7669
- });
7670
8244
  ctx.patchProps((p) => {
7671
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
8245
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g;
7672
8246
  const arr = (_a2 = p.fields) != null ? _a2 : [];
7673
8247
  const idx = arr.findIndex((f) => f.id === fieldId);
7674
8248
  arr.splice(idx + 1, 0, cloned);
@@ -7706,52 +8280,56 @@ function duplicateField(ctx, fieldId, opts) {
7706
8280
  }
7707
8281
  if (optionIdMap.has(key)) {
7708
8282
  const newKey = optionIdMap.get(key);
7709
- const merged = /* @__PURE__ */ new Set([...(_g2 = nextMap[newKey]) != null ? _g2 : [], ...targets]);
8283
+ const merged = /* @__PURE__ */ new Set([...(_g = nextMap[newKey]) != null ? _g : [], ...targets]);
7710
8284
  nextMap[newKey] = Array.from(merged);
7711
8285
  }
7712
8286
  }
7713
8287
  p[mapKey] = nextMap;
7714
8288
  }
8289
+ copyOptionEffects(p, {
8290
+ triggerIdMap: new Map([
8291
+ [fieldId, id],
8292
+ ...Array.from(optionIdMap.entries())
8293
+ ]),
8294
+ targetFieldIdMap: /* @__PURE__ */ new Map([[fieldId, id]]),
8295
+ optionIdMap
8296
+ });
7715
8297
  }
7716
8298
  });
7717
8299
  return id;
7718
8300
  }
7719
8301
  function duplicateOption(ctx, fieldId, optionId, opts) {
7720
- var _a, _b, _c, _d, _e, _f;
7721
8302
  const props = ctx.getProps();
7722
- const fields = (_a = props.fields) != null ? _a : [];
7723
- const f = fields.find((x) => x.id === fieldId);
7724
- if (!f) throw new Error(`Field not found: ${fieldId}`);
7725
- const optIdx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
7726
- if (optIdx < 0) {
7727
- throw new Error(`Option not found: ${fieldId}::${optionId}`);
8303
+ const location = findMutableOption(props, optionId);
8304
+ if (!location || location.field.id !== fieldId) {
8305
+ throw new Error(`Option not found: ${fieldId}/${optionId}`);
7728
8306
  }
7729
- const src = ((_c = f.options) != null ? _c : [])[optIdx];
7730
- const newId2 = ctx.uniqueOptionId(
7731
- fieldId,
7732
- ((_d = opts.optionIdStrategy) != null ? _d : defaultOptionIdStrategy)(src.id)
7733
- );
7734
- const newLabel = ((_e = opts.labelStrategy) != null ? _e : nextCopyLabel)((_f = src.label) != null ? _f : src.id);
8307
+ const src = location.option;
8308
+ const optionIdMap = /* @__PURE__ */ new Map();
8309
+ const clone2 = cloneOptionTree(ctx, fieldId, src, opts, optionIdMap);
8310
+ const newId2 = clone2.id;
7735
8311
  ctx.patchProps((p) => {
7736
- var _a2, _b2, _c2;
7737
- const fld = ((_a2 = p.fields) != null ? _a2 : []).find((x) => x.id === fieldId);
7738
- const arr = (_b2 = fld.options) != null ? _b2 : [];
7739
- const clone2 = { ...src, id: newId2, label: newLabel };
7740
- arr.splice(optIdx + 1, 0, clone2);
7741
- fld.options = arr;
8312
+ var _a;
8313
+ const current = findMutableOption(p, optionId);
8314
+ if (!current) return;
8315
+ current.siblings.splice(current.index + 1, 0, clone2);
7742
8316
  if (opts.copyOptionMaps) {
7743
- const oldKey = `${fieldId}::${optionId}`;
7744
- const newKey = `${fieldId}::${newId2}`;
7745
8317
  for (const mapKey of [
7746
8318
  "includes_for_buttons",
7747
8319
  "excludes_for_buttons"
7748
8320
  ]) {
7749
- const m = (_c2 = p[mapKey]) != null ? _c2 : {};
7750
- if (m[oldKey]) {
7751
- m[newKey] = Array.from(new Set(m[oldKey]));
7752
- p[mapKey] = m;
8321
+ const m = (_a = p[mapKey]) != null ? _a : {};
8322
+ for (const [oldKey, newKey] of optionIdMap.entries()) {
8323
+ if (m[oldKey]) {
8324
+ m[newKey] = Array.from(new Set(m[oldKey]));
8325
+ p[mapKey] = m;
8326
+ }
7753
8327
  }
7754
8328
  }
8329
+ copyOptionEffects(p, {
8330
+ triggerIdMap: optionIdMap,
8331
+ optionIdMap
8332
+ });
7755
8333
  }
7756
8334
  });
7757
8335
  return newId2;
@@ -7813,54 +8391,6 @@ function removeNotice(ctx, id) {
7813
8391
 
7814
8392
  // src/react/canvas/editor/editor-nodes.ts
7815
8393
  import { cloneDeep as cloneDeep3 } from "lodash-es";
7816
-
7817
- // src/react/canvas/editor/editor-utils.ts
7818
- function ownerOfOption(props, optionId) {
7819
- var _a, _b;
7820
- for (const f of (_a = props.fields) != null ? _a : []) {
7821
- const idx = ((_b = f.options) != null ? _b : []).findIndex((o) => o.id === optionId);
7822
- if (idx >= 0) return { fieldId: f.id, index: idx };
7823
- }
7824
- return null;
7825
- }
7826
- function hasFieldOptions(field) {
7827
- return Array.isArray(field == null ? void 0 : field.options) && field.options.length > 0;
7828
- }
7829
- function isActualButtonField(field) {
7830
- return (field == null ? void 0 : field.button) === true && !hasFieldOptions(field);
7831
- }
7832
- function clearFieldButtonReceiverMaps(props, fieldId) {
7833
- var _a, _b;
7834
- if ((_a = props.includes_for_buttons) == null ? void 0 : _a[fieldId]) {
7835
- delete props.includes_for_buttons[fieldId];
7836
- }
7837
- if ((_b = props.excludes_for_buttons) == null ? void 0 : _b[fieldId]) {
7838
- delete props.excludes_for_buttons[fieldId];
7839
- }
7840
- if (props.includes_for_buttons && Object.keys(props.includes_for_buttons).length === 0) {
7841
- delete props.includes_for_buttons;
7842
- }
7843
- if (props.excludes_for_buttons && Object.keys(props.excludes_for_buttons).length === 0) {
7844
- delete props.excludes_for_buttons;
7845
- }
7846
- }
7847
- function ensureServiceExists(opts, id) {
7848
- if (typeof opts.serviceExists === "function") {
7849
- if (!opts.serviceExists(id)) {
7850
- throw new Error(`service_not_found:${String(id)}`);
7851
- }
7852
- return;
7853
- }
7854
- if (opts.serviceMap) {
7855
- if (!Object.prototype.hasOwnProperty.call(opts.serviceMap, id)) {
7856
- throw new Error(`service_not_found:${String(id)}`);
7857
- }
7858
- return;
7859
- }
7860
- throw new Error("service_checker_missing");
7861
- }
7862
-
7863
- // src/react/canvas/editor/editor-nodes.ts
7864
8394
  var RELATION_MAP_KEYS = [
7865
8395
  "includes_for_buttons",
7866
8396
  "excludes_for_buttons",
@@ -7920,6 +8450,43 @@ function cleanRelationMapsForDeleted(p, deleted) {
7920
8450
  if (!Object.keys(map).length) delete p[key];
7921
8451
  }
7922
8452
  }
8453
+ function cleanOptionEffectsForDeleted(p, deleted) {
8454
+ var _a, _b;
8455
+ const map = p.option_effects_for_buttons;
8456
+ if (!map) return;
8457
+ for (const triggerId of Object.keys(map)) {
8458
+ if (deleted.has(String(triggerId))) {
8459
+ delete map[triggerId];
8460
+ continue;
8461
+ }
8462
+ const targets = map[triggerId];
8463
+ for (const targetFieldId of Object.keys(targets != null ? targets : {})) {
8464
+ if (deleted.has(String(targetFieldId))) {
8465
+ delete targets[targetFieldId];
8466
+ continue;
8467
+ }
8468
+ const effect = targets[targetFieldId];
8469
+ if (!effect) continue;
8470
+ if (effect.include) {
8471
+ effect.include = effect.include.filter(
8472
+ (optionId) => !deleted.has(String(optionId))
8473
+ );
8474
+ if (!effect.include.length) delete effect.include;
8475
+ }
8476
+ if (effect.exclude) {
8477
+ effect.exclude = effect.exclude.filter(
8478
+ (optionId) => !deleted.has(String(optionId))
8479
+ );
8480
+ if (!effect.exclude.length) delete effect.exclude;
8481
+ }
8482
+ if (effect.forceVisible !== true && !((_a = effect.include) == null ? void 0 : _a.length) && !((_b = effect.exclude) == null ? void 0 : _b.length)) {
8483
+ delete targets[targetFieldId];
8484
+ }
8485
+ }
8486
+ if (!Object.keys(targets != null ? targets : {}).length) delete map[triggerId];
8487
+ }
8488
+ if (!Object.keys(map).length) delete p.option_effects_for_buttons;
8489
+ }
7923
8490
  function cleanOrderForTagsForDeleted(p, deleted) {
7924
8491
  var _a, _b;
7925
8492
  const map = p.order_for_tags;
@@ -7955,28 +8522,37 @@ function applyDeleteCleanup(p, deleted) {
7955
8522
  cleanTagRelationsForDeleted(p, deleted);
7956
8523
  cleanFieldBindsForDeleted(p, deleted);
7957
8524
  cleanRelationMapsForDeleted(p, deleted);
8525
+ cleanOptionEffectsForDeleted(p, deleted);
7958
8526
  cleanOrderForTagsForDeleted(p, deleted);
7959
8527
  cleanNoticesForDeleted(p, deleted);
7960
8528
  }
8529
+ function collectOptionSubtreeIds(option) {
8530
+ var _a;
8531
+ return [
8532
+ String(option.id),
8533
+ ...((_a = option.children) != null ? _a : []).flatMap((child) => collectOptionSubtreeIds(child))
8534
+ ];
8535
+ }
7961
8536
  function removeOptionInPlace(p, optionId) {
7962
8537
  var _a;
7963
- const owner = ownerOfOption(p, optionId);
7964
- if (!owner) return false;
7965
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
7966
- if (!(f == null ? void 0 : f.options)) return false;
7967
- const before = f.options.length;
7968
- f.options = f.options.filter((o) => o.id !== optionId);
7969
- return f.options.length !== before;
8538
+ const found = findMutableOption(p, optionId);
8539
+ if (!found) return [];
8540
+ const deleted = collectOptionSubtreeIds(found.option);
8541
+ found.siblings.splice(found.index, 1);
8542
+ if (found.parent && ((_a = found.parent.children) == null ? void 0 : _a.length) === 0) {
8543
+ delete found.parent.children;
8544
+ }
8545
+ return deleted;
7970
8546
  }
7971
8547
  function removeFieldInPlace(p, fieldId) {
7972
- var _a, _b, _c, _d, _e;
8548
+ var _a, _b, _c, _d;
7973
8549
  const field = ((_a = p.fields) != null ? _a : []).find((f) => f.id === fieldId);
7974
8550
  if (!field) return [];
7975
- const deleted = [fieldId, ...((_b = field.options) != null ? _b : []).map((o) => String(o.id))];
7976
- const before = ((_c = p.fields) != null ? _c : []).length;
7977
- p.fields = ((_d = p.fields) != null ? _d : []).filter((f) => f.id !== fieldId);
8551
+ const deleted = [fieldId, ...collectFieldOptionIds(field)];
8552
+ const before = ((_b = p.fields) != null ? _b : []).length;
8553
+ p.fields = ((_c = p.fields) != null ? _c : []).filter((f) => f.id !== fieldId);
7978
8554
  clearFieldButtonReceiverMaps(p, fieldId);
7979
- return ((_e = p.fields) != null ? _e : []).length !== before ? deleted : [];
8555
+ return ((_d = p.fields) != null ? _d : []).length !== before ? deleted : [];
7980
8556
  }
7981
8557
  function removeTagInPlace(p, tagId) {
7982
8558
  var _a, _b, _c;
@@ -7989,7 +8565,7 @@ function reLabel(ctx, id, nextLabel) {
7989
8565
  ctx.exec({
7990
8566
  name: "reLabel",
7991
8567
  do: () => ctx.patchProps((p) => {
7992
- var _a, _b, _c, _d, _e, _f, _g;
8568
+ var _a, _b, _c, _d, _e, _f;
7993
8569
  if (ctx.isTagId(id)) {
7994
8570
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
7995
8571
  if (!t) return;
@@ -7999,19 +8575,16 @@ function reLabel(ctx, id, nextLabel) {
7999
8575
  return;
8000
8576
  }
8001
8577
  if (ctx.isOptionId(id)) {
8002
- const own = ownerOfOption(p, id);
8003
- if (!own) return;
8004
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
8005
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
8578
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
8006
8579
  if (!o) return;
8007
- if (((_e = o.label) != null ? _e : "") === label) return;
8580
+ if (((_d = o.label) != null ? _d : "") === label) return;
8008
8581
  o.label = label;
8009
8582
  ctx.api.refreshGraph();
8010
8583
  return;
8011
8584
  }
8012
- const fld = ((_f = p.fields) != null ? _f : []).find((x) => x.id === id);
8585
+ const fld = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
8013
8586
  if (!fld) return;
8014
- if (((_g = fld.label) != null ? _g : "") === label) return;
8587
+ if (((_f = fld.label) != null ? _f : "") === label) return;
8015
8588
  fld.label = label;
8016
8589
  ctx.api.refreshGraph();
8017
8590
  }),
@@ -8101,11 +8674,7 @@ function updateOption(ctx, optionId, patch) {
8101
8674
  name: "updateOption",
8102
8675
  do: () => ctx.patchProps((p) => {
8103
8676
  var _a;
8104
- const owner = ownerOfOption(p, optionId);
8105
- if (!owner) return;
8106
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
8107
- if (!(f == null ? void 0 : f.options)) return;
8108
- const o = f.options.find((x) => x.id === optionId);
8677
+ const o = (_a = findMutableOption(p, optionId)) == null ? void 0 : _a.option;
8109
8678
  if (o) Object.assign(o, patch);
8110
8679
  }),
8111
8680
  undo: () => ctx.undo()
@@ -8118,9 +8687,9 @@ function removeOption(ctx, optionId) {
8118
8687
  ctx.exec({
8119
8688
  name: "removeOption",
8120
8689
  do: () => ctx.patchProps((p) => {
8121
- const removed = removeOptionInPlace(p, optionId);
8122
- if (!removed) return;
8123
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([optionId]));
8690
+ const removedIds = removeOptionInPlace(p, optionId);
8691
+ if (!removedIds.length) return;
8692
+ applyDeleteCleanup(p, new Set(removedIds));
8124
8693
  }),
8125
8694
  undo: () => ctx.undo()
8126
8695
  });
@@ -8131,7 +8700,7 @@ function editLabel(ctx, id, label) {
8131
8700
  ctx.exec({
8132
8701
  name: "editLabel",
8133
8702
  do: () => ctx.patchProps((p) => {
8134
- var _a, _b, _c, _d;
8703
+ var _a, _b, _c;
8135
8704
  if (ctx.isTagId(id)) {
8136
8705
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
8137
8706
  if (t) t.label = next;
@@ -8143,10 +8712,7 @@ function editLabel(ctx, id, label) {
8143
8712
  return;
8144
8713
  }
8145
8714
  if (ctx.isOptionId(id)) {
8146
- const own = ownerOfOption(p, id);
8147
- if (!own) return;
8148
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
8149
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
8715
+ const o = (_c = findMutableOption(p, id)) == null ? void 0 : _c.option;
8150
8716
  if (o) o.label = next;
8151
8717
  return;
8152
8718
  }
@@ -8171,7 +8737,7 @@ function setService(ctx, id, input) {
8171
8737
  ctx.exec({
8172
8738
  name: "setService",
8173
8739
  do: () => ctx.patchProps((p) => {
8174
- var _a, _b, _c, _d, _e, _f;
8740
+ var _a, _b, _c, _d, _e;
8175
8741
  const hasSidKey = Object.prototype.hasOwnProperty.call(
8176
8742
  input,
8177
8743
  "service_id"
@@ -8189,12 +8755,9 @@ function setService(ctx, id, input) {
8189
8755
  return;
8190
8756
  }
8191
8757
  if (ctx.isOptionId(id)) {
8192
- const own = ownerOfOption(p, id);
8193
- if (!own) return;
8194
- const f2 = ((_b = p.fields) != null ? _b : []).find((x) => x.id === own.fieldId);
8195
- const o = (_c = f2 == null ? void 0 : f2.options) == null ? void 0 : _c.find((x) => x.id === id);
8758
+ const o = (_b = findMutableOption(p, id)) == null ? void 0 : _b.option;
8196
8759
  if (!o) return;
8197
- const currentRole = (_d = o.pricing_role) != null ? _d : "base";
8760
+ const currentRole = (_c = o.pricing_role) != null ? _c : "base";
8198
8761
  const role = nextRole != null ? nextRole : currentRole;
8199
8762
  if (role === "utility") {
8200
8763
  if (hasSidKey && sid !== void 0) {
@@ -8215,7 +8778,7 @@ function setService(ctx, id, input) {
8215
8778
  }
8216
8779
  return;
8217
8780
  }
8218
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === id);
8781
+ const f = ((_d = p.fields) != null ? _d : []).find((x) => x.id === id);
8219
8782
  if (!f) {
8220
8783
  throw new Error(
8221
8784
  'setService only supports tag ("t:*"), option ("o:*"), or field ("f:*") ids'
@@ -8226,7 +8789,7 @@ function setService(ctx, id, input) {
8226
8789
  if (nextRole) {
8227
8790
  f.pricing_role = nextRole;
8228
8791
  }
8229
- const effectiveRole = (_f = f.pricing_role) != null ? _f : "base";
8792
+ const effectiveRole = (_e = f.pricing_role) != null ? _e : "base";
8230
8793
  if (isOptionBased) {
8231
8794
  if (hasSidKey) {
8232
8795
  ctx.api.emit("error", {
@@ -8332,18 +8895,21 @@ function addField(ctx, partial) {
8332
8895
  p.fields = ((_a2 = p.fields) != null ? _a2 : []).filter((f) => f.id !== id);
8333
8896
  })
8334
8897
  });
8898
+ return id;
8335
8899
  }
8336
8900
  function updateField(ctx, id, patch) {
8337
8901
  let prev;
8338
8902
  let prevIncludesForButton;
8339
8903
  let prevExcludesForButton;
8904
+ let prevOptionEffectsForButton;
8340
8905
  ctx.exec({
8341
8906
  name: "updateField",
8342
8907
  do: () => ctx.patchProps((p) => {
8343
- var _a, _b, _c, _d, _e, _f, _g;
8908
+ var _a, _b, _c, _d, _e, _f, _g, _h;
8344
8909
  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;
8345
8910
  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;
8346
- p.fields = ((_g = p.fields) != null ? _g : []).map((f) => {
8911
+ prevOptionEffectsForButton = ((_g = p.option_effects_for_buttons) == null ? void 0 : _g[id]) ? cloneDeep3(p.option_effects_for_buttons[id]) : void 0;
8912
+ p.fields = ((_h = p.fields) != null ? _h : []).map((f) => {
8347
8913
  if (f.id !== id) return f;
8348
8914
  prev = cloneDeep3(f);
8349
8915
  const nextField = { ...f, ...patch };
@@ -8354,7 +8920,7 @@ function updateField(ctx, id, patch) {
8354
8920
  });
8355
8921
  }),
8356
8922
  undo: () => ctx.patchProps((p) => {
8357
- var _a, _b, _c;
8923
+ var _a, _b, _c, _d;
8358
8924
  p.fields = ((_a = p.fields) != null ? _a : []).map(
8359
8925
  (f) => f.id === id && prev ? prev : f
8360
8926
  );
@@ -8372,6 +8938,12 @@ function updateField(ctx, id, patch) {
8372
8938
  [id]: [...prevExcludesForButton]
8373
8939
  };
8374
8940
  }
8941
+ if (prevOptionEffectsForButton) {
8942
+ p.option_effects_for_buttons = {
8943
+ ...(_d = p.option_effects_for_buttons) != null ? _d : {},
8944
+ [id]: cloneDeep3(prevOptionEffectsForButton)
8945
+ };
8946
+ }
8375
8947
  })
8376
8948
  });
8377
8949
  }
@@ -8418,9 +8990,9 @@ function remove(ctx, id) {
8418
8990
  ctx.exec({
8419
8991
  name: "removeOption",
8420
8992
  do: () => ctx.patchProps((p) => {
8421
- const removed = removeOptionInPlace(p, key);
8422
- if (!removed) return;
8423
- applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
8993
+ const removedIds = removeOptionInPlace(p, key);
8994
+ if (!removedIds.length) return;
8995
+ applyDeleteCleanup(p, new Set(removedIds));
8424
8996
  }),
8425
8997
  undo: () => ctx.undo()
8426
8998
  });
@@ -8437,10 +9009,7 @@ function removeMany(ctx, ids) {
8437
9009
  const existingFieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
8438
9010
  const existingTagIds = new Set(((_b = p.filters) != null ? _b : []).map((t) => String(t.id)));
8439
9011
  const existingOptionIds = new Set(
8440
- ((_c = p.fields) != null ? _c : []).flatMap((f) => {
8441
- var _a2;
8442
- return ((_a2 = f.options) != null ? _a2 : []).map((o) => String(o.id));
8443
- })
9012
+ ((_c = p.fields) != null ? _c : []).flatMap((f) => collectFieldOptionIds(f))
8444
9013
  );
8445
9014
  const fieldIds = ordered.filter((id) => ctx.isFieldId(id) && existingFieldIds.has(id));
8446
9015
  const fieldIdSet = new Set(fieldIds);
@@ -8453,7 +9022,9 @@ function removeMany(ctx, ids) {
8453
9022
  });
8454
9023
  const deleted = /* @__PURE__ */ new Set();
8455
9024
  for (const optionId of optionIds) {
8456
- if (removeOptionInPlace(p, optionId)) deleted.add(optionId);
9025
+ for (const removedId of removeOptionInPlace(p, optionId)) {
9026
+ deleted.add(removedId);
9027
+ }
8457
9028
  }
8458
9029
  for (const fieldId of fieldIds) {
8459
9030
  const removedIds = removeFieldInPlace(p, fieldId);
@@ -8468,7 +9039,7 @@ function removeMany(ctx, ids) {
8468
9039
  });
8469
9040
  }
8470
9041
  function getNode(ctx, id) {
8471
- var _a, _b, _c, _d;
9042
+ var _a, _b, _c;
8472
9043
  const props = ctx.getProps();
8473
9044
  if (ctx.isTagId(id)) {
8474
9045
  const t = ((_a = props.filters) != null ? _a : []).find((x) => x.id === id);
@@ -8485,8 +9056,7 @@ function getNode(ctx, id) {
8485
9056
  }
8486
9057
  if (ctx.isOptionId(id)) {
8487
9058
  const own = ownerOfOption(props, id);
8488
- const f = own ? ((_c = props.fields) != null ? _c : []).find((x) => x.id === own.fieldId) : void 0;
8489
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
9059
+ const o = (_c = findMutableOption(props, id)) == null ? void 0 : _c.option;
8490
9060
  return {
8491
9061
  kind: "option",
8492
9062
  data: o,
@@ -9023,7 +9593,7 @@ function connect(ctx, kind, fromId, toId2) {
9023
9593
  ctx.exec({
9024
9594
  name: `connect:${kind}`,
9025
9595
  do: () => ctx.patchProps((p) => {
9026
- var _a, _b, _c, _d, _e, _f, _g, _h;
9596
+ var _a, _b, _c, _d, _e, _f, _g;
9027
9597
  if (kind === "bind") {
9028
9598
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
9029
9599
  if (wouldCreateTagCycle(ctx, p, fromId, toId2)) {
@@ -9103,12 +9673,10 @@ function connect(ctx, kind, fromId, toId2) {
9103
9673
  return;
9104
9674
  }
9105
9675
  if (toId2.startsWith("o:")) {
9106
- for (const f of (_g = p.fields) != null ? _g : []) {
9107
- const o = (_h = f.options) == null ? void 0 : _h.find((x) => x.id === toId2);
9108
- if (o) {
9109
- o.service_id = fromId;
9110
- return;
9111
- }
9676
+ const o = (_g = findMutableOption(p, toId2)) == null ? void 0 : _g.option;
9677
+ if (o) {
9678
+ o.service_id = fromId;
9679
+ return;
9112
9680
  }
9113
9681
  return;
9114
9682
  }
@@ -9125,7 +9693,7 @@ function disconnect(ctx, kind, fromId, toId2) {
9125
9693
  ctx.exec({
9126
9694
  name: `disconnect:${kind}`,
9127
9695
  do: () => ctx.patchProps((p) => {
9128
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
9696
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
9129
9697
  if (kind === "bind") {
9130
9698
  if (ctx.isTagId(fromId) && ctx.isTagId(toId2)) {
9131
9699
  const child = ((_a = p.filters) != null ? _a : []).find(
@@ -9201,12 +9769,10 @@ function disconnect(ctx, kind, fromId, toId2) {
9201
9769
  return;
9202
9770
  }
9203
9771
  if (toId2.startsWith("o:")) {
9204
- for (const f of (_i = p.fields) != null ? _i : []) {
9205
- const o = (_j = f.options) == null ? void 0 : _j.find((x) => x.id === toId2);
9206
- if (o) {
9207
- delete o.service_id;
9208
- return;
9209
- }
9772
+ const o = (_i = findMutableOption(p, toId2)) == null ? void 0 : _i.option;
9773
+ if (o) {
9774
+ delete o.service_id;
9775
+ return;
9210
9776
  }
9211
9777
  return;
9212
9778
  }
@@ -9229,6 +9795,250 @@ function addMappedField(p, mapKey, fromId, toId2) {
9229
9795
  p[mapKey] = maps;
9230
9796
  }
9231
9797
 
9798
+ // src/react/canvas/editor/editor-option-effects.ts
9799
+ function assertCanonicalId(id, label) {
9800
+ if (!id || id.includes("::") || id.includes("/")) {
9801
+ throw new Error(
9802
+ `${label}: expected a raw field or option id, not a composite/path id`
9803
+ );
9804
+ }
9805
+ }
9806
+ function assertTrigger(ctx, triggerId) {
9807
+ assertCanonicalId(triggerId, "option effect trigger");
9808
+ const trigger = ctx.getNode(triggerId);
9809
+ if (trigger.kind === "option" && trigger.data) return;
9810
+ if (trigger.kind === "field" && trigger.data && isActualButtonField(trigger.data)) {
9811
+ return;
9812
+ }
9813
+ throw new Error(
9814
+ "option effect trigger must be an option id or button field id"
9815
+ );
9816
+ }
9817
+ function assertTargetField(props, targetFieldId) {
9818
+ var _a;
9819
+ assertCanonicalId(targetFieldId, "option effect target");
9820
+ const field = ((_a = props.fields) != null ? _a : []).find((item) => item.id === targetFieldId);
9821
+ if (!field) {
9822
+ throw new Error(`option effect target field not found: ${targetFieldId}`);
9823
+ }
9824
+ return field;
9825
+ }
9826
+ function dedupe2(values) {
9827
+ if (!values) return void 0;
9828
+ const out = [];
9829
+ for (const value of values) {
9830
+ const id = String(value);
9831
+ if (!id || out.includes(id)) continue;
9832
+ out.push(id);
9833
+ }
9834
+ return out.length ? out : void 0;
9835
+ }
9836
+ function assertTargetOptions(props, targetFieldId, ids, kind) {
9837
+ if (!(ids == null ? void 0 : ids.length)) return;
9838
+ const field = assertTargetField(props, targetFieldId);
9839
+ const valid = fieldOptionIdSet(field);
9840
+ for (const id of ids) {
9841
+ assertCanonicalId(String(id), `option effect ${kind} option`);
9842
+ if (!valid.has(String(id))) {
9843
+ throw new Error(
9844
+ `option effect ${kind} option not found under ${targetFieldId}: ${String(id)}`
9845
+ );
9846
+ }
9847
+ }
9848
+ }
9849
+ function normalizeEffect(effect) {
9850
+ var _a;
9851
+ if (!effect) return void 0;
9852
+ const exclude2 = dedupe2(effect.exclude);
9853
+ const excluded = new Set(exclude2 != null ? exclude2 : []);
9854
+ const include2 = (_a = dedupe2(effect.include)) == null ? void 0 : _a.filter((id) => !excluded.has(id));
9855
+ const out = {};
9856
+ if (effect.forceVisible === true) out.forceVisible = true;
9857
+ if (include2 == null ? void 0 : include2.length) out.include = include2;
9858
+ if (exclude2 == null ? void 0 : exclude2.length) out.exclude = exclude2;
9859
+ return Object.keys(out).length ? out : void 0;
9860
+ }
9861
+ function ensureTargetMap(props, triggerId) {
9862
+ var _a, _b, _c;
9863
+ (_a = props.option_effects_for_buttons) != null ? _a : props.option_effects_for_buttons = {};
9864
+ (_c = (_b = props.option_effects_for_buttons)[triggerId]) != null ? _c : _b[triggerId] = {};
9865
+ return props.option_effects_for_buttons[triggerId];
9866
+ }
9867
+ function pruneEffectMap(props, triggerId) {
9868
+ const map = props.option_effects_for_buttons;
9869
+ if (!map) return;
9870
+ const keys = triggerId ? [triggerId] : Object.keys(map);
9871
+ for (const key of keys) {
9872
+ const targets = map[key];
9873
+ if (!targets || Object.keys(targets).length === 0) delete map[key];
9874
+ }
9875
+ if (Object.keys(map).length === 0) delete props.option_effects_for_buttons;
9876
+ }
9877
+ function validateEffect(ctx, props, triggerId, targetFieldId, effect) {
9878
+ assertTrigger(ctx, triggerId);
9879
+ assertTargetField(props, targetFieldId);
9880
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.include, "include");
9881
+ assertTargetOptions(props, targetFieldId, effect == null ? void 0 : effect.exclude, "exclude");
9882
+ return normalizeEffect(effect);
9883
+ }
9884
+ function setOptionEffect(ctx, triggerId, targetFieldId, effect) {
9885
+ ctx.exec({
9886
+ name: "setOptionEffect",
9887
+ do: () => ctx.patchProps((props) => {
9888
+ var _a;
9889
+ const normalized = validateEffect(
9890
+ ctx,
9891
+ props,
9892
+ triggerId,
9893
+ targetFieldId,
9894
+ effect
9895
+ );
9896
+ if (!normalized) {
9897
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
9898
+ if (map) delete map[targetFieldId];
9899
+ pruneEffectMap(props, triggerId);
9900
+ return;
9901
+ }
9902
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
9903
+ }),
9904
+ undo: () => ctx.undo()
9905
+ });
9906
+ }
9907
+ function patchOptionEffect(ctx, triggerId, targetFieldId, patch) {
9908
+ ctx.exec({
9909
+ name: "patchOptionEffect",
9910
+ do: () => ctx.patchProps((props) => {
9911
+ var _a, _b, _c, _d;
9912
+ const current = (_c = (_b = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
9913
+ const merged = {
9914
+ ...current,
9915
+ ...patch
9916
+ };
9917
+ const normalized = validateEffect(
9918
+ ctx,
9919
+ props,
9920
+ triggerId,
9921
+ targetFieldId,
9922
+ merged
9923
+ );
9924
+ if (!normalized) {
9925
+ const map = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId];
9926
+ if (map) delete map[targetFieldId];
9927
+ pruneEffectMap(props, triggerId);
9928
+ return;
9929
+ }
9930
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
9931
+ }),
9932
+ undo: () => ctx.undo()
9933
+ });
9934
+ }
9935
+ function clearOptionEffect(ctx, triggerId, targetFieldId) {
9936
+ ctx.exec({
9937
+ name: "clearOptionEffect",
9938
+ do: () => ctx.patchProps((props) => {
9939
+ var _a;
9940
+ const map = (_a = props.option_effects_for_buttons) == null ? void 0 : _a[triggerId];
9941
+ if (!map) return;
9942
+ delete map[targetFieldId];
9943
+ pruneEffectMap(props, triggerId);
9944
+ }),
9945
+ undo: () => ctx.undo()
9946
+ });
9947
+ }
9948
+ function clearOptionEffectsForTrigger(ctx, triggerId) {
9949
+ ctx.exec({
9950
+ name: "clearOptionEffectsForTrigger",
9951
+ do: () => ctx.patchProps((props) => {
9952
+ if (!props.option_effects_for_buttons) return;
9953
+ delete props.option_effects_for_buttons[triggerId];
9954
+ pruneEffectMap(props);
9955
+ }),
9956
+ undo: () => ctx.undo()
9957
+ });
9958
+ }
9959
+ function clearOptionEffectsForTarget(ctx, targetFieldId) {
9960
+ ctx.exec({
9961
+ name: "clearOptionEffectsForTarget",
9962
+ do: () => ctx.patchProps((props) => {
9963
+ var _a;
9964
+ const map = props.option_effects_for_buttons;
9965
+ if (!map) return;
9966
+ for (const triggerId of Object.keys(map)) {
9967
+ (_a = map[triggerId]) == null ? true : delete _a[targetFieldId];
9968
+ }
9969
+ pruneEffectMap(props);
9970
+ }),
9971
+ undo: () => ctx.undo()
9972
+ });
9973
+ }
9974
+ function addOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
9975
+ var _a;
9976
+ const additions = (_a = dedupe2(optionIds)) != null ? _a : [];
9977
+ if (!additions.length) return;
9978
+ ctx.exec({
9979
+ name: "addOptionEffectOptions",
9980
+ do: () => ctx.patchProps((props) => {
9981
+ var _a2, _b, _c, _d;
9982
+ const current = (_c = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId]) != null ? _c : {};
9983
+ const nextValues = dedupe2([
9984
+ ...(_d = current[kind]) != null ? _d : [],
9985
+ ...additions
9986
+ ]);
9987
+ const normalized = validateEffect(
9988
+ ctx,
9989
+ props,
9990
+ triggerId,
9991
+ targetFieldId,
9992
+ {
9993
+ ...current,
9994
+ [kind]: nextValues
9995
+ }
9996
+ );
9997
+ if (!normalized) return;
9998
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
9999
+ }),
10000
+ undo: () => ctx.undo()
10001
+ });
10002
+ }
10003
+ function removeOptionEffectOptions(ctx, triggerId, targetFieldId, kind, optionIds) {
10004
+ var _a;
10005
+ const removals = new Set((_a = dedupe2(optionIds)) != null ? _a : []);
10006
+ if (!removals.size) return;
10007
+ ctx.exec({
10008
+ name: "removeOptionEffectOptions",
10009
+ do: () => ctx.patchProps((props) => {
10010
+ var _a2, _b, _c, _d, _e;
10011
+ const current = (_b = (_a2 = props.option_effects_for_buttons) == null ? void 0 : _a2[triggerId]) == null ? void 0 : _b[targetFieldId];
10012
+ if (!current) return;
10013
+ const next = {
10014
+ ...current,
10015
+ [kind]: ((_c = current[kind]) != null ? _c : []).filter(
10016
+ (optionId) => !removals.has(optionId)
10017
+ )
10018
+ };
10019
+ const normalized = validateEffect(
10020
+ ctx,
10021
+ props,
10022
+ triggerId,
10023
+ targetFieldId,
10024
+ next
10025
+ );
10026
+ if (!normalized) {
10027
+ (_e = (_d = props.option_effects_for_buttons) == null ? void 0 : _d[triggerId]) == null ? true : delete _e[targetFieldId];
10028
+ pruneEffectMap(props, triggerId);
10029
+ return;
10030
+ }
10031
+ ensureTargetMap(props, triggerId)[targetFieldId] = normalized;
10032
+ }),
10033
+ undo: () => ctx.undo()
10034
+ });
10035
+ }
10036
+ function setOptionEffectForceVisible(ctx, triggerId, targetFieldId, forceVisible) {
10037
+ patchOptionEffect(ctx, triggerId, targetFieldId, {
10038
+ forceVisible: forceVisible === true ? true : void 0
10039
+ });
10040
+ }
10041
+
9232
10042
  // src/react/canvas/editor/editor-service-filter.ts
9233
10043
  function filterServicesForVisibleGroup2(ctx, candidates, input) {
9234
10044
  const coreInput = {
@@ -9768,6 +10578,36 @@ var Editor = class {
9768
10578
  addField(partial) {
9769
10579
  return addField(this.moduleCtx(), partial);
9770
10580
  }
10581
+ addFieldFromDescriptor(registry, partial, opts) {
10582
+ var _a, _b, _c, _d, _e;
10583
+ 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;
10584
+ const descriptor = resolveInputDescriptor(
10585
+ registry,
10586
+ String(partial.type),
10587
+ variant
10588
+ );
10589
+ const nextMeta = {
10590
+ ...(_c = partial.meta) != null ? _c : {}
10591
+ };
10592
+ if (((_d = descriptor == null ? void 0 : descriptor.multi) == null ? void 0 : _d.autoEnable) === true) {
10593
+ nextMeta.multi = true;
10594
+ }
10595
+ const fieldInput = {
10596
+ ...partial,
10597
+ ...Object.keys(nextMeta).length ? { meta: nextMeta } : {}
10598
+ };
10599
+ const fieldId = this.addField(fieldInput);
10600
+ if (((_e = descriptor == null ? void 0 : descriptor.options) == null ? void 0 : _e.autoCreate) === true) {
10601
+ this.autoCreateOptionsMany([fieldId], () => {
10602
+ var _a2, _b2, _c2, _d2;
10603
+ return {
10604
+ label: (_b2 = (_a2 = descriptor.options) == null ? void 0 : _a2.defaultLabel) != null ? _b2 : "Option label",
10605
+ value: (_d2 = (_c2 = descriptor.options) == null ? void 0 : _c2.defaultValue) != null ? _d2 : "option"
10606
+ };
10607
+ });
10608
+ }
10609
+ return fieldId;
10610
+ }
9771
10611
  updateField(id, patch) {
9772
10612
  return updateField(this.moduleCtx(), id, patch);
9773
10613
  }
@@ -9785,7 +10625,7 @@ var Editor = class {
9785
10625
  if (!ordered.length) return;
9786
10626
  this.transact("clearServiceMany", () => {
9787
10627
  this.patchProps((p) => {
9788
- var _a, _b, _c, _d;
10628
+ var _a, _b;
9789
10629
  for (const id of ordered) {
9790
10630
  if (this.isTagId(id)) {
9791
10631
  const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
@@ -9798,10 +10638,8 @@ var Editor = class {
9798
10638
  continue;
9799
10639
  }
9800
10640
  if (this.isOptionId(id)) {
9801
- const own = ownerOfOption(p, id);
9802
- if (!own) continue;
9803
- const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
9804
- const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
10641
+ const found = findMutableOption(p, id);
10642
+ const o = found == null ? void 0 : found.option;
9805
10643
  if (o && "service_id" in o) delete o.service_id;
9806
10644
  }
9807
10645
  }
@@ -9850,7 +10688,7 @@ var Editor = class {
9850
10688
  if (!selected.size) return;
9851
10689
  this.transact("clearRelationsMany", () => {
9852
10690
  this.patchProps((p) => {
9853
- var _a, _b, _c;
10691
+ var _a, _b, _c, _d, _e;
9854
10692
  const clearOwned = mode === "owned" || mode === "both";
9855
10693
  const clearIncoming = mode === "incoming" || mode === "both";
9856
10694
  for (const t of (_a = p.filters) != null ? _a : []) {
@@ -9890,6 +10728,44 @@ var Editor = class {
9890
10728
  }
9891
10729
  if (!Object.keys(map).length) delete p[k];
9892
10730
  }
10731
+ const effectMap = p.option_effects_for_buttons;
10732
+ if (effectMap) {
10733
+ for (const triggerId of Object.keys(effectMap)) {
10734
+ if (clearOwned && selected.has(String(triggerId))) {
10735
+ delete effectMap[triggerId];
10736
+ continue;
10737
+ }
10738
+ const targets = effectMap[triggerId];
10739
+ if (!targets || !clearIncoming) continue;
10740
+ for (const targetFieldId of Object.keys(targets)) {
10741
+ if (selected.has(String(targetFieldId))) {
10742
+ delete targets[targetFieldId];
10743
+ continue;
10744
+ }
10745
+ const effect = targets[targetFieldId];
10746
+ if (!effect) continue;
10747
+ if (effect.include) {
10748
+ effect.include = effect.include.filter(
10749
+ (optionId) => !selected.has(String(optionId))
10750
+ );
10751
+ if (!effect.include.length) delete effect.include;
10752
+ }
10753
+ if (effect.exclude) {
10754
+ effect.exclude = effect.exclude.filter(
10755
+ (optionId) => !selected.has(String(optionId))
10756
+ );
10757
+ if (!effect.exclude.length) delete effect.exclude;
10758
+ }
10759
+ if (effect.forceVisible !== true && !((_d = effect.include) == null ? void 0 : _d.length) && !((_e = effect.exclude) == null ? void 0 : _e.length)) {
10760
+ delete targets[targetFieldId];
10761
+ }
10762
+ }
10763
+ if (!Object.keys(targets).length) delete effectMap[triggerId];
10764
+ }
10765
+ if (!Object.keys(effectMap).length) {
10766
+ delete p.option_effects_for_buttons;
10767
+ }
10768
+ }
9893
10769
  });
9894
10770
  });
9895
10771
  }
@@ -9901,7 +10777,7 @@ var Editor = class {
9901
10777
  const suffix = (_b = input.suffix) != null ? _b : "";
9902
10778
  this.transact("renameLabelsMany", () => {
9903
10779
  this.patchProps((p) => {
9904
- var _a2, _b2, _c, _d, _e, _f, _g;
10780
+ var _a2, _b2, _c, _d, _e, _f;
9905
10781
  for (const id of ordered) {
9906
10782
  if (this.isTagId(id)) {
9907
10783
  const t = ((_a2 = p.filters) != null ? _a2 : []).find((x) => x.id === id);
@@ -9914,11 +10790,8 @@ var Editor = class {
9914
10790
  continue;
9915
10791
  }
9916
10792
  if (this.isOptionId(id)) {
9917
- const own = ownerOfOption(p, id);
9918
- if (!own) continue;
9919
- const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === own.fieldId);
9920
- const o = (_f = f == null ? void 0 : f.options) == null ? void 0 : _f.find((x) => x.id === id);
9921
- if (o) o.label = `${prefix}${(_g = o.label) != null ? _g : ""}${suffix}`.trim();
10793
+ const o = (_e = findMutableOption(p, id)) == null ? void 0 : _e.option;
10794
+ if (o) o.label = `${prefix}${(_f = o.label) != null ? _f : ""}${suffix}`.trim();
9922
10795
  }
9923
10796
  }
9924
10797
  });
@@ -9963,6 +10836,28 @@ var Editor = class {
9963
10836
  });
9964
10837
  });
9965
10838
  }
10839
+ setFieldMulti(fieldId, enabled) {
10840
+ const flag = enabled === true;
10841
+ this.transact("setFieldMulti", () => {
10842
+ this.patchProps((p) => {
10843
+ var _a, _b;
10844
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === fieldId);
10845
+ if (!f) return;
10846
+ const currentMeta = (_b = f.meta) != null ? _b : {};
10847
+ const nextMeta = { ...currentMeta };
10848
+ if (flag) {
10849
+ nextMeta.multi = true;
10850
+ } else {
10851
+ delete nextMeta.multi;
10852
+ }
10853
+ if (Object.keys(nextMeta).length === 0) {
10854
+ delete f.meta;
10855
+ } else {
10856
+ f.meta = nextMeta;
10857
+ }
10858
+ });
10859
+ });
10860
+ }
9966
10861
  autoCreateOptionsMany(ids, makeOption) {
9967
10862
  const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9968
10863
  if (!ordered.length) return;
@@ -10049,6 +10944,57 @@ var Editor = class {
10049
10944
  exclude(receiverId, idOrIds) {
10050
10945
  return exclude(this.moduleCtx(), receiverId, idOrIds);
10051
10946
  }
10947
+ setOptionEffect(triggerId, targetFieldId, effect) {
10948
+ return setOptionEffect(
10949
+ this.moduleCtx(),
10950
+ triggerId,
10951
+ targetFieldId,
10952
+ effect
10953
+ );
10954
+ }
10955
+ patchOptionEffect(triggerId, targetFieldId, patch) {
10956
+ return patchOptionEffect(
10957
+ this.moduleCtx(),
10958
+ triggerId,
10959
+ targetFieldId,
10960
+ patch
10961
+ );
10962
+ }
10963
+ clearOptionEffect(triggerId, targetFieldId) {
10964
+ return clearOptionEffect(this.moduleCtx(), triggerId, targetFieldId);
10965
+ }
10966
+ clearOptionEffectsForTrigger(triggerId) {
10967
+ return clearOptionEffectsForTrigger(this.moduleCtx(), triggerId);
10968
+ }
10969
+ clearOptionEffectsForTarget(targetFieldId) {
10970
+ return clearOptionEffectsForTarget(this.moduleCtx(), targetFieldId);
10971
+ }
10972
+ addOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
10973
+ return addOptionEffectOptions(
10974
+ this.moduleCtx(),
10975
+ triggerId,
10976
+ targetFieldId,
10977
+ kind,
10978
+ optionIds
10979
+ );
10980
+ }
10981
+ removeOptionEffectOptions(triggerId, targetFieldId, kind, optionIds) {
10982
+ return removeOptionEffectOptions(
10983
+ this.moduleCtx(),
10984
+ triggerId,
10985
+ targetFieldId,
10986
+ kind,
10987
+ optionIds
10988
+ );
10989
+ }
10990
+ setOptionEffectForceVisible(triggerId, targetFieldId, forceVisible) {
10991
+ return setOptionEffectForceVisible(
10992
+ this.moduleCtx(),
10993
+ triggerId,
10994
+ targetFieldId,
10995
+ forceVisible
10996
+ );
10997
+ }
10052
10998
  connect(kind, fromId, toId2) {
10053
10999
  return connect(this.moduleCtx(), kind, fromId, toId2);
10054
11000
  }
@@ -10417,11 +11363,10 @@ var Selection = class {
10417
11363
  * What counts as a "button selection" (trigger key):
10418
11364
  * - field key where the field has button === true (e.g. "f:dripfeed")
10419
11365
  * - option key (e.g. "o:fast")
10420
- * - composite key "fieldId::optionId" (e.g. "f:speed::o:fast")
10421
11366
  *
10422
11367
  * Grouping:
10423
11368
  * - button-field trigger groups under its own fieldId
10424
- * - option/composite groups under the option's owning fieldId (from nodeMap)
11369
+ * - option trigger groups under the option's owning fieldId (from nodeMap)
10425
11370
  *
10426
11371
  * Deterministic:
10427
11372
  * - preserves selection insertion order
@@ -10438,15 +11383,6 @@ var Selection = class {
10438
11383
  };
10439
11384
  for (const key of this.set) {
10440
11385
  if (!key) continue;
10441
- const idx = key.indexOf("::");
10442
- if (idx !== -1) {
10443
- const optionId = key.slice(idx + 2);
10444
- const optRef = nodeMap.get(optionId);
10445
- if ((optRef == null ? void 0 : optRef.kind) === "option" && typeof optRef.fieldId === "string") {
10446
- push(optRef.fieldId, key);
10447
- }
10448
- continue;
10449
- }
10450
11386
  const ref = nodeMap.get(key);
10451
11387
  if (!ref) continue;
10452
11388
  if (ref.kind === "option" && typeof ref.fieldId === "string") {
@@ -10467,7 +11403,6 @@ var Selection = class {
10467
11403
  * Returns only selection keys that are valid "trigger buttons":
10468
11404
  * - field keys where field.button === true
10469
11405
  * - option keys
10470
- * - composite keys "fieldId::optionId" (validated by optionId)
10471
11406
  * Excludes tags and non-button fields.
10472
11407
  */
10473
11408
  selectedButtons() {
@@ -10483,13 +11418,6 @@ var Selection = class {
10483
11418
  };
10484
11419
  for (const key of this.set) {
10485
11420
  if (!key) continue;
10486
- const idx = key.indexOf("::");
10487
- if (idx !== -1) {
10488
- const optionId = key.slice(idx + 2);
10489
- const optRef = nodeMap.get(optionId);
10490
- if ((optRef == null ? void 0 : optRef.kind) === "option") push(key);
10491
- continue;
10492
- }
10493
11421
  const ref = nodeMap.get(key);
10494
11422
  if (!ref) continue;
10495
11423
  if (ref.kind === "option") {
@@ -10528,17 +11456,7 @@ var Selection = class {
10528
11456
  const direct = fields.find((x) => x.id === id);
10529
11457
  if (direct) return direct;
10530
11458
  if (this.builder.isOptionId(id)) {
10531
- return fields.find(
10532
- (x) => {
10533
- var _a2;
10534
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
10535
- }
10536
- );
10537
- }
10538
- if (id.includes("::")) {
10539
- const [fieldId] = id.split("::");
10540
- if (!fieldId) return void 0;
10541
- return fields.find((x) => x.id === fieldId);
11459
+ return findOptionOwnerField(fields, id);
10542
11460
  }
10543
11461
  return void 0;
10544
11462
  };
@@ -10569,18 +11487,7 @@ var Selection = class {
10569
11487
  }
10570
11488
  for (const id of this.set) {
10571
11489
  if (this.builder.isOptionId(id)) {
10572
- const host = fields.find(
10573
- (x) => {
10574
- var _a2;
10575
- return ((_a2 = x.options) != null ? _a2 : []).some((o) => o.id === id);
10576
- }
10577
- );
10578
- if (host == null ? void 0 : host.bind_id)
10579
- return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
10580
- }
10581
- if (id.includes("::")) {
10582
- const [fid] = id.split("::");
10583
- const host = fields.find((x) => x.id === fid);
11490
+ const host = findOptionOwnerField(fields, id);
10584
11491
  if (host == null ? void 0 : host.bind_id)
10585
11492
  return Array.isArray(host.bind_id) ? host.bind_id[0] : host.bind_id;
10586
11493
  }
@@ -10594,7 +11501,11 @@ var Selection = class {
10594
11501
  const tagById = new Map(tags.map((t) => [t.id, t]));
10595
11502
  const tag = tagById.get(tagId);
10596
11503
  const selectedTriggerIds = this.selectedButtons();
10597
- const fieldIds = this.builder.visibleFields(tagId, selectedTriggerIds);
11504
+ const visibility = this.builder.resolveVisibility(
11505
+ tagId,
11506
+ selectedTriggerIds
11507
+ );
11508
+ const fieldIds = visibility.fieldIds;
10598
11509
  const fieldById = new Map(fields.map((f) => [f.id, f]));
10599
11510
  const visible = fieldIds.map((id) => fieldById.get(id)).filter(Boolean);
10600
11511
  const parentTags = [];
@@ -10620,6 +11531,14 @@ var Selection = class {
10620
11531
  let baseOverridden = false;
10621
11532
  for (const selId of this.set) {
10622
11533
  const opt = this.findOptionById(fields, selId);
11534
+ if (opt && !this.isSelectedOptionVisible(
11535
+ fields,
11536
+ selId,
11537
+ fieldIds,
11538
+ visibility.optionsByFieldId
11539
+ )) {
11540
+ continue;
11541
+ }
10623
11542
  if ((opt == null ? void 0 : opt.service_id) != null) {
10624
11543
  const role = (_d = opt.pricing_role) != null ? _d : "base";
10625
11544
  const cap = (_e = resolve == null ? void 0 : resolve(opt.service_id)) != null ? _e : { id: opt.service_id };
@@ -10650,6 +11569,8 @@ var Selection = class {
10650
11569
  tag,
10651
11570
  fields: visible,
10652
11571
  fieldIds,
11572
+ optionsByFieldId: visibility.optionsByFieldId,
11573
+ forcedFieldIds: visibility.forcedFieldIds,
10653
11574
  parentTags,
10654
11575
  childrenTags,
10655
11576
  services
@@ -10673,21 +11594,19 @@ var Selection = class {
10673
11594
  return baseOverridden;
10674
11595
  }
10675
11596
  findOptionById(fields, selId) {
10676
- var _a, _b;
10677
11597
  if (this.builder.isOptionId(selId)) {
10678
- for (const f of fields) {
10679
- const o = (_a = f.options) == null ? void 0 : _a.find((x) => x.id === selId);
10680
- if (o) return o;
10681
- }
10682
- }
10683
- if (selId.includes("::")) {
10684
- const [fid, oid] = selId.split("::");
10685
- const f = fields.find((x) => x.id === fid);
10686
- const o = (_b = f == null ? void 0 : f.options) == null ? void 0 : _b.find((x) => x.id === oid || x.id === selId);
10687
- if (o) return o;
11598
+ const field = findOptionOwnerField(fields, selId);
11599
+ return findFieldOption(field, selId);
10688
11600
  }
10689
11601
  return void 0;
10690
11602
  }
11603
+ isSelectedOptionVisible(fields, selId, visibleFieldIds, optionsByFieldId) {
11604
+ const visibleFields = new Set(visibleFieldIds);
11605
+ const field = findOptionOwnerField(fields, selId);
11606
+ if (!field || !visibleFields.has(field.id)) return false;
11607
+ const allowed = optionsByFieldId[field.id];
11608
+ return !allowed || allowed.includes(selId);
11609
+ }
10691
11610
  };
10692
11611
 
10693
11612
  // src/react/canvas/api.ts