@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.
Files changed (339) hide show
  1. package/dist/axis-columns.d.ts +70 -0
  2. package/dist/axis-columns.d.ts.map +1 -0
  3. package/dist/axis-columns.js +104 -0
  4. package/dist/axis-columns.js.map +1 -0
  5. package/dist/band-scale.d.ts.map +1 -1
  6. package/dist/calendar-resolver.d.ts +27 -0
  7. package/dist/calendar-resolver.d.ts.map +1 -0
  8. package/dist/calendar-resolver.js +132 -0
  9. package/dist/calendar-resolver.js.map +1 -0
  10. package/dist/calendar.d.ts +0 -19
  11. package/dist/calendar.d.ts.map +1 -1
  12. package/dist/calendar.js +4 -44
  13. package/dist/calendar.js.map +1 -1
  14. package/dist/canvas-width.d.ts +23 -0
  15. package/dist/canvas-width.d.ts.map +1 -0
  16. package/dist/canvas-width.js +26 -0
  17. package/dist/canvas-width.js.map +1 -0
  18. package/dist/capacity.d.ts +28 -0
  19. package/dist/capacity.d.ts.map +1 -1
  20. package/dist/capacity.js +64 -29
  21. package/dist/capacity.js.map +1 -1
  22. package/dist/chart-translation.d.ts +22 -0
  23. package/dist/chart-translation.d.ts.map +1 -0
  24. package/dist/chart-translation.js +83 -0
  25. package/dist/chart-translation.js.map +1 -0
  26. package/dist/container-header-geometry.d.ts +64 -0
  27. package/dist/container-header-geometry.d.ts.map +1 -0
  28. package/dist/container-header-geometry.js +89 -0
  29. package/dist/container-header-geometry.js.map +1 -0
  30. package/dist/dsl-utils.d.ts +3 -4
  31. package/dist/dsl-utils.d.ts.map +1 -1
  32. package/dist/dsl-utils.js +8 -25
  33. package/dist/dsl-utils.js.map +1 -1
  34. package/dist/edge-routing.d.ts +69 -78
  35. package/dist/edge-routing.d.ts.map +1 -1
  36. package/dist/edge-routing.js +271 -401
  37. package/dist/edge-routing.js.map +1 -1
  38. package/dist/frame-tab-geometry.d.ts +24 -14
  39. package/dist/frame-tab-geometry.d.ts.map +1 -1
  40. package/dist/frame-tab-geometry.js +69 -23
  41. package/dist/frame-tab-geometry.js.map +1 -1
  42. package/dist/header-card-geometry.d.ts +12 -0
  43. package/dist/header-card-geometry.d.ts.map +1 -1
  44. package/dist/header-card-geometry.js +14 -2
  45. package/dist/header-card-geometry.js.map +1 -1
  46. package/dist/header-logo.d.ts +28 -0
  47. package/dist/header-logo.d.ts.map +1 -0
  48. package/dist/header-logo.js +51 -0
  49. package/dist/header-logo.js.map +1 -0
  50. package/dist/i18n.d.ts +11 -0
  51. package/dist/i18n.d.ts.map +1 -1
  52. package/dist/i18n.js +11 -1
  53. package/dist/i18n.js.map +1 -1
  54. package/dist/index.d.ts +20 -8
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +17 -7
  57. package/dist/index.js.map +1 -1
  58. package/dist/inline-date-pin-geometry.d.ts +19 -11
  59. package/dist/inline-date-pin-geometry.d.ts.map +1 -1
  60. package/dist/inline-date-pin-geometry.js +45 -46
  61. package/dist/inline-date-pin-geometry.js.map +1 -1
  62. package/dist/item-bar-geometry.d.ts +192 -4
  63. package/dist/item-bar-geometry.d.ts.map +1 -1
  64. package/dist/item-bar-geometry.js +258 -5
  65. package/dist/item-bar-geometry.js.map +1 -1
  66. package/dist/item-port-geometry.d.ts +13 -0
  67. package/dist/item-port-geometry.d.ts.map +1 -0
  68. package/dist/item-port-geometry.js +63 -0
  69. package/dist/item-port-geometry.js.map +1 -0
  70. package/dist/layout-context.d.ts +115 -51
  71. package/dist/layout-context.d.ts.map +1 -1
  72. package/dist/layout-context.js +96 -5
  73. package/dist/layout-context.js.map +1 -1
  74. package/dist/layout-insights.d.ts.map +1 -1
  75. package/dist/layout-insights.js +73 -1
  76. package/dist/layout-insights.js.map +1 -1
  77. package/dist/layout.d.ts +35 -0
  78. package/dist/layout.d.ts.map +1 -1
  79. package/dist/layout.js +18 -1315
  80. package/dist/layout.js.map +1 -1
  81. package/dist/nodes/anchor-node.d.ts.map +1 -1
  82. package/dist/nodes/anchor-node.js +11 -4
  83. package/dist/nodes/anchor-node.js.map +1 -1
  84. package/dist/nodes/content-extent.d.ts +24 -0
  85. package/dist/nodes/content-extent.d.ts.map +1 -0
  86. package/dist/nodes/content-extent.js +100 -0
  87. package/dist/nodes/content-extent.js.map +1 -0
  88. package/dist/nodes/dependency-edges.d.ts +6 -0
  89. package/dist/nodes/dependency-edges.d.ts.map +1 -0
  90. package/dist/nodes/dependency-edges.js +148 -0
  91. package/dist/nodes/dependency-edges.js.map +1 -0
  92. package/dist/nodes/footnote-node.d.ts +24 -3
  93. package/dist/nodes/footnote-node.d.ts.map +1 -1
  94. package/dist/nodes/footnote-node.js +129 -25
  95. package/dist/nodes/footnote-node.js.map +1 -1
  96. package/dist/nodes/group-node.d.ts +10 -21
  97. package/dist/nodes/group-node.d.ts.map +1 -1
  98. package/dist/nodes/group-node.js +79 -154
  99. package/dist/nodes/group-node.js.map +1 -1
  100. package/dist/nodes/header-node.d.ts +56 -0
  101. package/dist/nodes/header-node.d.ts.map +1 -0
  102. package/dist/nodes/header-node.js +163 -0
  103. package/dist/nodes/header-node.js.map +1 -0
  104. package/dist/nodes/include-node.d.ts +22 -12
  105. package/dist/nodes/include-node.d.ts.map +1 -1
  106. package/dist/nodes/include-node.js +95 -62
  107. package/dist/nodes/include-node.js.map +1 -1
  108. package/dist/nodes/item-measure.d.ts +135 -0
  109. package/dist/nodes/item-measure.d.ts.map +1 -0
  110. package/dist/nodes/item-measure.js +514 -0
  111. package/dist/nodes/item-measure.js.map +1 -0
  112. package/dist/nodes/item-node.d.ts +162 -9
  113. package/dist/nodes/item-node.d.ts.map +1 -1
  114. package/dist/nodes/item-node.js +256 -69
  115. package/dist/nodes/item-node.js.map +1 -1
  116. package/dist/nodes/marker-band.d.ts +118 -0
  117. package/dist/nodes/marker-band.d.ts.map +1 -0
  118. package/dist/nodes/marker-band.js +303 -0
  119. package/dist/nodes/marker-band.js.map +1 -0
  120. package/dist/nodes/marker-geometry.js +1 -1
  121. package/dist/nodes/marker-geometry.js.map +1 -1
  122. package/dist/nodes/milestone-node.d.ts +112 -15
  123. package/dist/nodes/milestone-node.d.ts.map +1 -1
  124. package/dist/nodes/milestone-node.js +246 -95
  125. package/dist/nodes/milestone-node.js.map +1 -1
  126. package/dist/nodes/nowline-node.d.ts +8 -0
  127. package/dist/nodes/nowline-node.d.ts.map +1 -0
  128. package/dist/nodes/nowline-node.js +74 -0
  129. package/dist/nodes/nowline-node.js.map +1 -0
  130. package/dist/nodes/parallel-node.d.ts +7 -12
  131. package/dist/nodes/parallel-node.d.ts.map +1 -1
  132. package/dist/nodes/parallel-node.js +66 -32
  133. package/dist/nodes/parallel-node.js.map +1 -1
  134. package/dist/nodes/region-markers.d.ts +41 -0
  135. package/dist/nodes/region-markers.d.ts.map +1 -0
  136. package/dist/nodes/region-markers.js +130 -0
  137. package/dist/nodes/region-markers.js.map +1 -0
  138. package/dist/nodes/roadmap-node.d.ts +9 -72
  139. package/dist/nodes/roadmap-node.d.ts.map +1 -1
  140. package/dist/nodes/roadmap-node.js +38 -788
  141. package/dist/nodes/roadmap-node.js.map +1 -1
  142. package/dist/nodes/row-track.d.ts +33 -0
  143. package/dist/nodes/row-track.d.ts.map +1 -0
  144. package/dist/nodes/row-track.js +170 -0
  145. package/dist/nodes/row-track.js.map +1 -0
  146. package/dist/nodes/sequence-item.d.ts +25 -0
  147. package/dist/nodes/sequence-item.d.ts.map +1 -0
  148. package/dist/nodes/sequence-item.js +396 -0
  149. package/dist/nodes/sequence-item.js.map +1 -0
  150. package/dist/nodes/sequence-one.d.ts +5 -0
  151. package/dist/nodes/sequence-one.d.ts.map +1 -0
  152. package/dist/nodes/sequence-one.js +26 -0
  153. package/dist/nodes/sequence-one.js.map +1 -0
  154. package/dist/nodes/swimlane-node.d.ts +5 -17
  155. package/dist/nodes/swimlane-node.d.ts.map +1 -1
  156. package/dist/nodes/swimlane-node.js +62 -147
  157. package/dist/nodes/swimlane-node.js.map +1 -1
  158. package/dist/nodes/wave-node.d.ts +52 -0
  159. package/dist/nodes/wave-node.d.ts.map +1 -0
  160. package/dist/nodes/wave-node.js +483 -0
  161. package/dist/nodes/wave-node.js.map +1 -0
  162. package/dist/non-working-display.d.ts +14 -0
  163. package/dist/non-working-display.d.ts.map +1 -0
  164. package/dist/non-working-display.js +28 -0
  165. package/dist/non-working-display.js.map +1 -0
  166. package/dist/phases/date-window.d.ts +48 -0
  167. package/dist/phases/date-window.d.ts.map +1 -0
  168. package/dist/phases/date-window.js +130 -0
  169. package/dist/phases/date-window.js.map +1 -0
  170. package/dist/phases/finalize.d.ts +198 -0
  171. package/dist/phases/finalize.d.ts.map +1 -0
  172. package/dist/phases/finalize.js +1058 -0
  173. package/dist/phases/finalize.js.map +1 -0
  174. package/dist/phases/measure.d.ts +77 -0
  175. package/dist/phases/measure.d.ts.map +1 -0
  176. package/dist/phases/measure.js +222 -0
  177. package/dist/phases/measure.js.map +1 -0
  178. package/dist/phases/place.d.ts +28 -0
  179. package/dist/phases/place.d.ts.map +1 -0
  180. package/dist/phases/place.js +225 -0
  181. package/dist/phases/place.js.map +1 -0
  182. package/dist/renderable.d.ts.map +1 -1
  183. package/dist/renderable.js +23 -3
  184. package/dist/renderable.js.map +1 -1
  185. package/dist/resolve-today.d.ts +11 -1
  186. package/dist/resolve-today.d.ts.map +1 -1
  187. package/dist/resolve-today.js +26 -12
  188. package/dist/resolve-today.js.map +1 -1
  189. package/dist/row-packer.d.ts +42 -16
  190. package/dist/row-packer.d.ts.map +1 -1
  191. package/dist/row-packer.js +51 -78
  192. package/dist/row-packer.js.map +1 -1
  193. package/dist/schedule.d.ts +58 -1
  194. package/dist/schedule.d.ts.map +1 -1
  195. package/dist/schedule.js +280 -106
  196. package/dist/schedule.js.map +1 -1
  197. package/dist/start-rules.d.ts +27 -0
  198. package/dist/start-rules.d.ts.map +1 -0
  199. package/dist/start-rules.js +91 -0
  200. package/dist/start-rules.js.map +1 -0
  201. package/dist/start-terms.d.ts +23 -0
  202. package/dist/start-terms.d.ts.map +1 -0
  203. package/dist/start-terms.js +27 -0
  204. package/dist/start-terms.js.map +1 -0
  205. package/dist/status-dot.d.ts +46 -0
  206. package/dist/status-dot.d.ts.map +1 -0
  207. package/dist/status-dot.js +78 -0
  208. package/dist/status-dot.js.map +1 -0
  209. package/dist/style-resolution.d.ts.map +1 -1
  210. package/dist/style-resolution.js +23 -43
  211. package/dist/style-resolution.js.map +1 -1
  212. package/dist/text-measure.d.ts +28 -0
  213. package/dist/text-measure.d.ts.map +1 -0
  214. package/dist/text-measure.js +70 -0
  215. package/dist/text-measure.js.map +1 -0
  216. package/dist/themes/contrast.d.ts +4 -0
  217. package/dist/themes/contrast.d.ts.map +1 -0
  218. package/dist/themes/contrast.js +32 -0
  219. package/dist/themes/contrast.js.map +1 -0
  220. package/dist/themes/dark.d.ts.map +1 -1
  221. package/dist/themes/dark.js +28 -1
  222. package/dist/themes/dark.js.map +1 -1
  223. package/dist/themes/grayscale.d.ts.map +1 -1
  224. package/dist/themes/grayscale.js +28 -1
  225. package/dist/themes/grayscale.js.map +1 -1
  226. package/dist/themes/index.d.ts +4 -11
  227. package/dist/themes/index.d.ts.map +1 -1
  228. package/dist/themes/index.js +6 -4
  229. package/dist/themes/index.js.map +1 -1
  230. package/dist/themes/light.d.ts.map +1 -1
  231. package/dist/themes/light.js +28 -1
  232. package/dist/themes/light.js.map +1 -1
  233. package/dist/themes/shape.d.ts +31 -0
  234. package/dist/themes/shape.d.ts.map +1 -1
  235. package/dist/themes/shared.d.ts +44 -50
  236. package/dist/themes/shared.d.ts.map +1 -1
  237. package/dist/themes/shared.js +79 -83
  238. package/dist/themes/shared.js.map +1 -1
  239. package/dist/time-scale.d.ts +42 -6
  240. package/dist/time-scale.d.ts.map +1 -1
  241. package/dist/time-scale.js +109 -17
  242. package/dist/time-scale.js.map +1 -1
  243. package/dist/types.d.ts +528 -11
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/view-preset.d.ts +61 -6
  246. package/dist/view-preset.d.ts.map +1 -1
  247. package/dist/view-preset.js +439 -24
  248. package/dist/view-preset.js.map +1 -1
  249. package/dist/wave-barrier.d.ts +107 -0
  250. package/dist/wave-barrier.d.ts.map +1 -0
  251. package/dist/wave-barrier.js +174 -0
  252. package/dist/wave-barrier.js.map +1 -0
  253. package/dist/wave-hatch.d.ts +4 -0
  254. package/dist/wave-hatch.d.ts.map +1 -0
  255. package/dist/wave-hatch.js +21 -0
  256. package/dist/wave-hatch.js.map +1 -0
  257. package/dist/wave-layout.d.ts +99 -0
  258. package/dist/wave-layout.d.ts.map +1 -0
  259. package/dist/wave-layout.js +130 -0
  260. package/dist/wave-layout.js.map +1 -0
  261. package/dist/working-calendar.d.ts +128 -4
  262. package/dist/working-calendar.d.ts.map +1 -1
  263. package/dist/working-calendar.js +725 -51
  264. package/dist/working-calendar.js.map +1 -1
  265. package/dist/working-span.d.ts +11 -0
  266. package/dist/working-span.d.ts.map +1 -0
  267. package/dist/working-span.js +18 -0
  268. package/dist/working-span.js.map +1 -0
  269. package/package.json +7 -7
  270. package/src/axis-columns.ts +140 -0
  271. package/src/calendar-resolver.ts +161 -0
  272. package/src/calendar.ts +4 -45
  273. package/src/canvas-width.ts +36 -0
  274. package/src/capacity.ts +71 -30
  275. package/src/chart-translation.ts +88 -0
  276. package/src/container-header-geometry.ts +116 -0
  277. package/src/dsl-utils.ts +8 -26
  278. package/src/edge-routing.ts +347 -491
  279. package/src/frame-tab-geometry.ts +78 -25
  280. package/src/header-card-geometry.ts +17 -2
  281. package/src/header-logo.ts +67 -0
  282. package/src/i18n.ts +22 -1
  283. package/src/index.ts +97 -12
  284. package/src/inline-date-pin-geometry.ts +55 -58
  285. package/src/item-bar-geometry.ts +341 -5
  286. package/src/item-port-geometry.ts +69 -0
  287. package/src/layout-context.ts +210 -72
  288. package/src/layout-insights.ts +109 -2
  289. package/src/layout.ts +57 -1530
  290. package/src/nodes/anchor-node.ts +11 -4
  291. package/src/nodes/content-extent.ts +107 -0
  292. package/src/nodes/dependency-edges.ts +171 -0
  293. package/src/nodes/footnote-node.ts +171 -31
  294. package/src/nodes/group-node.ts +92 -215
  295. package/src/nodes/header-node.ts +255 -0
  296. package/src/nodes/include-node.ts +124 -110
  297. package/src/nodes/item-measure.ts +723 -0
  298. package/src/nodes/item-node.ts +358 -80
  299. package/src/nodes/marker-band.ts +406 -0
  300. package/src/nodes/marker-geometry.ts +1 -1
  301. package/src/nodes/milestone-node.ts +321 -136
  302. package/src/nodes/nowline-node.ts +87 -0
  303. package/src/nodes/parallel-node.ts +69 -39
  304. package/src/nodes/region-markers.ts +194 -0
  305. package/src/nodes/roadmap-node.ts +46 -950
  306. package/src/nodes/row-track.ts +217 -0
  307. package/src/nodes/sequence-item.ts +433 -0
  308. package/src/nodes/sequence-one.ts +40 -0
  309. package/src/nodes/swimlane-node.ts +66 -206
  310. package/src/nodes/wave-node.ts +609 -0
  311. package/src/non-working-display.ts +32 -0
  312. package/src/phases/date-window.ts +191 -0
  313. package/src/phases/finalize.ts +1303 -0
  314. package/src/phases/measure.ts +329 -0
  315. package/src/phases/place.ts +318 -0
  316. package/src/renderable.ts +23 -3
  317. package/src/resolve-today.ts +28 -25
  318. package/src/row-packer.ts +85 -84
  319. package/src/schedule.ts +362 -114
  320. package/src/start-rules.ts +113 -0
  321. package/src/start-terms.ts +38 -0
  322. package/src/status-dot.ts +81 -0
  323. package/src/style-resolution.ts +27 -57
  324. package/src/text-measure.ts +71 -0
  325. package/src/themes/contrast.ts +34 -0
  326. package/src/themes/dark.ts +28 -1
  327. package/src/themes/grayscale.ts +28 -1
  328. package/src/themes/index.ts +10 -7
  329. package/src/themes/light.ts +28 -1
  330. package/src/themes/shape.ts +38 -0
  331. package/src/themes/shared.ts +85 -93
  332. package/src/time-scale.ts +117 -19
  333. package/src/types.ts +529 -20
  334. package/src/view-preset.ts +520 -27
  335. package/src/wave-barrier.ts +242 -0
  336. package/src/wave-hatch.ts +25 -0
  337. package/src/wave-layout.ts +203 -0
  338. package/src/working-calendar.ts +875 -57
  339. package/src/working-span.ts +19 -0
@@ -0,0 +1,40 @@
1
+ // sequenceOne — the dispatcher a track calls for each child: an item goes
2
+ // to `sequenceItem`, a parallel block to `ParallelNode`, a group to
3
+ // `GroupNode`. It is the one helper the nodes still take injected
4
+ // (`LayoutHelpers`): the group and parallel nodes call it for their nested
5
+ // blocks, so importing it there would form a cycle (specs/layout-phases.md
6
+ // §8, decision 4).
7
+
8
+ import type { GroupBlock, ItemDeclaration, ParallelBlock } from '@nowline/core';
9
+ import { isGroupBlock, isItemDeclaration, isParallelBlock } from '@nowline/core';
10
+ import type { LayoutContext, TrackCursor } from '../layout-context.js';
11
+ import type { PositionedGroup, PositionedParallel, PositionedTrackChild } from '../types.js';
12
+ import { GroupNode } from './group-node.js';
13
+ import { ParallelNode } from './parallel-node.js';
14
+
15
+ import { sequenceItem } from './sequence-item.js';
16
+
17
+ function sequenceParallel(
18
+ node: ParallelBlock,
19
+ cursor: TrackCursor,
20
+ ctx: LayoutContext,
21
+ ): PositionedParallel {
22
+ return new ParallelNode(node, { sequenceOne }).place(cursor, ctx);
23
+ }
24
+
25
+ function sequenceGroup(node: GroupBlock, cursor: TrackCursor, ctx: LayoutContext): PositionedGroup {
26
+ return new GroupNode(node, { sequenceOne }).place(cursor, ctx);
27
+ }
28
+
29
+ export function sequenceOne(
30
+ node: ItemDeclaration | GroupBlock | ParallelBlock,
31
+ cursor: TrackCursor,
32
+ ctx: LayoutContext,
33
+ ): PositionedTrackChild {
34
+ if (isItemDeclaration(node)) return sequenceItem(node, cursor, ctx);
35
+ if (isParallelBlock(node)) return sequenceParallel(node, cursor, ctx);
36
+ if (isGroupBlock(node)) return sequenceGroup(node, cursor, ctx);
37
+ throw new Error(
38
+ `Unknown swimlane child type: ${(node as { $type?: string }).$type ?? 'unknown'}`,
39
+ );
40
+ }
@@ -4,29 +4,21 @@
4
4
  // fresh rows) and emits a `PositionedSwimlane` plus the band's used
5
5
  // height.
6
6
  //
7
- // Step-2 transitional shape:
8
- // - Item children flow through the injected `deps.sequenceItem`. Once
9
- // ItemNode's wire-in is complete (step 1 — done) the production
10
- // `sequenceItem` already routes through ItemNode internally; deferring
11
- // the recursive Renderable call here keeps the diff small.
12
- // - Parallel + group children flow through `deps.sequenceOne` until
13
- // their own nodes land in step 3.
14
- // - The Renderable interface's `measure(MeasureContext)` is not yet
15
- // wired through the production pipeline. The composition-root work
16
- // in step 5 (`RoadmapNode`) will exercise it; meanwhile the legacy
17
- // two-pass "buildSwimlane returns positioned + usedHeight" shape is
18
- // preserved.
7
+ // The lane's children go through the shared row track (`row-track.ts`):
8
+ // items are measured and placed by `sequenceItem`, and parallel and group
9
+ // children by the injected `helpers.sequenceOne`. A lane has no separate
10
+ // measure pass: its height is the result of packing its rows, so
11
+ // `place` returns the positioned lane with its used height
12
+ // (specs/layout-phases.md §2, non-goals).
19
13
 
20
14
  import type {
21
- EntityProperty,
22
15
  GroupBlock,
23
16
  ItemDeclaration,
24
17
  ParallelBlock,
25
18
  SwimlaneDeclaration,
26
19
  } from '@nowline/core';
27
- import { isItemDeclaration } from '@nowline/core';
28
- import { deriveItemDurationDays } from '../calendar.js';
29
20
  import {
21
+ capacityIconDxTerms,
30
22
  estimateCapacitySuffixWidth,
31
23
  formatCapacityNumber,
32
24
  parseCapacityValue,
@@ -35,17 +27,12 @@ import {
35
27
  import { propValue } from '../dsl-utils.js';
36
28
  import { frameTabGeometry } from '../frame-tab-geometry.js';
37
29
  import { computeLaneUtilization, resolveLaneUtilizationThresholds } from '../lane-utilization.js';
38
- import type { LayoutContext, TrackCursor } from '../layout-context.js';
39
- import { RowPacker } from '../row-packer.js';
30
+ import type { LayoutContext, LayoutHelpers } from '../layout-context.js';
31
+ import { resolveChildStart } from '../start-rules.js';
40
32
  import { resolveStyle } from '../style-resolution.js';
41
- import { ITEM_INSET_PX, MIN_ITEM_WIDTH } from '../themes/shared.js';
42
- import type {
43
- BoundingBox,
44
- PositionedCapacity,
45
- PositionedItem,
46
- PositionedSwimlane,
47
- PositionedTrackChild,
48
- } from '../types.js';
33
+ import { estimateTextWidth } from '../text-measure.js';
34
+ import type { BoundingBox, PositionedCapacity, PositionedSwimlane } from '../types.js';
35
+ import { sequenceRowTrack } from './row-track.js';
49
36
 
50
37
  /**
51
38
  * Font size (px) the renderer uses for the lane capacity badge. Mirrors
@@ -73,39 +60,13 @@ function resolveLaneCapacity(
73
60
  const text = formatCapacityNumber(value);
74
61
  const icon = resolveCapacityIcon(style.capacityIcon, symbols);
75
62
  const badgeWidthPx = estimateCapacitySuffixWidth(text, icon, LANE_CAPACITY_BADGE_FONT_SIZE_PX);
63
+ const iconDxTerms = capacityIconDxTerms(text, icon, LANE_CAPACITY_BADGE_FONT_SIZE_PX);
76
64
  return {
77
- capacity: { value, text, icon },
65
+ capacity: { value, text, icon, ...(iconDxTerms !== undefined ? { iconDxTerms } : {}) },
78
66
  badgeWidthPx,
79
67
  };
80
68
  }
81
69
 
82
- /** Helpers that SwimlaneNode delegates to until the rest of m2.5c lands. */
83
- export interface SwimlaneNodeDeps {
84
- sequenceItem: (
85
- child: ItemDeclaration,
86
- cursor: TrackCursor,
87
- ctx: LayoutContext,
88
- ownerOverride?: string,
89
- ) => PositionedItem;
90
- sequenceOne: (
91
- child: ItemDeclaration | GroupBlock | ParallelBlock,
92
- cursor: TrackCursor,
93
- ctx: LayoutContext,
94
- ) => PositionedTrackChild;
95
- resolveChildStart: (
96
- props: EntityProperty[],
97
- seqDefault: number,
98
- laneLeftX: number,
99
- ctx: LayoutContext,
100
- ) => number;
101
- newCursor: (x: number, y: number) => TrackCursor;
102
- estimateTextWidth: (text: string, fontSize: number) => number;
103
- /** Predict the extra vertical height an item's wrapped label-chip
104
- * rows will add to its bar; used to size the row-packer's row
105
- * pitch ahead of the call to `sequenceItem`. */
106
- predictItemChipExtraHeight: (item: ItemDeclaration, ctx: LayoutContext) => number;
107
- }
108
-
109
70
  export interface SwimlaneNodeInput {
110
71
  lane: SwimlaneDeclaration;
111
72
  bandIndex: number;
@@ -129,30 +90,21 @@ const TAB_BOTTOM_Y = 38; // tab (height 22) plus 6 px breathing room
129
90
  const TAB_GUTTER_PX = 8;
130
91
 
131
92
  /**
132
- * Right edge (canvas px) of the lane title tab. Delegates to the
133
- * shared `frameTabGeometry` helper that the renderer also uses, so
134
- * the chiclet's collision footprint and its painted footprint stay
135
- * exactly in sync. Capacity badge and footnote-indicator widths are
136
- * added when present so the first-item collision math reserves
137
- * enough horizontal space for the (now-wider) chiclet.
93
+ * The lane's owner as the frame tab shows it: the person's or team's
94
+ * title (nested teams included), else the bare id, plus which glyph leads
95
+ * it. Undefined without `owner:`.
138
96
  */
139
- function computeLaneTabRightX(
97
+ function resolveLaneOwner(
140
98
  lane: SwimlaneDeclaration,
141
- capacityBadgeWidthPx: number,
142
- footnoteIndicatorWidthPx: number,
143
- ): number {
144
- const title = lane.title ?? lane.name ?? '';
145
- if (!title) return 0;
99
+ ctx: LayoutContext,
100
+ ): { text: string; kind: 'person' | 'team' } | undefined {
146
101
  const ownerRaw = propValue(lane.properties, 'owner');
147
- // Box is laid out at `box.x = 0` for top-level lanes; the helper
148
- // adds the standard `FRAME_TAB_OFFSET_FROM_BOX_PX` itself.
149
- return frameTabGeometry(
150
- 0,
151
- title,
152
- ownerRaw ?? undefined,
153
- capacityBadgeWidthPx,
154
- footnoteIndicatorWidthPx,
155
- ).rightX;
102
+ if (!ownerRaw) return undefined;
103
+ const team = ctx.teams.get(ownerRaw);
104
+ if (team) return { text: team.title ?? ownerRaw, kind: 'team' };
105
+ const person = ctx.persons.get(ownerRaw);
106
+ if (person) return { text: person.title ?? ownerRaw, kind: 'person' };
107
+ return { text: ownerRaw, kind: 'team' };
156
108
  }
157
109
 
158
110
  /**
@@ -184,14 +136,17 @@ function firstChildStartX(
184
136
  lane: SwimlaneDeclaration,
185
137
  laneLeftX: number,
186
138
  ctx: LayoutContext,
187
- deps: Pick<SwimlaneNodeDeps, 'resolveChildStart'>,
188
139
  ): number | undefined {
189
140
  for (const child of lane.content) {
190
141
  if (child.$type === 'DescriptionDirective') continue;
191
- const props = isItemDeclaration(child)
192
- ? child.properties
193
- : ((child as ParallelBlock | GroupBlock).properties ?? []);
194
- return deps.resolveChildStart(props, laneLeftX, laneLeftX, ctx);
142
+ // Snapped like the start it predicts (a no-op under `hide`).
143
+ return ctx.scale.startX(
144
+ resolveChildStart(
145
+ child as ItemDeclaration | GroupBlock | ParallelBlock,
146
+ laneLeftX,
147
+ ctx,
148
+ ),
149
+ );
195
150
  }
196
151
  return undefined;
197
152
  }
@@ -199,8 +154,7 @@ function firstChildStartX(
199
154
  export class SwimlaneNode {
200
155
  constructor(
201
156
  public readonly input: SwimlaneNodeInput,
202
- // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via `const { deps } = this` destructuring inside methods, which the analyzer does not detect.
203
- private readonly deps: SwimlaneNodeDeps,
157
+ private readonly helpers: LayoutHelpers,
204
158
  ) {}
205
159
 
206
160
  get id(): string {
@@ -209,7 +163,7 @@ export class SwimlaneNode {
209
163
 
210
164
  place(origin: SwimlaneOrigin, ctx: LayoutContext): PlacedSwimlaneGeometry {
211
165
  const { lane, bandIndex } = this.input;
212
- const { deps } = this;
166
+ const { helpers } = this;
213
167
  const style = resolveStyle('swimlane', lane.properties, ctx.styleCtx);
214
168
  const laneLeftX = origin.x;
215
169
  const step = ctx.bandScale.step();
@@ -234,153 +188,57 @@ export class SwimlaneNode {
234
188
  const footnoteIndicators = collectFootnoteIndicators(lane, ctx);
235
189
  const footnoteIndicatorWidthPx =
236
190
  footnoteIndicators.length > 0
237
- ? deps.estimateTextWidth(
238
- footnoteIndicators.join(','),
239
- LANE_CAPACITY_BADGE_FONT_SIZE_PX,
240
- )
191
+ ? estimateTextWidth(footnoteIndicators.join(','), LANE_CAPACITY_BADGE_FONT_SIZE_PX)
241
192
  : 0;
242
- // Title-tab geometry (mirrors the renderer; see renderSwimlane).
243
- const tabRightX = computeLaneTabRightX(lane, badgeWidthPx, footnoteIndicatorWidthPx);
193
+ // Title-tab geometry, computed once from the owner's display
194
+ // title: the collision box below and the chiclet the renderer
195
+ // draws (`PositionedSwimlane.tab`) are the same rectangle. Every
196
+ // lane box sits at x 0 (`box` below); finalize sets only widths.
197
+ const boxX = 0;
198
+ const owner = resolveLaneOwner(lane, ctx);
199
+ const title = lane.title ?? lane.name ?? '';
200
+ const tab = title
201
+ ? frameTabGeometry(boxX, title, owner?.text, badgeWidthPx, footnoteIndicatorWidthPx)
202
+ : null;
203
+ const tabRightX = tab?.rightX ?? 0;
244
204
  // First-row Y: when the first child's desired x is past the title
245
205
  // tab, top-align with the tab and reclaim ~28 px per lane.
246
206
  // Otherwise drop below the tab.
247
- const firstChildDesiredX = firstChildStartX(lane, laneLeftX, ctx, deps);
207
+ const firstChildDesiredX = firstChildStartX(lane, laneLeftX, ctx);
248
208
  const canAlignFirstRowWithTab =
249
209
  !lane.title ||
250
210
  firstChildDesiredX === undefined ||
251
211
  firstChildDesiredX >= tabRightX + TAB_GUTTER_PX;
252
212
  const startY = origin.y + (canAlignFirstRowWithTab ? TAB_TOP_Y : TAB_BOTTOM_Y);
253
213
 
254
- // Topmost-fit row pack. See `RowPacker` for the full contract.
255
- // The packer owns the rows; we feed it children in DSL order and
256
- // it returns each child's resolved (rowIndex, y).
257
- const packer = new RowPacker({
258
- laneLeftX,
259
- originY: startY,
260
- minRowHeight: step,
261
- slackCorridors: ctx.slackCorridors,
262
- });
263
- let timeCursorX = laneLeftX;
264
-
265
- const children: PositionedTrackChild[] = [];
266
- for (const child of lane.content) {
267
- if (child.$type === 'DescriptionDirective') continue;
268
-
269
- if (!isItemDeclaration(child)) {
270
- const blockProps = (child as ParallelBlock | GroupBlock).properties ?? [];
271
- const blockStart = deps.resolveChildStart(blockProps, timeCursorX, laneLeftX, ctx);
272
- const { rowIndex, y: blockY } = packer.placeBlock();
273
- const cursor = deps.newCursor(blockStart, blockY);
274
- const positioned = deps.sequenceOne(
275
- child as ItemDeclaration | GroupBlock | ParallelBlock,
276
- cursor,
277
- ctx,
278
- );
279
- children.push(positioned);
280
- const blockEnd = positioned.box.x + positioned.box.width;
281
- const blockHeight = Math.max(step, cursor.height);
282
- packer.commitBlock({
283
- rowIndex,
284
- placed: positioned,
285
- blockHeight,
286
- blockEnd,
287
- });
288
- timeCursorX = Math.max(timeCursorX, blockEnd);
289
- continue;
290
- }
291
-
292
- const props = (child as ItemDeclaration).properties;
293
- const desiredStart = deps.resolveChildStart(props, timeCursorX, laneLeftX, ctx);
294
- // Predict the item's logical extent so the row-pack can decide
295
- // BEFORE handing off to sequenceItem. The arithmetic mirrors
296
- // the duration → width math in `sequenceItem` (see
297
- // packages/layout/src/layout.ts).
298
- const durationDays = deriveItemDurationDays(props, ctx.sizes, ctx.cal);
299
- const naturalWidth = Math.max(MIN_ITEM_WIDTH, durationDays * ctx.timeline.pixelsPerDay);
300
- const desiredEnd = desiredStart + naturalWidth;
301
- const childId = (child as ItemDeclaration).name ?? '';
214
+ // The shared row-track loop (row-track.ts) packs the lane's
215
+ // children below the tab offset chosen above.
216
+ const track = sequenceRowTrack(lane, { leftX: laneLeftX, firstRowY: startY }, ctx, helpers);
217
+ const { children } = track;
302
218
 
303
- const chipExtra = deps.predictItemChipExtraHeight(child as ItemDeclaration, ctx);
304
- const predictedHeight = step + chipExtra;
305
- const { rowIndex, y: rowY } = packer.placeItem({
306
- childId,
307
- desiredStart,
308
- desiredEnd,
309
- // Row pitch = `step()` + extra chip-row height. Keeps
310
- // the inter-row visible gap (= step - bandwidth) intact
311
- // when an item's labels wrap and grow the bar.
312
- predictedHeight,
313
- });
314
-
315
- const cursor = deps.newCursor(desiredStart, rowY);
316
- const positioned = deps.sequenceItem(child as ItemDeclaration, cursor, ctx);
317
- children.push(positioned);
318
-
319
- // Item end in LOGICAL space (one ITEM_INSET_PX past the visual
320
- // bar's right edge). The next chained item starts here and
321
- // lands edge-to-edge in time, with a 2 × ITEM_INSET_PX visible
322
- // gutter between bars.
323
- const itemLogicalEnd = positioned.box.x + positioned.box.width + ITEM_INSET_PX;
324
- timeCursorX = Math.max(timeCursorX, itemLogicalEnd);
325
-
326
- let spillReservation: number | null = null;
327
- const hasAnySpill =
328
- positioned.textSpills ||
329
- positioned.chipsOutside ||
330
- positioned.dotSpills ||
331
- positioned.iconSpills ||
332
- positioned.footnoteSpills;
333
- if (hasAnySpill) {
334
- // `decorationsRightX` already aggregates the spilled
335
- // dot / icon / footnote / caption right edges from
336
- // `sequenceItem`; the chip column tracks separately
337
- // via `chipsRightX`. Add a 6-px buffer so the next
338
- // chained item's bar leaves a visible gutter past
339
- // the spilled cluster instead of butting against it.
340
- const farRight = Math.max(
341
- positioned.decorationsRightX,
342
- positioned.chipsOutside ? positioned.chipsRightX : 0,
343
- );
344
- spillReservation = farRight + 6;
345
- }
346
-
347
- packer.commitItem({
348
- rowIndex,
349
- placed: positioned,
350
- logicalEnd: itemLogicalEnd,
351
- spillReservation,
352
- rowHeight: predictedHeight,
353
- });
354
- }
355
-
356
- // `packer.usedHeight()` measures from `startY` (the first row,
219
+ // `track.packer.usedHeight()` measures from `startY` (the first row,
357
220
  // below the title tab); the swimlane band spans from `origin.y`
358
221
  // (the band top, above the tab). Add the tab offset back so the
359
222
  // band height covers everything the user sees.
360
223
  const tabOffset = startY - origin.y;
361
- const bandHeight = Math.max(step + 32, tabOffset + packer.usedHeight() + 16);
224
+ const bandHeight = Math.max(step + 32, tabOffset + track.packer.usedHeight() + 16);
362
225
  // Include the chiclet's right edge in the lane's reported
363
226
  // right-extent so empty lanes still report a non-zero right edge
364
227
  // — `buildIncludeRegions` uses this to size the include's dashed
365
228
  // bracket around its content, and a lane with only a chiclet
366
229
  // (no items) should still fit visibly inside that bracket.
367
- const usedRightX = Math.max(packer.usedRightX(), tabRightX);
230
+ const usedRightX = Math.max(track.packer.usedRightX(), tabRightX);
231
+ // The band spans the canvas, whose width is final only after the
232
+ // post-placement growth: finalize (`fitWidths`, f4) sets a main
233
+ // lane's width to it and `fitIncludeRegionWidth` a region lane's to
234
+ // the region's. Placement never reads the canvas.
368
235
  const box: BoundingBox = {
369
- x: 0,
236
+ x: boxX,
370
237
  y: origin.y,
371
- width: ctx.chartRightX,
238
+ width: 0,
372
239
  height: bandHeight,
373
240
  };
374
241
 
375
- // Owner display string: id → title for teams/people; falls back to id.
376
- const ownerRaw = propValue(lane.properties, 'owner');
377
- let ownerDisplay: string | undefined;
378
- if (ownerRaw) {
379
- const team = ctx.teams.get(ownerRaw);
380
- const person = ctx.persons.get(ownerRaw);
381
- ownerDisplay = team?.title ?? person?.title ?? ownerRaw;
382
- }
383
-
384
242
  // Lane utilization underline (m12). Computed *after* children are
385
243
  // positioned so the contributors carry their final box.x / box.width.
386
244
  // Returns null when the lane has no `capacity:`, no contributing
@@ -405,15 +263,17 @@ export class SwimlaneNode {
405
263
  return {
406
264
  positioned: {
407
265
  id: lane.name,
408
- title: lane.title ?? lane.name ?? '',
266
+ title,
409
267
  box,
410
268
  bandIndex,
411
269
  children,
412
270
  nested: [],
413
271
  style,
414
- owner: ownerDisplay,
272
+ owner: owner?.text,
273
+ ownerKind: owner?.kind,
415
274
  footnoteIndicators,
416
275
  capacity,
276
+ tab,
417
277
  utilization,
418
278
  },
419
279
  usedHeight: bandHeight,