@outbuild-company/schedule-core 1.21.0 → 1.22.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.cjs CHANGED
@@ -346,6 +346,9 @@ var DISPATCH_ACTION_KIND = {
346
346
  CALENDAR_RECONCILE: "calendar-reconcile",
347
347
  ACTIVITY_PASTE_COPIED: "activity-paste-copied",
348
348
  ACTIVITY_DUPLICATE: "activity-duplicate",
349
+ CELL_COPY: "cell-copy",
350
+ CELL_CLIPBOARD_CLEAR: "cell-clipboard-clear",
351
+ CELL_PASTE_COPIED: "cell-paste-copied",
349
352
  PERSISTENCE_ACKNOWLEDGE: "persistence-acknowledge",
350
353
  BASELINE_APPLY: "baseline-apply",
351
354
  PONDERATOR_CRITERION_SET: "ponderator-criterion-set",
@@ -617,6 +620,26 @@ function createActivitiesApi(engine) {
617
620
  createDateGesturePreview: (gesture) => engine.createDateGesturePreview(gesture)
618
621
  };
619
622
  }
623
+ function createCellsApi(engine) {
624
+ return {
625
+ copy: (activityId, column, context = {}) => engine.dispatch({
626
+ ...context,
627
+ kind: DISPATCH_ACTION_KIND.CELL_COPY,
628
+ activityId,
629
+ column
630
+ }),
631
+ paste: (activityIds, column, context = {}) => engine.dispatch({
632
+ ...context,
633
+ kind: DISPATCH_ACTION_KIND.CELL_PASTE_COPIED,
634
+ activityIds,
635
+ column
636
+ }),
637
+ clearClipboard: (context = {}) => engine.dispatch({
638
+ ...context,
639
+ kind: DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR
640
+ })
641
+ };
642
+ }
620
643
  function createLinksApi(engine) {
621
644
  return {
622
645
  get: (id) => engine.getLinkView(id),
@@ -664,6 +687,7 @@ function createScheduleNamespaces(engine) {
664
687
  );
665
688
  return {
666
689
  activities: createActivitiesApi(engine),
690
+ cells: createCellsApi(engine),
667
691
  links: createLinksApi(engine),
668
692
  calendars: {
669
693
  get: (id) => engine.getCalendar(id),
@@ -3894,7 +3918,7 @@ function applyLinkOperation(op, deps) {
3894
3918
  return applyDelete(op, deps);
3895
3919
  }
3896
3920
  }
3897
- function applyLinkCreates(links, deps) {
3921
+ function validateLinkCreateBatch(links, deps) {
3898
3922
  if (links.length === 0) return { applied: true };
3899
3923
  const existingLinks = deps.port.getAllLinks();
3900
3924
  const duplicateKeys = /* @__PURE__ */ new Map();
@@ -3978,6 +4002,12 @@ function applyLinkCreates(links, deps) {
3978
4002
  if (!effectiveVerdict.ok) {
3979
4003
  return { applied: false, rejected: LINK_REJECTION_REASON.CYCLE };
3980
4004
  }
4005
+ return { applied: true };
4006
+ }
4007
+ function applyLinkCreates(links, deps) {
4008
+ if (links.length === 0) return { applied: true };
4009
+ const validation = validateLinkCreateBatch(links, deps);
4010
+ if (!validation.applied) return validation;
3981
4011
  for (const { operation, linkId } of links) {
3982
4012
  const preserved = deps.preserved?.get(linkId);
3983
4013
  deps.port.addLink({
@@ -11715,6 +11745,8 @@ var NON_SCHEDULING_INLINE_COLUMNS = /* @__PURE__ */ new Set([
11715
11745
  var NON_SCHEDULING_KINDS = /* @__PURE__ */ new Set([
11716
11746
  DISPATCH_ACTION_KIND.ACTIVITY_CLIPBOARD_CLEAR,
11717
11747
  DISPATCH_ACTION_KIND.ACTIVITY_COPY,
11748
+ DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR,
11749
+ DISPATCH_ACTION_KIND.CELL_COPY,
11718
11750
  DISPATCH_ACTION_KIND.SELECTION_TOGGLE,
11719
11751
  DISPATCH_ACTION_KIND.SELECTION_REPLACE,
11720
11752
  DISPATCH_ACTION_KIND.VISIBILITY_SET,
@@ -11733,6 +11765,8 @@ var NON_SCHEDULING_KINDS = /* @__PURE__ */ new Set([
11733
11765
  var PURE_VIEW_STATE_KINDS = /* @__PURE__ */ new Set([
11734
11766
  DISPATCH_ACTION_KIND.ACTIVITY_CLIPBOARD_CLEAR,
11735
11767
  DISPATCH_ACTION_KIND.ACTIVITY_COPY,
11768
+ DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR,
11769
+ DISPATCH_ACTION_KIND.CELL_COPY,
11736
11770
  DISPATCH_ACTION_KIND.SELECTION_TOGGLE,
11737
11771
  DISPATCH_ACTION_KIND.SELECTION_REPLACE,
11738
11772
  DISPATCH_ACTION_KIND.VISIBILITY_SET,
@@ -11750,7 +11784,7 @@ function reappliesViewState(action) {
11750
11784
  }
11751
11785
  var NO_STRUCTURAL_EXEMPTIONS = /* @__PURE__ */ new Set();
11752
11786
  function editsOnlyNonSchedulingColumns(action) {
11753
- if (action.kind === DISPATCH_ACTION_KIND.INLINE_EDIT) {
11787
+ if (action.kind === DISPATCH_ACTION_KIND.INLINE_EDIT || action.kind === DISPATCH_ACTION_KIND.CELL_PASTE_COPIED) {
11754
11788
  return NON_SCHEDULING_INLINE_COLUMNS.has(action.column);
11755
11789
  }
11756
11790
  if (action.kind === DISPATCH_ACTION_KIND.BULK_EDIT) {
@@ -11761,7 +11795,7 @@ function editsOnlyNonSchedulingColumns(action) {
11761
11795
  return false;
11762
11796
  }
11763
11797
  function editsOnlyDataDateColumns(action) {
11764
- if (action.kind === DISPATCH_ACTION_KIND.INLINE_EDIT) {
11798
+ if (action.kind === DISPATCH_ACTION_KIND.INLINE_EDIT || action.kind === DISPATCH_ACTION_KIND.CELL_PASTE_COPIED) {
11765
11799
  return DATA_DATE_COLUMNS.has(action.column);
11766
11800
  }
11767
11801
  return action.kind === DISPATCH_ACTION_KIND.BULK_EDIT && action.edits.every((edit) => DATA_DATE_COLUMNS.has(edit.column));
@@ -11803,6 +11837,9 @@ function dispatchChangesProgress(action) {
11803
11837
  if (action.kind === DISPATCH_ACTION_KIND.BULK_EDIT) {
11804
11838
  return action.edits.some((edit) => edit.column === COLUMN.PROGRESS);
11805
11839
  }
11840
+ if (action.kind === DISPATCH_ACTION_KIND.CELL_PASTE_COPIED) {
11841
+ return action.column === COLUMN.PROGRESS;
11842
+ }
11806
11843
  return false;
11807
11844
  }
11808
11845
  function resolveRunRealCost(action) {
@@ -11818,7 +11855,7 @@ var PROGRESS_AFFECTING_COLUMNS = /* @__PURE__ */ new Set([
11818
11855
  ]);
11819
11856
  function shouldRecomputeParentProgress(action) {
11820
11857
  if (action.kind === DISPATCH_ACTION_KIND.ACTIVITY_SET_PROGRESS) return true;
11821
- if (action.kind === DISPATCH_ACTION_KIND.INLINE_EDIT) {
11858
+ if (action.kind === DISPATCH_ACTION_KIND.INLINE_EDIT || action.kind === DISPATCH_ACTION_KIND.CELL_PASTE_COPIED) {
11822
11859
  return PROGRESS_AFFECTING_COLUMNS.has(action.column);
11823
11860
  }
11824
11861
  if (action.kind === DISPATCH_ACTION_KIND.BULK_EDIT) {
@@ -12790,6 +12827,15 @@ function mergeScheduledIds(firstSettleIds, secondSettleIds) {
12790
12827
  }
12791
12828
 
12792
12829
  // src/dispatch/shared/helpers.ts
12830
+ function getIncidentLinkSnapshots(adapter, activityId, isPredecessors) {
12831
+ const incidentLinkIds = isPredecessors ? adapter.getIncomingLinkIds(activityId) : adapter.getOutgoingLinkIds(activityId);
12832
+ const links = [];
12833
+ for (const linkId of incidentLinkIds) {
12834
+ const link = adapter.getLink(linkId);
12835
+ if (link) links.push(link);
12836
+ }
12837
+ return links;
12838
+ }
12793
12839
  function buildLinkChangesForBatch(applied, beforeLinks, adapter) {
12794
12840
  const out = [];
12795
12841
  for (const { op, finalLinkId, rejected: rejected2 } of applied) {
@@ -14016,12 +14062,11 @@ async function dispatchInlineEditLinks(action, options, deps) {
14016
14062
  byCorrelative.set(String(a.correlativeId), a.id);
14017
14063
  });
14018
14064
  const resolve = (corr) => byCorrelative.get(corr) ?? null;
14019
- const incidentLinkIds = isPredecessors ? adapter.getIncomingLinkIds(action.activityId) : adapter.getOutgoingLinkIds(action.activityId);
14020
- const currentLinks = [];
14021
- for (const linkId of incidentLinkIds) {
14022
- const link = adapter.getLink(linkId);
14023
- if (link) currentLinks.push(link);
14024
- }
14065
+ const currentLinks = getIncidentLinkSnapshots(
14066
+ adapter,
14067
+ action.activityId,
14068
+ isPredecessors
14069
+ );
14025
14070
  const diff = isPredecessors ? diffLinks({
14026
14071
  targetActivityId: action.activityId,
14027
14072
  currentLinks,
@@ -18776,6 +18821,170 @@ function replicateInternalLinks(state, deps) {
18776
18821
  return applied;
18777
18822
  }
18778
18823
 
18824
+ // src/cell-copy-paste/copy.ts
18825
+ var CELL_CLIPBOARD_KIND = {
18826
+ FIELD: "field",
18827
+ LINKS: "links"
18828
+ };
18829
+ function copyLinkSpecs(adapter, activityId, isPredecessors) {
18830
+ return getIncidentLinkSnapshots(adapter, activityId, isPredecessors).map(
18831
+ (link) => ({
18832
+ otherActivityId: isPredecessors ? link.source : link.target,
18833
+ type: link.type,
18834
+ lag: link.lag
18835
+ })
18836
+ );
18837
+ }
18838
+ function emptyChangeSet(action) {
18839
+ return {
18840
+ ok: true,
18841
+ changes: {
18842
+ source: action,
18843
+ activities: [],
18844
+ links: [],
18845
+ calendars: [],
18846
+ trackingEvents: []
18847
+ }
18848
+ };
18849
+ }
18850
+ function dispatchCellCopy(action, deps) {
18851
+ const activity = deps.adapter.getActivity(action.activityId);
18852
+ if (!activity) {
18853
+ return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
18854
+ }
18855
+ if (action.column === COLUMN.CUSTOM_PREDECESSORS || action.column === COLUMN.CUSTOM_SUCESSORS) {
18856
+ deps.clipboard.value = {
18857
+ kind: CELL_CLIPBOARD_KIND.LINKS,
18858
+ specs: copyLinkSpecs(
18859
+ deps.adapter,
18860
+ action.activityId,
18861
+ action.column === COLUMN.CUSTOM_PREDECESSORS
18862
+ )
18863
+ };
18864
+ return emptyChangeSet(action);
18865
+ }
18866
+ const pipeline = findPipelineForColumn(action.column);
18867
+ if (!pipeline) {
18868
+ return { ok: false, reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN };
18869
+ }
18870
+ const canonicalValue = readPipelineTargetValue(activity, pipeline.targetField);
18871
+ if (action.column === COLUMN.DURATION && !Number.isFinite(canonicalValue)) {
18872
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
18873
+ }
18874
+ const raw = action.column === COLUMN.DURATION ? canonicalValue / deps.hoursPerDay : cloneDomainValue(canonicalValue);
18875
+ deps.clipboard.value = { kind: CELL_CLIPBOARD_KIND.FIELD, raw };
18876
+ return emptyChangeSet(action);
18877
+ }
18878
+ function dispatchCellClipboardClear(action, clipboard) {
18879
+ clipboard.value = null;
18880
+ return emptyChangeSet(action);
18881
+ }
18882
+
18883
+ // src/cell-copy-paste/paste.ts
18884
+ function toDiffPlaceholder(activityId) {
18885
+ return String(activityId);
18886
+ }
18887
+ function fromDiffPlaceholder(adapter, placeholder) {
18888
+ const activityId = placeholder;
18889
+ return adapter.activityExists(activityId) ? activityId : null;
18890
+ }
18891
+ function linkOperationsForTarget(targetActivityId, isPredecessors, clipboard, deps) {
18892
+ const desiredSpecs = clipboard.specs.map((spec) => ({
18893
+ correlativeId: toDiffPlaceholder(spec.otherActivityId),
18894
+ type: spec.type,
18895
+ lag: spec.lag
18896
+ }));
18897
+ const currentLinks = getIncidentLinkSnapshots(
18898
+ deps.adapter,
18899
+ targetActivityId,
18900
+ isPredecessors
18901
+ );
18902
+ const resolve = (placeholder) => fromDiffPlaceholder(deps.adapter, placeholder);
18903
+ const diff = isPredecessors ? diffLinks({
18904
+ targetActivityId,
18905
+ currentLinks,
18906
+ desiredSpecs,
18907
+ resolveActivityByCorrelative: resolve
18908
+ }) : diffOutgoingLinks({
18909
+ sourceActivityId: targetActivityId,
18910
+ currentLinks,
18911
+ desiredSpecs,
18912
+ resolveActivityByCorrelative: resolve
18913
+ });
18914
+ return diff.operations;
18915
+ }
18916
+ async function dispatchCellPasteCopied(action, options, deps) {
18917
+ const clipboard = deps.cellClipboard.value;
18918
+ if (clipboard === null) {
18919
+ return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
18920
+ }
18921
+ if (action.activityIds.length === 0) {
18922
+ return { ok: false, reason: REJECTION_REASON.ACTIVITY_IDS_EMPTY };
18923
+ }
18924
+ const hasMissingActivity = action.activityIds.some(
18925
+ (activityId) => !deps.adapter.activityExists(activityId)
18926
+ );
18927
+ if (hasMissingActivity) {
18928
+ return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
18929
+ }
18930
+ if (action.column === COLUMN.CUSTOM_PREDECESSORS || action.column === COLUMN.CUSTOM_SUCESSORS) {
18931
+ if (clipboard.kind !== CELL_CLIPBOARD_KIND.LINKS) {
18932
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
18933
+ }
18934
+ const hasSelfReference = action.activityIds.some(
18935
+ (activityId) => clipboard.specs.some(
18936
+ (spec) => String(spec.otherActivityId) === String(activityId)
18937
+ )
18938
+ );
18939
+ if (hasSelfReference) {
18940
+ return { ok: false, reason: LINK_REJECTION_REASON.SELF_LINK };
18941
+ }
18942
+ const isPredecessors = action.column === COLUMN.CUSTOM_PREDECESSORS;
18943
+ const operations = action.activityIds.flatMap(
18944
+ (activityId) => linkOperationsForTarget(activityId, isPredecessors, clipboard, deps)
18945
+ );
18946
+ const createEntries = operations.filter(
18947
+ (operation) => operation.kind === LINK_OPERATION_KIND.CREATE
18948
+ ).map((operation) => ({
18949
+ operation,
18950
+ linkId: deps.linkIdGen.next()
18951
+ }));
18952
+ const creation = validateLinkCreateBatch(createEntries, {
18953
+ port: deps.adapter,
18954
+ rejectParentLinkMutations: options.rejectParentLinkMutations ?? false
18955
+ });
18956
+ if (!creation.applied) {
18957
+ return {
18958
+ ok: false,
18959
+ reason: creation.rejected ?? REJECTION_REASON.INVALID
18960
+ };
18961
+ }
18962
+ return dispatchLinkBatch(operations, action, options, deps);
18963
+ }
18964
+ if (clipboard.kind !== CELL_CLIPBOARD_KIND.FIELD) {
18965
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
18966
+ }
18967
+ if (!findPipelineForColumn(action.column)) {
18968
+ return { ok: false, reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN };
18969
+ }
18970
+ const column = action.column;
18971
+ const result = await dispatchBulkEdit(
18972
+ {
18973
+ kind: DISPATCH_ACTION_KIND.BULK_EDIT,
18974
+ edits: action.activityIds.map((activityId) => ({
18975
+ activityId,
18976
+ column,
18977
+ newValue: clipboard.raw
18978
+ })),
18979
+ ...action.eventSource === void 0 ? {} : { eventSource: action.eventSource }
18980
+ },
18981
+ options,
18982
+ deps
18983
+ );
18984
+ if (!result.ok) return result;
18985
+ return { ...result, changes: { ...result.changes, source: action } };
18986
+ }
18987
+
18779
18988
  // src/dispatch/dispatch.ts
18780
18989
  async function performDispatch(action, options, deps) {
18781
18990
  if (action.kind === DISPATCH_ACTION_KIND.ACTIVITY_COPY) {
@@ -18796,6 +19005,19 @@ async function performDispatch(action, options, deps) {
18796
19005
  if (action.kind === DISPATCH_ACTION_KIND.ACTIVITY_DUPLICATE) {
18797
19006
  return dispatchActivityDuplicate(action, options, deps);
18798
19007
  }
19008
+ if (action.kind === DISPATCH_ACTION_KIND.CELL_COPY) {
19009
+ return dispatchCellCopy(action, {
19010
+ adapter: deps.adapter,
19011
+ clipboard: deps.cellClipboard,
19012
+ hoursPerDay: deps.sector.hoursPerDay
19013
+ });
19014
+ }
19015
+ if (action.kind === DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR) {
19016
+ return dispatchCellClipboardClear(action, deps.cellClipboard);
19017
+ }
19018
+ if (action.kind === DISPATCH_ACTION_KIND.CELL_PASTE_COPIED) {
19019
+ return dispatchCellPasteCopied(action, options, deps);
19020
+ }
18799
19021
  if (action.kind === ACTIVITY_BATCH_ACTION_KIND) {
18800
19022
  return dispatchActivityBatch(action, options, deps);
18801
19023
  }
@@ -30468,7 +30690,7 @@ function validateP6EndingActivitySearch(input) {
30468
30690
  );
30469
30691
  }
30470
30692
  }
30471
- function emptyChangeSet(source) {
30693
+ function emptyChangeSet2(source) {
30472
30694
  return {
30473
30695
  source,
30474
30696
  activities: [],
@@ -30506,6 +30728,8 @@ function isCurrentFilterProjection(projection, filter, context) {
30506
30728
  var ScheduleCore = class _ScheduleCore {
30507
30729
  /** Activity reads and mutations. */
30508
30730
  activities;
30731
+ /** Single-cell clipboard reads and mutations. */
30732
+ cells;
30509
30733
  /** Dependency-link reads and mutations. */
30510
30734
  links;
30511
30735
  /** Calendar reads and reconciliation. */
@@ -30543,6 +30767,7 @@ var ScheduleCore = class _ScheduleCore {
30543
30767
  schedule: (patch) => this._scheduleP6(patch)
30544
30768
  });
30545
30769
  _activityClipboard = { value: null };
30770
+ _cellClipboard = { value: null };
30546
30771
  _status = SCHEDULE_CORE_STATUS.READY;
30547
30772
  coreRuntime;
30548
30773
  _isFork;
@@ -30587,6 +30812,9 @@ var ScheduleCore = class _ScheduleCore {
30587
30812
  this._activityClipboard.value = cloneDomainValue(
30588
30813
  source._activityClipboard.value
30589
30814
  );
30815
+ this._cellClipboard.value = cloneDomainValue(
30816
+ source._cellClipboard.value
30817
+ );
30590
30818
  this._undo = source._undo.fork();
30591
30819
  this._saveTracker = source._saveTracker.fork();
30592
30820
  this._scheduleRevision = source._scheduleRevision;
@@ -31815,7 +32043,7 @@ var ScheduleCore = class _ScheduleCore {
31815
32043
  });
31816
32044
  continue;
31817
32045
  }
31818
- const changes = emptyChangeSet(action);
32046
+ const changes = emptyChangeSet2(action);
31819
32047
  results.push({ ok: true, changes });
31820
32048
  acceptedChangeSets.push(changes);
31821
32049
  acceptedActions.push(action);
@@ -31944,6 +32172,7 @@ var ScheduleCore = class _ScheduleCore {
31944
32172
  customIdTracker: this.coreRuntime.customIdTracker,
31945
32173
  reporter: this.coreRuntime.reporter,
31946
32174
  activityClipboard: this._activityClipboard,
32175
+ cellClipboard: this._cellClipboard,
31947
32176
  stateRevision: this._stateRevision,
31948
32177
  defaultBaseCalendarId: this.coreRuntime.baseCalendars.find(
31949
32178
  (calendar) => calendar.baseDefault