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