@timeax/digital-service-engine 0.3.0 → 0.3.2

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.
@@ -37,6 +37,7 @@ __export(workspace_exports, {
37
37
  WorkspaceProvider: () => WorkspaceProvider,
38
38
  createMemoryWorkspaceBackend: () => createMemoryWorkspaceBackend,
39
39
  createPollAdapter: () => createPollAdapter,
40
+ deriveSelectionCapabilities: () => deriveSelectionCapabilities,
40
41
  useCanvas: () => useCanvas,
41
42
  useCanvasAPI: () => useCanvasAPI,
42
43
  useCanvasFromBuilder: () => useCanvasFromBuilder,
@@ -362,6 +363,70 @@ var React6 = __toESM(require("react"), 1);
362
363
  function setLoadableError4(updater, error) {
363
364
  updater((s) => ({ ...s, loading: false, error }));
364
365
  }
366
+ function parseTimestamp(value) {
367
+ if (value === void 0 || value === null) return void 0;
368
+ if (typeof value === "number") {
369
+ return Number.isFinite(value) ? value : void 0;
370
+ }
371
+ const parsed = Date.parse(value);
372
+ return Number.isFinite(parsed) ? parsed : void 0;
373
+ }
374
+ function templateTime(template) {
375
+ var _a;
376
+ return (_a = parseTimestamp(template.updatedAt)) != null ? _a : parseTimestamp(template.createdAt);
377
+ }
378
+ function shouldReplaceTemplates(params) {
379
+ if (!params.requestedSince) return true;
380
+ if (!params.lastUpdatedAt) return false;
381
+ const requested = parseTimestamp(params.requestedSince);
382
+ const last = parseTimestamp(params.lastUpdatedAt);
383
+ if (requested === void 0 || last === void 0) {
384
+ return false;
385
+ }
386
+ return requested < last;
387
+ }
388
+ function pickNewestTemplate(current, incoming) {
389
+ const currentTime = templateTime(current);
390
+ const incomingTime = templateTime(incoming);
391
+ if (currentTime !== void 0 && incomingTime !== void 0) {
392
+ return incomingTime >= currentTime ? incoming : current;
393
+ }
394
+ if (currentTime === void 0 && incomingTime !== void 0) return incoming;
395
+ if (currentTime !== void 0 && incomingTime === void 0) return current;
396
+ return incoming;
397
+ }
398
+ function mergeTemplates(current, incoming, opts) {
399
+ var _a;
400
+ const sinceTime = parseTimestamp(opts == null ? void 0 : opts.since);
401
+ const incomingIds = new Set(incoming.map((template) => template.id));
402
+ const deletedIds = new Set((_a = opts == null ? void 0 : opts.deletedIds) != null ? _a : []);
403
+ const byId = /* @__PURE__ */ new Map();
404
+ for (const template of current != null ? current : []) {
405
+ if (deletedIds.has(template.id)) continue;
406
+ const updatedTime = templateTime(template);
407
+ const shouldHaveAppearedInDelta = (opts == null ? void 0 : opts.reconcileMissingSince) === true && sinceTime !== void 0 && updatedTime !== void 0 && updatedTime > sinceTime;
408
+ const missingFromDelta = shouldHaveAppearedInDelta && !incomingIds.has(template.id);
409
+ if (!missingFromDelta) {
410
+ byId.set(template.id, template);
411
+ }
412
+ }
413
+ for (const template of incoming) {
414
+ if (deletedIds.has(template.id)) continue;
415
+ const existing = byId.get(template.id);
416
+ byId.set(
417
+ template.id,
418
+ existing ? pickNewestTemplate(existing, template) : template
419
+ );
420
+ }
421
+ return Array.from(byId.values()).sort((a, b) => {
422
+ const aTime = templateTime(a);
423
+ const bTime = templateTime(b);
424
+ if (aTime !== void 0 && bTime !== void 0 && aTime !== bTime) {
425
+ return bTime - aTime;
426
+ }
427
+ return a.name.localeCompare(b.name);
428
+ });
429
+ }
365
430
  function useTemplatesSlice(params) {
366
431
  const {
367
432
  backend,
@@ -389,16 +454,26 @@ function useTemplatesSlice(params) {
389
454
  };
390
455
  }
391
456
  setTemplates((s) => ({ ...s, loading: true }));
457
+ const requestedSince = (_b = params2 == null ? void 0 : params2.since) != null ? _b : templates.updatedAt;
392
458
  const res = await backend.templates.refresh({
393
459
  workspaceId,
394
460
  branchId,
395
- since: (_b = params2 == null ? void 0 : params2.since) != null ? _b : templates.updatedAt
461
+ since: requestedSince
396
462
  });
397
463
  if (res.ok) {
398
- setTemplates({
399
- data: res.value,
400
- loading: false,
401
- updatedAt: runtime.now()
464
+ setTemplates((current) => {
465
+ const replace = shouldReplaceTemplates({
466
+ requestedSince,
467
+ lastUpdatedAt: current.updatedAt
468
+ });
469
+ return {
470
+ data: replace ? res.value : mergeTemplates(current.data, res.value, {
471
+ since: requestedSince,
472
+ reconcileMissingSince: false
473
+ }),
474
+ loading: false,
475
+ updatedAt: runtime.now()
476
+ };
402
477
  });
403
478
  return res;
404
479
  } else {
@@ -416,14 +491,14 @@ function useTemplatesSlice(params) {
416
491
  );
417
492
  const createTemplate = React6.useCallback(
418
493
  async (input) => {
419
- var _a, _b;
494
+ var _a;
420
495
  const res = await backend.templates.create(workspaceId, {
421
496
  ...input,
422
- branchId: (_a = input.branchId) != null ? _a : getCurrentBranchId()
497
+ branchId: input.branchId !== null ? input.branchId : getCurrentBranchId()
423
498
  });
424
499
  if (res.ok) {
425
500
  await refreshTemplates({
426
- branchId: (_b = res.value.branchId) != null ? _b : getCurrentBranchId()
501
+ branchId: (_a = res.value.branchId) != null ? _a : getCurrentBranchId()
427
502
  });
428
503
  }
429
504
  return res;
@@ -483,11 +558,23 @@ function useTemplatesSlice(params) {
483
558
  async (id) => {
484
559
  const res = await backend.templates.delete(id);
485
560
  if (res.ok) {
486
- await refreshTemplates({ branchId: getCurrentBranchId() });
561
+ const deleteRefreshSince = runtime.now();
562
+ setTemplates((current) => {
563
+ var _a, _b;
564
+ return {
565
+ ...current,
566
+ data: (_b = (_a = current.data) == null ? void 0 : _a.filter((template) => template.id !== id)) != null ? _b : current.data,
567
+ updatedAt: deleteRefreshSince
568
+ };
569
+ });
570
+ await refreshTemplates({
571
+ branchId: getCurrentBranchId(),
572
+ since: deleteRefreshSince
573
+ });
487
574
  }
488
575
  return res;
489
576
  },
490
- [backend.templates, getCurrentBranchId, refreshTemplates]
577
+ [backend.templates, getCurrentBranchId, refreshTemplates, runtime]
491
578
  );
492
579
  const invalidateTemplates = React6.useCallback(() => {
493
580
  setTemplates((s) => ({ ...s, updatedAt: void 0 }));
@@ -2750,7 +2837,7 @@ function WorkspaceProvider(props) {
2750
2837
  },
2751
2838
  branchContext: bootCtl.refreshBranchContext,
2752
2839
  templates: async (params) => {
2753
- await templatesSlice.refreshTemplates(params);
2840
+ return await templatesSlice.refreshTemplates(params);
2754
2841
  },
2755
2842
  participants: async (params) => {
2756
2843
  await branchesSlice.refreshParticipants(params);
@@ -7436,13 +7523,7 @@ function duplicate(ctx, ref, opts = {}) {
7436
7523
  try {
7437
7524
  let newId2 = "";
7438
7525
  ctx.transact("duplicate", () => {
7439
- if (ref.kind === "tag") {
7440
- newId2 = duplicateTag(ctx, ref.id, opts);
7441
- } else if (ref.kind === "field") {
7442
- newId2 = duplicateField(ctx, ref.id, opts);
7443
- } else {
7444
- newId2 = duplicateOption(ctx, ref.fieldId, ref.id, opts);
7445
- }
7526
+ newId2 = duplicateInPlace(ctx, ref, opts);
7446
7527
  });
7447
7528
  return newId2;
7448
7529
  } catch (err) {
@@ -7450,6 +7531,74 @@ function duplicate(ctx, ref, opts = {}) {
7450
7531
  throw err;
7451
7532
  }
7452
7533
  }
7534
+ function duplicateMany(ctx, ids, opts = {}) {
7535
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
7536
+ if (!ordered.length) return [];
7537
+ const snapBefore = ctx.makeSnapshot("duplicateMany:before");
7538
+ try {
7539
+ const created = [];
7540
+ ctx.transact("duplicateMany", () => {
7541
+ var _a, _b, _c;
7542
+ const props = ctx.getProps();
7543
+ const selectedFields = /* @__PURE__ */ new Set();
7544
+ for (const id of ordered) {
7545
+ if (ctx.isFieldId(id) && ((_a = props.fields) != null ? _a : []).some((f) => f.id === id)) {
7546
+ selectedFields.add(id);
7547
+ }
7548
+ }
7549
+ for (const id of ordered) {
7550
+ if (ctx.isTagId(id)) {
7551
+ if (!((_b = ctx.getProps().filters) != null ? _b : []).some((t) => t.id === id)) continue;
7552
+ created.push(
7553
+ duplicateInPlace(ctx, { kind: "tag", id }, opts)
7554
+ );
7555
+ continue;
7556
+ }
7557
+ if (ctx.isFieldId(id)) {
7558
+ if (!((_c = ctx.getProps().fields) != null ? _c : []).some((f) => f.id === id)) continue;
7559
+ created.push(
7560
+ duplicateInPlace(ctx, { kind: "field", id }, opts)
7561
+ );
7562
+ continue;
7563
+ }
7564
+ if (ctx.isOptionId(id)) {
7565
+ const owner = ownerFieldOfOption(ctx.getProps(), id);
7566
+ if (!owner) continue;
7567
+ if (selectedFields.has(owner.fieldId)) continue;
7568
+ created.push(
7569
+ duplicateInPlace(
7570
+ ctx,
7571
+ { kind: "option", fieldId: owner.fieldId, id },
7572
+ opts
7573
+ )
7574
+ );
7575
+ }
7576
+ }
7577
+ });
7578
+ return created;
7579
+ } catch (err) {
7580
+ ctx.loadSnapshot(snapBefore, "undo");
7581
+ throw err;
7582
+ }
7583
+ }
7584
+ function duplicateInPlace(ctx, ref, opts = {}) {
7585
+ if (ref.kind === "tag") {
7586
+ return duplicateTag(ctx, ref.id, opts);
7587
+ }
7588
+ if (ref.kind === "field") {
7589
+ return duplicateField(ctx, ref.id, opts);
7590
+ }
7591
+ return duplicateOption(ctx, ref.fieldId, ref.id, opts);
7592
+ }
7593
+ function ownerFieldOfOption(props, optionId) {
7594
+ var _a, _b;
7595
+ for (const field of (_a = props.fields) != null ? _a : []) {
7596
+ if (((_b = field.options) != null ? _b : []).some((o) => o.id === optionId)) {
7597
+ return { fieldId: field.id };
7598
+ }
7599
+ }
7600
+ return null;
7601
+ }
7453
7602
  function duplicateTag(ctx, tagId, opts) {
7454
7603
  var _a, _b, _c, _d;
7455
7604
  const props = ctx.getProps();
@@ -7736,6 +7885,129 @@ function ensureServiceExists(opts, id) {
7736
7885
  }
7737
7886
 
7738
7887
  // src/react/canvas/editor/editor-nodes.ts
7888
+ var RELATION_MAP_KEYS = [
7889
+ "includes_for_buttons",
7890
+ "excludes_for_buttons",
7891
+ "includes_for_options",
7892
+ "excludes_for_options"
7893
+ ];
7894
+ function stripDeletedIds(ids) {
7895
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
7896
+ return { ordered, set: new Set(ordered) };
7897
+ }
7898
+ function cleanTagRelationsForDeleted(p, deleted) {
7899
+ var _a;
7900
+ for (const t of (_a = p.filters) != null ? _a : []) {
7901
+ if (t.bind_id && deleted.has(String(t.bind_id))) delete t.bind_id;
7902
+ if (t.includes) {
7903
+ const next = t.includes.filter((x) => !deleted.has(String(x)));
7904
+ if (next.length) t.includes = next;
7905
+ else delete t.includes;
7906
+ }
7907
+ if (t.excludes) {
7908
+ const next = t.excludes.filter((x) => !deleted.has(String(x)));
7909
+ if (next.length) t.excludes = next;
7910
+ else delete t.excludes;
7911
+ }
7912
+ }
7913
+ }
7914
+ function cleanFieldBindsForDeleted(p, deleted) {
7915
+ var _a;
7916
+ for (const f of (_a = p.fields) != null ? _a : []) {
7917
+ const bind = f.bind_id;
7918
+ if (!bind) continue;
7919
+ if (Array.isArray(bind)) {
7920
+ const next = bind.filter((x) => !deleted.has(String(x)));
7921
+ if (next.length) f.bind_id = next;
7922
+ else delete f.bind_id;
7923
+ continue;
7924
+ }
7925
+ if (deleted.has(String(bind))) delete f.bind_id;
7926
+ }
7927
+ }
7928
+ function cleanRelationMapsForDeleted(p, deleted) {
7929
+ var _a;
7930
+ for (const key of RELATION_MAP_KEYS) {
7931
+ const map = p[key];
7932
+ if (!map) continue;
7933
+ for (const mapKey of Object.keys(map)) {
7934
+ if (deleted.has(String(mapKey))) {
7935
+ delete map[mapKey];
7936
+ continue;
7937
+ }
7938
+ const next = ((_a = map[mapKey]) != null ? _a : []).filter(
7939
+ (item) => !deleted.has(String(item))
7940
+ );
7941
+ if (next.length) map[mapKey] = next;
7942
+ else delete map[mapKey];
7943
+ }
7944
+ if (!Object.keys(map).length) delete p[key];
7945
+ }
7946
+ }
7947
+ function cleanOrderForTagsForDeleted(p, deleted) {
7948
+ var _a, _b;
7949
+ const map = p.order_for_tags;
7950
+ if (!map) return;
7951
+ const fieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
7952
+ for (const key of Object.keys(map)) {
7953
+ if (deleted.has(String(key))) {
7954
+ delete map[key];
7955
+ continue;
7956
+ }
7957
+ const next = ((_b = map[key]) != null ? _b : []).filter(
7958
+ (fid) => !deleted.has(String(fid)) && fieldIds.has(String(fid))
7959
+ );
7960
+ if (next.length) map[key] = next;
7961
+ else delete map[key];
7962
+ }
7963
+ if (!Object.keys(map).length) delete p.order_for_tags;
7964
+ }
7965
+ function cleanNoticesForDeleted(p, deleted) {
7966
+ var _a;
7967
+ if (!((_a = p.notices) == null ? void 0 : _a.length)) return;
7968
+ p.notices = p.notices.filter((n) => {
7969
+ const target = n.target;
7970
+ if (!target || target.scope === "global") return true;
7971
+ if (target.scope === "node" && deleted.has(String(target.node_id))) {
7972
+ return false;
7973
+ }
7974
+ return true;
7975
+ });
7976
+ if (!p.notices.length) delete p.notices;
7977
+ }
7978
+ function applyDeleteCleanup(p, deleted) {
7979
+ cleanTagRelationsForDeleted(p, deleted);
7980
+ cleanFieldBindsForDeleted(p, deleted);
7981
+ cleanRelationMapsForDeleted(p, deleted);
7982
+ cleanOrderForTagsForDeleted(p, deleted);
7983
+ cleanNoticesForDeleted(p, deleted);
7984
+ }
7985
+ function removeOptionInPlace(p, optionId) {
7986
+ var _a;
7987
+ const owner = ownerOfOption(p, optionId);
7988
+ if (!owner) return false;
7989
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
7990
+ if (!(f == null ? void 0 : f.options)) return false;
7991
+ const before = f.options.length;
7992
+ f.options = f.options.filter((o) => o.id !== optionId);
7993
+ return f.options.length !== before;
7994
+ }
7995
+ function removeFieldInPlace(p, fieldId) {
7996
+ var _a, _b, _c, _d, _e;
7997
+ const field = ((_a = p.fields) != null ? _a : []).find((f) => f.id === fieldId);
7998
+ if (!field) return [];
7999
+ const deleted = [fieldId, ...((_b = field.options) != null ? _b : []).map((o) => String(o.id))];
8000
+ const before = ((_c = p.fields) != null ? _c : []).length;
8001
+ p.fields = ((_d = p.fields) != null ? _d : []).filter((f) => f.id !== fieldId);
8002
+ clearFieldButtonReceiverMaps(p, fieldId);
8003
+ return ((_e = p.fields) != null ? _e : []).length !== before ? deleted : [];
8004
+ }
8005
+ function removeTagInPlace(p, tagId) {
8006
+ var _a, _b, _c;
8007
+ const before = ((_a = p.filters) != null ? _a : []).length;
8008
+ p.filters = ((_b = p.filters) != null ? _b : []).filter((t) => t.id !== tagId);
8009
+ return ((_c = p.filters) != null ? _c : []).length !== before;
8010
+ }
7739
8011
  function reLabel(ctx, id, nextLabel) {
7740
8012
  const label = String(nextLabel != null ? nextLabel : "").trim();
7741
8013
  ctx.exec({
@@ -7870,22 +8142,9 @@ function removeOption(ctx, optionId) {
7870
8142
  ctx.exec({
7871
8143
  name: "removeOption",
7872
8144
  do: () => ctx.patchProps((p) => {
7873
- var _a;
7874
- const owner = ownerOfOption(p, optionId);
7875
- if (!owner) return;
7876
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
7877
- if (!(f == null ? void 0 : f.options)) return;
7878
- f.options = f.options.filter((o) => o.id !== optionId);
7879
- const maps = [
7880
- "includes_for_options",
7881
- "excludes_for_options"
7882
- ];
7883
- for (const m of maps) {
7884
- const map = p[m];
7885
- if (!map) continue;
7886
- if (map[optionId]) delete map[optionId];
7887
- if (!Object.keys(map).length) delete p[m];
7888
- }
8145
+ const removed = removeOptionInPlace(p, optionId);
8146
+ if (!removed) return;
8147
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([optionId]));
7889
8148
  }),
7890
8149
  undo: () => ctx.undo()
7891
8150
  });
@@ -8074,21 +8333,10 @@ function removeTag(ctx, id) {
8074
8333
  ctx.exec({
8075
8334
  name: "removeTag",
8076
8335
  do: () => ctx.patchProps((p) => {
8077
- var _a, _b, _c, _d, _e;
8078
8336
  prevSlice = (0, import_lodash_es3.cloneDeep)(p);
8079
- p.filters = ((_a = p.filters) != null ? _a : []).filter((t) => t.id !== id);
8080
- for (const t of (_b = p.filters) != null ? _b : []) {
8081
- if (t.bind_id === id) delete t.bind_id;
8082
- t.includes = ((_c = t.includes) != null ? _c : []).filter((x) => x !== id);
8083
- t.excludes = ((_d = t.excludes) != null ? _d : []).filter((x) => x !== id);
8084
- }
8085
- for (const f of (_e = p.fields) != null ? _e : []) {
8086
- if (Array.isArray(f.bind_id)) {
8087
- f.bind_id = f.bind_id.filter((x) => x !== id);
8088
- } else if (f.bind_id === id) {
8089
- delete f.bind_id;
8090
- }
8091
- }
8337
+ const removed = removeTagInPlace(p, id);
8338
+ if (!removed) return;
8339
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([id]));
8092
8340
  }),
8093
8341
  undo: () => ctx.replaceProps(prevSlice)
8094
8342
  });
@@ -8156,58 +8404,23 @@ function removeField(ctx, id) {
8156
8404
  ctx.exec({
8157
8405
  name: "removeField",
8158
8406
  do: () => ctx.patchProps((p) => {
8159
- var _a, _b, _c, _d, _e, _f;
8160
8407
  prevSlice = (0, import_lodash_es3.cloneDeep)(p);
8161
- p.fields = ((_a = p.fields) != null ? _a : []).filter((f) => f.id !== id);
8162
- clearFieldButtonReceiverMaps(p, id);
8163
- for (const mapKey of [
8164
- "includes_for_buttons",
8165
- "excludes_for_buttons"
8166
- ]) {
8167
- const m = p[mapKey];
8168
- if (!m) continue;
8169
- for (const k of Object.keys(m)) {
8170
- m[k] = ((_b = m[k]) != null ? _b : []).filter((fid) => fid !== id);
8171
- if (!((_c = m[k]) == null ? void 0 : _c.length)) delete m[k];
8172
- }
8173
- }
8174
- for (const t of (_d = p.filters) != null ? _d : []) {
8175
- t.includes = ((_e = t.includes) != null ? _e : []).filter((x) => x !== id);
8176
- t.excludes = ((_f = t.excludes) != null ? _f : []).filter((x) => x !== id);
8177
- }
8408
+ const removedIds = removeFieldInPlace(p, id);
8409
+ if (!removedIds.length) return;
8410
+ applyDeleteCleanup(p, new Set(removedIds));
8178
8411
  }),
8179
8412
  undo: () => ctx.replaceProps(prevSlice)
8180
8413
  });
8181
8414
  }
8182
8415
  function remove(ctx, id) {
8416
+ const key = String(id);
8183
8417
  if (ctx.isTagId(id)) {
8184
8418
  ctx.exec({
8185
8419
  name: "removeTag",
8186
8420
  do: () => ctx.patchProps((p) => {
8187
- var _a, _b, _c, _d, _e, _f, _g, _h;
8188
- p.filters = ((_a = p.filters) != null ? _a : []).filter((t) => t.id !== id);
8189
- for (const t of (_b = p.filters) != null ? _b : []) {
8190
- if (t.bind_id === id) delete t.bind_id;
8191
- t.includes = ((_c = t.includes) != null ? _c : []).filter((x) => x !== id);
8192
- t.excludes = ((_d = t.excludes) != null ? _d : []).filter((x) => x !== id);
8193
- }
8194
- for (const f of (_e = p.fields) != null ? _e : []) {
8195
- if (Array.isArray(f.bind_id)) {
8196
- f.bind_id = f.bind_id.filter((x) => x !== id);
8197
- } else if (f.bind_id === id) {
8198
- delete f.bind_id;
8199
- }
8200
- }
8201
- if ((_f = p.order_for_tags) == null ? void 0 : _f[id]) delete p.order_for_tags[id];
8202
- for (const k of Object.keys((_g = p.order_for_tags) != null ? _g : {})) {
8203
- p.order_for_tags[k] = ((_h = p.order_for_tags[k]) != null ? _h : []).filter(
8204
- (fid) => {
8205
- var _a2;
8206
- return ((_a2 = p.fields) != null ? _a2 : []).some((f) => f.id === fid);
8207
- }
8208
- );
8209
- if (!p.order_for_tags[k].length) delete p.order_for_tags[k];
8210
- }
8421
+ const removed = removeTagInPlace(p, key);
8422
+ if (!removed) return;
8423
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
8211
8424
  }),
8212
8425
  undo: () => ctx.undo()
8213
8426
  });
@@ -8217,42 +8430,67 @@ function remove(ctx, id) {
8217
8430
  ctx.exec({
8218
8431
  name: "removeField",
8219
8432
  do: () => ctx.patchProps((p) => {
8220
- var _a, _b, _c, _d, _e, _f, _g, _h;
8221
- p.fields = ((_a = p.fields) != null ? _a : []).filter((f) => f.id !== id);
8222
- for (const t of (_b = p.filters) != null ? _b : []) {
8223
- t.includes = ((_c = t.includes) != null ? _c : []).filter((x) => x !== id);
8224
- t.excludes = ((_d = t.excludes) != null ? _d : []).filter((x) => x !== id);
8225
- }
8226
- for (const k of Object.keys((_e = p.order_for_tags) != null ? _e : {})) {
8227
- p.order_for_tags[k] = ((_f = p.order_for_tags[k]) != null ? _f : []).filter(
8228
- (fid) => fid !== id
8229
- );
8230
- if (!p.order_for_tags[k].length) delete p.order_for_tags[k];
8231
- }
8232
- const maps = [
8233
- "includes_for_options",
8234
- "excludes_for_options"
8235
- ];
8236
- for (const m of maps) {
8237
- const map = p[m];
8238
- if (!map) continue;
8239
- for (const key of Object.keys(map)) {
8240
- map[key] = ((_g = map[key]) != null ? _g : []).filter((fid) => fid !== id);
8241
- if (!((_h = map[key]) == null ? void 0 : _h.length)) delete map[key];
8242
- }
8243
- if (!Object.keys(map).length) delete p[m];
8244
- }
8433
+ const removedIds = removeFieldInPlace(p, key);
8434
+ if (!removedIds.length) return;
8435
+ applyDeleteCleanup(p, new Set(removedIds));
8245
8436
  }),
8246
8437
  undo: () => ctx.undo()
8247
8438
  });
8248
8439
  return;
8249
8440
  }
8250
8441
  if (ctx.isOptionId(id)) {
8251
- removeOption(ctx, id);
8442
+ ctx.exec({
8443
+ name: "removeOption",
8444
+ do: () => ctx.patchProps((p) => {
8445
+ const removed = removeOptionInPlace(p, key);
8446
+ if (!removed) return;
8447
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
8448
+ }),
8449
+ undo: () => ctx.undo()
8450
+ });
8252
8451
  return;
8253
8452
  }
8254
8453
  throw new Error("remove: unknown id prefix");
8255
8454
  }
8455
+ function removeMany(ctx, ids) {
8456
+ const { ordered } = stripDeletedIds(ids);
8457
+ if (!ordered.length) return;
8458
+ ctx.transact("removeMany", () => {
8459
+ ctx.patchProps((p) => {
8460
+ var _a, _b, _c;
8461
+ const existingFieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
8462
+ const existingTagIds = new Set(((_b = p.filters) != null ? _b : []).map((t) => String(t.id)));
8463
+ const existingOptionIds = new Set(
8464
+ ((_c = p.fields) != null ? _c : []).flatMap((f) => {
8465
+ var _a2;
8466
+ return ((_a2 = f.options) != null ? _a2 : []).map((o) => String(o.id));
8467
+ })
8468
+ );
8469
+ const fieldIds = ordered.filter((id) => ctx.isFieldId(id) && existingFieldIds.has(id));
8470
+ const fieldIdSet = new Set(fieldIds);
8471
+ const tagIds = ordered.filter((id) => ctx.isTagId(id) && existingTagIds.has(id));
8472
+ const optionIds = ordered.filter((id) => {
8473
+ if (!ctx.isOptionId(id) || !existingOptionIds.has(id)) return false;
8474
+ const owner = ownerOfOption(p, id);
8475
+ if (!owner) return false;
8476
+ return !fieldIdSet.has(String(owner.fieldId));
8477
+ });
8478
+ const deleted = /* @__PURE__ */ new Set();
8479
+ for (const optionId of optionIds) {
8480
+ if (removeOptionInPlace(p, optionId)) deleted.add(optionId);
8481
+ }
8482
+ for (const fieldId of fieldIds) {
8483
+ const removedIds = removeFieldInPlace(p, fieldId);
8484
+ for (const rid of removedIds) deleted.add(rid);
8485
+ }
8486
+ for (const tagId of tagIds) {
8487
+ if (removeTagInPlace(p, tagId)) deleted.add(tagId);
8488
+ }
8489
+ if (!deleted.size) return;
8490
+ applyDeleteCleanup(p, deleted);
8491
+ });
8492
+ });
8493
+ }
8256
8494
  function getNode(ctx, id) {
8257
8495
  var _a, _b, _c, _d;
8258
8496
  const props = ctx.getProps();
@@ -9497,6 +9735,9 @@ var Editor = class {
9497
9735
  duplicate(ref, opts = {}) {
9498
9736
  return duplicate(this.moduleCtx(), ref, opts);
9499
9737
  }
9738
+ duplicateMany(ids, opts = {}) {
9739
+ return duplicateMany(this.moduleCtx(), ids, opts);
9740
+ }
9500
9741
  reLabel(id, nextLabel) {
9501
9742
  return reLabel(this.moduleCtx(), id, nextLabel);
9502
9743
  }
@@ -9560,6 +9801,260 @@ var Editor = class {
9560
9801
  remove(id) {
9561
9802
  return remove(this.moduleCtx(), id);
9562
9803
  }
9804
+ removeMany(ids) {
9805
+ return removeMany(this.moduleCtx(), ids);
9806
+ }
9807
+ clearServiceMany(ids) {
9808
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9809
+ if (!ordered.length) return;
9810
+ this.transact("clearServiceMany", () => {
9811
+ this.patchProps((p) => {
9812
+ var _a, _b, _c, _d;
9813
+ for (const id of ordered) {
9814
+ if (this.isTagId(id)) {
9815
+ const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
9816
+ if (t && "service_id" in t) delete t.service_id;
9817
+ continue;
9818
+ }
9819
+ if (this.isFieldId(id)) {
9820
+ const f = ((_b = p.fields) != null ? _b : []).find((x) => x.id === id);
9821
+ if (f && "service_id" in f) delete f.service_id;
9822
+ continue;
9823
+ }
9824
+ if (this.isOptionId(id)) {
9825
+ const own = ownerOfOption(p, id);
9826
+ if (!own) continue;
9827
+ const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
9828
+ const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
9829
+ if (o && "service_id" in o) delete o.service_id;
9830
+ }
9831
+ }
9832
+ });
9833
+ });
9834
+ }
9835
+ rebindMany(ids, targetTagId, opts) {
9836
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9837
+ if (!ordered.length) return;
9838
+ this.transact("rebindMany", () => {
9839
+ this.patchProps((p) => {
9840
+ var _a, _b, _c;
9841
+ const targetExists = ((_a = p.filters) != null ? _a : []).some((t) => t.id === targetTagId);
9842
+ if (!targetExists) return;
9843
+ for (const id of ordered) {
9844
+ if (this.isFieldId(id)) {
9845
+ const f = ((_b = p.fields) != null ? _b : []).find((x) => x.id === id);
9846
+ if (!f) continue;
9847
+ f.bind_id = targetTagId;
9848
+ continue;
9849
+ }
9850
+ if (this.isTagId(id)) {
9851
+ const t = ((_c = p.filters) != null ? _c : []).find((x) => x.id === id);
9852
+ if (!t) continue;
9853
+ if (!(opts == null ? void 0 : opts.allowTagCycles) && wouldCreateTagCycle(this.moduleCtx(), p, targetTagId, id)) {
9854
+ continue;
9855
+ }
9856
+ t.bind_id = targetTagId;
9857
+ }
9858
+ }
9859
+ });
9860
+ });
9861
+ }
9862
+ includeMany(receiverId, ids) {
9863
+ const accepted = Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))).filter((id) => id !== receiverId).filter((id) => this.getNode(id).data != null);
9864
+ if (!accepted.length) return;
9865
+ include(this.moduleCtx(), receiverId, accepted);
9866
+ }
9867
+ excludeMany(receiverId, ids) {
9868
+ const accepted = Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))).filter((id) => id !== receiverId).filter((id) => this.getNode(id).data != null);
9869
+ if (!accepted.length) return;
9870
+ exclude(this.moduleCtx(), receiverId, accepted);
9871
+ }
9872
+ clearRelationsMany(ids, mode = "both") {
9873
+ const selected = new Set(Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))));
9874
+ if (!selected.size) return;
9875
+ this.transact("clearRelationsMany", () => {
9876
+ this.patchProps((p) => {
9877
+ var _a, _b, _c;
9878
+ const clearOwned = mode === "owned" || mode === "both";
9879
+ const clearIncoming = mode === "incoming" || mode === "both";
9880
+ for (const t of (_a = p.filters) != null ? _a : []) {
9881
+ if (clearOwned && selected.has(t.id)) {
9882
+ delete t.includes;
9883
+ delete t.excludes;
9884
+ }
9885
+ if (clearIncoming) {
9886
+ if (t.includes) {
9887
+ t.includes = t.includes.filter((x) => !selected.has(String(x)));
9888
+ if (!t.includes.length) delete t.includes;
9889
+ }
9890
+ if (t.excludes) {
9891
+ t.excludes = t.excludes.filter((x) => !selected.has(String(x)));
9892
+ if (!t.excludes.length) delete t.excludes;
9893
+ }
9894
+ }
9895
+ }
9896
+ const maps = [
9897
+ "includes_for_buttons",
9898
+ "excludes_for_buttons",
9899
+ "includes_for_options",
9900
+ "excludes_for_options"
9901
+ ];
9902
+ for (const k of maps) {
9903
+ const map = p[k];
9904
+ if (!map) continue;
9905
+ for (const key of Object.keys(map)) {
9906
+ if (clearOwned && selected.has(String(key))) {
9907
+ delete map[key];
9908
+ continue;
9909
+ }
9910
+ if (clearIncoming) {
9911
+ map[key] = ((_b = map[key]) != null ? _b : []).filter((x) => !selected.has(String(x)));
9912
+ if (!((_c = map[key]) == null ? void 0 : _c.length)) delete map[key];
9913
+ }
9914
+ }
9915
+ if (!Object.keys(map).length) delete p[k];
9916
+ }
9917
+ });
9918
+ });
9919
+ }
9920
+ renameLabelsMany(ids, input) {
9921
+ var _a, _b;
9922
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9923
+ if (!ordered.length) return;
9924
+ const prefix = (_a = input.prefix) != null ? _a : "";
9925
+ const suffix = (_b = input.suffix) != null ? _b : "";
9926
+ this.transact("renameLabelsMany", () => {
9927
+ this.patchProps((p) => {
9928
+ var _a2, _b2, _c, _d, _e, _f, _g;
9929
+ for (const id of ordered) {
9930
+ if (this.isTagId(id)) {
9931
+ const t = ((_a2 = p.filters) != null ? _a2 : []).find((x) => x.id === id);
9932
+ if (t) t.label = `${prefix}${(_b2 = t.label) != null ? _b2 : ""}${suffix}`.trim();
9933
+ continue;
9934
+ }
9935
+ if (this.isFieldId(id)) {
9936
+ const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === id);
9937
+ if (f) f.label = `${prefix}${(_d = f.label) != null ? _d : ""}${suffix}`.trim();
9938
+ continue;
9939
+ }
9940
+ if (this.isOptionId(id)) {
9941
+ const own = ownerOfOption(p, id);
9942
+ if (!own) continue;
9943
+ const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === own.fieldId);
9944
+ const o = (_f = f == null ? void 0 : f.options) == null ? void 0 : _f.find((x) => x.id === id);
9945
+ if (o) o.label = `${prefix}${(_g = o.label) != null ? _g : ""}${suffix}`.trim();
9946
+ }
9947
+ }
9948
+ });
9949
+ });
9950
+ }
9951
+ setPricingRoleMany(ids, role) {
9952
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9953
+ if (!ordered.length) return;
9954
+ this.transact("setPricingRoleMany", () => {
9955
+ for (const id of ordered) {
9956
+ if (this.isFieldId(id) || this.isOptionId(id)) {
9957
+ this.setService(id, { pricing_role: role });
9958
+ }
9959
+ }
9960
+ });
9961
+ }
9962
+ clearFieldDefaultsMany(ids) {
9963
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9964
+ if (!ordered.length) return;
9965
+ this.transact("clearFieldDefaultsMany", () => {
9966
+ this.patchProps((p) => {
9967
+ var _a;
9968
+ for (const id of ordered) {
9969
+ if (!this.isFieldId(id)) continue;
9970
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
9971
+ if (f && "defaults" in f) delete f.defaults;
9972
+ }
9973
+ });
9974
+ });
9975
+ }
9976
+ clearFieldValidationMany(ids) {
9977
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9978
+ if (!ordered.length) return;
9979
+ this.transact("clearFieldValidationMany", () => {
9980
+ this.patchProps((p) => {
9981
+ var _a;
9982
+ for (const id of ordered) {
9983
+ if (!this.isFieldId(id)) continue;
9984
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
9985
+ if (f && "validation" in f) delete f.validation;
9986
+ }
9987
+ });
9988
+ });
9989
+ }
9990
+ autoCreateOptionsMany(ids, makeOption) {
9991
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9992
+ if (!ordered.length) return;
9993
+ this.transact("autoCreateOptionsMany", () => {
9994
+ this.patchProps((p) => {
9995
+ var _a, _b, _c, _d;
9996
+ for (const id of ordered) {
9997
+ if (!this.isFieldId(id)) continue;
9998
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
9999
+ if (!f) continue;
10000
+ const opts = (_b = f.options) != null ? _b : f.options = [];
10001
+ if (opts.length > 0) continue;
10002
+ const next = (_c = makeOption == null ? void 0 : makeOption(id)) != null ? _c : { label: "Option label", value: "option" };
10003
+ opts.push({
10004
+ id: (_d = next.id) != null ? _d : this.moduleCtx().genId("o"),
10005
+ label: next.label,
10006
+ value: next.value
10007
+ });
10008
+ }
10009
+ });
10010
+ });
10011
+ }
10012
+ clearAllOptionsMany(ids) {
10013
+ var _a, _b;
10014
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
10015
+ if (!ordered.length) return;
10016
+ const optionIds = [];
10017
+ const props = this.getProps();
10018
+ for (const id of ordered) {
10019
+ if (!this.isFieldId(id)) continue;
10020
+ const f = ((_a = props.fields) != null ? _a : []).find((x) => x.id === id);
10021
+ for (const o of (_b = f == null ? void 0 : f.options) != null ? _b : []) optionIds.push(o.id);
10022
+ }
10023
+ if (!optionIds.length) return;
10024
+ removeMany(this.moduleCtx(), optionIds);
10025
+ }
10026
+ removeNoticesForNodes(ids) {
10027
+ const selected = new Set(Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))));
10028
+ if (!selected.size) return;
10029
+ this.transact("removeNoticesForNodes", () => {
10030
+ this.patchProps((p) => {
10031
+ var _a;
10032
+ if (!((_a = p.notices) == null ? void 0 : _a.length)) return;
10033
+ p.notices = p.notices.filter((n) => {
10034
+ const target = n.target;
10035
+ if (!target || target.scope === "global") return true;
10036
+ if (target.scope === "node") return !selected.has(String(target.node_id));
10037
+ return true;
10038
+ });
10039
+ if (!p.notices.length) delete p.notices;
10040
+ });
10041
+ });
10042
+ }
10043
+ setNoticesVisibilityForNodes(ids, type) {
10044
+ const selected = new Set(Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))));
10045
+ if (!selected.size) return;
10046
+ this.transact("setNoticesVisibilityForNodes", () => {
10047
+ this.patchProps((p) => {
10048
+ var _a;
10049
+ for (const n of (_a = p.notices) != null ? _a : []) {
10050
+ const target = n.target;
10051
+ if ((target == null ? void 0 : target.scope) === "node" && selected.has(String(target.node_id))) {
10052
+ n.type = type;
10053
+ }
10054
+ }
10055
+ });
10056
+ });
10057
+ }
9563
10058
  getNode(id) {
9564
10059
  return getNode(this.moduleCtx(), id);
9565
10060
  }
@@ -9803,9 +10298,8 @@ var Editor = class {
9803
10298
  Array.isArray(canvas.selection) ? canvas.selection : Array.from(canvas.selection)
9804
10299
  );
9805
10300
  }
9806
- } else {
9807
- this.api.refreshGraph();
9808
10301
  }
10302
+ this.api.refreshGraph();
9809
10303
  this.emit("editor:change", { props: s.props, reason, snapshot: s });
9810
10304
  }
9811
10305
  pushHistory(snap) {
@@ -10893,12 +11387,12 @@ function useCanvasOwned(initialProps, canvasOpts, builderOpts) {
10893
11387
  // src/react/workspace/context/hooks/use-canvas.ts
10894
11388
  var React16 = __toESM(require("react"), 1);
10895
11389
  var import_react4 = require("react");
10896
- function deriveSelectionInfo(props, ids) {
11390
+ function deriveSelectionInfo(nodeMap, ids) {
10897
11391
  const tags = [];
10898
11392
  const fields = [];
10899
11393
  const options = [];
10900
11394
  for (const id of ids) {
10901
- const node = props.get(id);
11395
+ const node = nodeMap.get(id);
10902
11396
  if (!node) continue;
10903
11397
  if (node.kind == "tag") {
10904
11398
  tags.push(id);
@@ -10925,6 +11419,49 @@ function deriveSelectionInfo(props, ids) {
10925
11419
  optionIds: uniq2(options)
10926
11420
  };
10927
11421
  }
11422
+ function noticeTargetsSelection(notice, selected) {
11423
+ const target = notice.target;
11424
+ if (target.scope === "global") return false;
11425
+ return selected.has(String(target.node_id));
11426
+ }
11427
+ function deriveSelectionCapabilities(props, selectionInfo) {
11428
+ var _a, _b, _c;
11429
+ const selected = new Set(selectionInfo.ids.map(String));
11430
+ const fields = (_a = props == null ? void 0 : props.fields) != null ? _a : [];
11431
+ const tags = (_b = props == null ? void 0 : props.filters) != null ? _b : [];
11432
+ const notices = (_c = props == null ? void 0 : props.notices) != null ? _c : [];
11433
+ const hasSelectedFieldWithOptions = fields.some(
11434
+ (f) => {
11435
+ var _a2, _b2;
11436
+ return selected.has(String(f.id)) && ((_b2 = (_a2 = f.options) == null ? void 0 : _a2.length) != null ? _b2 : 0) > 0;
11437
+ }
11438
+ );
11439
+ const hasServiceBearingNodes = tags.some(
11440
+ (t) => selected.has(String(t.id)) && t.service_id !== void 0 && t.service_id !== null
11441
+ ) || fields.some(
11442
+ (f) => selected.has(String(f.id)) && f.service_id !== void 0 && f.service_id !== null
11443
+ ) || fields.some(
11444
+ (f) => {
11445
+ var _a2;
11446
+ return ((_a2 = f.options) != null ? _a2 : []).some(
11447
+ (o) => selected.has(String(o.id)) && o.service_id !== void 0 && o.service_id !== null
11448
+ );
11449
+ }
11450
+ );
11451
+ const hasNoticesForSelection = notices.some(
11452
+ (n) => noticeTargetsSelection(n, selected)
11453
+ );
11454
+ return {
11455
+ hasTags: selectionInfo.tagIds.length > 0,
11456
+ hasFields: selectionInfo.fieldIds.length > 0,
11457
+ hasOptions: selectionInfo.optionIds.length > 0,
11458
+ hasServiceBearingNodes,
11459
+ hasSelectedFieldWithOptions,
11460
+ hasNoticesForSelection,
11461
+ canIncludeExcludeTargets: selectionInfo.tagIds.length + selectionInfo.fieldIds.length + selectionInfo.optionIds.length > 0,
11462
+ canRebind: selectionInfo.fieldIds.length > 0 || selectionInfo.tagIds.length > 0
11463
+ };
11464
+ }
10928
11465
  function tagBindIds(tag) {
10929
11466
  const bind = tag.bind_id;
10930
11467
  if (!bind) return [];
@@ -11064,7 +11601,11 @@ function useCanvas() {
11064
11601
  });
11065
11602
  return off;
11066
11603
  }, [api]);
11067
- const selector = (0, import_react4.useMemo)(() => createNodeIndex(api.builder), [props]);
11604
+ const selector = (0, import_react4.useMemo)(() => createNodeIndex(api.builder), [api.builder, props]);
11605
+ const selectionCapabilities = React16.useMemo(
11606
+ () => deriveSelectionCapabilities(props, selectionInfo),
11607
+ [props, selectionInfo]
11608
+ );
11068
11609
  return React16.useMemo(
11069
11610
  () => ({
11070
11611
  api,
@@ -11073,6 +11614,7 @@ function useCanvas() {
11073
11614
  props,
11074
11615
  selection,
11075
11616
  selectionInfo,
11617
+ selectionCapabilities,
11076
11618
  selector,
11077
11619
  activeId,
11078
11620
  setActive
@@ -11084,6 +11626,7 @@ function useCanvas() {
11084
11626
  props,
11085
11627
  selection,
11086
11628
  selectionInfo,
11629
+ selectionCapabilities,
11087
11630
  selector,
11088
11631
  activeId,
11089
11632
  setActive
@@ -13771,6 +14314,7 @@ var import_jsx_runtime11 = require("react/jsx-runtime");
13771
14314
  WorkspaceProvider,
13772
14315
  createMemoryWorkspaceBackend,
13773
14316
  createPollAdapter,
14317
+ deriveSelectionCapabilities,
13774
14318
  useCanvas,
13775
14319
  useCanvasAPI,
13776
14320
  useCanvasFromBuilder,