@outbuild-company/schedule-core 1.35.3 → 1.36.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
@@ -685,9 +685,9 @@ interface GroupLevelWindow {
685
685
  readonly depth: number;
686
686
  }
687
687
  /**
688
- * One grouping level below the primary one. Nesting more than a second level is
689
- * expressed through the level window on a hierarchical attribute, not by chaining
690
- * these.
688
+ * One grouping level: the attribute to band by, the window of its outline levels
689
+ * that becomes bands, and the band order. The primary level lives on `GroupState`
690
+ * itself; every further level is chained through `GroupState.nested`.
691
691
  */
692
692
  interface GroupCriterion extends GroupLevelWindow {
693
693
  readonly attributeKey: string;
@@ -704,11 +704,15 @@ interface GroupCriterion extends GroupLevelWindow {
704
704
  type WbsDepth = number | null;
705
705
  /** The depth that leaves no WBS row on screen. */
706
706
  declare const FLAT_WBS_DEPTH = 0;
707
- /** Runtime grouping configuration active on a schedule. */
707
+ /**
708
+ * Runtime grouping configuration active on a schedule. The interface itself is
709
+ * the primary criterion; `nested` holds the criteria chained below it, in order,
710
+ * so a schedule can band by several attributes at once.
711
+ */
708
712
  interface GroupState extends GroupCriterion {
709
713
  readonly includeWbs: boolean;
710
714
  readonly collapsedIds: ReadonlySet<string>;
711
- readonly secondary: GroupCriterion | null;
715
+ readonly nested: ReadonlyArray<GroupCriterion>;
712
716
  }
713
717
  /**
714
718
  * JSON-friendly criterion. `startLevel` is optional so views persisted before
@@ -721,11 +725,17 @@ interface GroupCriterionInput {
721
725
  readonly depth: number;
722
726
  readonly direction: OrderDirection;
723
727
  }
724
- /** JSON-friendly grouping command accepted by `group-set`. */
728
+ /**
729
+ * JSON-friendly grouping command accepted by `group-set`. `nested` carries the
730
+ * levels chained below the primary, in order; `secondary` is the legacy
731
+ * single-nested-level field kept so views persisted before chaining still
732
+ * deserialize, and it is read only when `nested` is absent.
733
+ */
725
734
  interface GroupStateInput extends GroupCriterionInput {
726
735
  readonly includeWbs: boolean;
727
736
  readonly collapsedIds: ReadonlyArray<string>;
728
737
  readonly secondary?: GroupCriterionInput | null;
738
+ readonly nested?: ReadonlyArray<GroupCriterionInput>;
729
739
  }
730
740
  /** A single display row: either a synthetic group band or a real activity. */
731
741
  type VisualRow = {
@@ -743,7 +753,7 @@ type VisualRow = {
743
753
  declare function buildGroupRowId(attributeKey: string, valueId: string): string;
744
754
  /**
745
755
  * Extends a band id with a nested level. The parent path has to stay in the id
746
- * because the same secondary value shows up under several primary bands, and
756
+ * because the same nested value shows up under several parent bands, and
747
757
  * consumers key rows (and persisted collapse state) by this id.
748
758
  */
749
759
  declare function appendGroupRowId(parentRowId: string, attributeKey: string, valueId: string): string;
package/dist/index.d.ts CHANGED
@@ -685,9 +685,9 @@ interface GroupLevelWindow {
685
685
  readonly depth: number;
686
686
  }
687
687
  /**
688
- * One grouping level below the primary one. Nesting more than a second level is
689
- * expressed through the level window on a hierarchical attribute, not by chaining
690
- * these.
688
+ * One grouping level: the attribute to band by, the window of its outline levels
689
+ * that becomes bands, and the band order. The primary level lives on `GroupState`
690
+ * itself; every further level is chained through `GroupState.nested`.
691
691
  */
692
692
  interface GroupCriterion extends GroupLevelWindow {
693
693
  readonly attributeKey: string;
@@ -704,11 +704,15 @@ interface GroupCriterion extends GroupLevelWindow {
704
704
  type WbsDepth = number | null;
705
705
  /** The depth that leaves no WBS row on screen. */
706
706
  declare const FLAT_WBS_DEPTH = 0;
707
- /** Runtime grouping configuration active on a schedule. */
707
+ /**
708
+ * Runtime grouping configuration active on a schedule. The interface itself is
709
+ * the primary criterion; `nested` holds the criteria chained below it, in order,
710
+ * so a schedule can band by several attributes at once.
711
+ */
708
712
  interface GroupState extends GroupCriterion {
709
713
  readonly includeWbs: boolean;
710
714
  readonly collapsedIds: ReadonlySet<string>;
711
- readonly secondary: GroupCriterion | null;
715
+ readonly nested: ReadonlyArray<GroupCriterion>;
712
716
  }
713
717
  /**
714
718
  * JSON-friendly criterion. `startLevel` is optional so views persisted before
@@ -721,11 +725,17 @@ interface GroupCriterionInput {
721
725
  readonly depth: number;
722
726
  readonly direction: OrderDirection;
723
727
  }
724
- /** JSON-friendly grouping command accepted by `group-set`. */
728
+ /**
729
+ * JSON-friendly grouping command accepted by `group-set`. `nested` carries the
730
+ * levels chained below the primary, in order; `secondary` is the legacy
731
+ * single-nested-level field kept so views persisted before chaining still
732
+ * deserialize, and it is read only when `nested` is absent.
733
+ */
725
734
  interface GroupStateInput extends GroupCriterionInput {
726
735
  readonly includeWbs: boolean;
727
736
  readonly collapsedIds: ReadonlyArray<string>;
728
737
  readonly secondary?: GroupCriterionInput | null;
738
+ readonly nested?: ReadonlyArray<GroupCriterionInput>;
729
739
  }
730
740
  /** A single display row: either a synthetic group band or a real activity. */
731
741
  type VisualRow = {
@@ -743,7 +753,7 @@ type VisualRow = {
743
753
  declare function buildGroupRowId(attributeKey: string, valueId: string): string;
744
754
  /**
745
755
  * Extends a band id with a nested level. The parent path has to stay in the id
746
- * because the same secondary value shows up under several primary bands, and
756
+ * because the same nested value shows up under several parent bands, and
747
757
  * consumers key rows (and persisted collapse state) by this id.
748
758
  */
749
759
  declare function appendGroupRowId(parentRowId: string, attributeKey: string, valueId: string): string;
package/dist/index.js CHANGED
@@ -2304,24 +2304,37 @@ function toLevelWindow(input) {
2304
2304
  return { startLevel, depth: input.depth };
2305
2305
  }
2306
2306
  var INVALID_CRITERION = /* @__PURE__ */ Symbol("invalid-group-criterion");
2307
- function toSecondaryCriterion(input) {
2308
- const secondary = input.secondary;
2309
- if (secondary === void 0 || secondary === null) return null;
2310
- if (typeof secondary !== "object") return INVALID_CRITERION;
2311
- if (typeof secondary.attributeKey !== "string" || secondary.attributeKey.length === 0) {
2307
+ var MAX_NESTED_GROUP_CRITERIA = 3;
2308
+ function toNestedCriterion(input, usedKeys) {
2309
+ if (typeof input !== "object" || input === null) return INVALID_CRITERION;
2310
+ if (typeof input.attributeKey !== "string" || input.attributeKey.length === 0) {
2312
2311
  return INVALID_CRITERION;
2313
2312
  }
2314
- if (secondary.attributeKey === input.attributeKey) return INVALID_CRITERION;
2315
- const window2 = toLevelWindow(secondary);
2313
+ if (usedKeys.has(input.attributeKey)) return INVALID_CRITERION;
2314
+ const window2 = toLevelWindow(input);
2316
2315
  if (window2 === null) return INVALID_CRITERION;
2317
- if (!isValidDirection(secondary.direction)) return INVALID_CRITERION;
2316
+ if (!isValidDirection(input.direction)) return INVALID_CRITERION;
2318
2317
  return {
2319
- attributeKey: secondary.attributeKey,
2318
+ attributeKey: input.attributeKey,
2320
2319
  startLevel: window2.startLevel,
2321
2320
  depth: window2.depth,
2322
- direction: secondary.direction
2321
+ direction: input.direction
2323
2322
  };
2324
2323
  }
2324
+ function toNestedCriteria(input, primaryKey) {
2325
+ const rawNested = input.nested ?? (input.secondary == null ? [] : [input.secondary]);
2326
+ if (!Array.isArray(rawNested)) return INVALID_CRITERION;
2327
+ if (rawNested.length > MAX_NESTED_GROUP_CRITERIA) return INVALID_CRITERION;
2328
+ const usedKeys = /* @__PURE__ */ new Set([primaryKey]);
2329
+ const nested = [];
2330
+ for (const rawCriterion of rawNested) {
2331
+ const criterion = toNestedCriterion(rawCriterion, usedKeys);
2332
+ if (criterion === INVALID_CRITERION) return INVALID_CRITERION;
2333
+ usedKeys.add(criterion.attributeKey);
2334
+ nested.push(criterion);
2335
+ }
2336
+ return nested;
2337
+ }
2325
2338
  function toGroupState(input) {
2326
2339
  if (typeof input.attributeKey !== "string" || input.attributeKey.length === 0) {
2327
2340
  return null;
@@ -2335,8 +2348,8 @@ function toGroupState(input) {
2335
2348
  if (!Array.isArray(input.collapsedIds) || input.collapsedIds.some((id) => typeof id !== "string")) {
2336
2349
  return null;
2337
2350
  }
2338
- const secondary = toSecondaryCriterion(input);
2339
- if (secondary === INVALID_CRITERION) return null;
2351
+ const nested = toNestedCriteria(input, input.attributeKey);
2352
+ if (nested === INVALID_CRITERION) return null;
2340
2353
  return {
2341
2354
  attributeKey: input.attributeKey,
2342
2355
  startLevel: window2.startLevel,
@@ -2344,7 +2357,7 @@ function toGroupState(input) {
2344
2357
  direction: input.direction,
2345
2358
  includeWbs: input.includeWbs,
2346
2359
  collapsedIds: new Set(input.collapsedIds),
2347
- secondary
2360
+ nested
2348
2361
  };
2349
2362
  }
2350
2363
  function emptyViewChanges(action) {
@@ -25886,7 +25899,7 @@ function emitActivities(node, context) {
25886
25899
  });
25887
25900
  }
25888
25901
  }
25889
- function emitNode(node, childDirection, allowSecondary, context) {
25902
+ function emitNode(node, childDirection, nextNestedIndex, context) {
25890
25903
  const { group, rows } = context;
25891
25904
  rows.push({
25892
25905
  kind: VISUAL_ROW_KIND.GROUP,
@@ -25898,20 +25911,24 @@ function emitNode(node, childDirection, allowSecondary, context) {
25898
25911
  });
25899
25912
  if (group.collapsedIds.has(node.id)) return;
25900
25913
  for (const child of sortedChildNodes(node, childDirection)) {
25901
- emitNode(child, childDirection, allowSecondary, context);
25914
+ emitNode(child, childDirection, nextNestedIndex, context);
25902
25915
  }
25903
25916
  if (node.activities.length === 0) return;
25904
- const secondary = group.secondary;
25905
- if (allowSecondary && secondary != null && context.secondaryCatalog != null) {
25906
- const secondaryRoot = buildGroupTree(
25917
+ const nestedCriterion = group.nested[nextNestedIndex];
25918
+ const nestedCatalog = context.nestedCatalogs[nextNestedIndex] ?? null;
25919
+ if (nestedCriterion != null && nestedCatalog != null) {
25920
+ const nestedRoot = buildGroupTree(
25907
25921
  node.activities,
25908
- secondary,
25909
- context.secondaryCatalog,
25922
+ nestedCriterion,
25923
+ nestedCatalog,
25910
25924
  createNode(node.id, "", "", null, node.level, 0),
25911
25925
  true
25912
25926
  );
25913
- for (const child of sortedChildNodes(secondaryRoot, secondary.direction)) {
25914
- emitNode(child, secondary.direction, false, context);
25927
+ for (const child of sortedChildNodes(
25928
+ nestedRoot,
25929
+ nestedCriterion.direction
25930
+ )) {
25931
+ emitNode(child, nestedCriterion.direction, nextNestedIndex + 1, context);
25915
25932
  }
25916
25933
  return;
25917
25934
  }
@@ -25954,11 +25971,11 @@ function projectVisualRows(input) {
25954
25971
  comparator: input.comparator,
25955
25972
  orderMode: input.orderMode,
25956
25973
  wbsDepth: input.wbsDepth,
25957
- secondaryCatalog: input.secondaryCatalog,
25974
+ nestedCatalogs: input.nestedCatalogs,
25958
25975
  rows
25959
25976
  };
25960
25977
  for (const child of sortedChildNodes(root, group.direction)) {
25961
- emitNode(child, group.direction, true, context);
25978
+ emitNode(child, group.direction, 0, context);
25962
25979
  }
25963
25980
  return rows;
25964
25981
  }
@@ -26364,7 +26381,9 @@ var ScheduleState = class _ScheduleState {
26364
26381
  if (this._cache.visualRows) return this._cache.visualRows;
26365
26382
  const group = this._viewAttributes.getGroup();
26366
26383
  const catalog = group === null ? null : this._viewAttributes.getCatalog(group.attributeKey);
26367
- const secondaryCatalog = group?.secondary == null ? null : this._viewAttributes.getCatalog(group.secondary.attributeKey);
26384
+ const nestedCatalogs = group === null ? [] : group.nested.map(
26385
+ (criterion) => this._viewAttributes.getCatalog(criterion.attributeKey)
26386
+ );
26368
26387
  const activitiesInVisualOrder = [];
26369
26388
  for (const activityId of this.getVisualOrderIds()) {
26370
26389
  const activity = this.getActivity(activityId);
@@ -26377,7 +26396,7 @@ var ScheduleState = class _ScheduleState {
26377
26396
  hasChildren: (activityId) => this.hasChildren(activityId),
26378
26397
  group,
26379
26398
  catalog,
26380
- secondaryCatalog,
26399
+ nestedCatalogs,
26381
26400
  comparator: this.getOrderComparator(),
26382
26401
  orderMode: this.getOrderMode(),
26383
26402
  wbsDepth: this._viewAttributes.getWbsDepth()