@nowline/layout 0.8.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) 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/calendar-resolver.d.ts +27 -0
  6. package/dist/calendar-resolver.d.ts.map +1 -0
  7. package/dist/calendar-resolver.js +132 -0
  8. package/dist/calendar-resolver.js.map +1 -0
  9. package/dist/calendar.d.ts +0 -19
  10. package/dist/calendar.d.ts.map +1 -1
  11. package/dist/calendar.js +4 -44
  12. package/dist/calendar.js.map +1 -1
  13. package/dist/canvas-width.d.ts +23 -0
  14. package/dist/canvas-width.d.ts.map +1 -0
  15. package/dist/canvas-width.js +26 -0
  16. package/dist/canvas-width.js.map +1 -0
  17. package/dist/capacity.d.ts +28 -0
  18. package/dist/capacity.d.ts.map +1 -1
  19. package/dist/capacity.js +64 -29
  20. package/dist/capacity.js.map +1 -1
  21. package/dist/chart-translation.d.ts +22 -0
  22. package/dist/chart-translation.d.ts.map +1 -0
  23. package/dist/chart-translation.js +83 -0
  24. package/dist/chart-translation.js.map +1 -0
  25. package/dist/container-header-geometry.d.ts +64 -0
  26. package/dist/container-header-geometry.d.ts.map +1 -0
  27. package/dist/container-header-geometry.js +89 -0
  28. package/dist/container-header-geometry.js.map +1 -0
  29. package/dist/dsl-utils.d.ts +3 -4
  30. package/dist/dsl-utils.d.ts.map +1 -1
  31. package/dist/dsl-utils.js +8 -25
  32. package/dist/dsl-utils.js.map +1 -1
  33. package/dist/edge-routing.d.ts +69 -78
  34. package/dist/edge-routing.d.ts.map +1 -1
  35. package/dist/edge-routing.js +271 -401
  36. package/dist/edge-routing.js.map +1 -1
  37. package/dist/frame-tab-geometry.d.ts +24 -14
  38. package/dist/frame-tab-geometry.d.ts.map +1 -1
  39. package/dist/frame-tab-geometry.js +69 -23
  40. package/dist/frame-tab-geometry.js.map +1 -1
  41. package/dist/header-card-geometry.d.ts +12 -0
  42. package/dist/header-card-geometry.d.ts.map +1 -1
  43. package/dist/header-card-geometry.js +14 -2
  44. package/dist/header-card-geometry.js.map +1 -1
  45. package/dist/header-logo.d.ts +28 -0
  46. package/dist/header-logo.d.ts.map +1 -0
  47. package/dist/header-logo.js +51 -0
  48. package/dist/header-logo.js.map +1 -0
  49. package/dist/i18n.d.ts +11 -0
  50. package/dist/i18n.d.ts.map +1 -1
  51. package/dist/i18n.js +11 -1
  52. package/dist/i18n.js.map +1 -1
  53. package/dist/index.d.ts +20 -8
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +17 -7
  56. package/dist/index.js.map +1 -1
  57. package/dist/inline-date-pin-geometry.d.ts +19 -11
  58. package/dist/inline-date-pin-geometry.d.ts.map +1 -1
  59. package/dist/inline-date-pin-geometry.js +45 -46
  60. package/dist/inline-date-pin-geometry.js.map +1 -1
  61. package/dist/item-bar-geometry.d.ts +192 -4
  62. package/dist/item-bar-geometry.d.ts.map +1 -1
  63. package/dist/item-bar-geometry.js +258 -5
  64. package/dist/item-bar-geometry.js.map +1 -1
  65. package/dist/item-port-geometry.d.ts +13 -0
  66. package/dist/item-port-geometry.d.ts.map +1 -0
  67. package/dist/item-port-geometry.js +63 -0
  68. package/dist/item-port-geometry.js.map +1 -0
  69. package/dist/layout-context.d.ts +115 -51
  70. package/dist/layout-context.d.ts.map +1 -1
  71. package/dist/layout-context.js +96 -5
  72. package/dist/layout-context.js.map +1 -1
  73. package/dist/layout-insights.d.ts.map +1 -1
  74. package/dist/layout-insights.js +73 -1
  75. package/dist/layout-insights.js.map +1 -1
  76. package/dist/layout.d.ts +35 -0
  77. package/dist/layout.d.ts.map +1 -1
  78. package/dist/layout.js +18 -1315
  79. package/dist/layout.js.map +1 -1
  80. package/dist/nodes/anchor-node.d.ts.map +1 -1
  81. package/dist/nodes/anchor-node.js +11 -4
  82. package/dist/nodes/anchor-node.js.map +1 -1
  83. package/dist/nodes/content-extent.d.ts +24 -0
  84. package/dist/nodes/content-extent.d.ts.map +1 -0
  85. package/dist/nodes/content-extent.js +100 -0
  86. package/dist/nodes/content-extent.js.map +1 -0
  87. package/dist/nodes/dependency-edges.d.ts +6 -0
  88. package/dist/nodes/dependency-edges.d.ts.map +1 -0
  89. package/dist/nodes/dependency-edges.js +148 -0
  90. package/dist/nodes/dependency-edges.js.map +1 -0
  91. package/dist/nodes/footnote-node.d.ts +24 -3
  92. package/dist/nodes/footnote-node.d.ts.map +1 -1
  93. package/dist/nodes/footnote-node.js +129 -25
  94. package/dist/nodes/footnote-node.js.map +1 -1
  95. package/dist/nodes/group-node.d.ts +10 -21
  96. package/dist/nodes/group-node.d.ts.map +1 -1
  97. package/dist/nodes/group-node.js +79 -152
  98. package/dist/nodes/group-node.js.map +1 -1
  99. package/dist/nodes/header-node.d.ts +56 -0
  100. package/dist/nodes/header-node.d.ts.map +1 -0
  101. package/dist/nodes/header-node.js +163 -0
  102. package/dist/nodes/header-node.js.map +1 -0
  103. package/dist/nodes/include-node.d.ts +22 -12
  104. package/dist/nodes/include-node.d.ts.map +1 -1
  105. package/dist/nodes/include-node.js +95 -62
  106. package/dist/nodes/include-node.js.map +1 -1
  107. package/dist/nodes/item-measure.d.ts +135 -0
  108. package/dist/nodes/item-measure.d.ts.map +1 -0
  109. package/dist/nodes/item-measure.js +514 -0
  110. package/dist/nodes/item-measure.js.map +1 -0
  111. package/dist/nodes/item-node.d.ts +162 -9
  112. package/dist/nodes/item-node.d.ts.map +1 -1
  113. package/dist/nodes/item-node.js +256 -69
  114. package/dist/nodes/item-node.js.map +1 -1
  115. package/dist/nodes/marker-band.d.ts +118 -0
  116. package/dist/nodes/marker-band.d.ts.map +1 -0
  117. package/dist/nodes/marker-band.js +303 -0
  118. package/dist/nodes/marker-band.js.map +1 -0
  119. package/dist/nodes/marker-geometry.js +1 -1
  120. package/dist/nodes/marker-geometry.js.map +1 -1
  121. package/dist/nodes/milestone-node.d.ts +112 -15
  122. package/dist/nodes/milestone-node.d.ts.map +1 -1
  123. package/dist/nodes/milestone-node.js +246 -95
  124. package/dist/nodes/milestone-node.js.map +1 -1
  125. package/dist/nodes/nowline-node.d.ts +8 -0
  126. package/dist/nodes/nowline-node.d.ts.map +1 -0
  127. package/dist/nodes/nowline-node.js +74 -0
  128. package/dist/nodes/nowline-node.js.map +1 -0
  129. package/dist/nodes/parallel-node.d.ts +7 -12
  130. package/dist/nodes/parallel-node.d.ts.map +1 -1
  131. package/dist/nodes/parallel-node.js +66 -30
  132. package/dist/nodes/parallel-node.js.map +1 -1
  133. package/dist/nodes/region-markers.d.ts +41 -0
  134. package/dist/nodes/region-markers.d.ts.map +1 -0
  135. package/dist/nodes/region-markers.js +130 -0
  136. package/dist/nodes/region-markers.js.map +1 -0
  137. package/dist/nodes/roadmap-node.d.ts +9 -72
  138. package/dist/nodes/roadmap-node.d.ts.map +1 -1
  139. package/dist/nodes/roadmap-node.js +38 -788
  140. package/dist/nodes/roadmap-node.js.map +1 -1
  141. package/dist/nodes/row-track.d.ts +33 -0
  142. package/dist/nodes/row-track.d.ts.map +1 -0
  143. package/dist/nodes/row-track.js +170 -0
  144. package/dist/nodes/row-track.js.map +1 -0
  145. package/dist/nodes/sequence-item.d.ts +25 -0
  146. package/dist/nodes/sequence-item.d.ts.map +1 -0
  147. package/dist/nodes/sequence-item.js +396 -0
  148. package/dist/nodes/sequence-item.js.map +1 -0
  149. package/dist/nodes/sequence-one.d.ts +5 -0
  150. package/dist/nodes/sequence-one.d.ts.map +1 -0
  151. package/dist/nodes/sequence-one.js +26 -0
  152. package/dist/nodes/sequence-one.js.map +1 -0
  153. package/dist/nodes/swimlane-node.d.ts +5 -17
  154. package/dist/nodes/swimlane-node.d.ts.map +1 -1
  155. package/dist/nodes/swimlane-node.js +62 -145
  156. package/dist/nodes/swimlane-node.js.map +1 -1
  157. package/dist/nodes/wave-node.d.ts +52 -0
  158. package/dist/nodes/wave-node.d.ts.map +1 -0
  159. package/dist/nodes/wave-node.js +483 -0
  160. package/dist/nodes/wave-node.js.map +1 -0
  161. package/dist/non-working-display.d.ts +14 -0
  162. package/dist/non-working-display.d.ts.map +1 -0
  163. package/dist/non-working-display.js +28 -0
  164. package/dist/non-working-display.js.map +1 -0
  165. package/dist/phases/date-window.d.ts +48 -0
  166. package/dist/phases/date-window.d.ts.map +1 -0
  167. package/dist/phases/date-window.js +130 -0
  168. package/dist/phases/date-window.js.map +1 -0
  169. package/dist/phases/finalize.d.ts +198 -0
  170. package/dist/phases/finalize.d.ts.map +1 -0
  171. package/dist/phases/finalize.js +1058 -0
  172. package/dist/phases/finalize.js.map +1 -0
  173. package/dist/phases/measure.d.ts +77 -0
  174. package/dist/phases/measure.d.ts.map +1 -0
  175. package/dist/phases/measure.js +222 -0
  176. package/dist/phases/measure.js.map +1 -0
  177. package/dist/phases/place.d.ts +28 -0
  178. package/dist/phases/place.d.ts.map +1 -0
  179. package/dist/phases/place.js +225 -0
  180. package/dist/phases/place.js.map +1 -0
  181. package/dist/renderable.d.ts.map +1 -1
  182. package/dist/renderable.js +23 -3
  183. package/dist/renderable.js.map +1 -1
  184. package/dist/resolve-today.d.ts +11 -1
  185. package/dist/resolve-today.d.ts.map +1 -1
  186. package/dist/resolve-today.js +26 -12
  187. package/dist/resolve-today.js.map +1 -1
  188. package/dist/row-packer.d.ts +42 -16
  189. package/dist/row-packer.d.ts.map +1 -1
  190. package/dist/row-packer.js +51 -78
  191. package/dist/row-packer.js.map +1 -1
  192. package/dist/schedule.d.ts +58 -1
  193. package/dist/schedule.d.ts.map +1 -1
  194. package/dist/schedule.js +280 -106
  195. package/dist/schedule.js.map +1 -1
  196. package/dist/start-rules.d.ts +27 -0
  197. package/dist/start-rules.d.ts.map +1 -0
  198. package/dist/start-rules.js +91 -0
  199. package/dist/start-rules.js.map +1 -0
  200. package/dist/start-terms.d.ts +23 -0
  201. package/dist/start-terms.d.ts.map +1 -0
  202. package/dist/start-terms.js +27 -0
  203. package/dist/start-terms.js.map +1 -0
  204. package/dist/status-dot.d.ts +46 -0
  205. package/dist/status-dot.d.ts.map +1 -0
  206. package/dist/status-dot.js +78 -0
  207. package/dist/status-dot.js.map +1 -0
  208. package/dist/style-resolution.d.ts.map +1 -1
  209. package/dist/style-resolution.js +23 -43
  210. package/dist/style-resolution.js.map +1 -1
  211. package/dist/text-measure.d.ts +28 -0
  212. package/dist/text-measure.d.ts.map +1 -0
  213. package/dist/text-measure.js +70 -0
  214. package/dist/text-measure.js.map +1 -0
  215. package/dist/themes/contrast.d.ts +4 -0
  216. package/dist/themes/contrast.d.ts.map +1 -0
  217. package/dist/themes/contrast.js +32 -0
  218. package/dist/themes/contrast.js.map +1 -0
  219. package/dist/themes/dark.d.ts.map +1 -1
  220. package/dist/themes/dark.js +28 -1
  221. package/dist/themes/dark.js.map +1 -1
  222. package/dist/themes/grayscale.d.ts.map +1 -1
  223. package/dist/themes/grayscale.js +28 -1
  224. package/dist/themes/grayscale.js.map +1 -1
  225. package/dist/themes/index.d.ts +4 -11
  226. package/dist/themes/index.d.ts.map +1 -1
  227. package/dist/themes/index.js +6 -4
  228. package/dist/themes/index.js.map +1 -1
  229. package/dist/themes/light.d.ts.map +1 -1
  230. package/dist/themes/light.js +28 -1
  231. package/dist/themes/light.js.map +1 -1
  232. package/dist/themes/shape.d.ts +31 -0
  233. package/dist/themes/shape.d.ts.map +1 -1
  234. package/dist/themes/shared.d.ts +44 -50
  235. package/dist/themes/shared.d.ts.map +1 -1
  236. package/dist/themes/shared.js +79 -83
  237. package/dist/themes/shared.js.map +1 -1
  238. package/dist/time-scale.d.ts +42 -6
  239. package/dist/time-scale.d.ts.map +1 -1
  240. package/dist/time-scale.js +109 -17
  241. package/dist/time-scale.js.map +1 -1
  242. package/dist/types.d.ts +528 -11
  243. package/dist/types.d.ts.map +1 -1
  244. package/dist/view-preset.d.ts +61 -6
  245. package/dist/view-preset.d.ts.map +1 -1
  246. package/dist/view-preset.js +439 -24
  247. package/dist/view-preset.js.map +1 -1
  248. package/dist/wave-barrier.d.ts +107 -0
  249. package/dist/wave-barrier.d.ts.map +1 -0
  250. package/dist/wave-barrier.js +174 -0
  251. package/dist/wave-barrier.js.map +1 -0
  252. package/dist/wave-hatch.d.ts +4 -0
  253. package/dist/wave-hatch.d.ts.map +1 -0
  254. package/dist/wave-hatch.js +21 -0
  255. package/dist/wave-hatch.js.map +1 -0
  256. package/dist/wave-layout.d.ts +99 -0
  257. package/dist/wave-layout.d.ts.map +1 -0
  258. package/dist/wave-layout.js +130 -0
  259. package/dist/wave-layout.js.map +1 -0
  260. package/dist/working-calendar.d.ts +128 -4
  261. package/dist/working-calendar.d.ts.map +1 -1
  262. package/dist/working-calendar.js +725 -51
  263. package/dist/working-calendar.js.map +1 -1
  264. package/dist/working-span.d.ts +11 -0
  265. package/dist/working-span.d.ts.map +1 -0
  266. package/dist/working-span.js +18 -0
  267. package/dist/working-span.js.map +1 -0
  268. package/package.json +4 -4
  269. package/src/axis-columns.ts +140 -0
  270. package/src/calendar-resolver.ts +161 -0
  271. package/src/calendar.ts +4 -45
  272. package/src/canvas-width.ts +36 -0
  273. package/src/capacity.ts +71 -30
  274. package/src/chart-translation.ts +88 -0
  275. package/src/container-header-geometry.ts +116 -0
  276. package/src/dsl-utils.ts +8 -26
  277. package/src/edge-routing.ts +347 -491
  278. package/src/frame-tab-geometry.ts +78 -25
  279. package/src/header-card-geometry.ts +17 -2
  280. package/src/header-logo.ts +67 -0
  281. package/src/i18n.ts +22 -1
  282. package/src/index.ts +97 -12
  283. package/src/inline-date-pin-geometry.ts +55 -58
  284. package/src/item-bar-geometry.ts +341 -5
  285. package/src/item-port-geometry.ts +69 -0
  286. package/src/layout-context.ts +210 -72
  287. package/src/layout-insights.ts +109 -2
  288. package/src/layout.ts +57 -1530
  289. package/src/nodes/anchor-node.ts +11 -4
  290. package/src/nodes/content-extent.ts +107 -0
  291. package/src/nodes/dependency-edges.ts +171 -0
  292. package/src/nodes/footnote-node.ts +171 -31
  293. package/src/nodes/group-node.ts +92 -214
  294. package/src/nodes/header-node.ts +255 -0
  295. package/src/nodes/include-node.ts +124 -110
  296. package/src/nodes/item-measure.ts +723 -0
  297. package/src/nodes/item-node.ts +358 -80
  298. package/src/nodes/marker-band.ts +406 -0
  299. package/src/nodes/marker-geometry.ts +1 -1
  300. package/src/nodes/milestone-node.ts +321 -136
  301. package/src/nodes/nowline-node.ts +87 -0
  302. package/src/nodes/parallel-node.ts +69 -38
  303. package/src/nodes/region-markers.ts +194 -0
  304. package/src/nodes/roadmap-node.ts +46 -950
  305. package/src/nodes/row-track.ts +217 -0
  306. package/src/nodes/sequence-item.ts +433 -0
  307. package/src/nodes/sequence-one.ts +40 -0
  308. package/src/nodes/swimlane-node.ts +66 -205
  309. package/src/nodes/wave-node.ts +609 -0
  310. package/src/non-working-display.ts +32 -0
  311. package/src/phases/date-window.ts +191 -0
  312. package/src/phases/finalize.ts +1303 -0
  313. package/src/phases/measure.ts +329 -0
  314. package/src/phases/place.ts +318 -0
  315. package/src/renderable.ts +23 -3
  316. package/src/resolve-today.ts +28 -25
  317. package/src/row-packer.ts +85 -84
  318. package/src/schedule.ts +362 -114
  319. package/src/start-rules.ts +113 -0
  320. package/src/start-terms.ts +38 -0
  321. package/src/status-dot.ts +81 -0
  322. package/src/style-resolution.ts +27 -57
  323. package/src/text-measure.ts +71 -0
  324. package/src/themes/contrast.ts +34 -0
  325. package/src/themes/dark.ts +28 -1
  326. package/src/themes/grayscale.ts +28 -1
  327. package/src/themes/index.ts +10 -7
  328. package/src/themes/light.ts +28 -1
  329. package/src/themes/shape.ts +38 -0
  330. package/src/themes/shared.ts +85 -93
  331. package/src/time-scale.ts +117 -19
  332. package/src/types.ts +529 -20
  333. package/src/view-preset.ts +520 -27
  334. package/src/wave-barrier.ts +242 -0
  335. package/src/wave-hatch.ts +25 -0
  336. package/src/wave-layout.ts +203 -0
  337. package/src/working-calendar.ts +875 -57
  338. 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,7 +154,7 @@ function firstChildStartX(
199
154
  export class SwimlaneNode {
200
155
  constructor(
201
156
  public readonly input: SwimlaneNodeInput,
202
- private readonly deps: SwimlaneNodeDeps,
157
+ private readonly helpers: LayoutHelpers,
203
158
  ) {}
204
159
 
205
160
  get id(): string {
@@ -208,7 +163,7 @@ export class SwimlaneNode {
208
163
 
209
164
  place(origin: SwimlaneOrigin, ctx: LayoutContext): PlacedSwimlaneGeometry {
210
165
  const { lane, bandIndex } = this.input;
211
- const { deps } = this;
166
+ const { helpers } = this;
212
167
  const style = resolveStyle('swimlane', lane.properties, ctx.styleCtx);
213
168
  const laneLeftX = origin.x;
214
169
  const step = ctx.bandScale.step();
@@ -233,153 +188,57 @@ export class SwimlaneNode {
233
188
  const footnoteIndicators = collectFootnoteIndicators(lane, ctx);
234
189
  const footnoteIndicatorWidthPx =
235
190
  footnoteIndicators.length > 0
236
- ? deps.estimateTextWidth(
237
- footnoteIndicators.join(','),
238
- LANE_CAPACITY_BADGE_FONT_SIZE_PX,
239
- )
191
+ ? estimateTextWidth(footnoteIndicators.join(','), LANE_CAPACITY_BADGE_FONT_SIZE_PX)
240
192
  : 0;
241
- // Title-tab geometry (mirrors the renderer; see renderSwimlane).
242
- 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;
243
204
  // First-row Y: when the first child's desired x is past the title
244
205
  // tab, top-align with the tab and reclaim ~28 px per lane.
245
206
  // Otherwise drop below the tab.
246
- const firstChildDesiredX = firstChildStartX(lane, laneLeftX, ctx, deps);
207
+ const firstChildDesiredX = firstChildStartX(lane, laneLeftX, ctx);
247
208
  const canAlignFirstRowWithTab =
248
209
  !lane.title ||
249
210
  firstChildDesiredX === undefined ||
250
211
  firstChildDesiredX >= tabRightX + TAB_GUTTER_PX;
251
212
  const startY = origin.y + (canAlignFirstRowWithTab ? TAB_TOP_Y : TAB_BOTTOM_Y);
252
213
 
253
- // Topmost-fit row pack. See `RowPacker` for the full contract.
254
- // The packer owns the rows; we feed it children in DSL order and
255
- // it returns each child's resolved (rowIndex, y).
256
- const packer = new RowPacker({
257
- laneLeftX,
258
- originY: startY,
259
- minRowHeight: step,
260
- slackCorridors: ctx.slackCorridors,
261
- });
262
- let timeCursorX = laneLeftX;
263
-
264
- const children: PositionedTrackChild[] = [];
265
- for (const child of lane.content) {
266
- if (child.$type === 'DescriptionDirective') continue;
267
-
268
- if (!isItemDeclaration(child)) {
269
- const blockProps = (child as ParallelBlock | GroupBlock).properties ?? [];
270
- const blockStart = deps.resolveChildStart(blockProps, timeCursorX, laneLeftX, ctx);
271
- const { rowIndex, y: blockY } = packer.placeBlock();
272
- const cursor = deps.newCursor(blockStart, blockY);
273
- const positioned = deps.sequenceOne(
274
- child as ItemDeclaration | GroupBlock | ParallelBlock,
275
- cursor,
276
- ctx,
277
- );
278
- children.push(positioned);
279
- const blockEnd = positioned.box.x + positioned.box.width;
280
- const blockHeight = Math.max(step, cursor.height);
281
- packer.commitBlock({
282
- rowIndex,
283
- placed: positioned,
284
- blockHeight,
285
- blockEnd,
286
- });
287
- timeCursorX = Math.max(timeCursorX, blockEnd);
288
- continue;
289
- }
290
-
291
- const props = (child as ItemDeclaration).properties;
292
- const desiredStart = deps.resolveChildStart(props, timeCursorX, laneLeftX, ctx);
293
- // Predict the item's logical extent so the row-pack can decide
294
- // BEFORE handing off to sequenceItem. The arithmetic mirrors
295
- // the duration → width math in `sequenceItem` (see
296
- // packages/layout/src/layout.ts).
297
- const durationDays = deriveItemDurationDays(props, ctx.sizes, ctx.cal);
298
- const naturalWidth = Math.max(MIN_ITEM_WIDTH, durationDays * ctx.timeline.pixelsPerDay);
299
- const desiredEnd = desiredStart + naturalWidth;
300
- 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;
301
218
 
302
- const chipExtra = deps.predictItemChipExtraHeight(child as ItemDeclaration, ctx);
303
- const predictedHeight = step + chipExtra;
304
- const { rowIndex, y: rowY } = packer.placeItem({
305
- childId,
306
- desiredStart,
307
- desiredEnd,
308
- // Row pitch = `step()` + extra chip-row height. Keeps
309
- // the inter-row visible gap (= step - bandwidth) intact
310
- // when an item's labels wrap and grow the bar.
311
- predictedHeight,
312
- });
313
-
314
- const cursor = deps.newCursor(desiredStart, rowY);
315
- const positioned = deps.sequenceItem(child as ItemDeclaration, cursor, ctx);
316
- children.push(positioned);
317
-
318
- // Item end in LOGICAL space (one ITEM_INSET_PX past the visual
319
- // bar's right edge). The next chained item starts here and
320
- // lands edge-to-edge in time, with a 2 × ITEM_INSET_PX visible
321
- // gutter between bars.
322
- const itemLogicalEnd = positioned.box.x + positioned.box.width + ITEM_INSET_PX;
323
- timeCursorX = Math.max(timeCursorX, itemLogicalEnd);
324
-
325
- let spillReservation: number | null = null;
326
- const hasAnySpill =
327
- positioned.textSpills ||
328
- positioned.chipsOutside ||
329
- positioned.dotSpills ||
330
- positioned.iconSpills ||
331
- positioned.footnoteSpills;
332
- if (hasAnySpill) {
333
- // `decorationsRightX` already aggregates the spilled
334
- // dot / icon / footnote / caption right edges from
335
- // `sequenceItem`; the chip column tracks separately
336
- // via `chipsRightX`. Add a 6-px buffer so the next
337
- // chained item's bar leaves a visible gutter past
338
- // the spilled cluster instead of butting against it.
339
- const farRight = Math.max(
340
- positioned.decorationsRightX,
341
- positioned.chipsOutside ? positioned.chipsRightX : 0,
342
- );
343
- spillReservation = farRight + 6;
344
- }
345
-
346
- packer.commitItem({
347
- rowIndex,
348
- placed: positioned,
349
- logicalEnd: itemLogicalEnd,
350
- spillReservation,
351
- rowHeight: predictedHeight,
352
- });
353
- }
354
-
355
- // `packer.usedHeight()` measures from `startY` (the first row,
219
+ // `track.packer.usedHeight()` measures from `startY` (the first row,
356
220
  // below the title tab); the swimlane band spans from `origin.y`
357
221
  // (the band top, above the tab). Add the tab offset back so the
358
222
  // band height covers everything the user sees.
359
223
  const tabOffset = startY - origin.y;
360
- const bandHeight = Math.max(step + 32, tabOffset + packer.usedHeight() + 16);
224
+ const bandHeight = Math.max(step + 32, tabOffset + track.packer.usedHeight() + 16);
361
225
  // Include the chiclet's right edge in the lane's reported
362
226
  // right-extent so empty lanes still report a non-zero right edge
363
227
  // — `buildIncludeRegions` uses this to size the include's dashed
364
228
  // bracket around its content, and a lane with only a chiclet
365
229
  // (no items) should still fit visibly inside that bracket.
366
- 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.
367
235
  const box: BoundingBox = {
368
- x: 0,
236
+ x: boxX,
369
237
  y: origin.y,
370
- width: ctx.chartRightX,
238
+ width: 0,
371
239
  height: bandHeight,
372
240
  };
373
241
 
374
- // Owner display string: id → title for teams/people; falls back to id.
375
- const ownerRaw = propValue(lane.properties, 'owner');
376
- let ownerDisplay: string | undefined;
377
- if (ownerRaw) {
378
- const team = ctx.teams.get(ownerRaw);
379
- const person = ctx.persons.get(ownerRaw);
380
- ownerDisplay = team?.title ?? person?.title ?? ownerRaw;
381
- }
382
-
383
242
  // Lane utilization underline (m12). Computed *after* children are
384
243
  // positioned so the contributors carry their final box.x / box.width.
385
244
  // Returns null when the lane has no `capacity:`, no contributing
@@ -404,15 +263,17 @@ export class SwimlaneNode {
404
263
  return {
405
264
  positioned: {
406
265
  id: lane.name,
407
- title: lane.title ?? lane.name ?? '',
266
+ title,
408
267
  box,
409
268
  bandIndex,
410
269
  children,
411
270
  nested: [],
412
271
  style,
413
- owner: ownerDisplay,
272
+ owner: owner?.text,
273
+ ownerKind: owner?.kind,
414
274
  footnoteIndicators,
415
275
  capacity,
276
+ tab,
416
277
  utilization,
417
278
  },
418
279
  usedHeight: bandHeight,