@nowline/layout 0.8.5 → 0.9.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/band-scale.d.ts.map +1 -1
- 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 -154
- 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 -32
- 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 -147
- 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 +7 -7
- 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 -215
- 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 -39
- 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 -206
- 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/capacity.ts
CHANGED
|
@@ -20,24 +20,18 @@
|
|
|
20
20
|
// the lane badge in m7 and the overload sweep in m8).
|
|
21
21
|
|
|
22
22
|
import type { SymbolDeclaration } from '@nowline/core';
|
|
23
|
+
import { BUILTIN_CAPACITY_ICONS, propKey } from '@nowline/core/internal';
|
|
24
|
+
import { estimateTextWidth } from './text-measure.js';
|
|
23
25
|
import type { ResolvedCapacityIconRef } from './types.js';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
* Built-in `capacity-icon:` names the renderer understands directly
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* diverge briefly during refactors but should converge before each release.
|
|
28
|
+
* Built-in `capacity-icon:` names the renderer understands directly
|
|
29
|
+
* (`BUILTIN_CAPACITY_ICONS` in core's `value-tables.ts`): the validator uses
|
|
30
|
+
* the list to decide whether a value is a known built-in; the layout uses it
|
|
31
|
+
* to decide whether to forward the value as-is or dereference it through the
|
|
32
|
+
* glyph map.
|
|
32
33
|
*/
|
|
33
|
-
const
|
|
34
|
-
'none',
|
|
35
|
-
'multiplier',
|
|
36
|
-
'person',
|
|
37
|
-
'people',
|
|
38
|
-
'points',
|
|
39
|
-
'time',
|
|
40
|
-
]);
|
|
34
|
+
const BUILTIN_CAPACITY_ICON_SET: ReadonlySet<string> = new Set(BUILTIN_CAPACITY_ICONS);
|
|
41
35
|
|
|
42
36
|
const POSITIVE_INT_RE = /^\d+$/;
|
|
43
37
|
const POSITIVE_DECIMAL_RE = /^\d+\.\d+$/;
|
|
@@ -104,16 +98,6 @@ export function formatCapacityNumber(value: number): string {
|
|
|
104
98
|
*/
|
|
105
99
|
export type ResolvedCapacityIcon = ResolvedCapacityIconRef;
|
|
106
100
|
|
|
107
|
-
/**
|
|
108
|
-
* Read a property value off a Langium-shaped EntityProperty, normalizing the
|
|
109
|
-
* trailing-colon form. Validator-side uses the same trick — the grammar
|
|
110
|
-
* stores `key` as the *raw* token, including the colon for `unicode:`-style
|
|
111
|
-
* property keys.
|
|
112
|
-
*/
|
|
113
|
-
function propKey(prop: { key: string }): string {
|
|
114
|
-
return prop.key.endsWith(':') ? prop.key.slice(0, -1) : prop.key;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
101
|
function symbolUnicode(decl: SymbolDeclaration): string | undefined {
|
|
118
102
|
for (const p of decl.properties) {
|
|
119
103
|
if (propKey(p) === 'unicode' && p.value) return p.value;
|
|
@@ -147,7 +131,7 @@ export function resolveCapacityIcon(
|
|
|
147
131
|
symbols: Map<string, SymbolDeclaration>,
|
|
148
132
|
): ResolvedCapacityIcon | null {
|
|
149
133
|
if (icon === 'none') return null;
|
|
150
|
-
if (
|
|
134
|
+
if (BUILTIN_CAPACITY_ICON_SET.has(icon)) {
|
|
151
135
|
return {
|
|
152
136
|
kind: 'builtin',
|
|
153
137
|
name: icon as 'multiplier' | 'person' | 'people' | 'points' | 'time',
|
|
@@ -181,11 +165,68 @@ export function estimateCapacitySuffixWidth(
|
|
|
181
165
|
// no separator gap, glyph width approx. one character.
|
|
182
166
|
return (text.length + 1) * fontSizePx * 0.58;
|
|
183
167
|
}
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
168
|
+
// Gap + 1em glyph. Estimating the glyph as 1em (rather than 0.58em, the
|
|
169
|
+
// per-character width) is intentional: SVG icons render at their full
|
|
170
|
+
// font-size square, and most Unicode literals authors use for capacity
|
|
171
|
+
// (★, 💰, ⚙) similarly read at near-em widths. A built-in SVG icon sits
|
|
172
|
+
// `CAPACITY_ICON_GAP_EM` past the number; a literal flows `0.1em` after
|
|
173
|
+
// it inside the `<text>`.
|
|
188
174
|
const glyphWidthEm = 1.0;
|
|
189
|
-
const gapEm = 0.1;
|
|
175
|
+
const gapEm = icon.kind === 'builtin' ? CAPACITY_ICON_GAP_EM : 0.1;
|
|
190
176
|
return text.length * fontSizePx * 0.58 + (gapEm + glyphWidthEm) * fontSizePx;
|
|
191
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Em-distance the renderer reserves between an item's metaText and its
|
|
181
|
+
* capacity suffix (or any inline trailing token). Half a space's worth
|
|
182
|
+
* — wide enough to read as a separator, narrow enough not to look like
|
|
183
|
+
* a stray gap. SVG `<tspan dx>` honors this exactly so we don't depend
|
|
184
|
+
* on per-character estimation for the gap itself.
|
|
185
|
+
*/
|
|
186
|
+
export const META_SUFFIX_DX_EM = 0.6;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Gap (em) between a capacity number and its built-in SVG icon. The icon's
|
|
190
|
+
* x is an estimate (the number's width at 0.58 em/char) while the number is
|
|
191
|
+
* drawn with real font metrics, and common UI fonts draw digits at up to
|
|
192
|
+
* ~0.64 em, so the gap has to absorb that error and still read as space.
|
|
193
|
+
*/
|
|
194
|
+
const CAPACITY_ICON_GAP_EM = 0.25;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Where a built-in SVG capacity icon (person, people, points, time) sits:
|
|
198
|
+
* the px terms that, added in order to the start x of the text run the
|
|
199
|
+
* suffix is painted in, give the icon's x, at `fontSizePx`. Undefined for
|
|
200
|
+
* every other glyph (`null`, the multiplier, a literal), which the
|
|
201
|
+
* renderer paints inside the `<text>`.
|
|
202
|
+
*
|
|
203
|
+
* The terms are kept apart rather than summed so the renderer adds them to
|
|
204
|
+
* its x one at a time, in this order, exactly as it did when it measured
|
|
205
|
+
* them itself; a pre-summed offset can round differently at the last bit.
|
|
206
|
+
*
|
|
207
|
+
* With `metaText`, the run is the item meta line: the meta text's
|
|
208
|
+
* `estimateTextWidth` width and the `META_SUFFIX_DX_EM` separator (the
|
|
209
|
+
* first two terms are where the renderer starts the number), then the
|
|
210
|
+
* number's width and the gap. Without it, the number starts the run (an
|
|
211
|
+
* item with no meta text, or a lane badge): its width, then the gap.
|
|
212
|
+
* Both paths place the icon from the same number estimate and gap, so an
|
|
213
|
+
* item suffix reads like a lane badge.
|
|
214
|
+
*/
|
|
215
|
+
export function capacityIconDxTerms(
|
|
216
|
+
text: string,
|
|
217
|
+
icon: ResolvedCapacityIcon | null,
|
|
218
|
+
fontSizePx: number,
|
|
219
|
+
metaText?: string,
|
|
220
|
+
): readonly number[] | undefined {
|
|
221
|
+
if (icon?.kind !== 'builtin' || icon.name === 'multiplier') return undefined;
|
|
222
|
+
const gapPx = fontSizePx * CAPACITY_ICON_GAP_EM;
|
|
223
|
+
if (metaText) {
|
|
224
|
+
return [
|
|
225
|
+
estimateTextWidth(metaText, fontSizePx),
|
|
226
|
+
fontSizePx * META_SUFFIX_DX_EM,
|
|
227
|
+
estimateTextWidth(text, fontSizePx),
|
|
228
|
+
gapPx,
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
return [estimateTextWidth(text, fontSizePx), gapPx];
|
|
232
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// The chart's one vertical translation.
|
|
2
|
+
//
|
|
3
|
+
// Placement lays the chart out in local coordinates, with its top at y 0
|
|
4
|
+
// (`phases/place.ts`). Its top on the canvas is known only once the header
|
|
5
|
+
// rows above it stop growing (a now-pill row reserved on an extended axis,
|
|
6
|
+
// a marker band the final pack grew), so finalize translates the whole
|
|
7
|
+
// chart there, once, on every layout (`growHeaderRows`, f7). An isolated
|
|
8
|
+
// region also moves its own lanes down below its marker band, once, while
|
|
9
|
+
// it is placed (`include-node.ts`).
|
|
10
|
+
//
|
|
11
|
+
// Every y the chart's positioned model carries is listed exactly once,
|
|
12
|
+
// here. A new y-bearing field on `PositionedItem` / `PositionedGroup` /
|
|
13
|
+
// `PositionedParallel` has to join it or it stays at its local y, a chart
|
|
14
|
+
// top above where it belongs on every render.
|
|
15
|
+
//
|
|
16
|
+
// Arrow attach ports are deliberately NOT stored anywhere that needs
|
|
17
|
+
// translating: they are derived from each item's final box when the
|
|
18
|
+
// arrows are built (see `item-port-geometry.ts`).
|
|
19
|
+
|
|
20
|
+
import type { MilestonePlacement } from './nodes/milestone-node.js';
|
|
21
|
+
import type {
|
|
22
|
+
PositionedIncludeRegion,
|
|
23
|
+
PositionedMilestone,
|
|
24
|
+
PositionedSwimlane,
|
|
25
|
+
PositionedTrackChild,
|
|
26
|
+
} from './types.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Move a positioned item, group or parallel (and everything inside it)
|
|
30
|
+
* down by `dy`: its box, its inline-date glyphs, and for an item its
|
|
31
|
+
* label chips and `before:` overflow box.
|
|
32
|
+
*/
|
|
33
|
+
export function translateTrackChildY(child: PositionedTrackChild, dy: number): void {
|
|
34
|
+
child.box.y += dy;
|
|
35
|
+
for (const pin of child.inlineDatePins ?? []) pin.glyphTopLeft.y += dy;
|
|
36
|
+
if (child.kind === 'item') {
|
|
37
|
+
for (const chip of child.labelChips) chip.box.y += dy;
|
|
38
|
+
if (child.overflowBox) child.overflowBox.y += dy;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const c of child.children) translateTrackChildY(c, dy);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Move a swimlane band, its children and its nested lanes down by `dy`. */
|
|
45
|
+
export function translateSwimlaneY(lane: PositionedSwimlane, dy: number): void {
|
|
46
|
+
lane.box.y += dy;
|
|
47
|
+
for (const child of lane.children) translateTrackChildY(child, dy);
|
|
48
|
+
for (const nested of lane.nested) translateSwimlaneY(nested, dy);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Move an include region, its nested lanes and its own markers down by `dy`. */
|
|
52
|
+
export function translateIncludeY(region: PositionedIncludeRegion, dy: number): void {
|
|
53
|
+
region.box.y += dy;
|
|
54
|
+
for (const lane of region.nestedSwimlanes) translateSwimlaneY(lane, dy);
|
|
55
|
+
for (const marker of [...(region.anchors ?? []), ...(region.milestones ?? [])]) {
|
|
56
|
+
marker.center.y += dy;
|
|
57
|
+
marker.labelBox.y += dy;
|
|
58
|
+
marker.cutTopY += dy;
|
|
59
|
+
marker.cutBottomY += dy;
|
|
60
|
+
}
|
|
61
|
+
for (const milestone of region.milestones ?? []) {
|
|
62
|
+
for (const arrow of milestone.slackArrows ?? []) arrow.y += dy;
|
|
63
|
+
}
|
|
64
|
+
for (const edge of region.edges ?? []) {
|
|
65
|
+
for (const point of edge.waypoints) point.y += dy;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Move the chart part of a host milestone down by `dy`: its cut line and
|
|
71
|
+
* the slack arrows that start on an item. Its diamond and label sit in the
|
|
72
|
+
* header's marker band, outside the chart, and stay; so do the arrows that
|
|
73
|
+
* start on a marker row, which `positionMilestones` read from the final
|
|
74
|
+
* pack. `placement` is the milestone's placement, whose arrows are in the
|
|
75
|
+
* same order as the positioned ones.
|
|
76
|
+
*/
|
|
77
|
+
export function translateMilestoneChartY(
|
|
78
|
+
milestone: PositionedMilestone,
|
|
79
|
+
placement: MilestonePlacement,
|
|
80
|
+
dy: number,
|
|
81
|
+
): void {
|
|
82
|
+
milestone.cutTopY += dy;
|
|
83
|
+
milestone.cutBottomY += dy;
|
|
84
|
+
milestone.slackArrows?.forEach((arrow, i) => {
|
|
85
|
+
const start = placement.slackArrows?.[i];
|
|
86
|
+
if (start && 'y' in start) arrow.y += dy;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Container geometry — a group or parallel is drawn above and around its
|
|
2
|
+
// content, never beside it (specs/true-width-layout.md §3.3):
|
|
3
|
+
//
|
|
4
|
+
// header the title over a bracket that spans exactly the container's
|
|
5
|
+
// dates; the title starts at the container's start and may run
|
|
6
|
+
// past the bracket's end, and inline-date glyphs sit beside it.
|
|
7
|
+
// body a shaded rectangle spanning exactly the container's dates, from
|
|
8
|
+
// the header down to below the last child, padded above and below
|
|
9
|
+
// the content, never left or right.
|
|
10
|
+
//
|
|
11
|
+
// A container's `box` is the whole of it: `box.y` is the header's top,
|
|
12
|
+
// `box.y + headerHeight` the body's top, and `box.x` / `box.width` its dates.
|
|
13
|
+
// Layout (the reservation, the glyph placement, the row footprint) and the
|
|
14
|
+
// renderer (the bracket, the title, the body) both read these helpers, so the
|
|
15
|
+
// painted header and the space layout keeps for it can't drift apart.
|
|
16
|
+
|
|
17
|
+
import { INLINE_DATE_GLYPH_GAP_PX, INLINE_DATE_GLYPH_TILE_SIZE_PX } from './item-bar-geometry.js';
|
|
18
|
+
import { TEXT_SIZE_PX } from './themes/shared.js';
|
|
19
|
+
import type { PositionedTrackChild } from './types.js';
|
|
20
|
+
|
|
21
|
+
/** Height (px) of a container's header band: title row, then the bracket. */
|
|
22
|
+
export const CONTAINER_HEADER_BAND_PX = 18;
|
|
23
|
+
|
|
24
|
+
/** The bracket's horizontal line, measured down from the header's top. Its
|
|
25
|
+
* end ticks run from here down to the body's top. */
|
|
26
|
+
export const CONTAINER_BRACKET_LINE_OFFSET_PX = 13;
|
|
27
|
+
|
|
28
|
+
/** Font size (px) of a header title. */
|
|
29
|
+
export const CONTAINER_HEADER_TITLE_FONT_SIZE_PX = TEXT_SIZE_PX.xs;
|
|
30
|
+
|
|
31
|
+
/** Baseline of a header title, measured down from the header's top. */
|
|
32
|
+
export const CONTAINER_HEADER_TITLE_BASELINE_PX = 10;
|
|
33
|
+
|
|
34
|
+
/** Padding (px) between a container's body edge and its content, above and
|
|
35
|
+
* below only. */
|
|
36
|
+
export const CONTAINER_BODY_PAD_PX = 6;
|
|
37
|
+
|
|
38
|
+
/** Clear space (px) the row keeps past a header that runs beyond its
|
|
39
|
+
* container's end, the same buffer an item's spilled caption keeps. */
|
|
40
|
+
export const CONTAINER_HEADER_CLEARANCE_PX = 6;
|
|
41
|
+
|
|
42
|
+
/** Fill opacity of a container body directly in a lane: its `bg`, as a
|
|
43
|
+
* light tint over the lane band. */
|
|
44
|
+
export const CONTAINER_BODY_FILL_OPACITY = 0.12;
|
|
45
|
+
|
|
46
|
+
/** Fill opacity of a container body nested one level, inside another
|
|
47
|
+
* container's body: a lighter tint, so the two stack to a step darker
|
|
48
|
+
* rather than to a new colour (specs/true-width-layout.md §8 O4). */
|
|
49
|
+
export const CONTAINER_NESTED_BODY_FILL_OPACITY = 0.06;
|
|
50
|
+
|
|
51
|
+
/** Nesting depth (1 for a container directly in a lane) past which a body
|
|
52
|
+
* is outlined instead of filled, so tints never compound three deep
|
|
53
|
+
* (specs/true-width-layout.md §8 O4). */
|
|
54
|
+
export const CONTAINER_MAX_FILLED_DEPTH = 2;
|
|
55
|
+
|
|
56
|
+
/** Vertical chrome a container reserves around its content. */
|
|
57
|
+
export interface ContainerChrome {
|
|
58
|
+
/** Header band height: 0 when the header would be empty. */
|
|
59
|
+
header: number;
|
|
60
|
+
/** Body padding above the content, and again below it. */
|
|
61
|
+
pad: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The chrome a container reserves. A wave-only group draws nothing and
|
|
66
|
+
* reserves nothing (specs/waves.md §9.7). Any other container pads its body;
|
|
67
|
+
* it keeps a header band when there is a title, an inline-date glyph or a
|
|
68
|
+
* visible bracket to put in it.
|
|
69
|
+
*/
|
|
70
|
+
export function containerChrome(opts: {
|
|
71
|
+
drawn: boolean;
|
|
72
|
+
hasTitle: boolean;
|
|
73
|
+
hasPins: boolean;
|
|
74
|
+
hasBracket: boolean;
|
|
75
|
+
}): ContainerChrome {
|
|
76
|
+
if (!opts.drawn) return { header: 0, pad: 0 };
|
|
77
|
+
const hasHeader = opts.hasTitle || opts.hasPins || opts.hasBracket;
|
|
78
|
+
return { header: hasHeader ? CONTAINER_HEADER_BAND_PX : 0, pad: CONTAINER_BODY_PAD_PX };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Left x of the header title: the container's start, or one gap past an
|
|
83
|
+
* `after:` glyph, which takes the header's leftmost slot.
|
|
84
|
+
*/
|
|
85
|
+
export function containerHeaderTitleX(boxX: number, hasAfterGlyph: boolean): number {
|
|
86
|
+
return hasAfterGlyph ? boxX + INLINE_DATE_GLYPH_TILE_SIZE_PX + INLINE_DATE_GLYPH_GAP_PX : boxX;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Estimated width (px) of a header title. Same pessimistic ~0.58 em/char
|
|
91
|
+
* heuristic the caption-spill math uses, so the row reserves a little more
|
|
92
|
+
* than the text needs rather than less.
|
|
93
|
+
*/
|
|
94
|
+
export function containerHeaderTitleWidth(title: string): number {
|
|
95
|
+
return title.length * CONTAINER_HEADER_TITLE_FONT_SIZE_PX * 0.58;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Rightmost x a container's own header paints: its title (at its estimated
|
|
100
|
+
* width) and its inline-date glyphs. `-Infinity` for an item, or a container
|
|
101
|
+
* with neither.
|
|
102
|
+
*/
|
|
103
|
+
export function containerHeaderRightX(block: PositionedTrackChild): number {
|
|
104
|
+
if (block.kind === 'item') return Number.NEGATIVE_INFINITY;
|
|
105
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
106
|
+
const pins = block.inlineDatePins ?? [];
|
|
107
|
+
for (const pin of pins) right = Math.max(right, pin.glyphTopLeft.x + pin.glyphSize);
|
|
108
|
+
if (block.title && block.headerHeight > 0) {
|
|
109
|
+
const hasAfter = pins.some((pin) => pin.side === 'after');
|
|
110
|
+
right = Math.max(
|
|
111
|
+
right,
|
|
112
|
+
containerHeaderTitleX(block.box.x, hasAfter) + containerHeaderTitleWidth(block.title),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return right;
|
|
116
|
+
}
|
package/src/dsl-utils.ts
CHANGED
|
@@ -1,30 +1,12 @@
|
|
|
1
1
|
// Tiny DSL utilities shared between `layout.ts` and the per-entity nodes
|
|
2
|
-
// under `nodes/`. Kept intentionally trivial and pure:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// own those concerns.
|
|
2
|
+
// under `nodes/`. Kept intentionally trivial and pure: core's property
|
|
3
|
+
// lookups and strict ISO-date parser, re-exported, and a date formatter.
|
|
4
|
+
// Anything that needs configuration or non-trivial resolution (durations,
|
|
5
|
+
// calendars, styles) stays in the modules that own those concerns.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
export { parseIsoDate, propValue, propValues } from '@nowline/core/internal';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export function propValue(props: EntityProperty[], key: string): string | undefined {
|
|
15
|
-
return props.find((p) => stripColon(p.key) === key)?.value;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function propValues(props: EntityProperty[], key: string): string[] {
|
|
19
|
-
const p = props.find((x) => stripColon(x.key) === key);
|
|
20
|
-
if (!p) return [];
|
|
21
|
-
return p.value !== undefined ? [p.value] : [...p.values];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function parseDate(raw: string | undefined): Date | null {
|
|
25
|
-
if (!raw) return null;
|
|
26
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
|
|
27
|
-
if (!m) return null;
|
|
28
|
-
const d = new Date(Date.UTC(parseInt(m[1], 10), parseInt(m[2], 10) - 1, parseInt(m[3], 10)));
|
|
29
|
-
return Number.isNaN(d.getTime()) ? null : d;
|
|
9
|
+
/** A UTC-midnight date as `YYYY-MM-DD`, the inverse of `parseIsoDate`. */
|
|
10
|
+
export function formatIsoDate(date: Date): string {
|
|
11
|
+
return date.toISOString().slice(0, 10);
|
|
30
12
|
}
|