@outbuild-company/schedule-core 1.25.0 → 1.26.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
@@ -724,6 +724,12 @@ interface ScheduleAutoScheduleAction {
724
724
  *
725
725
  * Link `lag` inputs are working days and are converted to stored working hours.
726
726
  * Consumer analytics context is carried by optional `eventSource` fields.
727
+ *
728
+ * `CELL_COPY`'s `copiedText` and `CELL_PASTE_COPIED`'s `clipboardText` carry the
729
+ * host's OS-clipboard text alongside the structured value. When `clipboardText`
730
+ * no longer matches the `copiedText` stashed at copy time (or nothing was ever
731
+ * copied through the core), the paste falls back to that raw text through the
732
+ * target column's own `parseInput` instead of the stashed structured value.
727
733
  */
728
734
  type DispatchAction = {
729
735
  kind: typeof DISPATCH_ACTION_KIND.ACTIVITY_COPY;
@@ -752,6 +758,7 @@ type DispatchAction = {
752
758
  kind: typeof DISPATCH_ACTION_KIND.CELL_COPY;
753
759
  activityId: ActivityId;
754
760
  column: EditableColumnName;
761
+ copiedText?: string;
755
762
  eventSource?: string;
756
763
  } | {
757
764
  kind: typeof DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR;
@@ -760,6 +767,7 @@ type DispatchAction = {
760
767
  kind: typeof DISPATCH_ACTION_KIND.CELL_PASTE_COPIED;
761
768
  activityIds: ReadonlyArray<ActivityId>;
762
769
  column: EditableColumnName;
770
+ clipboardText?: string;
763
771
  eventSource?: string;
764
772
  } | {
765
773
  kind: typeof DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE;
@@ -2154,25 +2162,35 @@ interface ScheduleCellsApi {
2154
2162
  * Copies one activity's cell value to core-owned clipboard state.
2155
2163
  *
2156
2164
  * Predecessor and successor columns copy the underlying dependency links;
2157
- * every other column copies its canonical field value.
2165
+ * every other column copies its canonical field value alongside `copiedText`,
2166
+ * the host's OS-clipboard text at copy time.
2158
2167
  * @param activityId - Source activity.
2159
2168
  * @param column - Source column.
2169
+ * @param copiedText - Host OS-clipboard text to stash alongside the value.
2160
2170
  * @param context - Optional consumer event source.
2161
2171
  * @returns The complete dispatch result.
2162
2172
  */
2163
- copy(activityId: ActivityId, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2173
+ copy(activityId: ActivityId, column: EditableColumnName, copiedText?: string, context?: MutationContext): Promise<DispatchResult>;
2164
2174
  /**
2165
2175
  * Pastes the copied cell value into one or more activities' column.
2166
2176
  *
2167
2177
  * Predecessor and successor columns paste cross-compatibly with each
2168
- * other; every other column is rejected unless its own pipeline accepts
2169
- * the copied value.
2178
+ * other and ignore `clipboardText`. For every other column: `tags`,
2179
+ * `responsables`, `subcontract_id`, `constraint_type` and `calendar_id`
2180
+ * only accept a paste whose source was the exact same column, rejected
2181
+ * with `CELL_PASTE_COLUMN_MISMATCH` otherwise; every other column pair is
2182
+ * left to the target column's own pipeline. When `clipboardText` no
2183
+ * longer matches the text stashed at copy time (or nothing was ever
2184
+ * copied through the core), the stashed value is dropped and
2185
+ * `clipboardText` itself is pasted through the target column's own
2186
+ * `parseInput` instead.
2170
2187
  * @param activityIds - Destination activities.
2171
2188
  * @param column - Destination column.
2189
+ * @param clipboardText - Host OS-clipboard text at paste time.
2172
2190
  * @param context - Optional consumer event source.
2173
2191
  * @returns The complete dispatch result.
2174
2192
  */
2175
- paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2193
+ paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, clipboardText?: string, context?: MutationContext): Promise<DispatchResult>;
2176
2194
  /** @returns The complete result of clearing core-owned cell clipboard state. */
2177
2195
  clearClipboard(context?: MutationContext): Promise<DispatchResult>;
2178
2196
  }
@@ -3669,6 +3687,7 @@ declare const REJECTION_REASON: {
3669
3687
  readonly NO_ACTIVITY_SELECTION: "no_activity_selection";
3670
3688
  readonly CLIPBOARD_EMPTY: "clipboard_empty";
3671
3689
  readonly CLIPBOARD_SNAPSHOT_INVALID: "clipboard_snapshot_invalid";
3690
+ readonly CELL_PASTE_COLUMN_MISMATCH: "cell_paste_column_mismatch";
3672
3691
  readonly ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed";
3673
3692
  readonly SOLE_SCHEDULE_ROOT: "sole_schedule_root";
3674
3693
  readonly ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict";
package/dist/index.d.ts CHANGED
@@ -724,6 +724,12 @@ interface ScheduleAutoScheduleAction {
724
724
  *
725
725
  * Link `lag` inputs are working days and are converted to stored working hours.
726
726
  * Consumer analytics context is carried by optional `eventSource` fields.
727
+ *
728
+ * `CELL_COPY`'s `copiedText` and `CELL_PASTE_COPIED`'s `clipboardText` carry the
729
+ * host's OS-clipboard text alongside the structured value. When `clipboardText`
730
+ * no longer matches the `copiedText` stashed at copy time (or nothing was ever
731
+ * copied through the core), the paste falls back to that raw text through the
732
+ * target column's own `parseInput` instead of the stashed structured value.
727
733
  */
728
734
  type DispatchAction = {
729
735
  kind: typeof DISPATCH_ACTION_KIND.ACTIVITY_COPY;
@@ -752,6 +758,7 @@ type DispatchAction = {
752
758
  kind: typeof DISPATCH_ACTION_KIND.CELL_COPY;
753
759
  activityId: ActivityId;
754
760
  column: EditableColumnName;
761
+ copiedText?: string;
755
762
  eventSource?: string;
756
763
  } | {
757
764
  kind: typeof DISPATCH_ACTION_KIND.CELL_CLIPBOARD_CLEAR;
@@ -760,6 +767,7 @@ type DispatchAction = {
760
767
  kind: typeof DISPATCH_ACTION_KIND.CELL_PASTE_COPIED;
761
768
  activityIds: ReadonlyArray<ActivityId>;
762
769
  column: EditableColumnName;
770
+ clipboardText?: string;
763
771
  eventSource?: string;
764
772
  } | {
765
773
  kind: typeof DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE;
@@ -2154,25 +2162,35 @@ interface ScheduleCellsApi {
2154
2162
  * Copies one activity's cell value to core-owned clipboard state.
2155
2163
  *
2156
2164
  * Predecessor and successor columns copy the underlying dependency links;
2157
- * every other column copies its canonical field value.
2165
+ * every other column copies its canonical field value alongside `copiedText`,
2166
+ * the host's OS-clipboard text at copy time.
2158
2167
  * @param activityId - Source activity.
2159
2168
  * @param column - Source column.
2169
+ * @param copiedText - Host OS-clipboard text to stash alongside the value.
2160
2170
  * @param context - Optional consumer event source.
2161
2171
  * @returns The complete dispatch result.
2162
2172
  */
2163
- copy(activityId: ActivityId, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2173
+ copy(activityId: ActivityId, column: EditableColumnName, copiedText?: string, context?: MutationContext): Promise<DispatchResult>;
2164
2174
  /**
2165
2175
  * Pastes the copied cell value into one or more activities' column.
2166
2176
  *
2167
2177
  * Predecessor and successor columns paste cross-compatibly with each
2168
- * other; every other column is rejected unless its own pipeline accepts
2169
- * the copied value.
2178
+ * other and ignore `clipboardText`. For every other column: `tags`,
2179
+ * `responsables`, `subcontract_id`, `constraint_type` and `calendar_id`
2180
+ * only accept a paste whose source was the exact same column, rejected
2181
+ * with `CELL_PASTE_COLUMN_MISMATCH` otherwise; every other column pair is
2182
+ * left to the target column's own pipeline. When `clipboardText` no
2183
+ * longer matches the text stashed at copy time (or nothing was ever
2184
+ * copied through the core), the stashed value is dropped and
2185
+ * `clipboardText` itself is pasted through the target column's own
2186
+ * `parseInput` instead.
2170
2187
  * @param activityIds - Destination activities.
2171
2188
  * @param column - Destination column.
2189
+ * @param clipboardText - Host OS-clipboard text at paste time.
2172
2190
  * @param context - Optional consumer event source.
2173
2191
  * @returns The complete dispatch result.
2174
2192
  */
2175
- paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, context?: MutationContext): Promise<DispatchResult>;
2193
+ paste(activityIds: ReadonlyArray<ActivityId>, column: EditableColumnName, clipboardText?: string, context?: MutationContext): Promise<DispatchResult>;
2176
2194
  /** @returns The complete result of clearing core-owned cell clipboard state. */
2177
2195
  clearClipboard(context?: MutationContext): Promise<DispatchResult>;
2178
2196
  }
@@ -3669,6 +3687,7 @@ declare const REJECTION_REASON: {
3669
3687
  readonly NO_ACTIVITY_SELECTION: "no_activity_selection";
3670
3688
  readonly CLIPBOARD_EMPTY: "clipboard_empty";
3671
3689
  readonly CLIPBOARD_SNAPSHOT_INVALID: "clipboard_snapshot_invalid";
3690
+ readonly CELL_PASTE_COLUMN_MISMATCH: "cell_paste_column_mismatch";
3672
3691
  readonly ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed";
3673
3692
  readonly SOLE_SCHEDULE_ROOT: "sole_schedule_root";
3674
3693
  readonly ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict";
package/dist/index.js CHANGED
@@ -628,17 +628,19 @@ function createActivitiesApi(engine) {
628
628
  }
629
629
  function createCellsApi(engine) {
630
630
  return {
631
- copy: (activityId, column, context = {}) => engine.dispatch({
631
+ copy: (activityId, column, copiedText, context = {}) => engine.dispatch({
632
632
  ...context,
633
633
  kind: DISPATCH_ACTION_KIND.CELL_COPY,
634
634
  activityId,
635
- column
635
+ column,
636
+ ...copiedText === void 0 ? {} : { copiedText }
636
637
  }),
637
- paste: (activityIds, column, context = {}) => engine.dispatch({
638
+ paste: (activityIds, column, clipboardText, context = {}) => engine.dispatch({
638
639
  ...context,
639
640
  kind: DISPATCH_ACTION_KIND.CELL_PASTE_COPIED,
640
641
  activityIds,
641
- column
642
+ column,
643
+ ...clipboardText === void 0 ? {} : { clipboardText }
642
644
  }),
643
645
  clearClipboard: (context = {}) => engine.dispatch({
644
646
  ...context,
@@ -955,6 +957,7 @@ var REJECTION_REASON = {
955
957
  NO_ACTIVITY_SELECTION: "no_activity_selection",
956
958
  CLIPBOARD_EMPTY: "clipboard_empty",
957
959
  CLIPBOARD_SNAPSHOT_INVALID: "clipboard_snapshot_invalid",
960
+ CELL_PASTE_COLUMN_MISMATCH: "cell_paste_column_mismatch",
958
961
  ACTIVITY_PASTE_INVARIANT_FAILED: "activity_paste_invariant_failed",
959
962
  SOLE_SCHEDULE_ROOT: "sole_schedule_root",
960
963
  ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict",
@@ -19010,7 +19013,12 @@ function dispatchCellCopy(action, deps) {
19010
19013
  return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19011
19014
  }
19012
19015
  const raw = action.column === COLUMN.DURATION ? canonicalValue / deps.hoursPerDay : cloneDomainValue(canonicalValue);
19013
- deps.clipboard.value = { kind: CELL_CLIPBOARD_KIND.FIELD, raw };
19016
+ deps.clipboard.value = {
19017
+ kind: CELL_CLIPBOARD_KIND.FIELD,
19018
+ raw,
19019
+ sourceColumn: action.column,
19020
+ text: action.copiedText ?? null
19021
+ };
19014
19022
  return emptyChangeSet(action);
19015
19023
  }
19016
19024
  function dispatchCellClipboardClear(action, clipboard) {
@@ -19019,6 +19027,39 @@ function dispatchCellClipboardClear(action, clipboard) {
19019
19027
  }
19020
19028
 
19021
19029
  // src/cell-copy-paste/paste.ts
19030
+ var RESTRICTED_CELL_COLUMNS = /* @__PURE__ */ new Set([
19031
+ COLUMN.TAGS,
19032
+ COLUMN.RESPONSABLES,
19033
+ COLUMN.SUBCONTRACT_ID,
19034
+ COLUMN.CONSTRAINT_TYPE,
19035
+ COLUMN.CALENDAR_ID
19036
+ ]);
19037
+ function isIncompatibleCellPaste(sourceColumn, targetColumn) {
19038
+ if (sourceColumn === targetColumn) return false;
19039
+ return RESTRICTED_CELL_COLUMNS.has(sourceColumn) || RESTRICTED_CELL_COLUMNS.has(targetColumn);
19040
+ }
19041
+ function resolveExternalPasteText(stashedField, clipboardText) {
19042
+ if (clipboardText === void 0) return null;
19043
+ const rawIsTrustworthy = stashedField !== null && (stashedField.text === null || stashedField.text === clipboardText);
19044
+ return rawIsTrustworthy ? null : clipboardText;
19045
+ }
19046
+ function resolveFieldPasteValue(clipboard, column, clipboardText) {
19047
+ const stashedField = clipboard !== null && clipboard.kind === CELL_CLIPBOARD_KIND.FIELD ? clipboard : null;
19048
+ const externalText = resolveExternalPasteText(stashedField, clipboardText);
19049
+ if (externalText !== null) {
19050
+ return { ok: true, newValue: externalText };
19051
+ }
19052
+ if (clipboard !== null && clipboard.kind !== CELL_CLIPBOARD_KIND.FIELD) {
19053
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19054
+ }
19055
+ if (stashedField === null) {
19056
+ return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
19057
+ }
19058
+ if (isIncompatibleCellPaste(stashedField.sourceColumn, column)) {
19059
+ return { ok: false, reason: REJECTION_REASON.CELL_PASTE_COLUMN_MISMATCH };
19060
+ }
19061
+ return { ok: true, newValue: stashedField.raw };
19062
+ }
19022
19063
  function toDiffPlaceholder(activityId) {
19023
19064
  return String(activityId);
19024
19065
  }
@@ -19051,11 +19092,45 @@ function linkOperationsForTarget(targetActivityId, isPredecessors, clipboard, de
19051
19092
  });
19052
19093
  return diff.operations;
19053
19094
  }
19054
- async function dispatchCellPasteCopied(action, options, deps) {
19055
- const clipboard = deps.cellClipboard.value;
19095
+ async function dispatchCellPasteLinkColumn(action, clipboard, options, deps) {
19056
19096
  if (clipboard === null) {
19057
19097
  return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
19058
19098
  }
19099
+ if (clipboard.kind !== CELL_CLIPBOARD_KIND.LINKS) {
19100
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19101
+ }
19102
+ const hasSelfReference = action.activityIds.some(
19103
+ (activityId) => clipboard.specs.some(
19104
+ (spec) => String(spec.otherActivityId) === String(activityId)
19105
+ )
19106
+ );
19107
+ if (hasSelfReference) {
19108
+ return { ok: false, reason: LINK_REJECTION_REASON.SELF_LINK };
19109
+ }
19110
+ const isPredecessors = action.column === COLUMN.CUSTOM_PREDECESSORS;
19111
+ const operations = action.activityIds.flatMap(
19112
+ (activityId) => linkOperationsForTarget(activityId, isPredecessors, clipboard, deps)
19113
+ );
19114
+ const createEntries = operations.filter(
19115
+ (operation) => operation.kind === LINK_OPERATION_KIND.CREATE
19116
+ ).map((operation) => ({
19117
+ operation,
19118
+ linkId: deps.linkIdGen.next()
19119
+ }));
19120
+ const creation = validateLinkCreateBatch(createEntries, {
19121
+ port: deps.adapter,
19122
+ rejectParentLinkMutations: options.rejectParentLinkMutations ?? false
19123
+ });
19124
+ if (!creation.applied) {
19125
+ return {
19126
+ ok: false,
19127
+ reason: creation.rejected ?? REJECTION_REASON.INVALID
19128
+ };
19129
+ }
19130
+ return dispatchLinkBatch(operations, action, options, deps);
19131
+ }
19132
+ async function dispatchCellPasteCopied(action, options, deps) {
19133
+ const clipboard = deps.cellClipboard.value;
19059
19134
  if (action.activityIds.length === 0) {
19060
19135
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_IDS_EMPTY };
19061
19136
  }
@@ -19066,53 +19141,25 @@ async function dispatchCellPasteCopied(action, options, deps) {
19066
19141
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
19067
19142
  }
19068
19143
  if (action.column === COLUMN.CUSTOM_PREDECESSORS || action.column === COLUMN.CUSTOM_SUCESSORS) {
19069
- if (clipboard.kind !== CELL_CLIPBOARD_KIND.LINKS) {
19070
- return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19071
- }
19072
- const hasSelfReference = action.activityIds.some(
19073
- (activityId) => clipboard.specs.some(
19074
- (spec) => String(spec.otherActivityId) === String(activityId)
19075
- )
19076
- );
19077
- if (hasSelfReference) {
19078
- return { ok: false, reason: LINK_REJECTION_REASON.SELF_LINK };
19079
- }
19080
- const isPredecessors = action.column === COLUMN.CUSTOM_PREDECESSORS;
19081
- const operations = action.activityIds.flatMap(
19082
- (activityId) => linkOperationsForTarget(activityId, isPredecessors, clipboard, deps)
19083
- );
19084
- const createEntries = operations.filter(
19085
- (operation) => operation.kind === LINK_OPERATION_KIND.CREATE
19086
- ).map((operation) => ({
19087
- operation,
19088
- linkId: deps.linkIdGen.next()
19089
- }));
19090
- const creation = validateLinkCreateBatch(createEntries, {
19091
- port: deps.adapter,
19092
- rejectParentLinkMutations: options.rejectParentLinkMutations ?? false
19093
- });
19094
- if (!creation.applied) {
19095
- return {
19096
- ok: false,
19097
- reason: creation.rejected ?? REJECTION_REASON.INVALID
19098
- };
19099
- }
19100
- return dispatchLinkBatch(operations, action, options, deps);
19101
- }
19102
- if (clipboard.kind !== CELL_CLIPBOARD_KIND.FIELD) {
19103
- return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
19144
+ return dispatchCellPasteLinkColumn(action, clipboard, options, deps);
19104
19145
  }
19105
19146
  if (!findPipelineForColumn(action.column)) {
19106
19147
  return { ok: false, reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN };
19107
19148
  }
19108
19149
  const column = action.column;
19150
+ const resolution = resolveFieldPasteValue(
19151
+ clipboard,
19152
+ column,
19153
+ action.clipboardText
19154
+ );
19155
+ if (!resolution.ok) return resolution;
19109
19156
  const result = await dispatchBulkEdit(
19110
19157
  {
19111
19158
  kind: DISPATCH_ACTION_KIND.BULK_EDIT,
19112
19159
  edits: action.activityIds.map((activityId) => ({
19113
19160
  activityId,
19114
19161
  column,
19115
- newValue: clipboard.raw
19162
+ newValue: resolution.newValue
19116
19163
  })),
19117
19164
  ...action.eventSource === void 0 ? {} : { eventSource: action.eventSource }
19118
19165
  },