@nowline/layout 0.2.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/LICENSE +190 -0
- package/README.md +103 -0
- package/dist/band-scale.d.ts +56 -0
- package/dist/band-scale.d.ts.map +1 -0
- package/dist/band-scale.js +86 -0
- package/dist/band-scale.js.map +1 -0
- package/dist/calendar.d.ts +79 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +210 -0
- package/dist/calendar.js.map +1 -0
- package/dist/capacity.d.ts +72 -0
- package/dist/capacity.d.ts.map +1 -0
- package/dist/capacity.js +163 -0
- package/dist/capacity.js.map +1 -0
- package/dist/dsl-utils.d.ts +5 -0
- package/dist/dsl-utils.d.ts.map +1 -0
- package/dist/dsl-utils.js +28 -0
- package/dist/dsl-utils.js.map +1 -0
- package/dist/edge-routing.d.ts +89 -0
- package/dist/edge-routing.d.ts.map +1 -0
- package/dist/edge-routing.js +435 -0
- package/dist/edge-routing.js.map +1 -0
- package/dist/frame-tab-geometry.d.ts +78 -0
- package/dist/frame-tab-geometry.d.ts.map +1 -0
- package/dist/frame-tab-geometry.js +115 -0
- package/dist/frame-tab-geometry.js.map +1 -0
- package/dist/header-card-geometry.d.ts +29 -0
- package/dist/header-card-geometry.d.ts.map +1 -0
- package/dist/header-card-geometry.js +41 -0
- package/dist/header-card-geometry.js.map +1 -0
- package/dist/i18n.d.ts +48 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +114 -0
- package/dist/i18n.js.map +1 -0
- package/dist/include-chrome-geometry.d.ts +86 -0
- package/dist/include-chrome-geometry.d.ts.map +1 -0
- package/dist/include-chrome-geometry.js +104 -0
- package/dist/include-chrome-geometry.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/item-bar-geometry.d.ts +127 -0
- package/dist/item-bar-geometry.d.ts.map +1 -0
- package/dist/item-bar-geometry.js +173 -0
- package/dist/item-bar-geometry.js.map +1 -0
- package/dist/lane-utilization.d.ts +90 -0
- package/dist/lane-utilization.d.ts.map +1 -0
- package/dist/lane-utilization.js +206 -0
- package/dist/lane-utilization.js.map +1 -0
- package/dist/layout-context.d.ts +143 -0
- package/dist/layout-context.d.ts.map +1 -0
- package/dist/layout-context.js +8 -0
- package/dist/layout-context.js.map +1 -0
- package/dist/layout.d.ts +18 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +1213 -0
- package/dist/layout.js.map +1 -0
- package/dist/nodes/anchor-node.d.ts +16 -0
- package/dist/nodes/anchor-node.d.ts.map +1 -0
- package/dist/nodes/anchor-node.js +68 -0
- package/dist/nodes/anchor-node.js.map +1 -0
- package/dist/nodes/footnote-node.d.ts +10 -0
- package/dist/nodes/footnote-node.d.ts.map +1 -0
- package/dist/nodes/footnote-node.js +41 -0
- package/dist/nodes/footnote-node.js.map +1 -0
- package/dist/nodes/group-node.d.ts +29 -0
- package/dist/nodes/group-node.d.ts.map +1 -0
- package/dist/nodes/group-node.js +187 -0
- package/dist/nodes/group-node.js.map +1 -0
- package/dist/nodes/include-node.d.ts +16 -0
- package/dist/nodes/include-node.d.ts.map +1 -0
- package/dist/nodes/include-node.js +117 -0
- package/dist/nodes/include-node.js.map +1 -0
- package/dist/nodes/item-node.d.ts +51 -0
- package/dist/nodes/item-node.d.ts.map +1 -0
- package/dist/nodes/item-node.js +108 -0
- package/dist/nodes/item-node.js.map +1 -0
- package/dist/nodes/marker-geometry.d.ts +22 -0
- package/dist/nodes/marker-geometry.d.ts.map +1 -0
- package/dist/nodes/marker-geometry.js +38 -0
- package/dist/nodes/marker-geometry.js.map +1 -0
- package/dist/nodes/milestone-node.d.ts +48 -0
- package/dist/nodes/milestone-node.d.ts.map +1 -0
- package/dist/nodes/milestone-node.js +210 -0
- package/dist/nodes/milestone-node.js.map +1 -0
- package/dist/nodes/parallel-node.d.ts +21 -0
- package/dist/nodes/parallel-node.d.ts.map +1 -0
- package/dist/nodes/parallel-node.js +80 -0
- package/dist/nodes/parallel-node.js.map +1 -0
- package/dist/nodes/roadmap-node.d.ts +76 -0
- package/dist/nodes/roadmap-node.d.ts.map +1 -0
- package/dist/nodes/roadmap-node.js +788 -0
- package/dist/nodes/roadmap-node.js.map +1 -0
- package/dist/nodes/swimlane-node.d.ts +38 -0
- package/dist/nodes/swimlane-node.d.ts.map +1 -0
- package/dist/nodes/swimlane-node.js +308 -0
- package/dist/nodes/swimlane-node.js.map +1 -0
- package/dist/renderable.d.ts +44 -0
- package/dist/renderable.d.ts.map +1 -0
- package/dist/renderable.js +21 -0
- package/dist/renderable.js.map +1 -0
- package/dist/row-packer.d.ts +125 -0
- package/dist/row-packer.d.ts.map +1 -0
- package/dist/row-packer.js +169 -0
- package/dist/row-packer.js.map +1 -0
- package/dist/style-resolution.d.ts +14 -0
- package/dist/style-resolution.d.ts.map +1 -0
- package/dist/style-resolution.js +191 -0
- package/dist/style-resolution.js.map +1 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +241 -0
- package/dist/themes/dark.js.map +1 -0
- package/dist/themes/index.d.ts +15 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +30 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/light.d.ts +4 -0
- package/dist/themes/light.d.ts.map +1 -0
- package/dist/themes/light.js +248 -0
- package/dist/themes/light.js.map +1 -0
- package/dist/themes/shape.d.ts +194 -0
- package/dist/themes/shape.d.ts.map +1 -0
- package/dist/themes/shape.js +6 -0
- package/dist/themes/shape.js.map +1 -0
- package/dist/themes/shared.d.ts +145 -0
- package/dist/themes/shared.d.ts.map +1 -0
- package/dist/themes/shared.js +310 -0
- package/dist/themes/shared.js.map +1 -0
- package/dist/time-scale.d.ts +39 -0
- package/dist/time-scale.d.ts.map +1 -0
- package/dist/time-scale.js +62 -0
- package/dist/time-scale.js.map +1 -0
- package/dist/types.d.ts +483 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/view-preset.d.ts +23 -0
- package/dist/view-preset.d.ts.map +1 -0
- package/dist/view-preset.js +146 -0
- package/dist/view-preset.js.map +1 -0
- package/dist/working-calendar.d.ts +14 -0
- package/dist/working-calendar.d.ts.map +1 -0
- package/dist/working-calendar.js +74 -0
- package/dist/working-calendar.js.map +1 -0
- package/package.json +37 -0
- package/src/band-scale.ts +115 -0
- package/src/calendar.ts +244 -0
- package/src/capacity.ts +191 -0
- package/src/dsl-utils.ts +30 -0
- package/src/edge-routing.ts +550 -0
- package/src/frame-tab-geometry.ts +165 -0
- package/src/header-card-geometry.ts +48 -0
- package/src/i18n.ts +124 -0
- package/src/include-chrome-geometry.ts +156 -0
- package/src/index.ts +116 -0
- package/src/item-bar-geometry.ts +222 -0
- package/src/lane-utilization.ts +259 -0
- package/src/layout-context.ts +182 -0
- package/src/layout.ts +1446 -0
- package/src/nodes/anchor-node.ts +77 -0
- package/src/nodes/footnote-node.ts +60 -0
- package/src/nodes/group-node.ts +252 -0
- package/src/nodes/include-node.ts +168 -0
- package/src/nodes/item-node.ts +171 -0
- package/src/nodes/marker-geometry.ts +43 -0
- package/src/nodes/milestone-node.ts +263 -0
- package/src/nodes/parallel-node.ts +101 -0
- package/src/nodes/roadmap-node.ts +957 -0
- package/src/nodes/swimlane-node.ts +423 -0
- package/src/renderable.ts +68 -0
- package/src/row-packer.ts +271 -0
- package/src/style-resolution.ts +243 -0
- package/src/themes/dark.ts +244 -0
- package/src/themes/index.ts +36 -0
- package/src/themes/light.ts +251 -0
- package/src/themes/shape.ts +230 -0
- package/src/themes/shared.ts +369 -0
- package/src/time-scale.ts +78 -0
- package/src/types.ts +607 -0
- package/src/view-preset.ts +180 -0
- package/src/working-calendar.ts +91 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
// Channel-based dependency-arrow router. Replaces the single-elbow
|
|
2
|
+
// `routeEdge` shipped in m2g with one that:
|
|
3
|
+
//
|
|
4
|
+
// - Drops the vertical leg in the cleanest inter-column gutter (item
|
|
5
|
+
// bars are obstacles).
|
|
6
|
+
// - Nudges the elbow X away from any visible parallel/group bracket
|
|
7
|
+
// within `BRACKET_NUDGE_PX` so arrows breathe instead of hugging
|
|
8
|
+
// the bracket line.
|
|
9
|
+
// - Assigns distinct slots when multiple edges share a channel so
|
|
10
|
+
// parallel arrows don't stack on top of one another.
|
|
11
|
+
// - Falls back to under-bar routing (rendered behind the bars with a
|
|
12
|
+
// thinner stroke) when no clean channel exists in the source-target
|
|
13
|
+
// gap.
|
|
14
|
+
//
|
|
15
|
+
// Containers are NOT treated as obstacles. Endpoints inside a parallel
|
|
16
|
+
// or group route directly through the items-only obstacle map and use
|
|
17
|
+
// the under-bar fallback when needed. Looping around container edges
|
|
18
|
+
// to avoid a single intersecting bar produced unsatisfying detours
|
|
19
|
+
// (see `specs/handoffs/handoff-channel-routing-design.md`).
|
|
20
|
+
/** Distance (px) the elbow X must keep from any visible bracket. Picked
|
|
21
|
+
* to leave a small visible gap between the arrow's vertical leg and
|
|
22
|
+
* the bracket stroke without forcing the arrow far from its natural
|
|
23
|
+
* mid-gutter line. */
|
|
24
|
+
export const BRACKET_NUDGE_PX = 4;
|
|
25
|
+
/** Minimum horizontal source stub (px) — distance from the source's
|
|
26
|
+
* exit point to the vertical-leg elbow. Below this, the source-side
|
|
27
|
+
* arrow body collapses into the bar's right edge with no visible
|
|
28
|
+
* horizontal segment. Treated as a hard constraint when picking
|
|
29
|
+
* the channel X for left-to-right edges. */
|
|
30
|
+
export const MIN_SOURCE_STUB_PX = 6;
|
|
31
|
+
/** Minimum horizontal target stub (px) — distance from the vertical-leg
|
|
32
|
+
* elbow to the target's left edge. Below this, the arrowhead has no
|
|
33
|
+
* horizontal lead-in: the leg appears to plunge directly into the bar
|
|
34
|
+
* without a visible target-side stub. Treated as a hard constraint
|
|
35
|
+
* when picking the channel X for left-to-right edges; conflicts with
|
|
36
|
+
* obstacle / bracket clearance trigger the under-bar fallback. */
|
|
37
|
+
export const MIN_TARGET_STUB_PX = 6;
|
|
38
|
+
/** Spacing between slots inside one channel. With a 12 px gutter and
|
|
39
|
+
* this spacing, three slots (-1, 0, +1) fit comfortably; more than
|
|
40
|
+
* three sharing a channel collapses to centerline. */
|
|
41
|
+
const SLOT_SPACING_PX = 3;
|
|
42
|
+
/** Tolerance (px) when grouping edges by channel X for slot assignment.
|
|
43
|
+
* Edges within this distance share a channel. */
|
|
44
|
+
const SLOT_GROUP_TOLERANCE_PX = 1;
|
|
45
|
+
/** Stub length leaving each endpoint before the elbow turn for
|
|
46
|
+
* right-to-left edges. The router also inserts a quarter-arc at the
|
|
47
|
+
* elbow corner; the renderer's `roundedOrthogonalPath` rounds the
|
|
48
|
+
* corners. Left-to-right edges use the per-side `MIN_*_STUB_PX`
|
|
49
|
+
* constants instead — they provide tighter, asymmetric control over
|
|
50
|
+
* the source vs target sides. */
|
|
51
|
+
const STUB_OUT_PX = 10;
|
|
52
|
+
export class ChannelGrid {
|
|
53
|
+
items;
|
|
54
|
+
brackets;
|
|
55
|
+
constructor(input) {
|
|
56
|
+
this.items = input.itemBars;
|
|
57
|
+
this.brackets = input.brackets;
|
|
58
|
+
}
|
|
59
|
+
/** True when a vertical line at `x` intersects any item bar within
|
|
60
|
+
* the Y span `[yMin, yMax]`. */
|
|
61
|
+
hasObstacle(x, yMin, yMax) {
|
|
62
|
+
const lo = Math.min(yMin, yMax);
|
|
63
|
+
const hi = Math.max(yMin, yMax);
|
|
64
|
+
for (const bar of this.items) {
|
|
65
|
+
if (x <= bar.x || x >= bar.x + bar.width)
|
|
66
|
+
continue;
|
|
67
|
+
if (bar.y + bar.height <= lo || bar.y >= hi)
|
|
68
|
+
continue;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/** Visible brackets within `radius` px of `x` whose Y span overlaps
|
|
74
|
+
* `[yMin, yMax]`. The clearance nudge uses these to shift the
|
|
75
|
+
* elbow X away from the bracket. */
|
|
76
|
+
bracketsNear(x, yMin, yMax, radius) {
|
|
77
|
+
const lo = Math.min(yMin, yMax);
|
|
78
|
+
const hi = Math.max(yMin, yMax);
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const b of this.brackets) {
|
|
81
|
+
if (Math.abs(b.x - x) > radius)
|
|
82
|
+
continue;
|
|
83
|
+
if (b.yBottom <= lo || b.yTop >= hi)
|
|
84
|
+
continue;
|
|
85
|
+
out.push(b);
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Walk a positioned swimlane tree and collect every painted item bar
|
|
92
|
+
* (visual edges) AND every visible bracket stroke. Output feeds
|
|
93
|
+
* `ChannelGrid` so the router knows what to avoid.
|
|
94
|
+
*
|
|
95
|
+
* Swimlanes contained in include regions count too — items inside an
|
|
96
|
+
* isolated region share the parent timeline so cross-region arrows
|
|
97
|
+
* still need the obstacle/bracket data to route cleanly.
|
|
98
|
+
*/
|
|
99
|
+
export function collectRoutingObstacles(swimlanes, includes) {
|
|
100
|
+
const itemBars = [];
|
|
101
|
+
const brackets = [];
|
|
102
|
+
const visitChild = (child) => {
|
|
103
|
+
if (child.kind === 'item') {
|
|
104
|
+
itemBars.push(child.box);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (child.kind === 'parallel') {
|
|
108
|
+
// Parallel `bracket:solid|dashed` paints `[ ]` strokes at
|
|
109
|
+
// the box's left/right edges with 12 px vertical padding
|
|
110
|
+
// (matches `renderParallel`). The bracket has THREE
|
|
111
|
+
// strokes per side — a vertical bar at the box edge, plus
|
|
112
|
+
// top/bottom horizontal "feet" extending 4 px inward
|
|
113
|
+
// toward the parallel's center. We model the verticals as
|
|
114
|
+
// full-height bracket lines AND emit thin-Y-band entries
|
|
115
|
+
// at the foot tips so the clearance nudge sees the foot
|
|
116
|
+
// extent (otherwise an elbow nudged just past the
|
|
117
|
+
// vertical at +4 px would land squarely on the inward
|
|
118
|
+
// foot tip — see issue #2 in the channel-routing handoff).
|
|
119
|
+
if (child.style.bracket === 'solid' || child.style.bracket === 'dashed') {
|
|
120
|
+
const padding = 12;
|
|
121
|
+
const stub = 4;
|
|
122
|
+
const yTop = child.box.y - padding;
|
|
123
|
+
const yBottom = child.box.y + child.box.height + padding;
|
|
124
|
+
const lx = child.box.x;
|
|
125
|
+
const rx = child.box.x + child.box.width;
|
|
126
|
+
brackets.push({ x: lx, yTop, yBottom });
|
|
127
|
+
brackets.push({ x: rx, yTop, yBottom });
|
|
128
|
+
// Foot tips: tiny Y bands centered on each foot row
|
|
129
|
+
// so the entries only fire when the elbow's Y span
|
|
130
|
+
// actually crosses the foot line.
|
|
131
|
+
brackets.push({ x: lx + stub, yTop: yTop - 1, yBottom: yTop + 1 });
|
|
132
|
+
brackets.push({ x: lx + stub, yTop: yBottom - 1, yBottom: yBottom + 1 });
|
|
133
|
+
brackets.push({ x: rx - stub, yTop: yTop - 1, yBottom: yTop + 1 });
|
|
134
|
+
brackets.push({ x: rx - stub, yTop: yBottom - 1, yBottom: yBottom + 1 });
|
|
135
|
+
}
|
|
136
|
+
for (const sub of child.children)
|
|
137
|
+
visitChild(sub);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (child.kind === 'group') {
|
|
141
|
+
// Bracket-style groups (no fill) paint a left-side `[`
|
|
142
|
+
// glyph along `box.x`. Filled-style groups have no bracket
|
|
143
|
+
// — the chiclet is the visual instead.
|
|
144
|
+
const isFilled = child.style.bg !== 'none' && child.style.bg !== '#ffffff';
|
|
145
|
+
if (!isFilled && child.style.bracket && child.style.bracket !== 'none') {
|
|
146
|
+
brackets.push({
|
|
147
|
+
x: child.box.x,
|
|
148
|
+
yTop: child.box.y,
|
|
149
|
+
yBottom: child.box.y + child.box.height,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
for (const sub of child.children)
|
|
153
|
+
visitChild(sub);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const visitSwimlane = (lane) => {
|
|
158
|
+
for (const child of lane.children)
|
|
159
|
+
visitChild(child);
|
|
160
|
+
for (const nested of lane.nested)
|
|
161
|
+
visitSwimlane(nested);
|
|
162
|
+
};
|
|
163
|
+
for (const lane of swimlanes)
|
|
164
|
+
visitSwimlane(lane);
|
|
165
|
+
for (const region of includes) {
|
|
166
|
+
for (const lane of region.nestedSwimlanes)
|
|
167
|
+
visitSwimlane(lane);
|
|
168
|
+
}
|
|
169
|
+
return { itemBars, brackets };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Pick a channel X for one edge (no slot offset yet). Returns the
|
|
173
|
+
* provisional X, an `underBar` flag, and the satisfiable stub range
|
|
174
|
+
* so the bracket-clearance nudge can stay inside it. Same-row edges
|
|
175
|
+
* (`from.y ≈ to.y`) get a degenerate "channel" at midX so the
|
|
176
|
+
* renderer emits a straight line.
|
|
177
|
+
*/
|
|
178
|
+
function pickChannelX(req, grid) {
|
|
179
|
+
const { from, to } = req;
|
|
180
|
+
if (Math.abs(from.y - to.y) < 0.5) {
|
|
181
|
+
return { x: (from.x + to.x) / 2, underBar: false, range: null };
|
|
182
|
+
}
|
|
183
|
+
const yMin = Math.min(from.y, to.y);
|
|
184
|
+
const yMax = Math.max(from.y, to.y);
|
|
185
|
+
if (from.x <= to.x) {
|
|
186
|
+
// Left-to-right edge. The elbow X must satisfy both the
|
|
187
|
+
// min-source-stub and min-target-stub constraints — when the
|
|
188
|
+
// gutter is wider than the sum of the stubs, that's a band;
|
|
189
|
+
// when it's tighter the band collapses or inverts and the
|
|
190
|
+
// arrow drops to under-bar at the target-stub line.
|
|
191
|
+
const minX = from.x + MIN_SOURCE_STUB_PX;
|
|
192
|
+
const maxX = to.x - MIN_TARGET_STUB_PX;
|
|
193
|
+
if (minX > maxX) {
|
|
194
|
+
// Gutter narrower than the combined min stubs — give up
|
|
195
|
+
// and honor the target-stub line so the arrowhead still
|
|
196
|
+
// has its visible lead-in. Source stub absorbs the loss.
|
|
197
|
+
return {
|
|
198
|
+
x: to.x - MIN_TARGET_STUB_PX,
|
|
199
|
+
underBar: true,
|
|
200
|
+
range: null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const range = { minX, maxX };
|
|
204
|
+
// Try the natural gutter midpoint, clamped into the range.
|
|
205
|
+
const naturalMid = (from.x + to.x) / 2;
|
|
206
|
+
const mid = Math.max(minX, Math.min(maxX, naturalMid));
|
|
207
|
+
if (!grid.hasObstacle(mid, yMin, yMax)) {
|
|
208
|
+
return { x: mid, underBar: false, range };
|
|
209
|
+
}
|
|
210
|
+
// Walk inside the satisfiable range looking for a clear strip.
|
|
211
|
+
const span = maxX - minX;
|
|
212
|
+
for (let step = 1; step <= span; step++) {
|
|
213
|
+
const left = mid - step;
|
|
214
|
+
if (left >= minX && !grid.hasObstacle(left, yMin, yMax)) {
|
|
215
|
+
return { x: left, underBar: false, range };
|
|
216
|
+
}
|
|
217
|
+
const right = mid + step;
|
|
218
|
+
if (right <= maxX && !grid.hasObstacle(right, yMin, yMax)) {
|
|
219
|
+
return { x: right, underBar: false, range };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { x: mid, underBar: true, range };
|
|
223
|
+
}
|
|
224
|
+
// Right-to-left edge: source ends past target's left edge. Try a
|
|
225
|
+
// channel just right of source first; then walk leftward across
|
|
226
|
+
// the source/target span looking for any clear vertical strip.
|
|
227
|
+
const probes = [from.x + STUB_OUT_PX, to.x - STUB_OUT_PX];
|
|
228
|
+
for (const probe of probes) {
|
|
229
|
+
if (probe > 0 && !grid.hasObstacle(probe, yMin, yMax)) {
|
|
230
|
+
return { x: probe, underBar: false, range: null };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Fallback to under-bar at the source-side stub.
|
|
234
|
+
return { x: from.x + STUB_OUT_PX, underBar: true, range: null };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Apply the bracket-clearance nudge to a chosen channel X. Shifts `x`
|
|
238
|
+
* away from the nearest visible bracket within `BRACKET_NUDGE_PX`,
|
|
239
|
+
* preferring the direction that stays inside the satisfiable stub
|
|
240
|
+
* range. When neither direction fits — or the candidate position is
|
|
241
|
+
* still within nudge distance of another bracket — returns the input
|
|
242
|
+
* X clamped to the range and signals `forceUnderBar` so the leg paints
|
|
243
|
+
* BEHIND the bars and bracket strokes (z-order: bracket renders after
|
|
244
|
+
* under-bar edges, so the bracket cleanly covers the colliding
|
|
245
|
+
* portion).
|
|
246
|
+
*/
|
|
247
|
+
function nudgeAwayFromBrackets(x, range, req, grid) {
|
|
248
|
+
const yMin = Math.min(req.from.y, req.to.y);
|
|
249
|
+
const yMax = Math.max(req.from.y, req.to.y);
|
|
250
|
+
const near = grid.bracketsNear(x, yMin, yMax, BRACKET_NUDGE_PX);
|
|
251
|
+
if (near.length === 0)
|
|
252
|
+
return { x, forceUnderBar: false };
|
|
253
|
+
// Find the closest bracket — that's the one driving the nudge.
|
|
254
|
+
let closest = near[0];
|
|
255
|
+
let closestDist = Math.abs(near[0].x - x);
|
|
256
|
+
for (let i = 1; i < near.length; i++) {
|
|
257
|
+
const d = Math.abs(near[i].x - x);
|
|
258
|
+
if (d < closestDist) {
|
|
259
|
+
closest = near[i];
|
|
260
|
+
closestDist = d;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Try both sides of the closest bracket. Accept the first
|
|
264
|
+
// candidate that stays within the satisfiable range AND clears
|
|
265
|
+
// every other nearby bracket. The recheck radius is one epsilon
|
|
266
|
+
// tighter than `BRACKET_NUDGE_PX` so the closest bracket itself
|
|
267
|
+
// (now sitting at exactly the nudge distance) doesn't falsely
|
|
268
|
+
// trip the rejection — and so a SECOND bracket exactly at the
|
|
269
|
+
// nudge distance is also accepted as "just clear enough".
|
|
270
|
+
const candidates = [closest.x + BRACKET_NUDGE_PX, closest.x - BRACKET_NUDGE_PX];
|
|
271
|
+
const recheckRadius = BRACKET_NUDGE_PX - 0.01;
|
|
272
|
+
for (const candidate of candidates) {
|
|
273
|
+
if (range && (candidate < range.minX || candidate > range.maxX))
|
|
274
|
+
continue;
|
|
275
|
+
// Clamp right-to-left candidates to the natural gap so they
|
|
276
|
+
// don't escape past either endpoint (range is null on that path).
|
|
277
|
+
const clamped = range
|
|
278
|
+
? candidate
|
|
279
|
+
: Math.max(Math.min(req.from.x, req.to.x) + 1, Math.min(Math.max(req.from.x, req.to.x) - 1, candidate));
|
|
280
|
+
const stillNear = grid.bracketsNear(clamped, yMin, yMax, recheckRadius);
|
|
281
|
+
if (stillNear.length === 0) {
|
|
282
|
+
return { x: clamped, forceUnderBar: false };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// No nudge fits. Keep the channel at its current X (clamped into
|
|
286
|
+
// the range) and force under-bar so item bars + bracket strokes
|
|
287
|
+
// mask the colliding portion of the leg.
|
|
288
|
+
const fallback = range ? Math.min(Math.max(x, range.minX), range.maxX) : x;
|
|
289
|
+
return { x: fallback, forceUnderBar: true };
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Greedy interval coloring per channel. Edges sharing a channel
|
|
293
|
+
* (within `SLOT_GROUP_TOLERANCE_PX`) get distinct slot indices;
|
|
294
|
+
* overlapping Y spans land on different slots. Slot 0 is the
|
|
295
|
+
* centerline; ±1 sit `SLOT_SPACING_PX` to either side; further slots
|
|
296
|
+
* collapse back to the centerline (rare; visual stacking accepted).
|
|
297
|
+
*/
|
|
298
|
+
function assignSlots(edges) {
|
|
299
|
+
// Group by channel X (sort first so close-X edges collapse).
|
|
300
|
+
const byX = [...edges].sort((a, b) => {
|
|
301
|
+
if (Math.abs(a.channelX - b.channelX) >= SLOT_GROUP_TOLERANCE_PX) {
|
|
302
|
+
return a.channelX - b.channelX;
|
|
303
|
+
}
|
|
304
|
+
// Within the same channel, deterministic by orderIndex.
|
|
305
|
+
return a.orderIndex - b.orderIndex;
|
|
306
|
+
});
|
|
307
|
+
const slotsByOrderIndex = new Map();
|
|
308
|
+
let groupStart = 0;
|
|
309
|
+
while (groupStart < byX.length) {
|
|
310
|
+
let groupEnd = groupStart + 1;
|
|
311
|
+
while (groupEnd < byX.length &&
|
|
312
|
+
Math.abs(byX[groupEnd].channelX - byX[groupStart].channelX) < SLOT_GROUP_TOLERANCE_PX) {
|
|
313
|
+
groupEnd++;
|
|
314
|
+
}
|
|
315
|
+
const group = byX.slice(groupStart, groupEnd);
|
|
316
|
+
// Within the group, greedy color by Y span. Sort by min Y then
|
|
317
|
+
// (deterministically) by orderIndex.
|
|
318
|
+
const sortedGroup = [...group].sort((a, b) => {
|
|
319
|
+
const aMin = Math.min(a.req.from.y, a.req.to.y);
|
|
320
|
+
const bMin = Math.min(b.req.from.y, b.req.to.y);
|
|
321
|
+
if (Math.abs(aMin - bMin) > 0.5)
|
|
322
|
+
return aMin - bMin;
|
|
323
|
+
return a.orderIndex - b.orderIndex;
|
|
324
|
+
});
|
|
325
|
+
// Track per-slot maxY assigned so far.
|
|
326
|
+
const slotMaxY = [];
|
|
327
|
+
for (const entry of sortedGroup) {
|
|
328
|
+
const eMin = Math.min(entry.req.from.y, entry.req.to.y);
|
|
329
|
+
const eMax = Math.max(entry.req.from.y, entry.req.to.y);
|
|
330
|
+
let assigned = -1;
|
|
331
|
+
for (let s = 0; s < slotMaxY.length; s++) {
|
|
332
|
+
if (slotMaxY[s] <= eMin) {
|
|
333
|
+
assigned = s;
|
|
334
|
+
slotMaxY[s] = eMax;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (assigned === -1) {
|
|
339
|
+
assigned = slotMaxY.length;
|
|
340
|
+
slotMaxY.push(eMax);
|
|
341
|
+
}
|
|
342
|
+
slotsByOrderIndex.set(entry.orderIndex, assigned);
|
|
343
|
+
}
|
|
344
|
+
groupStart = groupEnd;
|
|
345
|
+
}
|
|
346
|
+
return slotsByOrderIndex;
|
|
347
|
+
}
|
|
348
|
+
/** Convert a slot index to its signed offset around the channel
|
|
349
|
+
* centerline. Slots 0/1/2/... become 0, +SLOT, -SLOT, +2*SLOT,
|
|
350
|
+
* -2*SLOT... so the FIRST edge sits on the centerline (no shift)
|
|
351
|
+
* and additional edges fan out alternately. Past 2 ± slots, slots
|
|
352
|
+
* collapse back to the centerline (visual stacking accepted). */
|
|
353
|
+
function slotOffset(slot) {
|
|
354
|
+
if (slot === 0)
|
|
355
|
+
return 0;
|
|
356
|
+
if (slot > 4)
|
|
357
|
+
return 0; // collapse beyond ±2 slots
|
|
358
|
+
const half = Math.ceil(slot / 2);
|
|
359
|
+
const sign = slot % 2 === 1 ? 1 : -1;
|
|
360
|
+
return sign * half * SLOT_SPACING_PX;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Marker → item stub. Source X sits on the marker's vertical cut
|
|
364
|
+
* line; the stub is a short horizontal segment to the target's left
|
|
365
|
+
* visual edge at the target's row mid-Y. No channel selection, no
|
|
366
|
+
* obstacle check — the cut line is the stem.
|
|
367
|
+
*/
|
|
368
|
+
function routeMarkerStub(req) {
|
|
369
|
+
// When the cut line sits AT or PAST the target's left edge (item
|
|
370
|
+
// hugs the anchor's date column), nudge the source 1 px back so
|
|
371
|
+
// routeEdge produces a non-degenerate path with a visible
|
|
372
|
+
// arrowhead.
|
|
373
|
+
const from = req.from.x >= req.to.x ? { x: req.to.x - 1, y: req.from.y } : req.from;
|
|
374
|
+
return {
|
|
375
|
+
fromId: req.fromId,
|
|
376
|
+
toId: req.toId,
|
|
377
|
+
waypoints: [from, req.to],
|
|
378
|
+
underBar: false,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Route every dependency edge in one batch so slot assignment can
|
|
383
|
+
* coordinate across edges that share a channel. Marker → item edges
|
|
384
|
+
* route as direct stubs and bypass the channel router.
|
|
385
|
+
*/
|
|
386
|
+
export function routeChannelEdges(requests, grid) {
|
|
387
|
+
const results = new Array(requests.length);
|
|
388
|
+
const channelEdges = [];
|
|
389
|
+
for (let i = 0; i < requests.length; i++) {
|
|
390
|
+
const req = requests[i];
|
|
391
|
+
if (req.isMarkerSource) {
|
|
392
|
+
results[i] = routeMarkerStub(req);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
// Same-row contiguous chains drop entirely. The caller skips
|
|
396
|
+
// them before passing in, but defend against direct calls.
|
|
397
|
+
if (Math.abs(req.from.y - req.to.y) < 0.5 && req.to.x - req.from.x < 20) {
|
|
398
|
+
results[i] = {
|
|
399
|
+
fromId: req.fromId,
|
|
400
|
+
toId: req.toId,
|
|
401
|
+
waypoints: [req.from, req.to],
|
|
402
|
+
underBar: false,
|
|
403
|
+
};
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const { x: provisionalX, underBar: provisionalUnderBar, range } = pickChannelX(req, grid);
|
|
407
|
+
const { x: channelX, forceUnderBar } = nudgeAwayFromBrackets(provisionalX, range, req, grid);
|
|
408
|
+
const underBar = provisionalUnderBar || forceUnderBar;
|
|
409
|
+
channelEdges.push({ req, channelX, underBar, orderIndex: i });
|
|
410
|
+
}
|
|
411
|
+
const slots = assignSlots(channelEdges);
|
|
412
|
+
for (const entry of channelEdges) {
|
|
413
|
+
const offset = slotOffset(slots.get(entry.orderIndex) ?? 0);
|
|
414
|
+
const slotX = entry.channelX + offset;
|
|
415
|
+
results[entry.orderIndex] = {
|
|
416
|
+
fromId: entry.req.fromId,
|
|
417
|
+
toId: entry.req.toId,
|
|
418
|
+
waypoints: buildOrthogonalPath(entry.req.from, entry.req.to, slotX),
|
|
419
|
+
underBar: entry.underBar,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return results;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Build the orthogonal polyline through a chosen vertical channel.
|
|
426
|
+
* Source-out-stub → vertical leg → target-in-stub. Same-row edges
|
|
427
|
+
* collapse to two points (handled by the caller's same-row skip).
|
|
428
|
+
*/
|
|
429
|
+
function buildOrthogonalPath(from, to, slotX) {
|
|
430
|
+
if (Math.abs(from.y - to.y) < 0.5) {
|
|
431
|
+
return [from, to];
|
|
432
|
+
}
|
|
433
|
+
return [from, { x: slotX, y: from.y }, { x: slotX, y: to.y }, to];
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=edge-routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-routing.js","sourceRoot":"","sources":["../src/edge-routing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Px-per-char for a 12 pt 600-weight title in `FONT_STACK.sans`.
|
|
3
|
+
* Calibrated against system-ui at 12 pt bold (~6.3 px/char actual);
|
|
4
|
+
* 6.5 leaves a small safety margin without producing a wide gap to
|
|
5
|
+
* the owner / badge that follows.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FRAME_TAB_TITLE_PX_PER_CHAR = 6.5;
|
|
8
|
+
/**
|
|
9
|
+
* Px-per-char for a 10 pt regular owner suffix in `FONT_STACK.sans`.
|
|
10
|
+
* Calibrated against system-ui at 10 pt regular (~5 px/char actual).
|
|
11
|
+
*/
|
|
12
|
+
export declare const FRAME_TAB_OWNER_PX_PER_CHAR = 5;
|
|
13
|
+
/**
|
|
14
|
+
* Visible gap (px) between adjacent text elements inside the chiclet:
|
|
15
|
+
* title→owner, owner→badge, and (no-owner) title→badge. Small enough
|
|
16
|
+
* to read as a single chip but big enough that the eye can still
|
|
17
|
+
* separate the tokens.
|
|
18
|
+
*/
|
|
19
|
+
export declare const FRAME_TAB_INNER_GAP_PX = 6;
|
|
20
|
+
/** Horizontal inset (px) from the chiclet's left edge to the title text. */
|
|
21
|
+
export declare const FRAME_TAB_LEFT_INSET_PX = 12;
|
|
22
|
+
/** Horizontal inset (px) from the rightmost element's right edge to the chiclet's right edge. */
|
|
23
|
+
export declare const FRAME_TAB_RIGHT_INSET_PX = 12;
|
|
24
|
+
/**
|
|
25
|
+
* Minimum total chiclet width (px). Acts as a floor so very short
|
|
26
|
+
* solo titles ("Q1", "Mob") don't produce a tiny chip that's hard to
|
|
27
|
+
* notice. Owner / badge presence almost always pushes the chiclet
|
|
28
|
+
* past this floor on its own.
|
|
29
|
+
*/
|
|
30
|
+
export declare const FRAME_TAB_MIN_WIDTH_PX = 56;
|
|
31
|
+
/** Horizontal offset (px) from the swimlane box's left edge to the tab's left edge. */
|
|
32
|
+
export declare const FRAME_TAB_OFFSET_FROM_BOX_PX = 10;
|
|
33
|
+
export interface FrameTabGeometry {
|
|
34
|
+
/** Estimated rendered width (px) of the title text, no min-clamp. */
|
|
35
|
+
titleTextWidth: number;
|
|
36
|
+
/** Estimated rendered width (px) of the owner suffix; 0 when no owner. */
|
|
37
|
+
ownerTextWidth: number;
|
|
38
|
+
/** Width (px) of the capacity badge as supplied by the caller; 0 when none. */
|
|
39
|
+
capacityBadgeWidth: number;
|
|
40
|
+
/** Width (px) of the footnote indicator text as supplied by the caller; 0 when none. */
|
|
41
|
+
footnoteIndicatorWidth: number;
|
|
42
|
+
/** Canvas X (px) where the title text is painted. */
|
|
43
|
+
titleX: number;
|
|
44
|
+
/** Canvas X (px) where the owner text is painted; 0 when no owner. */
|
|
45
|
+
ownerX: number;
|
|
46
|
+
/** Canvas X (px) where the capacity badge starts; 0 when no badge. */
|
|
47
|
+
badgeX: number;
|
|
48
|
+
/**
|
|
49
|
+
* Canvas X (px) for the right edge of the footnote indicator text
|
|
50
|
+
* (use with `text-anchor: end`); 0 when no footnote. Sits inside
|
|
51
|
+
* the chiclet just before the right inset.
|
|
52
|
+
*/
|
|
53
|
+
footnoteRightX: number;
|
|
54
|
+
/** Left X (canvas px) of the chiclet rectangle. */
|
|
55
|
+
tabX: number;
|
|
56
|
+
/** Total chiclet width (px). */
|
|
57
|
+
tabW: number;
|
|
58
|
+
/** Right X (canvas px) of the chiclet — convenience for layout collisions. */
|
|
59
|
+
rightX: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Single source of truth for the swimlane chiclet's geometry.
|
|
63
|
+
*
|
|
64
|
+
* `capacityBadgeWidth` and `footnoteIndicatorWidth` are supplied by
|
|
65
|
+
* the caller — they depend on resolved icon shape / footnote-indicator
|
|
66
|
+
* string which neither the layout nor the renderer wants to duplicate.
|
|
67
|
+
* Pass 0 (or omit) when the lane has no capacity badge / footnote
|
|
68
|
+
* indicators to render.
|
|
69
|
+
*
|
|
70
|
+
* Layout order inside the chiclet, left → right:
|
|
71
|
+
*
|
|
72
|
+
* [LEFT_INSET] title (INNER_GAP) owner (INNER_GAP) badge (INNER_GAP) footnote [RIGHT_INSET]
|
|
73
|
+
*
|
|
74
|
+
* Each element is optional except title; gaps are inserted only between
|
|
75
|
+
* present elements.
|
|
76
|
+
*/
|
|
77
|
+
export declare function frameTabGeometry(boxX: number, title: string, owner: string | undefined, capacityBadgeWidth?: number, footnoteIndicatorWidth?: number): FrameTabGeometry;
|
|
78
|
+
//# sourceMappingURL=frame-tab-geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-tab-geometry.d.ts","sourceRoot":"","sources":["../src/frame-tab-geometry.ts"],"names":[],"mappings":"AA0BA;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,iGAAiG;AACjG,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,uFAAuF;AACvF,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C,MAAM,WAAW,gBAAgB;IAC7B,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,cAAc,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wFAAwF;IACxF,sBAAsB,EAAE,MAAM,CAAC;IAE/B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,kBAAkB,GAAE,MAAU,EAC9B,sBAAsB,GAAE,MAAU,GACnC,gBAAgB,CA8ClB"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Frame-tab chiclet geometry — the rounded label tab that overhangs a
|
|
2
|
+
// swimlane's frame. Both the layout (collision math) and the renderer
|
|
3
|
+
// (drawing) call `frameTabGeometry` so the chiclet's painted footprint
|
|
4
|
+
// matches the bounding box layout reserves for it, and so the same
|
|
5
|
+
// helper computes WHERE inside the chiclet each text element lands.
|
|
6
|
+
//
|
|
7
|
+
// Design note: this helper separates two concerns that used to be
|
|
8
|
+
// conflated in a single set of "column widths":
|
|
9
|
+
//
|
|
10
|
+
// * Text-end positions (`titleX`, `ownerX`, `badgeX`) are computed
|
|
11
|
+
// from estimated actual text widths plus a small explicit
|
|
12
|
+
// `FRAME_TAB_INNER_GAP_PX`. The renderer paints elements at these
|
|
13
|
+
// X coordinates directly — no second placement pass.
|
|
14
|
+
//
|
|
15
|
+
// * Chiclet width (`tabW`) is computed from the right edge of the
|
|
16
|
+
// last painted element plus `FRAME_TAB_RIGHT_INSET_PX`, with a
|
|
17
|
+
// small minimum so a 2–3 char solo title still produces a usable
|
|
18
|
+
// chip. This means short labels naturally shrink-wrap their
|
|
19
|
+
// chiclet rather than reserving a wide whitespace column.
|
|
20
|
+
//
|
|
21
|
+
// The per-char width factors are calibrated to actual avg-char-width
|
|
22
|
+
// of the system sans-serif stack at the relevant font sizes / weights.
|
|
23
|
+
// They lean slightly conservative (~5 % over actual) so the chiclet
|
|
24
|
+
// never visually clips its label even when the runtime font's metrics
|
|
25
|
+
// exceed the calibration target.
|
|
26
|
+
/**
|
|
27
|
+
* Px-per-char for a 12 pt 600-weight title in `FONT_STACK.sans`.
|
|
28
|
+
* Calibrated against system-ui at 12 pt bold (~6.3 px/char actual);
|
|
29
|
+
* 6.5 leaves a small safety margin without producing a wide gap to
|
|
30
|
+
* the owner / badge that follows.
|
|
31
|
+
*/
|
|
32
|
+
export const FRAME_TAB_TITLE_PX_PER_CHAR = 6.5;
|
|
33
|
+
/**
|
|
34
|
+
* Px-per-char for a 10 pt regular owner suffix in `FONT_STACK.sans`.
|
|
35
|
+
* Calibrated against system-ui at 10 pt regular (~5 px/char actual).
|
|
36
|
+
*/
|
|
37
|
+
export const FRAME_TAB_OWNER_PX_PER_CHAR = 5;
|
|
38
|
+
/**
|
|
39
|
+
* Visible gap (px) between adjacent text elements inside the chiclet:
|
|
40
|
+
* title→owner, owner→badge, and (no-owner) title→badge. Small enough
|
|
41
|
+
* to read as a single chip but big enough that the eye can still
|
|
42
|
+
* separate the tokens.
|
|
43
|
+
*/
|
|
44
|
+
export const FRAME_TAB_INNER_GAP_PX = 6;
|
|
45
|
+
/** Horizontal inset (px) from the chiclet's left edge to the title text. */
|
|
46
|
+
export const FRAME_TAB_LEFT_INSET_PX = 12;
|
|
47
|
+
/** Horizontal inset (px) from the rightmost element's right edge to the chiclet's right edge. */
|
|
48
|
+
export const FRAME_TAB_RIGHT_INSET_PX = 12;
|
|
49
|
+
/**
|
|
50
|
+
* Minimum total chiclet width (px). Acts as a floor so very short
|
|
51
|
+
* solo titles ("Q1", "Mob") don't produce a tiny chip that's hard to
|
|
52
|
+
* notice. Owner / badge presence almost always pushes the chiclet
|
|
53
|
+
* past this floor on its own.
|
|
54
|
+
*/
|
|
55
|
+
export const FRAME_TAB_MIN_WIDTH_PX = 56;
|
|
56
|
+
/** Horizontal offset (px) from the swimlane box's left edge to the tab's left edge. */
|
|
57
|
+
export const FRAME_TAB_OFFSET_FROM_BOX_PX = 10;
|
|
58
|
+
/**
|
|
59
|
+
* Single source of truth for the swimlane chiclet's geometry.
|
|
60
|
+
*
|
|
61
|
+
* `capacityBadgeWidth` and `footnoteIndicatorWidth` are supplied by
|
|
62
|
+
* the caller — they depend on resolved icon shape / footnote-indicator
|
|
63
|
+
* string which neither the layout nor the renderer wants to duplicate.
|
|
64
|
+
* Pass 0 (or omit) when the lane has no capacity badge / footnote
|
|
65
|
+
* indicators to render.
|
|
66
|
+
*
|
|
67
|
+
* Layout order inside the chiclet, left → right:
|
|
68
|
+
*
|
|
69
|
+
* [LEFT_INSET] title (INNER_GAP) owner (INNER_GAP) badge (INNER_GAP) footnote [RIGHT_INSET]
|
|
70
|
+
*
|
|
71
|
+
* Each element is optional except title; gaps are inserted only between
|
|
72
|
+
* present elements.
|
|
73
|
+
*/
|
|
74
|
+
export function frameTabGeometry(boxX, title, owner, capacityBadgeWidth = 0, footnoteIndicatorWidth = 0) {
|
|
75
|
+
const tabX = boxX + FRAME_TAB_OFFSET_FROM_BOX_PX;
|
|
76
|
+
const titleX = tabX + FRAME_TAB_LEFT_INSET_PX;
|
|
77
|
+
const titleTextWidth = title.length * FRAME_TAB_TITLE_PX_PER_CHAR;
|
|
78
|
+
let cursorX = titleX + titleTextWidth;
|
|
79
|
+
let ownerTextWidth = 0;
|
|
80
|
+
let ownerX = 0;
|
|
81
|
+
if (owner) {
|
|
82
|
+
ownerTextWidth = `owner: ${owner}`.length * FRAME_TAB_OWNER_PX_PER_CHAR;
|
|
83
|
+
ownerX = cursorX + FRAME_TAB_INNER_GAP_PX;
|
|
84
|
+
cursorX = ownerX + ownerTextWidth;
|
|
85
|
+
}
|
|
86
|
+
let badgeX = 0;
|
|
87
|
+
if (capacityBadgeWidth > 0) {
|
|
88
|
+
badgeX = cursorX + FRAME_TAB_INNER_GAP_PX;
|
|
89
|
+
cursorX = badgeX + capacityBadgeWidth;
|
|
90
|
+
}
|
|
91
|
+
let footnoteRightX = 0;
|
|
92
|
+
if (footnoteIndicatorWidth > 0) {
|
|
93
|
+
// Footnote indicator paints with `text-anchor: end`, so its
|
|
94
|
+
// X is the RIGHT edge of the text. Add the gap before it and
|
|
95
|
+
// its own width to the running content cursor.
|
|
96
|
+
footnoteRightX = cursorX + FRAME_TAB_INNER_GAP_PX + footnoteIndicatorWidth;
|
|
97
|
+
cursorX = footnoteRightX;
|
|
98
|
+
}
|
|
99
|
+
const contentW = cursorX - tabX + FRAME_TAB_RIGHT_INSET_PX;
|
|
100
|
+
const tabW = Math.max(FRAME_TAB_MIN_WIDTH_PX, contentW);
|
|
101
|
+
return {
|
|
102
|
+
titleTextWidth,
|
|
103
|
+
ownerTextWidth,
|
|
104
|
+
capacityBadgeWidth,
|
|
105
|
+
footnoteIndicatorWidth,
|
|
106
|
+
titleX,
|
|
107
|
+
ownerX,
|
|
108
|
+
badgeX,
|
|
109
|
+
footnoteRightX,
|
|
110
|
+
tabX,
|
|
111
|
+
tabW,
|
|
112
|
+
rightX: tabX + tabW,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=frame-tab-geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-tab-geometry.js","sourceRoot":"","sources":["../src/frame-tab-geometry.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,sEAAsE;AACtE,uEAAuE;AACvE,mEAAmE;AACnE,oEAAoE;AACpE,EAAE;AACF,kEAAkE;AAClE,gDAAgD;AAChD,EAAE;AACF,qEAAqE;AACrE,8DAA8D;AAC9D,sEAAsE;AACtE,yDAAyD;AACzD,EAAE;AACF,oEAAoE;AACpE,mEAAmE;AACnE,qEAAqE;AACrE,gEAAgE;AAChE,8DAA8D;AAC9D,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,oEAAoE;AACpE,sEAAsE;AACtE,iCAAiC;AAEjC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC,4EAA4E;AAC5E,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAE1C,iGAAiG;AACjG,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAEzC,uFAAuF;AACvF,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAiC/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAC5B,IAAY,EACZ,KAAa,EACb,KAAyB,EACzB,qBAA6B,CAAC,EAC9B,yBAAiC,CAAC;IAElC,MAAM,IAAI,GAAG,IAAI,GAAG,4BAA4B,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,GAAG,uBAAuB,CAAC;IAE9C,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,GAAG,2BAA2B,CAAC;IAClE,IAAI,OAAO,GAAG,MAAM,GAAG,cAAc,CAAC;IAEtC,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,KAAK,EAAE,CAAC;QACR,cAAc,GAAG,UAAU,KAAK,EAAE,CAAC,MAAM,GAAG,2BAA2B,CAAC;QACxE,MAAM,GAAG,OAAO,GAAG,sBAAsB,CAAC;QAC1C,OAAO,GAAG,MAAM,GAAG,cAAc,CAAC;IACtC,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,kBAAkB,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,GAAG,OAAO,GAAG,sBAAsB,CAAC;QAC1C,OAAO,GAAG,MAAM,GAAG,kBAAkB,CAAC;IAC1C,CAAC;IAED,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,sBAAsB,GAAG,CAAC,EAAE,CAAC;QAC7B,4DAA4D;QAC5D,6DAA6D;QAC7D,+CAA+C;QAC/C,cAAc,GAAG,OAAO,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;QAC3E,OAAO,GAAG,cAAc,CAAC;IAC7B,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,wBAAwB,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;IAExD,OAAO;QACH,cAAc;QACd,cAAc;QACd,kBAAkB;QAClB,sBAAsB;QACtB,MAAM;QACN,MAAM;QACN,MAAM;QACN,cAAc;QACd,IAAI;QACJ,IAAI;QACJ,MAAM,EAAE,IAAI,GAAG,IAAI;KACtB,CAAC;AACN,CAAC"}
|