@outbuild-company/schedule-core 1.8.0 → 1.9.0

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.
package/dist/index.d.cts CHANGED
@@ -942,6 +942,7 @@ declare class ScheduleCore {
942
942
  isCriticalPathSettled(): boolean;
943
943
  whenCriticalPathSettled(): Promise<void>;
944
944
  private _withReappliedViewState;
945
+ private _recordRelativePlacementPin;
945
946
  /**
946
947
  * The single place the two view-state passes are chained. Dispatch, undo and
947
948
  * redo all route through here: when this existed only inside the dispatch
package/dist/index.d.ts CHANGED
@@ -942,6 +942,7 @@ declare class ScheduleCore {
942
942
  isCriticalPathSettled(): boolean;
943
943
  whenCriticalPathSettled(): Promise<void>;
944
944
  private _withReappliedViewState;
945
+ private _recordRelativePlacementPin;
945
946
  /**
946
947
  * The single place the two view-state passes are chained. Dispatch, undo and
947
948
  * redo all route through here: when this existed only inside the dispatch
package/dist/index.js CHANGED
@@ -418,6 +418,107 @@ function getFieldDescriptor(field) {
418
418
  return FIELD_REGISTRY[field] ?? null;
419
419
  }
420
420
 
421
+ // src/internal/state/view-state.ts
422
+ function isRelativePlacementPinValid(activityId, pin, parentOf) {
423
+ const branchParentId = String(pin.branchParentId);
424
+ const currentParentId = String(parentOf(activityId) ?? "0");
425
+ if (currentParentId !== branchParentId) return false;
426
+ const referenceId = String(pin.referenceId);
427
+ return referenceId === branchParentId || String(parentOf(referenceId) ?? "0") === branchParentId;
428
+ }
429
+ var ViewStateStore = class {
430
+ checkedSet = /* @__PURE__ */ new Set();
431
+ hiddenSet = /* @__PURE__ */ new Set();
432
+ activeFilter = null;
433
+ activeOrder = null;
434
+ relativePlacementPins = /* @__PURE__ */ new Map();
435
+ isChecked(activityId) {
436
+ return this.checkedSet.has(activityId);
437
+ }
438
+ isVisible(activityId) {
439
+ return !this.hiddenSet.has(activityId);
440
+ }
441
+ hiddenIds() {
442
+ return [...this.hiddenSet];
443
+ }
444
+ getActiveFilter() {
445
+ return this.activeFilter;
446
+ }
447
+ setActiveFilter(nextFilter) {
448
+ this.activeFilter = nextFilter;
449
+ }
450
+ getActiveOrder() {
451
+ return this.activeOrder;
452
+ }
453
+ setActiveOrder(nextOrder) {
454
+ this.activeOrder = nextOrder;
455
+ }
456
+ setRelativePlacementPin(activityId, pin) {
457
+ this.relativePlacementPins.set(String(activityId), {
458
+ referenceId: String(pin.referenceId),
459
+ branchParentId: String(pin.branchParentId),
460
+ side: pin.side
461
+ });
462
+ }
463
+ getRelativePlacementPins() {
464
+ return this.relativePlacementPins;
465
+ }
466
+ restoreRelativePlacementPins(pins) {
467
+ this.relativePlacementPins = new Map(pins);
468
+ }
469
+ checkedIds() {
470
+ return [...this.checkedSet];
471
+ }
472
+ checkedCount() {
473
+ return this.checkedSet.size;
474
+ }
475
+ setChecked(activityId, nextChecked) {
476
+ if (nextChecked === this.checkedSet.has(activityId)) return false;
477
+ if (nextChecked) this.checkedSet.add(activityId);
478
+ else this.checkedSet.delete(activityId);
479
+ return true;
480
+ }
481
+ setVisible(activityId, nextVisible) {
482
+ if (nextVisible === !this.hiddenSet.has(activityId)) return false;
483
+ if (nextVisible) this.hiddenSet.delete(activityId);
484
+ else this.hiddenSet.add(activityId);
485
+ return true;
486
+ }
487
+ forget(activityId) {
488
+ this.checkedSet.delete(activityId);
489
+ this.hiddenSet.delete(activityId);
490
+ this.relativePlacementPins.delete(activityId);
491
+ for (const [pinnedId, pin] of this.relativePlacementPins) {
492
+ if (pin.referenceId === activityId) {
493
+ this.relativePlacementPins.delete(pinnedId);
494
+ }
495
+ }
496
+ }
497
+ hydrate(seed2) {
498
+ this.checkedSet = new Set(seed2.checkedIds ?? []);
499
+ this.hiddenSet = new Set(seed2.hiddenIds ?? []);
500
+ this.activeFilter = null;
501
+ this.activeOrder = null;
502
+ this.relativePlacementPins = /* @__PURE__ */ new Map();
503
+ }
504
+ snapshot() {
505
+ return {
506
+ checked: new Set(this.checkedSet),
507
+ hidden: new Set(this.hiddenSet),
508
+ activeFilter: this.activeFilter,
509
+ activeOrder: this.activeOrder,
510
+ relativePlacementPins: new Map(this.relativePlacementPins)
511
+ };
512
+ }
513
+ restore(snapshot) {
514
+ this.checkedSet = new Set(snapshot.checked);
515
+ this.hiddenSet = new Set(snapshot.hidden);
516
+ this.activeFilter = snapshot.activeFilter;
517
+ this.activeOrder = snapshot.activeOrder;
518
+ this.relativePlacementPins = new Map(snapshot.relativePlacementPins);
519
+ }
520
+ };
521
+
421
522
  // src/internal/filter/evaluate-filter.ts
422
523
  function toTargetDate(value) {
423
524
  if (value instanceof Date) return value;
@@ -560,13 +661,8 @@ function seedFilterProjection(input) {
560
661
  );
561
662
  }
562
663
  const isMatch = matchesFilter(activity, filter, context);
563
- const isPinned = input.pinnedVisibleIds?.has(activityId) === true;
564
- visibleReferenceCounts.set(
565
- activityId,
566
- (isMatch ? 1 : 0) + (isPinned ? 1 : 0)
567
- );
664
+ visibleReferenceCounts.set(activityId, isMatch ? 1 : 0);
568
665
  if (isMatch) matchedIds.add(activityId);
569
- if (isPinned) pinnedVisibleIds.add(activityId);
570
666
  }
571
667
  return {
572
668
  matchedIds,
@@ -576,6 +672,29 @@ function seedFilterProjection(input) {
576
672
  remainingChildren
577
673
  };
578
674
  }
675
+ function applyRelativePlacementPins(seed2, pins) {
676
+ if (pins === void 0) return;
677
+ for (const [activityId, pin] of pins) {
678
+ const pinnedId = String(activityId);
679
+ const referenceId = String(pin.referenceId);
680
+ if (!seed2.parentIds.has(pinnedId) || !seed2.parentIds.has(referenceId) || !isRelativePlacementPinValid(
681
+ pinnedId,
682
+ pin,
683
+ (activityId2) => seed2.parentIds.get(activityId2) ?? null
684
+ ) || (seed2.visibleReferenceCounts.get(referenceId) ?? 0) === 0) {
685
+ continue;
686
+ }
687
+ seed2.pinnedVisibleIds.add(pinnedId);
688
+ let currentId = pinnedId;
689
+ while (currentId !== null) {
690
+ seed2.visibleReferenceCounts.set(
691
+ currentId,
692
+ (seed2.visibleReferenceCounts.get(currentId) ?? 0) + 1
693
+ );
694
+ currentId = seed2.parentIds.get(currentId) ?? null;
695
+ }
696
+ }
697
+ }
579
698
  function propagateVisibleReferenceCounts(seed2) {
580
699
  const { visibleReferenceCounts, parentIds, remainingChildren } = seed2;
581
700
  const pendingIds = [];
@@ -607,6 +726,7 @@ function collectVisibleIds(visibleReferenceCounts) {
607
726
  function buildFilterProjection(input) {
608
727
  const seed2 = seedFilterProjection(input);
609
728
  propagateVisibleReferenceCounts(seed2);
729
+ applyRelativePlacementPins(seed2, input.relativePlacementPins);
610
730
  const visibleIds = collectVisibleIds(seed2.visibleReferenceCounts);
611
731
  return {
612
732
  filter: input.filter,
@@ -794,7 +914,8 @@ function resolveFilter(adapter, filter, hoursPerDay) {
794
914
  activities,
795
915
  parentOf: (activityId) => adapter.getParentId(activityId),
796
916
  filter,
797
- context: buildFilterContext(hoursPerDay)
917
+ context: buildFilterContext(hoursPerDay),
918
+ relativePlacementPins: adapter.getRelativePlacementPins()
798
919
  });
799
920
  return { visibleIds: projection.visibleIds, projection };
800
921
  }
@@ -845,25 +966,118 @@ function normalizeParentKey(parent) {
845
966
 
846
967
  // src/internal/hierarchy/visual-order.ts
847
968
  var NOT_SET_SORT_VALUE = Number.MAX_SAFE_INTEGER;
848
- function getChildrenInVisualOrder(parentId, adapter) {
969
+ var EMPTY_RELATIVE_PLACEMENT_PINS = /* @__PURE__ */ new Map();
970
+ function buildRelativePlacementPinIndex(adapter) {
971
+ const index = /* @__PURE__ */ new Map();
972
+ for (const [activityId, pin] of adapter.getRelativePlacementPins?.() ?? []) {
973
+ const pinnedId = String(activityId);
974
+ const referenceId = String(pin.referenceId);
975
+ if (adapter.getActivity(pinnedId) === null || adapter.getActivity(referenceId) === null || !isRelativePlacementPinValid(
976
+ pinnedId,
977
+ pin,
978
+ (activityId2) => adapter.getParentId(activityId2)
979
+ )) {
980
+ continue;
981
+ }
982
+ const targetParentId = String(pin.branchParentId);
983
+ const branchPins = index.get(targetParentId) ?? /* @__PURE__ */ new Map();
984
+ branchPins.set(pinnedId, pin);
985
+ index.set(targetParentId, branchPins);
986
+ }
987
+ return index;
988
+ }
989
+ function getChildrenInVisualOrder(parentId, adapter, pinIndex = buildRelativePlacementPinIndex(adapter)) {
849
990
  const children = collectChildrenSnapshots(parentId, adapter);
850
991
  const userOrder = adapter.getOrderComparator?.() ?? null;
851
992
  children.sort(
852
993
  userOrder === null ? compareActivitiesInVisualOrder : (a, b) => userOrder(a, b) || compareActivitiesInVisualOrder(a, b)
853
994
  );
854
- return children.map((a) => String(a.id));
995
+ return applyRelativePlacementPins2(
996
+ children.map((activity) => String(activity.id)),
997
+ parentId,
998
+ pinIndex.get(String(parentId)) ?? EMPTY_RELATIVE_PLACEMENT_PINS
999
+ );
1000
+ }
1001
+ function applyRelativePlacementPins2(orderedIds, parentId, pins) {
1002
+ const order = buildLinkedActivityOrder(orderedIds);
1003
+ for (const [activityId, pin] of pins) {
1004
+ const pinnedId = String(activityId);
1005
+ const referenceId = String(pin.referenceId);
1006
+ const isFirstChild = referenceId === String(parentId);
1007
+ if (!order.previous.has(pinnedId) || !isFirstChild && (!order.previous.has(referenceId) || pinnedId === referenceId)) {
1008
+ continue;
1009
+ }
1010
+ detachActivity(order, pinnedId);
1011
+ if (isFirstChild) insertActivityFirst(order, pinnedId);
1012
+ else insertActivityBeside(order, pinnedId, referenceId, pin.side);
1013
+ }
1014
+ return materializeActivityOrder(order);
1015
+ }
1016
+ function buildLinkedActivityOrder(orderedIds) {
1017
+ const previous = /* @__PURE__ */ new Map();
1018
+ const next = /* @__PURE__ */ new Map();
1019
+ for (let index = 0; index < orderedIds.length; index++) {
1020
+ const activityId = orderedIds[index];
1021
+ if (activityId === void 0) continue;
1022
+ previous.set(activityId, orderedIds[index - 1] ?? null);
1023
+ next.set(activityId, orderedIds[index + 1] ?? null);
1024
+ }
1025
+ return { previous, next, head: orderedIds[0] ?? null };
1026
+ }
1027
+ function detachActivity(order, activityId) {
1028
+ const before = order.previous.get(activityId) ?? null;
1029
+ const after = order.next.get(activityId) ?? null;
1030
+ if (before === null) order.head = after;
1031
+ else order.next.set(before, after);
1032
+ if (after !== null) order.previous.set(after, before);
1033
+ }
1034
+ function insertActivityFirst(order, activityId) {
1035
+ order.previous.set(activityId, null);
1036
+ order.next.set(activityId, order.head);
1037
+ if (order.head !== null) order.previous.set(order.head, activityId);
1038
+ order.head = activityId;
1039
+ }
1040
+ function insertActivityBeside(order, activityId, referenceId, side) {
1041
+ if (side === "before") {
1042
+ const before = order.previous.get(referenceId) ?? null;
1043
+ order.previous.set(activityId, before);
1044
+ order.next.set(activityId, referenceId);
1045
+ order.previous.set(referenceId, activityId);
1046
+ if (before === null) order.head = activityId;
1047
+ else order.next.set(before, activityId);
1048
+ return;
1049
+ }
1050
+ const after = order.next.get(referenceId) ?? null;
1051
+ order.previous.set(activityId, referenceId);
1052
+ order.next.set(activityId, after);
1053
+ order.next.set(referenceId, activityId);
1054
+ if (after !== null) order.previous.set(after, activityId);
1055
+ }
1056
+ function materializeActivityOrder(order) {
1057
+ const result = [];
1058
+ let activityId = order.head;
1059
+ while (activityId !== null) {
1060
+ result.push(activityId);
1061
+ activityId = order.next.get(activityId) ?? null;
1062
+ }
1063
+ return result;
855
1064
  }
856
1065
  function iterateInVisualOrder(adapter) {
857
1066
  const result = [];
1067
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
858
1068
  const visit = (activity) => {
859
1069
  result.push(activity);
860
- const childIds = getChildrenInVisualOrder(String(activity.id), adapter);
1070
+ const childIds = getChildrenInVisualOrder(
1071
+ String(activity.id),
1072
+ adapter,
1073
+ pinIndex
1074
+ );
861
1075
  for (const childId of childIds) {
862
1076
  const child = adapter.getActivity(childId);
863
1077
  if (child) visit(child);
864
1078
  }
865
1079
  };
866
- const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter);
1080
+ const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter, pinIndex);
867
1081
  for (const rootId of rootIds) {
868
1082
  const root = adapter.getActivity(rootId);
869
1083
  if (root) visit(root);
@@ -924,8 +1138,9 @@ function setHas(set, id) {
924
1138
  // src/dispatch/shared/collect-branch-order.ts
925
1139
  function collectBranchOrder(adapter) {
926
1140
  const branches = [];
1141
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
927
1142
  const visit = (parentId) => {
928
- const childIds = getChildrenInVisualOrder(parentId, adapter);
1143
+ const childIds = getChildrenInVisualOrder(parentId, adapter, pinIndex);
929
1144
  if (childIds.length > 1) {
930
1145
  branches.push({ parentId, childIds });
931
1146
  }
@@ -938,8 +1153,9 @@ function collectBranchOrder(adapter) {
938
1153
  }
939
1154
  function collectBranchOrderForParents(adapter, parentIds) {
940
1155
  const branches = [];
1156
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
941
1157
  for (const parentId of parentIds) {
942
- const childIds = getChildrenInVisualOrder(parentId, adapter);
1158
+ const childIds = getChildrenInVisualOrder(parentId, adapter, pinIndex);
943
1159
  if (childIds.length > 1) branches.push({ parentId, childIds });
944
1160
  }
945
1161
  return branches;
@@ -4084,8 +4300,8 @@ var costPipeline = {
4084
4300
  }
4085
4301
  return canEdit();
4086
4302
  },
4087
- parseInput(rawValue, activity) {
4088
- if (String(rawValue).trim() === "") return parsed(activity.cost);
4303
+ parseInput(rawValue) {
4304
+ if (String(rawValue).trim() === "") return parsed(0);
4089
4305
  const numericValue = parseFloat(String(rawValue));
4090
4306
  if (Number.isNaN(numericValue)) return parseError("not_a_number");
4091
4307
  return parsed(numericValue);
@@ -4134,8 +4350,8 @@ var usedCostPipeline = {
4134
4350
  }
4135
4351
  return canEdit();
4136
4352
  },
4137
- parseInput(rawValue, activity) {
4138
- if (String(rawValue).trim() === "") return parsed(activity.usedCost);
4353
+ parseInput(rawValue) {
4354
+ if (String(rawValue).trim() === "") return parsed(0);
4139
4355
  const numericValue = parseFloat(String(rawValue));
4140
4356
  if (Number.isNaN(numericValue)) return parseError("not_a_number");
4141
4357
  return parsed(numericValue);
@@ -17660,77 +17876,6 @@ var HierarchyIndex = class {
17660
17876
  }
17661
17877
  };
17662
17878
 
17663
- // src/internal/state/view-state.ts
17664
- var ViewStateStore = class {
17665
- checkedSet = /* @__PURE__ */ new Set();
17666
- hiddenSet = /* @__PURE__ */ new Set();
17667
- activeFilter = null;
17668
- activeOrder = null;
17669
- isChecked(activityId) {
17670
- return this.checkedSet.has(activityId);
17671
- }
17672
- isVisible(activityId) {
17673
- return !this.hiddenSet.has(activityId);
17674
- }
17675
- hiddenIds() {
17676
- return [...this.hiddenSet];
17677
- }
17678
- getActiveFilter() {
17679
- return this.activeFilter;
17680
- }
17681
- setActiveFilter(nextFilter) {
17682
- this.activeFilter = nextFilter;
17683
- }
17684
- getActiveOrder() {
17685
- return this.activeOrder;
17686
- }
17687
- setActiveOrder(nextOrder) {
17688
- this.activeOrder = nextOrder;
17689
- }
17690
- checkedIds() {
17691
- return [...this.checkedSet];
17692
- }
17693
- checkedCount() {
17694
- return this.checkedSet.size;
17695
- }
17696
- setChecked(activityId, nextChecked) {
17697
- if (nextChecked === this.checkedSet.has(activityId)) return false;
17698
- if (nextChecked) this.checkedSet.add(activityId);
17699
- else this.checkedSet.delete(activityId);
17700
- return true;
17701
- }
17702
- setVisible(activityId, nextVisible) {
17703
- if (nextVisible === !this.hiddenSet.has(activityId)) return false;
17704
- if (nextVisible) this.hiddenSet.delete(activityId);
17705
- else this.hiddenSet.add(activityId);
17706
- return true;
17707
- }
17708
- forget(activityId) {
17709
- this.checkedSet.delete(activityId);
17710
- this.hiddenSet.delete(activityId);
17711
- }
17712
- hydrate(seed2) {
17713
- this.checkedSet = new Set(seed2.checkedIds ?? []);
17714
- this.hiddenSet = new Set(seed2.hiddenIds ?? []);
17715
- this.activeFilter = null;
17716
- this.activeOrder = null;
17717
- }
17718
- snapshot() {
17719
- return {
17720
- checked: new Set(this.checkedSet),
17721
- hidden: new Set(this.hiddenSet),
17722
- activeFilter: this.activeFilter,
17723
- activeOrder: this.activeOrder
17724
- };
17725
- }
17726
- restore(snapshot) {
17727
- this.checkedSet = new Set(snapshot.checked);
17728
- this.hiddenSet = new Set(snapshot.hidden);
17729
- this.activeFilter = snapshot.activeFilter;
17730
- this.activeOrder = snapshot.activeOrder;
17731
- }
17732
- };
17733
-
17734
17879
  // src/internal/state/schedule-state.ts
17735
17880
  var ScheduleState = class _ScheduleState {
17736
17881
  _activities = /* @__PURE__ */ new Map();
@@ -17909,6 +18054,18 @@ var ScheduleState = class _ScheduleState {
17909
18054
  this._viewState.setActiveOrder(nextOrder);
17910
18055
  this.invalidateDerivedOrder();
17911
18056
  }
18057
+ setRelativePlacementPin(activityId, pin) {
18058
+ this._viewState.setRelativePlacementPin(String(activityId), pin);
18059
+ this.invalidateVisualOrderIds();
18060
+ }
18061
+ getRelativePlacementPins() {
18062
+ return this._viewState.getRelativePlacementPins();
18063
+ }
18064
+ restoreRelativePlacementPins(pins) {
18065
+ this._viewState.restoreRelativePlacementPins(pins);
18066
+ this._filterProjection = null;
18067
+ this.invalidateVisualOrderIds();
18068
+ }
17912
18069
  /**
17913
18070
  * Rebuild-on-demand, memoized: at most one build per invalidation, never one
17914
18071
  * per comparison. getChildrenInVisualOrder asks for this once and then sorts a
@@ -18115,10 +18272,14 @@ var ScheduleState = class _ScheduleState {
18115
18272
  const activity = this.getActivity(activityId);
18116
18273
  if (activity) this._writeCapture.noteRemove(String(activityId), activity);
18117
18274
  const key = String(activityId);
18118
- if (this._viewState.isChecked(key) || !this._viewState.isVisible(key)) {
18275
+ const relativePlacementPins = this._viewState.getRelativePlacementPins();
18276
+ const hasDependentPin = [...relativePlacementPins.values()].some(
18277
+ ({ referenceId }) => referenceId === key
18278
+ );
18279
+ if (this._viewState.isChecked(key) || !this._viewState.isVisible(key) || relativePlacementPins.has(key) || hasDependentPin) {
18119
18280
  this._captureViewStateOnce();
18120
- this._viewState.forget(key);
18121
18281
  }
18282
+ this._viewState.forget(key);
18122
18283
  this._activities.delete(activityId);
18123
18284
  if (this._lastStartDate.has(String(activityId))) {
18124
18285
  this._captureLastStartDateOnce();
@@ -18376,13 +18537,15 @@ function buildHistoryChangeSet(changes) {
18376
18537
  links: linksWithoutAfter
18377
18538
  });
18378
18539
  }
18379
- function buildUndoEntry(changeSet, beforeSnap, beforeLinks, afterSnap, afterLinks) {
18540
+ function buildUndoEntry(changeSet, beforeSnap, beforeLinks, afterSnap, afterLinks, relativePlacementPinsBefore, relativePlacementPinsAfter) {
18380
18541
  return {
18381
18542
  changeSet,
18382
18543
  beforeSnap,
18383
18544
  beforeLinks: resetDerivedLinkSnapshots(beforeLinks),
18384
18545
  afterSnap,
18385
- afterLinks: resetDerivedLinkSnapshots(afterLinks)
18546
+ afterLinks: resetDerivedLinkSnapshots(afterLinks),
18547
+ relativePlacementPinsBefore,
18548
+ relativePlacementPinsAfter
18386
18549
  };
18387
18550
  }
18388
18551
  function resetDerivedLinkSnapshots(snapshots) {
@@ -18685,6 +18848,8 @@ function applyChangeSetSide(state, entry, side) {
18685
18848
  );
18686
18849
  }
18687
18850
  }
18851
+ const pins = side === "before" ? entry.relativePlacementPinsBefore : entry.relativePlacementPinsAfter;
18852
+ if (pins !== void 0) state.restoreRelativePlacementPins(pins);
18688
18853
  }
18689
18854
 
18690
18855
  // src/dispatch/undo/undo-recorder.ts
@@ -21029,12 +21194,41 @@ function collectChangedActivities(changes, adapter) {
21029
21194
  }
21030
21195
  return activities;
21031
21196
  }
21197
+ function changesAffectRelativePinProjection(changes, changedActivities, adapter, pins) {
21198
+ if (pins.size === 0) return false;
21199
+ if (changes.activities.some(
21200
+ (change) => change.kind === "created" && pins.has(String(change.id))
21201
+ )) {
21202
+ return true;
21203
+ }
21204
+ const referenceIds = new Set(
21205
+ [...pins.values()].map(({ referenceId }) => String(referenceId))
21206
+ );
21207
+ for (const activity of changedActivities) {
21208
+ let activityId = String(activity.id);
21209
+ while (activityId !== null) {
21210
+ if (referenceIds.has(activityId)) return true;
21211
+ activityId = adapter.getParentId(activityId);
21212
+ }
21213
+ }
21214
+ return false;
21215
+ }
21032
21216
  function isCurrentFilterProjection(projection, filter, context) {
21033
21217
  if (projection === null || projection.filter !== filter) return false;
21034
21218
  const hasCurrentHours = projection.context.hoursPerDay === context.hoursPerDay;
21035
21219
  const hasCurrentLocale = projection.context.locale === context.locale;
21036
21220
  return hasCurrentHours && hasCurrentLocale;
21037
21221
  }
21222
+ function relativePlacementPinsEqual(left, right) {
21223
+ if (left.size !== right.size) return false;
21224
+ for (const [activityId, pin] of left) {
21225
+ const other = right.get(activityId);
21226
+ if (other === void 0 || other.referenceId !== pin.referenceId || other.branchParentId !== pin.branchParentId || other.side !== pin.side) {
21227
+ return false;
21228
+ }
21229
+ }
21230
+ return true;
21231
+ }
21038
21232
  var ScheduleCore = class _ScheduleCore {
21039
21233
  _activityClipboard = { value: null };
21040
21234
  _status = SCHEDULE_CORE_STATUS.READY;
@@ -21482,6 +21676,8 @@ var ScheduleCore = class _ScheduleCore {
21482
21676
  dateFormat: this.coreRuntime.sector.dateFormat
21483
21677
  });
21484
21678
  const historyPolicy = getDispatchHistoryPolicy(action);
21679
+ const capturesRelativePlacementPins = action.kind === "activity-create" || action.kind === "activity-delete" || action.kind === "activity-batch" && action.mode === "replace";
21680
+ const relativePlacementPinsBefore = capturesRelativePlacementPins ? new Map(this.coreRuntime.state.getRelativePlacementPins()) : void 0;
21485
21681
  const generatorSnapshot = [
21486
21682
  this.coreRuntime.activityIdGenerator.snapshot(),
21487
21683
  this.coreRuntime.linkIdGenerator.snapshot(),
@@ -21518,18 +21714,26 @@ var ScheduleCore = class _ScheduleCore {
21518
21714
  this.coreRuntime.state.endWriteCapture();
21519
21715
  this.coreRuntime.customIdTracker.commitCustomIdTransaction();
21520
21716
  }
21717
+ if (result.ok) this._recordRelativePlacementPin(action, result.changes);
21521
21718
  if (result.ok && historyPolicy === "record" && changeSetIsSubstantive(result.changes)) {
21522
21719
  const created = captureCreatedSnapshots(
21523
21720
  this.coreRuntime.state,
21524
21721
  result.changes
21525
21722
  );
21723
+ const relativePlacementPinsAfter = capturesRelativePlacementPins ? new Map(this.coreRuntime.state.getRelativePlacementPins()) : void 0;
21724
+ const relativePlacementPinsChanged = relativePlacementPinsBefore !== void 0 && relativePlacementPinsAfter !== void 0 && !relativePlacementPinsEqual(
21725
+ relativePlacementPinsBefore,
21726
+ relativePlacementPinsAfter
21727
+ );
21526
21728
  this._undo.record(
21527
21729
  buildUndoEntry(
21528
21730
  buildHistoryChangeSet(result.changes),
21529
21731
  result.__beforeSnap,
21530
21732
  result.__beforeLinks,
21531
21733
  created.afterSnap,
21532
- created.afterLinks
21734
+ created.afterLinks,
21735
+ relativePlacementPinsChanged ? relativePlacementPinsBefore : void 0,
21736
+ relativePlacementPinsChanged ? relativePlacementPinsAfter : void 0
21533
21737
  ),
21534
21738
  computeCoalesceKey(action),
21535
21739
  Date.now()
@@ -21597,6 +21801,19 @@ var ScheduleCore = class _ScheduleCore {
21597
21801
  const merged = this._reapplyViewState(result.changes);
21598
21802
  return merged === null ? result : { ...result, changes: merged };
21599
21803
  }
21804
+ _recordRelativePlacementPin(action, changes) {
21805
+ const state = this.coreRuntime.state;
21806
+ if (action.kind !== "activity-create") return;
21807
+ const referenceId = action.afterSiblingId ?? action.beforeSiblingId ?? (action.eventSource === "Creation button (+)" && action.parentId !== ROOT_PARENT_ID ? action.parentId : void 0);
21808
+ if (referenceId === void 0) return;
21809
+ const created = changes.activities.find(({ kind }) => kind === "created");
21810
+ if (created === void 0) return;
21811
+ state.setRelativePlacementPin(created.id, {
21812
+ referenceId,
21813
+ branchParentId: action.parentId,
21814
+ side: action.beforeSiblingId === void 0 ? "after" : "before"
21815
+ });
21816
+ }
21600
21817
  /**
21601
21818
  * The single place the two view-state passes are chained. Dispatch, undo and
21602
21819
  * redo all route through here: when this existed only inside the dispatch
@@ -21619,39 +21836,7 @@ var ScheduleCore = class _ScheduleCore {
21619
21836
  sequenced.changes ?? withFilter ?? changes,
21620
21837
  sequenced.touchedParents
21621
21838
  );
21622
- const reapplied = withOrder ?? sequenced.changes ?? withFilter;
21623
- const finalChanges = reapplied ?? changes;
21624
- const createdIds = finalChanges.activities.filter((change) => change.kind === "created").map((change) => String(change.id));
21625
- if (createdIds.length === 0) return reapplied;
21626
- const createdAndAncestors = new Set(createdIds);
21627
- const filterProjection = this.coreRuntime.state.getFilterProjection();
21628
- for (const createdId of createdIds) {
21629
- if (filterProjection !== null) {
21630
- filterProjection.pinnedVisibleIds.add(createdId);
21631
- }
21632
- let activityId = createdId;
21633
- while (activityId !== null && activityId !== ROOT_PARENT_ID) {
21634
- createdAndAncestors.add(activityId);
21635
- if (filterProjection !== null) {
21636
- filterProjection.visibleIds.add(activityId);
21637
- filterProjection.visibleReferenceCounts.set(
21638
- activityId,
21639
- (filterProjection.visibleReferenceCounts.get(activityId) ?? 0) + 1
21640
- );
21641
- }
21642
- activityId = this.coreRuntime.state.getParentId(activityId);
21643
- }
21644
- }
21645
- const createdVisibility = applyVisibleSet(
21646
- this.coreRuntime.state,
21647
- createdAndAncestors,
21648
- createdAndAncestors
21649
- );
21650
- if (createdVisibility.length === 0) return reapplied;
21651
- return {
21652
- ...finalChanges,
21653
- viewState: [...finalChanges.viewState ?? [], ...createdVisibility]
21654
- };
21839
+ return withOrder ?? sequenced.changes ?? withFilter;
21655
21840
  }
21656
21841
  /**
21657
21842
  * Emits `order` for the branches this mutation resequenced, when no user order
@@ -21730,16 +21915,22 @@ var ScheduleCore = class _ScheduleCore {
21730
21915
  context
21731
21916
  );
21732
21917
  const changedActivities = changesRequireFilterProjectionRebuild(changes) ? null : collectChangedActivities(changes, adapter);
21918
+ const relativePlacementPins = adapter.getRelativePlacementPins();
21733
21919
  let affectedIds;
21734
21920
  let visibleIds;
21735
- const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent;
21921
+ const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent || changesAffectRelativePinProjection(
21922
+ changes,
21923
+ changedActivities,
21924
+ adapter,
21925
+ relativePlacementPins
21926
+ );
21736
21927
  if (needsProjectionRebuild) {
21737
21928
  const rebuiltProjection = buildFilterProjection({
21738
21929
  activities: adapter.getAllActivities(),
21739
21930
  parentOf: (activityId) => adapter.getParentId(activityId),
21740
21931
  filter,
21741
21932
  context,
21742
- ...projectionIsCurrent ? { pinnedVisibleIds: projection.pinnedVisibleIds } : {}
21933
+ relativePlacementPins
21743
21934
  });
21744
21935
  adapter.setFilterProjection(rebuiltProjection);
21745
21936
  visibleIds = rebuiltProjection.visibleIds;