@nowline/layout 0.8.6 → 0.10.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/axis-columns.d.ts +70 -0
- package/dist/axis-columns.d.ts.map +1 -0
- package/dist/axis-columns.js +104 -0
- package/dist/axis-columns.js.map +1 -0
- package/dist/calendar-resolver.d.ts +27 -0
- package/dist/calendar-resolver.d.ts.map +1 -0
- package/dist/calendar-resolver.js +132 -0
- package/dist/calendar-resolver.js.map +1 -0
- package/dist/calendar.d.ts +0 -19
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +4 -44
- package/dist/calendar.js.map +1 -1
- package/dist/canvas-width.d.ts +23 -0
- package/dist/canvas-width.d.ts.map +1 -0
- package/dist/canvas-width.js +26 -0
- package/dist/canvas-width.js.map +1 -0
- package/dist/capacity.d.ts +28 -0
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +64 -29
- package/dist/capacity.js.map +1 -1
- package/dist/chart-translation.d.ts +22 -0
- package/dist/chart-translation.d.ts.map +1 -0
- package/dist/chart-translation.js +83 -0
- package/dist/chart-translation.js.map +1 -0
- package/dist/container-header-geometry.d.ts +64 -0
- package/dist/container-header-geometry.d.ts.map +1 -0
- package/dist/container-header-geometry.js +89 -0
- package/dist/container-header-geometry.js.map +1 -0
- package/dist/dsl-utils.d.ts +3 -4
- package/dist/dsl-utils.d.ts.map +1 -1
- package/dist/dsl-utils.js +8 -25
- package/dist/dsl-utils.js.map +1 -1
- package/dist/edge-routing.d.ts +69 -78
- package/dist/edge-routing.d.ts.map +1 -1
- package/dist/edge-routing.js +271 -401
- package/dist/edge-routing.js.map +1 -1
- package/dist/frame-tab-geometry.d.ts +24 -14
- package/dist/frame-tab-geometry.d.ts.map +1 -1
- package/dist/frame-tab-geometry.js +69 -23
- package/dist/frame-tab-geometry.js.map +1 -1
- package/dist/header-card-geometry.d.ts +12 -0
- package/dist/header-card-geometry.d.ts.map +1 -1
- package/dist/header-card-geometry.js +14 -2
- package/dist/header-card-geometry.js.map +1 -1
- package/dist/header-logo.d.ts +28 -0
- package/dist/header-logo.d.ts.map +1 -0
- package/dist/header-logo.js +51 -0
- package/dist/header-logo.js.map +1 -0
- package/dist/i18n.d.ts +11 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +11 -1
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/inline-date-pin-geometry.d.ts +19 -11
- package/dist/inline-date-pin-geometry.d.ts.map +1 -1
- package/dist/inline-date-pin-geometry.js +45 -46
- package/dist/inline-date-pin-geometry.js.map +1 -1
- package/dist/item-bar-geometry.d.ts +192 -4
- package/dist/item-bar-geometry.d.ts.map +1 -1
- package/dist/item-bar-geometry.js +258 -5
- package/dist/item-bar-geometry.js.map +1 -1
- package/dist/item-port-geometry.d.ts +13 -0
- package/dist/item-port-geometry.d.ts.map +1 -0
- package/dist/item-port-geometry.js +63 -0
- package/dist/item-port-geometry.js.map +1 -0
- package/dist/layout-context.d.ts +115 -51
- package/dist/layout-context.d.ts.map +1 -1
- package/dist/layout-context.js +96 -5
- package/dist/layout-context.js.map +1 -1
- package/dist/layout-insights.d.ts.map +1 -1
- package/dist/layout-insights.js +73 -1
- package/dist/layout-insights.js.map +1 -1
- package/dist/layout.d.ts +35 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +18 -1315
- package/dist/layout.js.map +1 -1
- package/dist/nodes/anchor-node.d.ts.map +1 -1
- package/dist/nodes/anchor-node.js +11 -4
- package/dist/nodes/anchor-node.js.map +1 -1
- package/dist/nodes/content-extent.d.ts +24 -0
- package/dist/nodes/content-extent.d.ts.map +1 -0
- package/dist/nodes/content-extent.js +100 -0
- package/dist/nodes/content-extent.js.map +1 -0
- package/dist/nodes/dependency-edges.d.ts +6 -0
- package/dist/nodes/dependency-edges.d.ts.map +1 -0
- package/dist/nodes/dependency-edges.js +148 -0
- package/dist/nodes/dependency-edges.js.map +1 -0
- package/dist/nodes/footnote-node.d.ts +24 -3
- package/dist/nodes/footnote-node.d.ts.map +1 -1
- package/dist/nodes/footnote-node.js +129 -25
- package/dist/nodes/footnote-node.js.map +1 -1
- package/dist/nodes/group-node.d.ts +10 -21
- package/dist/nodes/group-node.d.ts.map +1 -1
- package/dist/nodes/group-node.js +79 -152
- package/dist/nodes/group-node.js.map +1 -1
- package/dist/nodes/header-node.d.ts +56 -0
- package/dist/nodes/header-node.d.ts.map +1 -0
- package/dist/nodes/header-node.js +163 -0
- package/dist/nodes/header-node.js.map +1 -0
- package/dist/nodes/include-node.d.ts +22 -12
- package/dist/nodes/include-node.d.ts.map +1 -1
- package/dist/nodes/include-node.js +95 -62
- package/dist/nodes/include-node.js.map +1 -1
- package/dist/nodes/item-measure.d.ts +135 -0
- package/dist/nodes/item-measure.d.ts.map +1 -0
- package/dist/nodes/item-measure.js +514 -0
- package/dist/nodes/item-measure.js.map +1 -0
- package/dist/nodes/item-node.d.ts +162 -9
- package/dist/nodes/item-node.d.ts.map +1 -1
- package/dist/nodes/item-node.js +256 -69
- package/dist/nodes/item-node.js.map +1 -1
- package/dist/nodes/marker-band.d.ts +118 -0
- package/dist/nodes/marker-band.d.ts.map +1 -0
- package/dist/nodes/marker-band.js +303 -0
- package/dist/nodes/marker-band.js.map +1 -0
- package/dist/nodes/marker-geometry.js +1 -1
- package/dist/nodes/marker-geometry.js.map +1 -1
- package/dist/nodes/milestone-node.d.ts +112 -15
- package/dist/nodes/milestone-node.d.ts.map +1 -1
- package/dist/nodes/milestone-node.js +246 -95
- package/dist/nodes/milestone-node.js.map +1 -1
- package/dist/nodes/nowline-node.d.ts +8 -0
- package/dist/nodes/nowline-node.d.ts.map +1 -0
- package/dist/nodes/nowline-node.js +74 -0
- package/dist/nodes/nowline-node.js.map +1 -0
- package/dist/nodes/parallel-node.d.ts +7 -12
- package/dist/nodes/parallel-node.d.ts.map +1 -1
- package/dist/nodes/parallel-node.js +66 -30
- package/dist/nodes/parallel-node.js.map +1 -1
- package/dist/nodes/region-markers.d.ts +41 -0
- package/dist/nodes/region-markers.d.ts.map +1 -0
- package/dist/nodes/region-markers.js +130 -0
- package/dist/nodes/region-markers.js.map +1 -0
- package/dist/nodes/roadmap-node.d.ts +9 -72
- package/dist/nodes/roadmap-node.d.ts.map +1 -1
- package/dist/nodes/roadmap-node.js +38 -788
- package/dist/nodes/roadmap-node.js.map +1 -1
- package/dist/nodes/row-track.d.ts +33 -0
- package/dist/nodes/row-track.d.ts.map +1 -0
- package/dist/nodes/row-track.js +170 -0
- package/dist/nodes/row-track.js.map +1 -0
- package/dist/nodes/sequence-item.d.ts +25 -0
- package/dist/nodes/sequence-item.d.ts.map +1 -0
- package/dist/nodes/sequence-item.js +396 -0
- package/dist/nodes/sequence-item.js.map +1 -0
- package/dist/nodes/sequence-one.d.ts +5 -0
- package/dist/nodes/sequence-one.d.ts.map +1 -0
- package/dist/nodes/sequence-one.js +26 -0
- package/dist/nodes/sequence-one.js.map +1 -0
- package/dist/nodes/swimlane-node.d.ts +5 -17
- package/dist/nodes/swimlane-node.d.ts.map +1 -1
- package/dist/nodes/swimlane-node.js +62 -145
- package/dist/nodes/swimlane-node.js.map +1 -1
- package/dist/nodes/wave-node.d.ts +52 -0
- package/dist/nodes/wave-node.d.ts.map +1 -0
- package/dist/nodes/wave-node.js +483 -0
- package/dist/nodes/wave-node.js.map +1 -0
- package/dist/non-working-display.d.ts +14 -0
- package/dist/non-working-display.d.ts.map +1 -0
- package/dist/non-working-display.js +28 -0
- package/dist/non-working-display.js.map +1 -0
- package/dist/phases/date-window.d.ts +48 -0
- package/dist/phases/date-window.d.ts.map +1 -0
- package/dist/phases/date-window.js +130 -0
- package/dist/phases/date-window.js.map +1 -0
- package/dist/phases/finalize.d.ts +198 -0
- package/dist/phases/finalize.d.ts.map +1 -0
- package/dist/phases/finalize.js +1058 -0
- package/dist/phases/finalize.js.map +1 -0
- package/dist/phases/measure.d.ts +77 -0
- package/dist/phases/measure.d.ts.map +1 -0
- package/dist/phases/measure.js +222 -0
- package/dist/phases/measure.js.map +1 -0
- package/dist/phases/place.d.ts +28 -0
- package/dist/phases/place.d.ts.map +1 -0
- package/dist/phases/place.js +225 -0
- package/dist/phases/place.js.map +1 -0
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +23 -3
- package/dist/renderable.js.map +1 -1
- package/dist/resolve-today.d.ts +11 -1
- package/dist/resolve-today.d.ts.map +1 -1
- package/dist/resolve-today.js +26 -12
- package/dist/resolve-today.js.map +1 -1
- package/dist/row-packer.d.ts +42 -16
- package/dist/row-packer.d.ts.map +1 -1
- package/dist/row-packer.js +51 -78
- package/dist/row-packer.js.map +1 -1
- package/dist/schedule.d.ts +58 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +280 -106
- package/dist/schedule.js.map +1 -1
- package/dist/start-rules.d.ts +27 -0
- package/dist/start-rules.d.ts.map +1 -0
- package/dist/start-rules.js +91 -0
- package/dist/start-rules.js.map +1 -0
- package/dist/start-terms.d.ts +23 -0
- package/dist/start-terms.d.ts.map +1 -0
- package/dist/start-terms.js +27 -0
- package/dist/start-terms.js.map +1 -0
- package/dist/status-dot.d.ts +46 -0
- package/dist/status-dot.d.ts.map +1 -0
- package/dist/status-dot.js +78 -0
- package/dist/status-dot.js.map +1 -0
- package/dist/style-resolution.d.ts.map +1 -1
- package/dist/style-resolution.js +23 -43
- package/dist/style-resolution.js.map +1 -1
- package/dist/text-measure.d.ts +28 -0
- package/dist/text-measure.d.ts.map +1 -0
- package/dist/text-measure.js +70 -0
- package/dist/text-measure.js.map +1 -0
- package/dist/themes/contrast.d.ts +4 -0
- package/dist/themes/contrast.d.ts.map +1 -0
- package/dist/themes/contrast.js +32 -0
- package/dist/themes/contrast.js.map +1 -0
- package/dist/themes/dark.d.ts.map +1 -1
- package/dist/themes/dark.js +28 -1
- package/dist/themes/dark.js.map +1 -1
- package/dist/themes/grayscale.d.ts.map +1 -1
- package/dist/themes/grayscale.js +28 -1
- package/dist/themes/grayscale.js.map +1 -1
- package/dist/themes/index.d.ts +4 -11
- package/dist/themes/index.d.ts.map +1 -1
- package/dist/themes/index.js +6 -4
- package/dist/themes/index.js.map +1 -1
- package/dist/themes/light.d.ts.map +1 -1
- package/dist/themes/light.js +28 -1
- package/dist/themes/light.js.map +1 -1
- package/dist/themes/shape.d.ts +31 -0
- package/dist/themes/shape.d.ts.map +1 -1
- package/dist/themes/shared.d.ts +44 -50
- package/dist/themes/shared.d.ts.map +1 -1
- package/dist/themes/shared.js +79 -83
- package/dist/themes/shared.js.map +1 -1
- package/dist/time-scale.d.ts +42 -6
- package/dist/time-scale.d.ts.map +1 -1
- package/dist/time-scale.js +109 -17
- package/dist/time-scale.js.map +1 -1
- package/dist/types.d.ts +528 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/view-preset.d.ts +61 -6
- package/dist/view-preset.d.ts.map +1 -1
- package/dist/view-preset.js +439 -24
- package/dist/view-preset.js.map +1 -1
- package/dist/wave-barrier.d.ts +107 -0
- package/dist/wave-barrier.d.ts.map +1 -0
- package/dist/wave-barrier.js +174 -0
- package/dist/wave-barrier.js.map +1 -0
- package/dist/wave-hatch.d.ts +4 -0
- package/dist/wave-hatch.d.ts.map +1 -0
- package/dist/wave-hatch.js +21 -0
- package/dist/wave-hatch.js.map +1 -0
- package/dist/wave-layout.d.ts +99 -0
- package/dist/wave-layout.d.ts.map +1 -0
- package/dist/wave-layout.js +130 -0
- package/dist/wave-layout.js.map +1 -0
- package/dist/working-calendar.d.ts +128 -4
- package/dist/working-calendar.d.ts.map +1 -1
- package/dist/working-calendar.js +725 -51
- package/dist/working-calendar.js.map +1 -1
- package/dist/working-span.d.ts +11 -0
- package/dist/working-span.d.ts.map +1 -0
- package/dist/working-span.js +18 -0
- package/dist/working-span.js.map +1 -0
- package/package.json +4 -4
- package/src/axis-columns.ts +140 -0
- package/src/calendar-resolver.ts +161 -0
- package/src/calendar.ts +4 -45
- package/src/canvas-width.ts +36 -0
- package/src/capacity.ts +71 -30
- package/src/chart-translation.ts +88 -0
- package/src/container-header-geometry.ts +116 -0
- package/src/dsl-utils.ts +8 -26
- package/src/edge-routing.ts +347 -491
- package/src/frame-tab-geometry.ts +78 -25
- package/src/header-card-geometry.ts +17 -2
- package/src/header-logo.ts +67 -0
- package/src/i18n.ts +22 -1
- package/src/index.ts +97 -12
- package/src/inline-date-pin-geometry.ts +55 -58
- package/src/item-bar-geometry.ts +341 -5
- package/src/item-port-geometry.ts +69 -0
- package/src/layout-context.ts +210 -72
- package/src/layout-insights.ts +109 -2
- package/src/layout.ts +57 -1530
- package/src/nodes/anchor-node.ts +11 -4
- package/src/nodes/content-extent.ts +107 -0
- package/src/nodes/dependency-edges.ts +171 -0
- package/src/nodes/footnote-node.ts +171 -31
- package/src/nodes/group-node.ts +92 -214
- package/src/nodes/header-node.ts +255 -0
- package/src/nodes/include-node.ts +124 -110
- package/src/nodes/item-measure.ts +723 -0
- package/src/nodes/item-node.ts +358 -80
- package/src/nodes/marker-band.ts +406 -0
- package/src/nodes/marker-geometry.ts +1 -1
- package/src/nodes/milestone-node.ts +321 -136
- package/src/nodes/nowline-node.ts +87 -0
- package/src/nodes/parallel-node.ts +69 -38
- package/src/nodes/region-markers.ts +194 -0
- package/src/nodes/roadmap-node.ts +46 -950
- package/src/nodes/row-track.ts +217 -0
- package/src/nodes/sequence-item.ts +433 -0
- package/src/nodes/sequence-one.ts +40 -0
- package/src/nodes/swimlane-node.ts +66 -205
- package/src/nodes/wave-node.ts +609 -0
- package/src/non-working-display.ts +32 -0
- package/src/phases/date-window.ts +191 -0
- package/src/phases/finalize.ts +1303 -0
- package/src/phases/measure.ts +329 -0
- package/src/phases/place.ts +318 -0
- package/src/renderable.ts +23 -3
- package/src/resolve-today.ts +28 -25
- package/src/row-packer.ts +85 -84
- package/src/schedule.ts +362 -114
- package/src/start-rules.ts +113 -0
- package/src/start-terms.ts +38 -0
- package/src/status-dot.ts +81 -0
- package/src/style-resolution.ts +27 -57
- package/src/text-measure.ts +71 -0
- package/src/themes/contrast.ts +34 -0
- package/src/themes/dark.ts +28 -1
- package/src/themes/grayscale.ts +28 -1
- package/src/themes/index.ts +10 -7
- package/src/themes/light.ts +28 -1
- package/src/themes/shape.ts +38 -0
- package/src/themes/shared.ts +85 -93
- package/src/time-scale.ts +117 -19
- package/src/types.ts +529 -20
- package/src/view-preset.ts +520 -27
- package/src/wave-barrier.ts +242 -0
- package/src/wave-hatch.ts +25 -0
- package/src/wave-layout.ts +203 -0
- package/src/working-calendar.ts +875 -57
- package/src/working-span.ts +19 -0
package/src/resolve-today.ts
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// --timezone is ignored. --timezone only governs the clock-based default
|
|
21
21
|
// (when --now is omitted).
|
|
22
22
|
|
|
23
|
+
import { ISO_DATE_RE, isRealIsoDate, parseIsoDate } from '@nowline/core/internal';
|
|
24
|
+
|
|
23
25
|
// ---- Types and errors -------------------------------------------------------
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -145,17 +147,25 @@ export function civilDateInZone(instant: Date, zone: NormalizedZone): Date {
|
|
|
145
147
|
|
|
146
148
|
// ---- Now-value parsing ------------------------------------------------------
|
|
147
149
|
|
|
148
|
-
// Bare YYYY-MM-DD (floating date, zone-independent)
|
|
149
|
-
const BARE_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
150
|
-
|
|
151
150
|
// ISO 8601 instant with explicit Z or ±HH / ±HH:MM / ±HHMM offset.
|
|
152
151
|
// Captures: [1] datetime part, [2] sign or Z, [3] hours, [4] minutes (optional)
|
|
153
152
|
const ISO_WITH_OFFSET_RE =
|
|
154
153
|
/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)(Z|([+-])(\d{2})(?::?(\d{2}))?)$/;
|
|
155
154
|
|
|
156
155
|
// ISO 8601 with time but no offset (floating local date-time).
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
const ISO_FLOATING_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* True when `raw` has one of the `now` string shapes but its leading
|
|
160
|
+
* `YYYY-MM-DD` is not a real calendar day (`2026-02-30`). {@link resolveToday}
|
|
161
|
+
* returns undefined for such a value, as for any unrecognised string; this
|
|
162
|
+
* lets an option parser say why.
|
|
163
|
+
*/
|
|
164
|
+
export function hasImpossibleNowDate(raw: string): boolean {
|
|
165
|
+
const isNowShape =
|
|
166
|
+
ISO_DATE_RE.test(raw) || ISO_WITH_OFFSET_RE.test(raw) || ISO_FLOATING_RE.test(raw);
|
|
167
|
+
return isNowShape && !isRealIsoDate(raw.slice(0, 10));
|
|
168
|
+
}
|
|
159
169
|
|
|
160
170
|
// ---- Public API -------------------------------------------------------------
|
|
161
171
|
|
|
@@ -193,11 +203,14 @@ export interface ResolveTodayOptions {
|
|
|
193
203
|
* Precedence (from highest to lowest):
|
|
194
204
|
* 1. `now === '-'` / `null` → `undefined` (suppress)
|
|
195
205
|
* 2. `now` is a `Date` object → returned as-is (caller must supply UTC midnight)
|
|
196
|
-
* 3. `now` bare `YYYY-MM-DD` → floating;
|
|
206
|
+
* 3. `now` bare `YYYY-MM-DD` → floating; UTC midnight of that day regardless of zone
|
|
197
207
|
* 4. `now` ISO 8601 + Z/offset → embedded offset wins; zone ignored
|
|
198
208
|
* 5. `now` ISO 8601 without offset→ floating; written date part; zone ignored
|
|
199
209
|
* 6. `now` omitted → civil date of `clock()` in effective `zone`
|
|
200
210
|
*
|
|
211
|
+
* A string whose `YYYY-MM-DD` is not a real calendar day (`2026-02-30`) is
|
|
212
|
+
* rejected like an unrecognised one, never rolled over to the next month.
|
|
213
|
+
*
|
|
201
214
|
* The `zone` is ONLY consulted for case 6 (clock-based default). Authored
|
|
202
215
|
* dates — item bars, milestones, anchors, the axis ticks — are floating and
|
|
203
216
|
* are never affected by this function.
|
|
@@ -214,17 +227,14 @@ export function resolveToday(opts: ResolveTodayOptions = {}): Date | undefined {
|
|
|
214
227
|
if (now instanceof Date) return now;
|
|
215
228
|
|
|
216
229
|
if (now !== undefined) {
|
|
230
|
+
// Every string form leads with a YYYY-MM-DD. `Date.UTC` and `new Date`
|
|
231
|
+
// would both roll an impossible one over (`2026-02-30` to March 2), so
|
|
232
|
+
// reject it here and let the option layer report it.
|
|
233
|
+
if (hasImpossibleNowDate(now)) return undefined;
|
|
234
|
+
|
|
217
235
|
// 3. Bare YYYY-MM-DD — floating; zone ignored
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
return new Date(
|
|
221
|
-
Date.UTC(
|
|
222
|
-
parseInt(bareMatch[1], 10),
|
|
223
|
-
parseInt(bareMatch[2], 10) - 1,
|
|
224
|
-
parseInt(bareMatch[3], 10),
|
|
225
|
-
),
|
|
226
|
-
);
|
|
227
|
-
}
|
|
236
|
+
const bare = parseIsoDate(now);
|
|
237
|
+
if (bare) return bare;
|
|
228
238
|
|
|
229
239
|
// 4. ISO 8601 instant with explicit Z or offset — embedded offset WINS
|
|
230
240
|
const withOffset = ISO_WITH_OFFSET_RE.exec(now);
|
|
@@ -245,15 +255,8 @@ export function resolveToday(opts: ResolveTodayOptions = {}): Date | undefined {
|
|
|
245
255
|
}
|
|
246
256
|
|
|
247
257
|
// 5. ISO 8601 without offset — floating; use written date part; zone ignored
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return new Date(
|
|
251
|
-
Date.UTC(
|
|
252
|
-
parseInt(floating[1], 10),
|
|
253
|
-
parseInt(floating[2], 10) - 1,
|
|
254
|
-
parseInt(floating[3], 10),
|
|
255
|
-
),
|
|
256
|
-
);
|
|
258
|
+
if (ISO_FLOATING_RE.test(now)) {
|
|
259
|
+
return parseIsoDate(now.slice(0, 10)) ?? undefined;
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
// Unrecognised string — caller should have validated before calling.
|
package/src/row-packer.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// RowPacker — the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// `rightEdge`, caption `spillX`, or a slack-arrow corridor.
|
|
1
|
+
// RowPacker — the row-pack engine shared by `SwimlaneNode` and
|
|
2
|
+
// `GroupNode`. A child never lands above the one before it
|
|
3
|
+
// (specs/true-width-layout.md §3.2), so it either joins the last row or
|
|
4
|
+
// opens a new one below it: it drops a row when it would collide with the
|
|
5
|
+
// last row's `rightEdge`, caption `spillX`, or a slack-arrow corridor.
|
|
6
|
+
// Each row's height is the max of its placed children's heights, with a
|
|
7
|
+
// per-packer minimum.
|
|
6
8
|
//
|
|
7
9
|
// Two-phase API per child:
|
|
8
10
|
// 1) `placeItem(...)` / `placeBlock(...)` decides which row the child
|
|
@@ -12,12 +14,8 @@
|
|
|
12
14
|
// back into the packer so the row's `rightEdge` / `spillX` advance
|
|
13
15
|
// and the row's height grows to accommodate the new child.
|
|
14
16
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// downward by the delta. Items keep their row-anchored top edge; the
|
|
18
|
-
// row simply gets taller. This keeps the contract "all children in a
|
|
19
|
-
// row share that row's top y" intact while letting individual children
|
|
20
|
-
// grow vertically (e.g. wrapped label-chiclet stacks in m3).
|
|
17
|
+
// A child only ever commits to the last row, so a row that grows has no
|
|
18
|
+
// rows below it to move.
|
|
21
19
|
|
|
22
20
|
import type { PositionedItem, PositionedTrackChild, SlackCorridor } from './types.js';
|
|
23
21
|
|
|
@@ -31,10 +29,11 @@ export interface PackedRow {
|
|
|
31
29
|
/** Reserved x for caption spill. Items whose `desiredStart < spillX`
|
|
32
30
|
* bump to a new row even if `rightEdge` would let them in. */
|
|
33
31
|
spillX: number;
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
32
|
+
/** How far below `y` an item joining this row sits: 0 for a row of items,
|
|
33
|
+
* and for a row a parallel or group opened, the distance from its top to
|
|
34
|
+
* its first track, so the item beside it aligns with that track rather
|
|
35
|
+
* than with the block's header band. */
|
|
36
|
+
contentOffsetY: number;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
export interface RowPackerOptions {
|
|
@@ -64,6 +63,10 @@ export interface ItemPlacementInput {
|
|
|
64
63
|
* initial height when this is the first item in a fresh row, or
|
|
65
64
|
* to grow the row when a taller item lands in an existing row. */
|
|
66
65
|
predictedHeight: number;
|
|
66
|
+
/** Ids of the items still to come in this track. A slack corridor
|
|
67
|
+
* whose arrow starts at one of them never bumps this item: rows
|
|
68
|
+
* never move up, so that item would follow it down. */
|
|
69
|
+
pendingIds?: ReadonlySet<string>;
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/** Result of a `placeItem` query. */
|
|
@@ -76,10 +79,9 @@ export interface ItemPlacement {
|
|
|
76
79
|
export interface ItemCommitInput {
|
|
77
80
|
rowIndex: number;
|
|
78
81
|
placed: PositionedItem;
|
|
79
|
-
/** Logical right edge of the placed item
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* bars). */
|
|
82
|
+
/** Logical right edge of the placed item: its span in time, so the
|
|
83
|
+
* next chained item butts edge-to-edge in logical space (each bar
|
|
84
|
+
* keeps its seams inside its own dates). */
|
|
83
85
|
logicalEnd: number;
|
|
84
86
|
/** Reserved spill x for captions that overflow the bar's right
|
|
85
87
|
* edge. `null` resets the row's spill reservation to `laneLeftX`. */
|
|
@@ -111,40 +113,58 @@ export interface BlockCommitInput {
|
|
|
111
113
|
blockHeight: number;
|
|
112
114
|
/** Logical right edge of the block. */
|
|
113
115
|
blockEnd: number;
|
|
116
|
+
/** Distance from the block's top to its first track (header band plus
|
|
117
|
+
* body pad); 0 when it has no header or children. An item that joins
|
|
118
|
+
* the block's row lands this far down. */
|
|
119
|
+
contentOffsetY?: number;
|
|
120
|
+
/** Reserved spill x for what the block paints past its right edge
|
|
121
|
+
* (a header title, a caption its children spill). Omitted or
|
|
122
|
+
* `null` resets the row's spill reservation to `laneLeftX`. */
|
|
123
|
+
spillReservation?: number | null;
|
|
124
|
+
/** Rightmost x anything inside the block paints, when it can pass
|
|
125
|
+
* `blockEnd` (see `trackChildPaintedRightX`): a container's box spans
|
|
126
|
+
* only its dates, so a caption its children spill is not in it. It widens
|
|
127
|
+
* `usedRightX()` (the lane's extent, and so the canvas); the
|
|
128
|
+
* caller reserves it on the row through `spillReservation`. */
|
|
129
|
+
paintedRight?: number;
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
export class RowPacker {
|
|
117
133
|
public readonly rows: PackedRow[];
|
|
118
134
|
private readonly opts: RowPackerOptions;
|
|
135
|
+
/** Rightmost `BlockCommitInput.paintedRight` committed so far. */
|
|
136
|
+
private paintedRightX: number;
|
|
119
137
|
|
|
120
138
|
constructor(opts: RowPackerOptions) {
|
|
121
139
|
this.opts = opts;
|
|
140
|
+
this.paintedRightX = opts.laneLeftX;
|
|
122
141
|
this.rows = [
|
|
123
142
|
{
|
|
124
143
|
y: opts.originY,
|
|
125
144
|
height: opts.minRowHeight,
|
|
126
145
|
rightEdge: opts.laneLeftX,
|
|
127
146
|
spillX: opts.laneLeftX,
|
|
128
|
-
|
|
147
|
+
contentOffsetY: 0,
|
|
129
148
|
},
|
|
130
149
|
];
|
|
131
150
|
}
|
|
132
151
|
|
|
133
152
|
/**
|
|
134
|
-
*
|
|
135
|
-
* colliding with sibling content or a slack
|
|
136
|
-
*
|
|
153
|
+
* Join the last row (the last committed child's) when the item's natural
|
|
154
|
+
* extent fits there without colliding with sibling content or a slack
|
|
155
|
+
* corridor; otherwise append a fresh row below it.
|
|
137
156
|
*/
|
|
138
157
|
placeItem(input: ItemPlacementInput): ItemPlacement {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
const last = this.rows[this.rows.length - 1];
|
|
159
|
+
const fits =
|
|
160
|
+
input.desiredStart >= last.rightEdge &&
|
|
161
|
+
input.desiredStart >= last.spillX &&
|
|
162
|
+
!this.rowIntersectsCorridor(last, input) &&
|
|
163
|
+
!this.rowCrossesOwnCorridor(last, input);
|
|
164
|
+
const row = fits
|
|
165
|
+
? last
|
|
166
|
+
: this.appendRow(Math.max(this.opts.minRowHeight, input.predictedHeight));
|
|
167
|
+
return { rowIndex: this.rows.length - 1, y: row.y + row.contentOffsetY };
|
|
148
168
|
}
|
|
149
169
|
|
|
150
170
|
/**
|
|
@@ -164,16 +184,16 @@ export class RowPacker {
|
|
|
164
184
|
|
|
165
185
|
/**
|
|
166
186
|
* Record a placed item back into its row. Grows the row's height to
|
|
167
|
-
* fit the item, advances `rightEdge
|
|
168
|
-
* shifts later rows down if this row got taller.
|
|
187
|
+
* fit the item, advances `rightEdge` and sets the caption spill.
|
|
169
188
|
*/
|
|
170
189
|
commitItem(input: ItemCommitInput): void {
|
|
171
190
|
const row = this.rows[input.rowIndex];
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
row.height = Math.max(
|
|
192
|
+
row.height,
|
|
193
|
+
row.contentOffsetY + Math.max(input.placed.box.height, input.rowHeight ?? 0),
|
|
194
|
+
);
|
|
174
195
|
row.rightEdge = input.logicalEnd;
|
|
175
196
|
row.spillX = input.spillReservation ?? this.opts.laneLeftX;
|
|
176
|
-
row.placedChildren.push(input.placed);
|
|
177
197
|
}
|
|
178
198
|
|
|
179
199
|
/**
|
|
@@ -182,10 +202,13 @@ export class RowPacker {
|
|
|
182
202
|
*/
|
|
183
203
|
commitBlock(input: BlockCommitInput): void {
|
|
184
204
|
const row = this.rows[input.rowIndex];
|
|
185
|
-
|
|
205
|
+
row.height = Math.max(row.height, input.blockHeight);
|
|
206
|
+
row.contentOffsetY = input.contentOffsetY ?? 0;
|
|
186
207
|
row.rightEdge = input.blockEnd;
|
|
187
|
-
row.spillX = this.opts.laneLeftX;
|
|
188
|
-
|
|
208
|
+
row.spillX = input.spillReservation ?? this.opts.laneLeftX;
|
|
209
|
+
if (input.paintedRight !== undefined && input.paintedRight > this.paintedRightX) {
|
|
210
|
+
this.paintedRightX = input.paintedRight;
|
|
211
|
+
}
|
|
189
212
|
}
|
|
190
213
|
|
|
191
214
|
/** Total vertical extent reached (last row's bottom - originY). */
|
|
@@ -195,10 +218,11 @@ export class RowPacker {
|
|
|
195
218
|
}
|
|
196
219
|
|
|
197
220
|
/** Rightmost extent reached — bar logical end OR caption spill,
|
|
198
|
-
* whichever is wider, across every row
|
|
199
|
-
* `
|
|
221
|
+
* whichever is wider, across every row, and anything a committed
|
|
222
|
+
* block paints past its own box (`BlockCommitInput.paintedRight`).
|
|
223
|
+
* Empty rows contribute `laneLeftX`. */
|
|
200
224
|
usedRightX(): number {
|
|
201
|
-
let x = this.
|
|
225
|
+
let x = this.paintedRightX;
|
|
202
226
|
for (const r of this.rows) {
|
|
203
227
|
if (r.rightEdge > x) x = r.rightEdge;
|
|
204
228
|
if (r.spillX > x) x = r.spillX;
|
|
@@ -213,7 +237,23 @@ export class RowPacker {
|
|
|
213
237
|
c.y < r.y + r.height &&
|
|
214
238
|
p.desiredStart < c.xEnd &&
|
|
215
239
|
p.desiredEnd > c.xStart &&
|
|
216
|
-
c.slackPredId !== p.childId
|
|
240
|
+
c.slackPredId !== p.childId &&
|
|
241
|
+
!p.pendingIds?.has(c.slackPredId),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* An overrun slack arrow runs left, from its source back to a milestone
|
|
247
|
+
* dated before the source ends. Rows never move up, so the source cannot
|
|
248
|
+
* push the work before it out of that path; it drops to a row where the
|
|
249
|
+
* path is clear instead.
|
|
250
|
+
*/
|
|
251
|
+
private rowCrossesOwnCorridor(r: PackedRow, p: ItemPlacementInput): boolean {
|
|
252
|
+
return this.opts.slackCorridors.some(
|
|
253
|
+
(c) =>
|
|
254
|
+
c.slackPredId === p.childId &&
|
|
255
|
+
c.xStart < p.desiredStart &&
|
|
256
|
+
r.rightEdge > Math.max(c.xStart, this.opts.laneLeftX),
|
|
217
257
|
);
|
|
218
258
|
}
|
|
219
259
|
|
|
@@ -224,48 +264,9 @@ export class RowPacker {
|
|
|
224
264
|
height,
|
|
225
265
|
rightEdge: this.opts.laneLeftX,
|
|
226
266
|
spillX: this.opts.laneLeftX,
|
|
227
|
-
|
|
267
|
+
contentOffsetY: 0,
|
|
228
268
|
};
|
|
229
269
|
this.rows.push(r);
|
|
230
270
|
return r;
|
|
231
271
|
}
|
|
232
|
-
|
|
233
|
-
private growRowHeight(rowIndex: number, newHeight: number): void {
|
|
234
|
-
const row = this.rows[rowIndex];
|
|
235
|
-
const delta = newHeight - row.height;
|
|
236
|
-
if (delta <= 0) return;
|
|
237
|
-
row.height = newHeight;
|
|
238
|
-
for (let i = rowIndex + 1; i < this.rows.length; i += 1) {
|
|
239
|
-
const r = this.rows[i];
|
|
240
|
-
r.y += delta;
|
|
241
|
-
for (const child of r.placedChildren) {
|
|
242
|
-
shiftPositionedY(child, delta);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Walk a positioned subtree and shift every absolute y by `dy`. Used
|
|
250
|
-
* when an earlier row in the packer grows, retroactively pushing every
|
|
251
|
-
* later row (and its placed subtrees) down to keep them clear.
|
|
252
|
-
*
|
|
253
|
-
* Items: shift the bar box plus label chips and any overflow box.
|
|
254
|
-
* Groups / parallels: shift the container box and recurse into
|
|
255
|
-
* children. The caller's `box.y` already moved with the parent.
|
|
256
|
-
*/
|
|
257
|
-
function shiftPositionedY(p: PositionedTrackChild, dy: number): void {
|
|
258
|
-
p.box.y += dy;
|
|
259
|
-
if (p.kind === 'item') {
|
|
260
|
-
for (const chip of p.labelChips) {
|
|
261
|
-
chip.box.y += dy;
|
|
262
|
-
}
|
|
263
|
-
if (p.overflowBox) {
|
|
264
|
-
p.overflowBox.y += dy;
|
|
265
|
-
}
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
for (const child of p.children) {
|
|
269
|
-
shiftPositionedY(child, dy);
|
|
270
|
-
}
|
|
271
272
|
}
|