@flyos/design-system 3.7.0 → 3.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flyos/design-system",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "FlyOS design system — shared components, directives, pipes, services, and models for Business App developers.",
5
5
  "keywords": [
6
6
  "flyos",
@@ -6333,6 +6333,22 @@ interface GanttRow {
6333
6333
  backgroundColor?: string;
6334
6334
  /** When true this row cannot be dragged/resized/linked even if the chart is editable. */
6335
6335
  readonly?: boolean;
6336
+ /**
6337
+ * Assert expandability before children are loaded (lazy trees). Defaults to "does any row
6338
+ * name me as parent?". Setting `true` renders a chevron for a row with no loaded children yet;
6339
+ * expanding it emits {@link FlyGanttComponent.rowExpand} instead of revealing nothing.
6340
+ */
6341
+ hasChildren?: boolean;
6342
+ /**
6343
+ * Optional baseline (plan-of-record) date, ISO `YYYY-MM-DD`. When both `baselineStart` and
6344
+ * `baselineEnd` are set and {@link FlyGanttComponent.showBaselines} is on, the chart renders a
6345
+ * thin muted underbar (or, for a `milestone` row, a hollow diamond) at these dates beneath the
6346
+ * current bar — the generic "baseline vs current" comparison. App-agnostic on purpose: this is
6347
+ * NOT "planned" or "original" in any one app's vocabulary, just a second pair of dates.
6348
+ */
6349
+ baselineStart?: string | null;
6350
+ /** Paired with {@link baselineStart}; both must be present for the underbar to render. */
6351
+ baselineEnd?: string | null;
6336
6352
  }
6337
6353
  /**
6338
6354
  * The four MS-Project relationship kinds, named `<predecessor endpoint><successor endpoint>`:
@@ -6426,6 +6442,15 @@ interface GanttTick {
6426
6442
  /** Stable key for `@for` tracking. */
6427
6443
  key: string;
6428
6444
  }
6445
+ /**
6446
+ * A logical (pre-RTL-mirror) horizontal window, in the same px space as {@link dateToX}. Ticks,
6447
+ * gridlines and weekend bands accept one to skip allocating a decoration cell wholly outside the
6448
+ * visible span — see {@link './gantt-window'} for how the render pane derives it from geometry.
6449
+ */
6450
+ interface GanttXWindow {
6451
+ x0: number;
6452
+ x1: number;
6453
+ }
6429
6454
 
6430
6455
  /** An in-flight link drag, in render space. `x0,y0` is the fixed origin; `x1,y1` follows the pointer. */
6431
6456
  interface GanttLinkGesture {
@@ -6437,6 +6462,63 @@ interface GanttLinkGesture {
6437
6462
  readonly y1: number;
6438
6463
  }
6439
6464
 
6465
+ /**
6466
+ * Virtualization ("scale") math + the viewport wiring for {@link FlyGanttComponent} — the A9
6467
+ * companion to `gantt-scale.ts`'s pure geometry.
6468
+ *
6469
+ * ## Why this is a separate file from `gantt-scale.ts`
6470
+ * The design memo (`.workflow/design/A9-multi-project-gantt.md` §4.5) puts all windowing math
6471
+ * directly in `gantt-scale.ts`, mirroring its existing pure-function seam. That file is, however,
6472
+ * already sitting at its LoC-ratchet ceiling (baselined at 542 lines; `+10%` caps it at 596) —
6473
+ * adding ~150 lines of window math there would fail `conventions-guard` outright. This module is
6474
+ * the sibling extraction the ratchet forces (same shape as `gantt-link-gestures.ts` and
6475
+ * `gantt-label-pane.ts`: a few pure functions plus one signal-based class composed by the host).
6476
+ * The functions below remain dependency-free — no Angular, no DOM — for the same reason
6477
+ * `gantt-scale.ts`'s are: jsdom lays nothing out, so pure functions are the only directly
6478
+ * testable form of this math (skill: design-system-gantt.md §8).
6479
+ *
6480
+ * ## The `viewBox`-pan trick this math drives
6481
+ * The body `<svg>` stays `position: absolute`, sized to only the rendered window
6482
+ * (`heightPx`/`topPx` below), with a `viewBox` y-offset equal to `topPx`. Because the SVG's
6483
+ * user-space origin follows the window, every geometry function in `gantt-scale.ts`
6484
+ * (`rowVms`, `mapX`, `projectBar`, `groupPath`, `diamondPoints`, link routing) keeps emitting
6485
+ * **absolute row-space coordinates** completely unaware that virtualization exists. Nothing here
6486
+ * subtracts `topPx` from a bar's `y` — that would be the wrong-turn the memo warns against.
6487
+ */
6488
+
6489
+ /** The slice of rows actually rendered, plus the geometry the body `<svg>` needs to pan to it. */
6490
+ interface RowWindow {
6491
+ /** First rendered row index (inclusive), 0-based into the full flattened list. */
6492
+ firstIndex: number;
6493
+ /** Last rendered row index (inclusive). `-1` when there are no rows at all. */
6494
+ lastIndex: number;
6495
+ /** `firstIndex * rowHeight` — the body `<svg>`'s `viewBox` y-offset AND its CSS `top`. */
6496
+ topPx: number;
6497
+ /** Rendered window's pixel height — the body `<svg>`'s own `height`/`viewBox` height. */
6498
+ heightPx: number;
6499
+ }
6500
+
6501
+ /**
6502
+ * Pure geometry for the baseline (plan-of-record) overlay — a NEW, app-agnostic capability, not
6503
+ * part of the A9 memo. `GanttRow.baselineStart`/`baselineEnd` are a second, optional pair of ISO
6504
+ * dates; when both are present and `showBaselines` is on, the chart renders a thin muted underbar
6505
+ * beneath a `bar`/`group` row, or a small hollow diamond at a `milestone` row's baseline date —
6506
+ * the generic "baseline vs current" comparison a consumer's own vocabulary later gives meaning to
6507
+ * (e.g. "planned vs actual"), which is exactly why nothing here is named after that vocabulary.
6508
+ *
6509
+ * Kept in its own pure, dependency-free module — same reasoning as `gantt-scale.ts`'s docstring —
6510
+ * and, in particular, routed through `dateToX`/`mapX`/`projectBar` so it mirrors under RTL for
6511
+ * free rather than ever computing its own x from scratch.
6512
+ */
6513
+
6514
+ /** Render-space geometry for one row's baseline marker, or `null` when there is nothing to draw. */
6515
+ interface GanttBaselineVm {
6516
+ /** `'bar'` — draw the underbar rect at `x`/`width`. `'milestone'` — draw the diamond at `x`. */
6517
+ shape: 'bar' | 'milestone';
6518
+ x: number;
6519
+ width: number;
6520
+ }
6521
+
6440
6522
  /** One fully-resolved, render-space row ready for the template. */
6441
6523
  interface GanttRowVm {
6442
6524
  flat: GanttFlatRow;
@@ -6458,6 +6540,8 @@ interface GanttRowVm {
6458
6540
  tint: string | null;
6459
6541
  ariaLabel: string;
6460
6542
  editable: boolean;
6543
+ /** Baseline overlay geometry, or `null` when the row has none (or baselines are hidden). */
6544
+ baseline: GanttBaselineVm | null;
6461
6545
  }
6462
6546
  interface LinkVm {
6463
6547
  key: string;
@@ -6493,9 +6577,12 @@ interface LinkVm {
6493
6577
  * placed among those controls matches them; the shell-chrome family (`--surface-card`, …) and
6494
6578
  * then a light-neutral literal are chained as fallbacks for consumers that map neither.
6495
6579
  *
6496
- * **Scale limit:** renders up to `maxRows` (default 500) visible rows; beyond that the list is
6497
- * capped and a footer notes the overflow. Heavy windowing/virtualization is intentionally
6498
- * deferred see the component skill.
6580
+ * **Scale (A9).** Row count is unbounded by default (`maxRows` now defaults to `0`); only the rows
6581
+ * in view (plus `overscanRows`) mount as DOM/SVG, panned via a `viewBox` y-offset on the body
6582
+ * `<svg>` so `./gantt-scale`'s geometry stays oblivious to windowing (`./gantt-window`'s doc).
6583
+ * Decorations window the same way; bars are never culled. `maxCanvasPx` coarsens the zoom
6584
+ * (`effectiveZoomChange`) past that width; `virtualized = false` restores the pre-A9 path; a
6585
+ * positive `maxRows` keeps the old hard cap (with its overflow footer) too.
6499
6586
  */
6500
6587
  declare class FlyGanttComponent {
6501
6588
  readonly i18n: I18nService;
@@ -6517,8 +6604,20 @@ declare class FlyGanttComponent {
6517
6604
  readonly resizableLabels: _angular_core.InputSignal<boolean>;
6518
6605
  /** Row band height in px. */
6519
6606
  readonly rowHeight: _angular_core.InputSignal<number>;
6520
- /** Hard cap on rendered rows; beyond it the list is truncated (see class doc). */
6607
+ /** Hard cap on the flattened row list; beyond it the list is truncated (see class doc).
6608
+ * `0` (the default) is uncapped — windowing (below) is what keeps an uncapped chart cheap. */
6521
6609
  readonly maxRows: _angular_core.InputSignal<number>;
6610
+ /** Controlled collapse state. `null` (the default) keeps the pre-A9 internal-state behaviour —
6611
+ * adopt controlled mode by echoing {@link collapsedIdsChange} back through this input. */
6612
+ readonly collapsedIds: _angular_core.InputSignal<ReadonlySet<string> | readonly string[] | null>;
6613
+ /** Rows rendered above and below the viewport window (virtualization overscan). */
6614
+ readonly overscanRows: _angular_core.InputSignal<number>;
6615
+ /** Canvas-width ceiling in px; a zoom that would exceed it is coarsened one step at a time. */
6616
+ readonly maxCanvasPx: _angular_core.InputSignal<number>;
6617
+ /** `false` restores the legacy render-everything path (no vertical/horizontal windowing). */
6618
+ readonly virtualized: _angular_core.InputSignal<boolean>;
6619
+ /** Render the baseline underbar/diamond for rows carrying `baselineStart`/`baselineEnd`. */
6620
+ readonly showBaselines: _angular_core.InputSignal<boolean>;
6522
6621
  /** Fires on a committed bar move / resize with the new ISO `start`/`end`. */
6523
6622
  readonly rowDatesChange: _angular_core.OutputEmitterRef<GanttRowDatesChange>;
6524
6623
  /**
@@ -6532,6 +6631,12 @@ declare class FlyGanttComponent {
6532
6631
  readonly rowDblClick: _angular_core.OutputEmitterRef<string>;
6533
6632
  /** Fires once per committed divider drag / keyboard resize with the new pane width in px. */
6534
6633
  readonly labelWidthChange: _angular_core.OutputEmitterRef<number>;
6634
+ /** Emitted whenever collapse state changes, in controlled or uncontrolled mode. */
6635
+ readonly collapsedIdsChange: _angular_core.OutputEmitterRef<readonly string[]>;
6636
+ /** A row with `hasChildren: true` but no loaded children was expanded — go fetch them. */
6637
+ readonly rowExpand: _angular_core.OutputEmitterRef<string>;
6638
+ /** The component coarsened the requested zoom to stay under `maxCanvasPx`. */
6639
+ readonly effectiveZoomChange: _angular_core.OutputEmitterRef<GanttZoom>;
6535
6640
  private readonly _uid;
6536
6641
  readonly markerId: string;
6537
6642
  readonly HEADER_H: number;
@@ -6540,8 +6645,10 @@ declare class FlyGanttComponent {
6540
6645
  readonly HANDLE_W = 8;
6541
6646
  readonly MIN_LABEL_W = 140;
6542
6647
  readonly MAX_LABEL_W = 720;
6648
+ readonly BASELINE_BAR_H: number;
6543
6649
  readonly selectedId: _angular_core.WritableSignal<string | null>;
6544
- private readonly _collapsed;
6650
+ /** Uncontrolled collapse state — the source of truth whenever the `collapsedIds` input is `null`. */
6651
+ private readonly _uncontrolledCollapsed;
6545
6652
  /** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
6546
6653
  private readonly _dragPreview;
6547
6654
  readonly dragTooltip: _angular_core.WritableSignal<{
@@ -6549,36 +6656,68 @@ declare class FlyGanttComponent {
6549
6656
  y: number;
6550
6657
  text: string;
6551
6658
  } | null>;
6552
- /** True while a divider drag is in flight (suppresses text selection host-wide). */
6553
- /** User-chosen pane width; `null` = still following the {@link labelWidth} input. */
6554
6659
  private readonly bodyRef;
6660
+ private readonly scrollElRef;
6661
+ private readonly canvasElRef;
6662
+ private readonly labelsElRef;
6555
6663
  readonly rtl: _angular_core.Signal<boolean>;
6556
- readonly pxPerDay: _angular_core.Signal<number>;
6557
- readonly domain: _angular_core.Signal<GanttDomain>;
6558
- readonly innerWidth: _angular_core.Signal<number>;
6559
- /** Label-pane width actually rendered — the user's dragged width, else the input. */
6560
- /** Full visible list after tree flatten/collapse. */
6664
+ /** Effective collapse set: the controlled input when the consumer supplied one, else internal state. */
6665
+ private readonly _collapsed;
6666
+ /** Full visible list after tree flatten/collapse (no `maxRows` cap applied yet). */
6561
6667
  private readonly _flat;
6562
- /** Capped list actually rendered. */
6668
+ /** `_flat`, capped at `maxRows` when it is positive — unchanged from the pre-A9 contract. */
6563
6669
  readonly visibleRows: _angular_core.Signal<GanttFlatRow[]>;
6564
6670
  readonly overflowCount: _angular_core.Signal<number>;
6671
+ /** TRUE total row count for `aria-rowcount` — independent of `maxRows` (pre-A9 defect §2.4/§6.2). */
6672
+ readonly totalRowCount: _angular_core.Signal<number>;
6673
+ /** Full spacer height — keeps the scrollbar honest; the `<svg>` itself only spans `svgHeight`. */
6565
6674
  readonly bodyHeight: _angular_core.Signal<number>;
6566
6675
  private readonly _resolvedDates;
6676
+ readonly domain: _angular_core.Signal<GanttDomain>;
6677
+ /** Zoom coarsened (if needed) to keep `domain span × pxPerDay` under `maxCanvasPx`. */
6678
+ private readonly _clampedScale;
6679
+ readonly effectiveZoom: _angular_core.Signal<GanttZoom>;
6680
+ readonly pxPerDay: _angular_core.Signal<number>;
6681
+ readonly innerWidth: _angular_core.Signal<number>;
6682
+ private readonly _viewport;
6683
+ /** {@link visibleRows} indices `[firstIndex, lastIndex]` currently rendered. */
6684
+ readonly rowWindow: _angular_core.Signal<RowWindow>;
6685
+ /** Logical horizontal window for decorations, or `null` when non-virtualized. */
6686
+ readonly xWindow: _angular_core.Signal<GanttXWindow | null>;
6687
+ /** Body `<svg>`'s CSS `top` / `viewBox` y-offset — the windowing trick (`./gantt-window`). */
6688
+ readonly svgTop: _angular_core.Signal<number>;
6689
+ /** Body `<svg>`'s own height (window only, not the full spacer). */
6690
+ readonly svgHeight: _angular_core.Signal<number>;
6691
+ private _lastEmittedZoom;
6692
+ constructor();
6567
6693
  readonly weekendBands: _angular_core.Signal<GanttTick[]>;
6568
6694
  readonly lowerTicks: _angular_core.Signal<GanttTick[]>;
6569
6695
  readonly upperTicks: _angular_core.Signal<GanttTick[]>;
6570
6696
  /** Render-space today x, or `null` when today is outside the domain (or disabled). */
6571
6697
  readonly todayX: _angular_core.Signal<number | null>;
6698
+ /** Id→absolute-index into {@link visibleRows}, O(N) on data/collapse only (memo §4.4). */
6699
+ private readonly _indexById;
6700
+ /** Only the windowed slice of {@link visibleRows} (memo §4.4). `index` stays the row's ABSOLUTE
6701
+ * position — `y = index × rowHeight` — the `viewBox` pan, not this math, hides the rest. */
6572
6702
  readonly rowVms: _angular_core.Signal<GanttRowVm[]>;
6573
6703
  /** Only the rows carrying a tint, so the band pass does not emit an empty rect per row. */
6574
6704
  readonly tintedRows: _angular_core.Signal<GanttRowVm[]>;
6575
- /** Dependency arrows between currently-rendered rows (missing/collapsed endpoints skipped). */
6705
+ /** Dependency arrows whose y-span intersects the row window — O(E), not O(N). Endpoints are
6706
+ * resolved via {@link _vmForFlat} directly (not off `rowVms()`) since an edge can cross the
6707
+ * window with one endpoint outside it — the memo §4.3 rule-2 fix for the pre-A9 "both
6708
+ * endpoints rendered" predicate, which would erase exactly those crossing arrows. */
6576
6709
  readonly linkVms: _angular_core.Signal<LinkVm[]>;
6710
+ /** Full geometry for one row — shared by `rowVms` and `linkVms` (endpoints outside the window). */
6711
+ private _vmForFlat;
6577
6712
  readonly barHeight: _angular_core.Signal<number>;
6578
6713
  barTop(vm: GanttRowVm): number;
6714
+ /** Baseline underbar's top edge — a thin strip directly beneath the main bar. */
6715
+ baselineBarTop(vm: GanttRowVm): number;
6579
6716
  private milestoneR;
6580
6717
  /** Diamond polygon points for a milestone marker. */
6581
6718
  diamondPoints(vm: GanttRowVm): string;
6719
+ /** Hollow outline diamond for a milestone row's baseline date (its own x, same row's y). */
6720
+ baselineDiamondPoints(vm: GanttRowVm): string;
6582
6721
  /**
6583
6722
  * Link connector x for a milestone. A diamond has no width, so the connector is nudged clear
6584
6723
  * of the marker in the forward-in-time direction (which flips under RTL).
@@ -6604,7 +6743,15 @@ declare class FlyGanttComponent {
6604
6743
  onLabelResizeKeydown(ev: Event): void;
6605
6744
  resetLabelWidth(): void;
6606
6745
  isCollapsed(id: string): boolean;
6746
+ /** Any row in the current data naming `id` as its parent — i.e. children are actually loaded. */
6747
+ private _hasLoadedChildren;
6607
6748
  toggleCollapse(id: string, ev?: Event): void;
6749
+ /** Expand every group (clears all collapse state). */
6750
+ expandAll(): void;
6751
+ /** Collapse every group that has (or asserts) children. */
6752
+ collapseAll(): void;
6753
+ /** Scroll a row into the rendered window and pin it there (deep-link / programmatic focus). */
6754
+ scrollToRow(id: string): void;
6608
6755
  /**
6609
6756
  * Enter/Space on a focused label row selects it (mirrors the pointer click).
6610
6757
  * Typed `Event` (not `KeyboardEvent`) because Angular's strict template checker
@@ -6643,7 +6790,7 @@ declare class FlyGanttComponent {
6643
6790
  private _mapTicks;
6644
6791
  private _ariaFor;
6645
6792
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FlyGanttComponent, never>;
6646
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyGanttComponent, "fly-gantt", never, { "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "dependencies": { "alias": "dependencies"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "showToday": { "alias": "showToday"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "resizableLabels": { "alias": "resizableLabels"; "required": false; "isSignal": true; }; "rowHeight": { "alias": "rowHeight"; "required": false; "isSignal": true; }; "maxRows": { "alias": "maxRows"; "required": false; "isSignal": true; }; }, { "rowDatesChange": "rowDatesChange"; "dependencyCreate": "dependencyCreate"; "dependencyDelete": "dependencyDelete"; "rowClick": "rowClick"; "rowDblClick": "rowDblClick"; "labelWidthChange": "labelWidthChange"; }, never, never, true, never>;
6793
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FlyGanttComponent, "fly-gantt", never, { "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "dependencies": { "alias": "dependencies"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "showToday": { "alias": "showToday"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "resizableLabels": { "alias": "resizableLabels"; "required": false; "isSignal": true; }; "rowHeight": { "alias": "rowHeight"; "required": false; "isSignal": true; }; "maxRows": { "alias": "maxRows"; "required": false; "isSignal": true; }; "collapsedIds": { "alias": "collapsedIds"; "required": false; "isSignal": true; }; "overscanRows": { "alias": "overscanRows"; "required": false; "isSignal": true; }; "maxCanvasPx": { "alias": "maxCanvasPx"; "required": false; "isSignal": true; }; "virtualized": { "alias": "virtualized"; "required": false; "isSignal": true; }; "showBaselines": { "alias": "showBaselines"; "required": false; "isSignal": true; }; }, { "rowDatesChange": "rowDatesChange"; "dependencyCreate": "dependencyCreate"; "dependencyDelete": "dependencyDelete"; "rowClick": "rowClick"; "rowDblClick": "rowDblClick"; "labelWidthChange": "labelWidthChange"; "collapsedIdsChange": "collapsedIdsChange"; "rowExpand": "rowExpand"; "effectiveZoomChange": "effectiveZoomChange"; }, never, never, true, never>;
6647
6794
  }
6648
6795
 
6649
6796
  /** The 14 rendered field kinds. Drives the control the renderer picks. */