@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.
@@ -311,6 +311,70 @@ import * as React6 from "react";
311
311
  function setLoadableError4(updater, error) {
312
312
  updater((s) => ({ ...s, loading: false, error }));
313
313
  }
314
+ function parseTimestamp(value) {
315
+ if (value === void 0 || value === null) return void 0;
316
+ if (typeof value === "number") {
317
+ return Number.isFinite(value) ? value : void 0;
318
+ }
319
+ const parsed = Date.parse(value);
320
+ return Number.isFinite(parsed) ? parsed : void 0;
321
+ }
322
+ function templateTime(template) {
323
+ var _a;
324
+ return (_a = parseTimestamp(template.updatedAt)) != null ? _a : parseTimestamp(template.createdAt);
325
+ }
326
+ function shouldReplaceTemplates(params) {
327
+ if (!params.requestedSince) return true;
328
+ if (!params.lastUpdatedAt) return false;
329
+ const requested = parseTimestamp(params.requestedSince);
330
+ const last = parseTimestamp(params.lastUpdatedAt);
331
+ if (requested === void 0 || last === void 0) {
332
+ return false;
333
+ }
334
+ return requested < last;
335
+ }
336
+ function pickNewestTemplate(current, incoming) {
337
+ const currentTime = templateTime(current);
338
+ const incomingTime = templateTime(incoming);
339
+ if (currentTime !== void 0 && incomingTime !== void 0) {
340
+ return incomingTime >= currentTime ? incoming : current;
341
+ }
342
+ if (currentTime === void 0 && incomingTime !== void 0) return incoming;
343
+ if (currentTime !== void 0 && incomingTime === void 0) return current;
344
+ return incoming;
345
+ }
346
+ function mergeTemplates(current, incoming, opts) {
347
+ var _a;
348
+ const sinceTime = parseTimestamp(opts == null ? void 0 : opts.since);
349
+ const incomingIds = new Set(incoming.map((template) => template.id));
350
+ const deletedIds = new Set((_a = opts == null ? void 0 : opts.deletedIds) != null ? _a : []);
351
+ const byId = /* @__PURE__ */ new Map();
352
+ for (const template of current != null ? current : []) {
353
+ if (deletedIds.has(template.id)) continue;
354
+ const updatedTime = templateTime(template);
355
+ const shouldHaveAppearedInDelta = (opts == null ? void 0 : opts.reconcileMissingSince) === true && sinceTime !== void 0 && updatedTime !== void 0 && updatedTime > sinceTime;
356
+ const missingFromDelta = shouldHaveAppearedInDelta && !incomingIds.has(template.id);
357
+ if (!missingFromDelta) {
358
+ byId.set(template.id, template);
359
+ }
360
+ }
361
+ for (const template of incoming) {
362
+ if (deletedIds.has(template.id)) continue;
363
+ const existing = byId.get(template.id);
364
+ byId.set(
365
+ template.id,
366
+ existing ? pickNewestTemplate(existing, template) : template
367
+ );
368
+ }
369
+ return Array.from(byId.values()).sort((a, b) => {
370
+ const aTime = templateTime(a);
371
+ const bTime = templateTime(b);
372
+ if (aTime !== void 0 && bTime !== void 0 && aTime !== bTime) {
373
+ return bTime - aTime;
374
+ }
375
+ return a.name.localeCompare(b.name);
376
+ });
377
+ }
314
378
  function useTemplatesSlice(params) {
315
379
  const {
316
380
  backend,
@@ -338,16 +402,26 @@ function useTemplatesSlice(params) {
338
402
  };
339
403
  }
340
404
  setTemplates((s) => ({ ...s, loading: true }));
405
+ const requestedSince = (_b = params2 == null ? void 0 : params2.since) != null ? _b : templates.updatedAt;
341
406
  const res = await backend.templates.refresh({
342
407
  workspaceId,
343
408
  branchId,
344
- since: (_b = params2 == null ? void 0 : params2.since) != null ? _b : templates.updatedAt
409
+ since: requestedSince
345
410
  });
346
411
  if (res.ok) {
347
- setTemplates({
348
- data: res.value,
349
- loading: false,
350
- updatedAt: runtime.now()
412
+ setTemplates((current) => {
413
+ const replace = shouldReplaceTemplates({
414
+ requestedSince,
415
+ lastUpdatedAt: current.updatedAt
416
+ });
417
+ return {
418
+ data: replace ? res.value : mergeTemplates(current.data, res.value, {
419
+ since: requestedSince,
420
+ reconcileMissingSince: false
421
+ }),
422
+ loading: false,
423
+ updatedAt: runtime.now()
424
+ };
351
425
  });
352
426
  return res;
353
427
  } else {
@@ -365,14 +439,14 @@ function useTemplatesSlice(params) {
365
439
  );
366
440
  const createTemplate = React6.useCallback(
367
441
  async (input) => {
368
- var _a, _b;
442
+ var _a;
369
443
  const res = await backend.templates.create(workspaceId, {
370
444
  ...input,
371
- branchId: (_a = input.branchId) != null ? _a : getCurrentBranchId()
445
+ branchId: input.branchId !== null ? input.branchId : getCurrentBranchId()
372
446
  });
373
447
  if (res.ok) {
374
448
  await refreshTemplates({
375
- branchId: (_b = res.value.branchId) != null ? _b : getCurrentBranchId()
449
+ branchId: (_a = res.value.branchId) != null ? _a : getCurrentBranchId()
376
450
  });
377
451
  }
378
452
  return res;
@@ -432,11 +506,23 @@ function useTemplatesSlice(params) {
432
506
  async (id) => {
433
507
  const res = await backend.templates.delete(id);
434
508
  if (res.ok) {
435
- await refreshTemplates({ branchId: getCurrentBranchId() });
509
+ const deleteRefreshSince = runtime.now();
510
+ setTemplates((current) => {
511
+ var _a, _b;
512
+ return {
513
+ ...current,
514
+ data: (_b = (_a = current.data) == null ? void 0 : _a.filter((template) => template.id !== id)) != null ? _b : current.data,
515
+ updatedAt: deleteRefreshSince
516
+ };
517
+ });
518
+ await refreshTemplates({
519
+ branchId: getCurrentBranchId(),
520
+ since: deleteRefreshSince
521
+ });
436
522
  }
437
523
  return res;
438
524
  },
439
- [backend.templates, getCurrentBranchId, refreshTemplates]
525
+ [backend.templates, getCurrentBranchId, refreshTemplates, runtime]
440
526
  );
441
527
  const invalidateTemplates = React6.useCallback(() => {
442
528
  setTemplates((s) => ({ ...s, updatedAt: void 0 }));
@@ -2699,7 +2785,7 @@ function WorkspaceProvider(props) {
2699
2785
  },
2700
2786
  branchContext: bootCtl.refreshBranchContext,
2701
2787
  templates: async (params) => {
2702
- await templatesSlice.refreshTemplates(params);
2788
+ return await templatesSlice.refreshTemplates(params);
2703
2789
  },
2704
2790
  participants: async (params) => {
2705
2791
  await branchesSlice.refreshParticipants(params);
@@ -7393,13 +7479,7 @@ function duplicate(ctx, ref, opts = {}) {
7393
7479
  try {
7394
7480
  let newId2 = "";
7395
7481
  ctx.transact("duplicate", () => {
7396
- if (ref.kind === "tag") {
7397
- newId2 = duplicateTag(ctx, ref.id, opts);
7398
- } else if (ref.kind === "field") {
7399
- newId2 = duplicateField(ctx, ref.id, opts);
7400
- } else {
7401
- newId2 = duplicateOption(ctx, ref.fieldId, ref.id, opts);
7402
- }
7482
+ newId2 = duplicateInPlace(ctx, ref, opts);
7403
7483
  });
7404
7484
  return newId2;
7405
7485
  } catch (err) {
@@ -7407,6 +7487,74 @@ function duplicate(ctx, ref, opts = {}) {
7407
7487
  throw err;
7408
7488
  }
7409
7489
  }
7490
+ function duplicateMany(ctx, ids, opts = {}) {
7491
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
7492
+ if (!ordered.length) return [];
7493
+ const snapBefore = ctx.makeSnapshot("duplicateMany:before");
7494
+ try {
7495
+ const created = [];
7496
+ ctx.transact("duplicateMany", () => {
7497
+ var _a, _b, _c;
7498
+ const props = ctx.getProps();
7499
+ const selectedFields = /* @__PURE__ */ new Set();
7500
+ for (const id of ordered) {
7501
+ if (ctx.isFieldId(id) && ((_a = props.fields) != null ? _a : []).some((f) => f.id === id)) {
7502
+ selectedFields.add(id);
7503
+ }
7504
+ }
7505
+ for (const id of ordered) {
7506
+ if (ctx.isTagId(id)) {
7507
+ if (!((_b = ctx.getProps().filters) != null ? _b : []).some((t) => t.id === id)) continue;
7508
+ created.push(
7509
+ duplicateInPlace(ctx, { kind: "tag", id }, opts)
7510
+ );
7511
+ continue;
7512
+ }
7513
+ if (ctx.isFieldId(id)) {
7514
+ if (!((_c = ctx.getProps().fields) != null ? _c : []).some((f) => f.id === id)) continue;
7515
+ created.push(
7516
+ duplicateInPlace(ctx, { kind: "field", id }, opts)
7517
+ );
7518
+ continue;
7519
+ }
7520
+ if (ctx.isOptionId(id)) {
7521
+ const owner = ownerFieldOfOption(ctx.getProps(), id);
7522
+ if (!owner) continue;
7523
+ if (selectedFields.has(owner.fieldId)) continue;
7524
+ created.push(
7525
+ duplicateInPlace(
7526
+ ctx,
7527
+ { kind: "option", fieldId: owner.fieldId, id },
7528
+ opts
7529
+ )
7530
+ );
7531
+ }
7532
+ }
7533
+ });
7534
+ return created;
7535
+ } catch (err) {
7536
+ ctx.loadSnapshot(snapBefore, "undo");
7537
+ throw err;
7538
+ }
7539
+ }
7540
+ function duplicateInPlace(ctx, ref, opts = {}) {
7541
+ if (ref.kind === "tag") {
7542
+ return duplicateTag(ctx, ref.id, opts);
7543
+ }
7544
+ if (ref.kind === "field") {
7545
+ return duplicateField(ctx, ref.id, opts);
7546
+ }
7547
+ return duplicateOption(ctx, ref.fieldId, ref.id, opts);
7548
+ }
7549
+ function ownerFieldOfOption(props, optionId) {
7550
+ var _a, _b;
7551
+ for (const field of (_a = props.fields) != null ? _a : []) {
7552
+ if (((_b = field.options) != null ? _b : []).some((o) => o.id === optionId)) {
7553
+ return { fieldId: field.id };
7554
+ }
7555
+ }
7556
+ return null;
7557
+ }
7410
7558
  function duplicateTag(ctx, tagId, opts) {
7411
7559
  var _a, _b, _c, _d;
7412
7560
  const props = ctx.getProps();
@@ -7693,6 +7841,129 @@ function ensureServiceExists(opts, id) {
7693
7841
  }
7694
7842
 
7695
7843
  // src/react/canvas/editor/editor-nodes.ts
7844
+ var RELATION_MAP_KEYS = [
7845
+ "includes_for_buttons",
7846
+ "excludes_for_buttons",
7847
+ "includes_for_options",
7848
+ "excludes_for_options"
7849
+ ];
7850
+ function stripDeletedIds(ids) {
7851
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
7852
+ return { ordered, set: new Set(ordered) };
7853
+ }
7854
+ function cleanTagRelationsForDeleted(p, deleted) {
7855
+ var _a;
7856
+ for (const t of (_a = p.filters) != null ? _a : []) {
7857
+ if (t.bind_id && deleted.has(String(t.bind_id))) delete t.bind_id;
7858
+ if (t.includes) {
7859
+ const next = t.includes.filter((x) => !deleted.has(String(x)));
7860
+ if (next.length) t.includes = next;
7861
+ else delete t.includes;
7862
+ }
7863
+ if (t.excludes) {
7864
+ const next = t.excludes.filter((x) => !deleted.has(String(x)));
7865
+ if (next.length) t.excludes = next;
7866
+ else delete t.excludes;
7867
+ }
7868
+ }
7869
+ }
7870
+ function cleanFieldBindsForDeleted(p, deleted) {
7871
+ var _a;
7872
+ for (const f of (_a = p.fields) != null ? _a : []) {
7873
+ const bind = f.bind_id;
7874
+ if (!bind) continue;
7875
+ if (Array.isArray(bind)) {
7876
+ const next = bind.filter((x) => !deleted.has(String(x)));
7877
+ if (next.length) f.bind_id = next;
7878
+ else delete f.bind_id;
7879
+ continue;
7880
+ }
7881
+ if (deleted.has(String(bind))) delete f.bind_id;
7882
+ }
7883
+ }
7884
+ function cleanRelationMapsForDeleted(p, deleted) {
7885
+ var _a;
7886
+ for (const key of RELATION_MAP_KEYS) {
7887
+ const map = p[key];
7888
+ if (!map) continue;
7889
+ for (const mapKey of Object.keys(map)) {
7890
+ if (deleted.has(String(mapKey))) {
7891
+ delete map[mapKey];
7892
+ continue;
7893
+ }
7894
+ const next = ((_a = map[mapKey]) != null ? _a : []).filter(
7895
+ (item) => !deleted.has(String(item))
7896
+ );
7897
+ if (next.length) map[mapKey] = next;
7898
+ else delete map[mapKey];
7899
+ }
7900
+ if (!Object.keys(map).length) delete p[key];
7901
+ }
7902
+ }
7903
+ function cleanOrderForTagsForDeleted(p, deleted) {
7904
+ var _a, _b;
7905
+ const map = p.order_for_tags;
7906
+ if (!map) return;
7907
+ const fieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
7908
+ for (const key of Object.keys(map)) {
7909
+ if (deleted.has(String(key))) {
7910
+ delete map[key];
7911
+ continue;
7912
+ }
7913
+ const next = ((_b = map[key]) != null ? _b : []).filter(
7914
+ (fid) => !deleted.has(String(fid)) && fieldIds.has(String(fid))
7915
+ );
7916
+ if (next.length) map[key] = next;
7917
+ else delete map[key];
7918
+ }
7919
+ if (!Object.keys(map).length) delete p.order_for_tags;
7920
+ }
7921
+ function cleanNoticesForDeleted(p, deleted) {
7922
+ var _a;
7923
+ if (!((_a = p.notices) == null ? void 0 : _a.length)) return;
7924
+ p.notices = p.notices.filter((n) => {
7925
+ const target = n.target;
7926
+ if (!target || target.scope === "global") return true;
7927
+ if (target.scope === "node" && deleted.has(String(target.node_id))) {
7928
+ return false;
7929
+ }
7930
+ return true;
7931
+ });
7932
+ if (!p.notices.length) delete p.notices;
7933
+ }
7934
+ function applyDeleteCleanup(p, deleted) {
7935
+ cleanTagRelationsForDeleted(p, deleted);
7936
+ cleanFieldBindsForDeleted(p, deleted);
7937
+ cleanRelationMapsForDeleted(p, deleted);
7938
+ cleanOrderForTagsForDeleted(p, deleted);
7939
+ cleanNoticesForDeleted(p, deleted);
7940
+ }
7941
+ function removeOptionInPlace(p, optionId) {
7942
+ var _a;
7943
+ const owner = ownerOfOption(p, optionId);
7944
+ if (!owner) return false;
7945
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
7946
+ if (!(f == null ? void 0 : f.options)) return false;
7947
+ const before = f.options.length;
7948
+ f.options = f.options.filter((o) => o.id !== optionId);
7949
+ return f.options.length !== before;
7950
+ }
7951
+ function removeFieldInPlace(p, fieldId) {
7952
+ var _a, _b, _c, _d, _e;
7953
+ const field = ((_a = p.fields) != null ? _a : []).find((f) => f.id === fieldId);
7954
+ if (!field) return [];
7955
+ const deleted = [fieldId, ...((_b = field.options) != null ? _b : []).map((o) => String(o.id))];
7956
+ const before = ((_c = p.fields) != null ? _c : []).length;
7957
+ p.fields = ((_d = p.fields) != null ? _d : []).filter((f) => f.id !== fieldId);
7958
+ clearFieldButtonReceiverMaps(p, fieldId);
7959
+ return ((_e = p.fields) != null ? _e : []).length !== before ? deleted : [];
7960
+ }
7961
+ function removeTagInPlace(p, tagId) {
7962
+ var _a, _b, _c;
7963
+ const before = ((_a = p.filters) != null ? _a : []).length;
7964
+ p.filters = ((_b = p.filters) != null ? _b : []).filter((t) => t.id !== tagId);
7965
+ return ((_c = p.filters) != null ? _c : []).length !== before;
7966
+ }
7696
7967
  function reLabel(ctx, id, nextLabel) {
7697
7968
  const label = String(nextLabel != null ? nextLabel : "").trim();
7698
7969
  ctx.exec({
@@ -7827,22 +8098,9 @@ function removeOption(ctx, optionId) {
7827
8098
  ctx.exec({
7828
8099
  name: "removeOption",
7829
8100
  do: () => ctx.patchProps((p) => {
7830
- var _a;
7831
- const owner = ownerOfOption(p, optionId);
7832
- if (!owner) return;
7833
- const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === owner.fieldId);
7834
- if (!(f == null ? void 0 : f.options)) return;
7835
- f.options = f.options.filter((o) => o.id !== optionId);
7836
- const maps = [
7837
- "includes_for_options",
7838
- "excludes_for_options"
7839
- ];
7840
- for (const m of maps) {
7841
- const map = p[m];
7842
- if (!map) continue;
7843
- if (map[optionId]) delete map[optionId];
7844
- if (!Object.keys(map).length) delete p[m];
7845
- }
8101
+ const removed = removeOptionInPlace(p, optionId);
8102
+ if (!removed) return;
8103
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([optionId]));
7846
8104
  }),
7847
8105
  undo: () => ctx.undo()
7848
8106
  });
@@ -8031,21 +8289,10 @@ function removeTag(ctx, id) {
8031
8289
  ctx.exec({
8032
8290
  name: "removeTag",
8033
8291
  do: () => ctx.patchProps((p) => {
8034
- var _a, _b, _c, _d, _e;
8035
8292
  prevSlice = cloneDeep3(p);
8036
- p.filters = ((_a = p.filters) != null ? _a : []).filter((t) => t.id !== id);
8037
- for (const t of (_b = p.filters) != null ? _b : []) {
8038
- if (t.bind_id === id) delete t.bind_id;
8039
- t.includes = ((_c = t.includes) != null ? _c : []).filter((x) => x !== id);
8040
- t.excludes = ((_d = t.excludes) != null ? _d : []).filter((x) => x !== id);
8041
- }
8042
- for (const f of (_e = p.fields) != null ? _e : []) {
8043
- if (Array.isArray(f.bind_id)) {
8044
- f.bind_id = f.bind_id.filter((x) => x !== id);
8045
- } else if (f.bind_id === id) {
8046
- delete f.bind_id;
8047
- }
8048
- }
8293
+ const removed = removeTagInPlace(p, id);
8294
+ if (!removed) return;
8295
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([id]));
8049
8296
  }),
8050
8297
  undo: () => ctx.replaceProps(prevSlice)
8051
8298
  });
@@ -8113,58 +8360,23 @@ function removeField(ctx, id) {
8113
8360
  ctx.exec({
8114
8361
  name: "removeField",
8115
8362
  do: () => ctx.patchProps((p) => {
8116
- var _a, _b, _c, _d, _e, _f;
8117
8363
  prevSlice = cloneDeep3(p);
8118
- p.fields = ((_a = p.fields) != null ? _a : []).filter((f) => f.id !== id);
8119
- clearFieldButtonReceiverMaps(p, id);
8120
- for (const mapKey of [
8121
- "includes_for_buttons",
8122
- "excludes_for_buttons"
8123
- ]) {
8124
- const m = p[mapKey];
8125
- if (!m) continue;
8126
- for (const k of Object.keys(m)) {
8127
- m[k] = ((_b = m[k]) != null ? _b : []).filter((fid) => fid !== id);
8128
- if (!((_c = m[k]) == null ? void 0 : _c.length)) delete m[k];
8129
- }
8130
- }
8131
- for (const t of (_d = p.filters) != null ? _d : []) {
8132
- t.includes = ((_e = t.includes) != null ? _e : []).filter((x) => x !== id);
8133
- t.excludes = ((_f = t.excludes) != null ? _f : []).filter((x) => x !== id);
8134
- }
8364
+ const removedIds = removeFieldInPlace(p, id);
8365
+ if (!removedIds.length) return;
8366
+ applyDeleteCleanup(p, new Set(removedIds));
8135
8367
  }),
8136
8368
  undo: () => ctx.replaceProps(prevSlice)
8137
8369
  });
8138
8370
  }
8139
8371
  function remove(ctx, id) {
8372
+ const key = String(id);
8140
8373
  if (ctx.isTagId(id)) {
8141
8374
  ctx.exec({
8142
8375
  name: "removeTag",
8143
8376
  do: () => ctx.patchProps((p) => {
8144
- var _a, _b, _c, _d, _e, _f, _g, _h;
8145
- p.filters = ((_a = p.filters) != null ? _a : []).filter((t) => t.id !== id);
8146
- for (const t of (_b = p.filters) != null ? _b : []) {
8147
- if (t.bind_id === id) delete t.bind_id;
8148
- t.includes = ((_c = t.includes) != null ? _c : []).filter((x) => x !== id);
8149
- t.excludes = ((_d = t.excludes) != null ? _d : []).filter((x) => x !== id);
8150
- }
8151
- for (const f of (_e = p.fields) != null ? _e : []) {
8152
- if (Array.isArray(f.bind_id)) {
8153
- f.bind_id = f.bind_id.filter((x) => x !== id);
8154
- } else if (f.bind_id === id) {
8155
- delete f.bind_id;
8156
- }
8157
- }
8158
- if ((_f = p.order_for_tags) == null ? void 0 : _f[id]) delete p.order_for_tags[id];
8159
- for (const k of Object.keys((_g = p.order_for_tags) != null ? _g : {})) {
8160
- p.order_for_tags[k] = ((_h = p.order_for_tags[k]) != null ? _h : []).filter(
8161
- (fid) => {
8162
- var _a2;
8163
- return ((_a2 = p.fields) != null ? _a2 : []).some((f) => f.id === fid);
8164
- }
8165
- );
8166
- if (!p.order_for_tags[k].length) delete p.order_for_tags[k];
8167
- }
8377
+ const removed = removeTagInPlace(p, key);
8378
+ if (!removed) return;
8379
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
8168
8380
  }),
8169
8381
  undo: () => ctx.undo()
8170
8382
  });
@@ -8174,42 +8386,67 @@ function remove(ctx, id) {
8174
8386
  ctx.exec({
8175
8387
  name: "removeField",
8176
8388
  do: () => ctx.patchProps((p) => {
8177
- var _a, _b, _c, _d, _e, _f, _g, _h;
8178
- p.fields = ((_a = p.fields) != null ? _a : []).filter((f) => f.id !== id);
8179
- for (const t of (_b = p.filters) != null ? _b : []) {
8180
- t.includes = ((_c = t.includes) != null ? _c : []).filter((x) => x !== id);
8181
- t.excludes = ((_d = t.excludes) != null ? _d : []).filter((x) => x !== id);
8182
- }
8183
- for (const k of Object.keys((_e = p.order_for_tags) != null ? _e : {})) {
8184
- p.order_for_tags[k] = ((_f = p.order_for_tags[k]) != null ? _f : []).filter(
8185
- (fid) => fid !== id
8186
- );
8187
- if (!p.order_for_tags[k].length) delete p.order_for_tags[k];
8188
- }
8189
- const maps = [
8190
- "includes_for_options",
8191
- "excludes_for_options"
8192
- ];
8193
- for (const m of maps) {
8194
- const map = p[m];
8195
- if (!map) continue;
8196
- for (const key of Object.keys(map)) {
8197
- map[key] = ((_g = map[key]) != null ? _g : []).filter((fid) => fid !== id);
8198
- if (!((_h = map[key]) == null ? void 0 : _h.length)) delete map[key];
8199
- }
8200
- if (!Object.keys(map).length) delete p[m];
8201
- }
8389
+ const removedIds = removeFieldInPlace(p, key);
8390
+ if (!removedIds.length) return;
8391
+ applyDeleteCleanup(p, new Set(removedIds));
8202
8392
  }),
8203
8393
  undo: () => ctx.undo()
8204
8394
  });
8205
8395
  return;
8206
8396
  }
8207
8397
  if (ctx.isOptionId(id)) {
8208
- removeOption(ctx, id);
8398
+ ctx.exec({
8399
+ name: "removeOption",
8400
+ do: () => ctx.patchProps((p) => {
8401
+ const removed = removeOptionInPlace(p, key);
8402
+ if (!removed) return;
8403
+ applyDeleteCleanup(p, /* @__PURE__ */ new Set([key]));
8404
+ }),
8405
+ undo: () => ctx.undo()
8406
+ });
8209
8407
  return;
8210
8408
  }
8211
8409
  throw new Error("remove: unknown id prefix");
8212
8410
  }
8411
+ function removeMany(ctx, ids) {
8412
+ const { ordered } = stripDeletedIds(ids);
8413
+ if (!ordered.length) return;
8414
+ ctx.transact("removeMany", () => {
8415
+ ctx.patchProps((p) => {
8416
+ var _a, _b, _c;
8417
+ const existingFieldIds = new Set(((_a = p.fields) != null ? _a : []).map((f) => String(f.id)));
8418
+ const existingTagIds = new Set(((_b = p.filters) != null ? _b : []).map((t) => String(t.id)));
8419
+ const existingOptionIds = new Set(
8420
+ ((_c = p.fields) != null ? _c : []).flatMap((f) => {
8421
+ var _a2;
8422
+ return ((_a2 = f.options) != null ? _a2 : []).map((o) => String(o.id));
8423
+ })
8424
+ );
8425
+ const fieldIds = ordered.filter((id) => ctx.isFieldId(id) && existingFieldIds.has(id));
8426
+ const fieldIdSet = new Set(fieldIds);
8427
+ const tagIds = ordered.filter((id) => ctx.isTagId(id) && existingTagIds.has(id));
8428
+ const optionIds = ordered.filter((id) => {
8429
+ if (!ctx.isOptionId(id) || !existingOptionIds.has(id)) return false;
8430
+ const owner = ownerOfOption(p, id);
8431
+ if (!owner) return false;
8432
+ return !fieldIdSet.has(String(owner.fieldId));
8433
+ });
8434
+ const deleted = /* @__PURE__ */ new Set();
8435
+ for (const optionId of optionIds) {
8436
+ if (removeOptionInPlace(p, optionId)) deleted.add(optionId);
8437
+ }
8438
+ for (const fieldId of fieldIds) {
8439
+ const removedIds = removeFieldInPlace(p, fieldId);
8440
+ for (const rid of removedIds) deleted.add(rid);
8441
+ }
8442
+ for (const tagId of tagIds) {
8443
+ if (removeTagInPlace(p, tagId)) deleted.add(tagId);
8444
+ }
8445
+ if (!deleted.size) return;
8446
+ applyDeleteCleanup(p, deleted);
8447
+ });
8448
+ });
8449
+ }
8213
8450
  function getNode(ctx, id) {
8214
8451
  var _a, _b, _c, _d;
8215
8452
  const props = ctx.getProps();
@@ -9454,6 +9691,9 @@ var Editor = class {
9454
9691
  duplicate(ref, opts = {}) {
9455
9692
  return duplicate(this.moduleCtx(), ref, opts);
9456
9693
  }
9694
+ duplicateMany(ids, opts = {}) {
9695
+ return duplicateMany(this.moduleCtx(), ids, opts);
9696
+ }
9457
9697
  reLabel(id, nextLabel) {
9458
9698
  return reLabel(this.moduleCtx(), id, nextLabel);
9459
9699
  }
@@ -9517,6 +9757,260 @@ var Editor = class {
9517
9757
  remove(id) {
9518
9758
  return remove(this.moduleCtx(), id);
9519
9759
  }
9760
+ removeMany(ids) {
9761
+ return removeMany(this.moduleCtx(), ids);
9762
+ }
9763
+ clearServiceMany(ids) {
9764
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9765
+ if (!ordered.length) return;
9766
+ this.transact("clearServiceMany", () => {
9767
+ this.patchProps((p) => {
9768
+ var _a, _b, _c, _d;
9769
+ for (const id of ordered) {
9770
+ if (this.isTagId(id)) {
9771
+ const t = ((_a = p.filters) != null ? _a : []).find((x) => x.id === id);
9772
+ if (t && "service_id" in t) delete t.service_id;
9773
+ continue;
9774
+ }
9775
+ if (this.isFieldId(id)) {
9776
+ const f = ((_b = p.fields) != null ? _b : []).find((x) => x.id === id);
9777
+ if (f && "service_id" in f) delete f.service_id;
9778
+ continue;
9779
+ }
9780
+ if (this.isOptionId(id)) {
9781
+ const own = ownerOfOption(p, id);
9782
+ if (!own) continue;
9783
+ const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === own.fieldId);
9784
+ const o = (_d = f == null ? void 0 : f.options) == null ? void 0 : _d.find((x) => x.id === id);
9785
+ if (o && "service_id" in o) delete o.service_id;
9786
+ }
9787
+ }
9788
+ });
9789
+ });
9790
+ }
9791
+ rebindMany(ids, targetTagId, opts) {
9792
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9793
+ if (!ordered.length) return;
9794
+ this.transact("rebindMany", () => {
9795
+ this.patchProps((p) => {
9796
+ var _a, _b, _c;
9797
+ const targetExists = ((_a = p.filters) != null ? _a : []).some((t) => t.id === targetTagId);
9798
+ if (!targetExists) return;
9799
+ for (const id of ordered) {
9800
+ if (this.isFieldId(id)) {
9801
+ const f = ((_b = p.fields) != null ? _b : []).find((x) => x.id === id);
9802
+ if (!f) continue;
9803
+ f.bind_id = targetTagId;
9804
+ continue;
9805
+ }
9806
+ if (this.isTagId(id)) {
9807
+ const t = ((_c = p.filters) != null ? _c : []).find((x) => x.id === id);
9808
+ if (!t) continue;
9809
+ if (!(opts == null ? void 0 : opts.allowTagCycles) && wouldCreateTagCycle(this.moduleCtx(), p, targetTagId, id)) {
9810
+ continue;
9811
+ }
9812
+ t.bind_id = targetTagId;
9813
+ }
9814
+ }
9815
+ });
9816
+ });
9817
+ }
9818
+ includeMany(receiverId, ids) {
9819
+ const accepted = Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))).filter((id) => id !== receiverId).filter((id) => this.getNode(id).data != null);
9820
+ if (!accepted.length) return;
9821
+ include(this.moduleCtx(), receiverId, accepted);
9822
+ }
9823
+ excludeMany(receiverId, ids) {
9824
+ const accepted = Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))).filter((id) => id !== receiverId).filter((id) => this.getNode(id).data != null);
9825
+ if (!accepted.length) return;
9826
+ exclude(this.moduleCtx(), receiverId, accepted);
9827
+ }
9828
+ clearRelationsMany(ids, mode = "both") {
9829
+ const selected = new Set(Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))));
9830
+ if (!selected.size) return;
9831
+ this.transact("clearRelationsMany", () => {
9832
+ this.patchProps((p) => {
9833
+ var _a, _b, _c;
9834
+ const clearOwned = mode === "owned" || mode === "both";
9835
+ const clearIncoming = mode === "incoming" || mode === "both";
9836
+ for (const t of (_a = p.filters) != null ? _a : []) {
9837
+ if (clearOwned && selected.has(t.id)) {
9838
+ delete t.includes;
9839
+ delete t.excludes;
9840
+ }
9841
+ if (clearIncoming) {
9842
+ if (t.includes) {
9843
+ t.includes = t.includes.filter((x) => !selected.has(String(x)));
9844
+ if (!t.includes.length) delete t.includes;
9845
+ }
9846
+ if (t.excludes) {
9847
+ t.excludes = t.excludes.filter((x) => !selected.has(String(x)));
9848
+ if (!t.excludes.length) delete t.excludes;
9849
+ }
9850
+ }
9851
+ }
9852
+ const maps = [
9853
+ "includes_for_buttons",
9854
+ "excludes_for_buttons",
9855
+ "includes_for_options",
9856
+ "excludes_for_options"
9857
+ ];
9858
+ for (const k of maps) {
9859
+ const map = p[k];
9860
+ if (!map) continue;
9861
+ for (const key of Object.keys(map)) {
9862
+ if (clearOwned && selected.has(String(key))) {
9863
+ delete map[key];
9864
+ continue;
9865
+ }
9866
+ if (clearIncoming) {
9867
+ map[key] = ((_b = map[key]) != null ? _b : []).filter((x) => !selected.has(String(x)));
9868
+ if (!((_c = map[key]) == null ? void 0 : _c.length)) delete map[key];
9869
+ }
9870
+ }
9871
+ if (!Object.keys(map).length) delete p[k];
9872
+ }
9873
+ });
9874
+ });
9875
+ }
9876
+ renameLabelsMany(ids, input) {
9877
+ var _a, _b;
9878
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9879
+ if (!ordered.length) return;
9880
+ const prefix = (_a = input.prefix) != null ? _a : "";
9881
+ const suffix = (_b = input.suffix) != null ? _b : "";
9882
+ this.transact("renameLabelsMany", () => {
9883
+ this.patchProps((p) => {
9884
+ var _a2, _b2, _c, _d, _e, _f, _g;
9885
+ for (const id of ordered) {
9886
+ if (this.isTagId(id)) {
9887
+ const t = ((_a2 = p.filters) != null ? _a2 : []).find((x) => x.id === id);
9888
+ if (t) t.label = `${prefix}${(_b2 = t.label) != null ? _b2 : ""}${suffix}`.trim();
9889
+ continue;
9890
+ }
9891
+ if (this.isFieldId(id)) {
9892
+ const f = ((_c = p.fields) != null ? _c : []).find((x) => x.id === id);
9893
+ if (f) f.label = `${prefix}${(_d = f.label) != null ? _d : ""}${suffix}`.trim();
9894
+ continue;
9895
+ }
9896
+ if (this.isOptionId(id)) {
9897
+ const own = ownerOfOption(p, id);
9898
+ if (!own) continue;
9899
+ const f = ((_e = p.fields) != null ? _e : []).find((x) => x.id === own.fieldId);
9900
+ const o = (_f = f == null ? void 0 : f.options) == null ? void 0 : _f.find((x) => x.id === id);
9901
+ if (o) o.label = `${prefix}${(_g = o.label) != null ? _g : ""}${suffix}`.trim();
9902
+ }
9903
+ }
9904
+ });
9905
+ });
9906
+ }
9907
+ setPricingRoleMany(ids, role) {
9908
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9909
+ if (!ordered.length) return;
9910
+ this.transact("setPricingRoleMany", () => {
9911
+ for (const id of ordered) {
9912
+ if (this.isFieldId(id) || this.isOptionId(id)) {
9913
+ this.setService(id, { pricing_role: role });
9914
+ }
9915
+ }
9916
+ });
9917
+ }
9918
+ clearFieldDefaultsMany(ids) {
9919
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9920
+ if (!ordered.length) return;
9921
+ this.transact("clearFieldDefaultsMany", () => {
9922
+ this.patchProps((p) => {
9923
+ var _a;
9924
+ for (const id of ordered) {
9925
+ if (!this.isFieldId(id)) continue;
9926
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
9927
+ if (f && "defaults" in f) delete f.defaults;
9928
+ }
9929
+ });
9930
+ });
9931
+ }
9932
+ clearFieldValidationMany(ids) {
9933
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9934
+ if (!ordered.length) return;
9935
+ this.transact("clearFieldValidationMany", () => {
9936
+ this.patchProps((p) => {
9937
+ var _a;
9938
+ for (const id of ordered) {
9939
+ if (!this.isFieldId(id)) continue;
9940
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
9941
+ if (f && "validation" in f) delete f.validation;
9942
+ }
9943
+ });
9944
+ });
9945
+ }
9946
+ autoCreateOptionsMany(ids, makeOption) {
9947
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9948
+ if (!ordered.length) return;
9949
+ this.transact("autoCreateOptionsMany", () => {
9950
+ this.patchProps((p) => {
9951
+ var _a, _b, _c, _d;
9952
+ for (const id of ordered) {
9953
+ if (!this.isFieldId(id)) continue;
9954
+ const f = ((_a = p.fields) != null ? _a : []).find((x) => x.id === id);
9955
+ if (!f) continue;
9956
+ const opts = (_b = f.options) != null ? _b : f.options = [];
9957
+ if (opts.length > 0) continue;
9958
+ const next = (_c = makeOption == null ? void 0 : makeOption(id)) != null ? _c : { label: "Option label", value: "option" };
9959
+ opts.push({
9960
+ id: (_d = next.id) != null ? _d : this.moduleCtx().genId("o"),
9961
+ label: next.label,
9962
+ value: next.value
9963
+ });
9964
+ }
9965
+ });
9966
+ });
9967
+ }
9968
+ clearAllOptionsMany(ids) {
9969
+ var _a, _b;
9970
+ const ordered = Array.from(new Set((ids != null ? ids : []).map((id) => String(id))));
9971
+ if (!ordered.length) return;
9972
+ const optionIds = [];
9973
+ const props = this.getProps();
9974
+ for (const id of ordered) {
9975
+ if (!this.isFieldId(id)) continue;
9976
+ const f = ((_a = props.fields) != null ? _a : []).find((x) => x.id === id);
9977
+ for (const o of (_b = f == null ? void 0 : f.options) != null ? _b : []) optionIds.push(o.id);
9978
+ }
9979
+ if (!optionIds.length) return;
9980
+ removeMany(this.moduleCtx(), optionIds);
9981
+ }
9982
+ removeNoticesForNodes(ids) {
9983
+ const selected = new Set(Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))));
9984
+ if (!selected.size) return;
9985
+ this.transact("removeNoticesForNodes", () => {
9986
+ this.patchProps((p) => {
9987
+ var _a;
9988
+ if (!((_a = p.notices) == null ? void 0 : _a.length)) return;
9989
+ p.notices = p.notices.filter((n) => {
9990
+ const target = n.target;
9991
+ if (!target || target.scope === "global") return true;
9992
+ if (target.scope === "node") return !selected.has(String(target.node_id));
9993
+ return true;
9994
+ });
9995
+ if (!p.notices.length) delete p.notices;
9996
+ });
9997
+ });
9998
+ }
9999
+ setNoticesVisibilityForNodes(ids, type) {
10000
+ const selected = new Set(Array.from(new Set((ids != null ? ids : []).map((id) => String(id)))));
10001
+ if (!selected.size) return;
10002
+ this.transact("setNoticesVisibilityForNodes", () => {
10003
+ this.patchProps((p) => {
10004
+ var _a;
10005
+ for (const n of (_a = p.notices) != null ? _a : []) {
10006
+ const target = n.target;
10007
+ if ((target == null ? void 0 : target.scope) === "node" && selected.has(String(target.node_id))) {
10008
+ n.type = type;
10009
+ }
10010
+ }
10011
+ });
10012
+ });
10013
+ }
9520
10014
  getNode(id) {
9521
10015
  return getNode(this.moduleCtx(), id);
9522
10016
  }
@@ -9760,9 +10254,8 @@ var Editor = class {
9760
10254
  Array.isArray(canvas.selection) ? canvas.selection : Array.from(canvas.selection)
9761
10255
  );
9762
10256
  }
9763
- } else {
9764
- this.api.refreshGraph();
9765
10257
  }
10258
+ this.api.refreshGraph();
9766
10259
  this.emit("editor:change", { props: s.props, reason, snapshot: s });
9767
10260
  }
9768
10261
  pushHistory(snap) {
@@ -10850,12 +11343,12 @@ function useCanvasOwned(initialProps, canvasOpts, builderOpts) {
10850
11343
  // src/react/workspace/context/hooks/use-canvas.ts
10851
11344
  import * as React16 from "react";
10852
11345
  import { useMemo as useMemo15 } from "react";
10853
- function deriveSelectionInfo(props, ids) {
11346
+ function deriveSelectionInfo(nodeMap, ids) {
10854
11347
  const tags = [];
10855
11348
  const fields = [];
10856
11349
  const options = [];
10857
11350
  for (const id of ids) {
10858
- const node = props.get(id);
11351
+ const node = nodeMap.get(id);
10859
11352
  if (!node) continue;
10860
11353
  if (node.kind == "tag") {
10861
11354
  tags.push(id);
@@ -10882,6 +11375,49 @@ function deriveSelectionInfo(props, ids) {
10882
11375
  optionIds: uniq2(options)
10883
11376
  };
10884
11377
  }
11378
+ function noticeTargetsSelection(notice, selected) {
11379
+ const target = notice.target;
11380
+ if (target.scope === "global") return false;
11381
+ return selected.has(String(target.node_id));
11382
+ }
11383
+ function deriveSelectionCapabilities(props, selectionInfo) {
11384
+ var _a, _b, _c;
11385
+ const selected = new Set(selectionInfo.ids.map(String));
11386
+ const fields = (_a = props == null ? void 0 : props.fields) != null ? _a : [];
11387
+ const tags = (_b = props == null ? void 0 : props.filters) != null ? _b : [];
11388
+ const notices = (_c = props == null ? void 0 : props.notices) != null ? _c : [];
11389
+ const hasSelectedFieldWithOptions = fields.some(
11390
+ (f) => {
11391
+ var _a2, _b2;
11392
+ return selected.has(String(f.id)) && ((_b2 = (_a2 = f.options) == null ? void 0 : _a2.length) != null ? _b2 : 0) > 0;
11393
+ }
11394
+ );
11395
+ const hasServiceBearingNodes = tags.some(
11396
+ (t) => selected.has(String(t.id)) && t.service_id !== void 0 && t.service_id !== null
11397
+ ) || fields.some(
11398
+ (f) => selected.has(String(f.id)) && f.service_id !== void 0 && f.service_id !== null
11399
+ ) || fields.some(
11400
+ (f) => {
11401
+ var _a2;
11402
+ return ((_a2 = f.options) != null ? _a2 : []).some(
11403
+ (o) => selected.has(String(o.id)) && o.service_id !== void 0 && o.service_id !== null
11404
+ );
11405
+ }
11406
+ );
11407
+ const hasNoticesForSelection = notices.some(
11408
+ (n) => noticeTargetsSelection(n, selected)
11409
+ );
11410
+ return {
11411
+ hasTags: selectionInfo.tagIds.length > 0,
11412
+ hasFields: selectionInfo.fieldIds.length > 0,
11413
+ hasOptions: selectionInfo.optionIds.length > 0,
11414
+ hasServiceBearingNodes,
11415
+ hasSelectedFieldWithOptions,
11416
+ hasNoticesForSelection,
11417
+ canIncludeExcludeTargets: selectionInfo.tagIds.length + selectionInfo.fieldIds.length + selectionInfo.optionIds.length > 0,
11418
+ canRebind: selectionInfo.fieldIds.length > 0 || selectionInfo.tagIds.length > 0
11419
+ };
11420
+ }
10885
11421
  function tagBindIds(tag) {
10886
11422
  const bind = tag.bind_id;
10887
11423
  if (!bind) return [];
@@ -11021,7 +11557,11 @@ function useCanvas() {
11021
11557
  });
11022
11558
  return off;
11023
11559
  }, [api]);
11024
- const selector = useMemo15(() => createNodeIndex(api.builder), [props]);
11560
+ const selector = useMemo15(() => createNodeIndex(api.builder), [api.builder, props]);
11561
+ const selectionCapabilities = React16.useMemo(
11562
+ () => deriveSelectionCapabilities(props, selectionInfo),
11563
+ [props, selectionInfo]
11564
+ );
11025
11565
  return React16.useMemo(
11026
11566
  () => ({
11027
11567
  api,
@@ -11030,6 +11570,7 @@ function useCanvas() {
11030
11570
  props,
11031
11571
  selection,
11032
11572
  selectionInfo,
11573
+ selectionCapabilities,
11033
11574
  selector,
11034
11575
  activeId,
11035
11576
  setActive
@@ -11041,6 +11582,7 @@ function useCanvas() {
11041
11582
  props,
11042
11583
  selection,
11043
11584
  selectionInfo,
11585
+ selectionCapabilities,
11044
11586
  selector,
11045
11587
  activeId,
11046
11588
  setActive
@@ -13727,6 +14269,7 @@ export {
13727
14269
  WorkspaceProvider,
13728
14270
  createMemoryWorkspaceBackend,
13729
14271
  createPollAdapter,
14272
+ deriveSelectionCapabilities,
13730
14273
  useCanvas,
13731
14274
  useCanvasAPI,
13732
14275
  useCanvasFromBuilder,