@nowline/layout 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +103 -0
- package/dist/band-scale.d.ts +56 -0
- package/dist/band-scale.d.ts.map +1 -0
- package/dist/band-scale.js +86 -0
- package/dist/band-scale.js.map +1 -0
- package/dist/calendar.d.ts +79 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +210 -0
- package/dist/calendar.js.map +1 -0
- package/dist/capacity.d.ts +72 -0
- package/dist/capacity.d.ts.map +1 -0
- package/dist/capacity.js +163 -0
- package/dist/capacity.js.map +1 -0
- package/dist/dsl-utils.d.ts +5 -0
- package/dist/dsl-utils.d.ts.map +1 -0
- package/dist/dsl-utils.js +28 -0
- package/dist/dsl-utils.js.map +1 -0
- package/dist/edge-routing.d.ts +89 -0
- package/dist/edge-routing.d.ts.map +1 -0
- package/dist/edge-routing.js +435 -0
- package/dist/edge-routing.js.map +1 -0
- package/dist/frame-tab-geometry.d.ts +78 -0
- package/dist/frame-tab-geometry.d.ts.map +1 -0
- package/dist/frame-tab-geometry.js +115 -0
- package/dist/frame-tab-geometry.js.map +1 -0
- package/dist/header-card-geometry.d.ts +29 -0
- package/dist/header-card-geometry.d.ts.map +1 -0
- package/dist/header-card-geometry.js +41 -0
- package/dist/header-card-geometry.js.map +1 -0
- package/dist/i18n.d.ts +48 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +114 -0
- package/dist/i18n.js.map +1 -0
- package/dist/include-chrome-geometry.d.ts +86 -0
- package/dist/include-chrome-geometry.d.ts.map +1 -0
- package/dist/include-chrome-geometry.js +104 -0
- package/dist/include-chrome-geometry.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/item-bar-geometry.d.ts +127 -0
- package/dist/item-bar-geometry.d.ts.map +1 -0
- package/dist/item-bar-geometry.js +173 -0
- package/dist/item-bar-geometry.js.map +1 -0
- package/dist/lane-utilization.d.ts +90 -0
- package/dist/lane-utilization.d.ts.map +1 -0
- package/dist/lane-utilization.js +206 -0
- package/dist/lane-utilization.js.map +1 -0
- package/dist/layout-context.d.ts +143 -0
- package/dist/layout-context.d.ts.map +1 -0
- package/dist/layout-context.js +8 -0
- package/dist/layout-context.js.map +1 -0
- package/dist/layout.d.ts +18 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +1213 -0
- package/dist/layout.js.map +1 -0
- package/dist/nodes/anchor-node.d.ts +16 -0
- package/dist/nodes/anchor-node.d.ts.map +1 -0
- package/dist/nodes/anchor-node.js +68 -0
- package/dist/nodes/anchor-node.js.map +1 -0
- package/dist/nodes/footnote-node.d.ts +10 -0
- package/dist/nodes/footnote-node.d.ts.map +1 -0
- package/dist/nodes/footnote-node.js +41 -0
- package/dist/nodes/footnote-node.js.map +1 -0
- package/dist/nodes/group-node.d.ts +29 -0
- package/dist/nodes/group-node.d.ts.map +1 -0
- package/dist/nodes/group-node.js +187 -0
- package/dist/nodes/group-node.js.map +1 -0
- package/dist/nodes/include-node.d.ts +16 -0
- package/dist/nodes/include-node.d.ts.map +1 -0
- package/dist/nodes/include-node.js +117 -0
- package/dist/nodes/include-node.js.map +1 -0
- package/dist/nodes/item-node.d.ts +51 -0
- package/dist/nodes/item-node.d.ts.map +1 -0
- package/dist/nodes/item-node.js +108 -0
- package/dist/nodes/item-node.js.map +1 -0
- package/dist/nodes/marker-geometry.d.ts +22 -0
- package/dist/nodes/marker-geometry.d.ts.map +1 -0
- package/dist/nodes/marker-geometry.js +38 -0
- package/dist/nodes/marker-geometry.js.map +1 -0
- package/dist/nodes/milestone-node.d.ts +48 -0
- package/dist/nodes/milestone-node.d.ts.map +1 -0
- package/dist/nodes/milestone-node.js +210 -0
- package/dist/nodes/milestone-node.js.map +1 -0
- package/dist/nodes/parallel-node.d.ts +21 -0
- package/dist/nodes/parallel-node.d.ts.map +1 -0
- package/dist/nodes/parallel-node.js +80 -0
- package/dist/nodes/parallel-node.js.map +1 -0
- package/dist/nodes/roadmap-node.d.ts +76 -0
- package/dist/nodes/roadmap-node.d.ts.map +1 -0
- package/dist/nodes/roadmap-node.js +788 -0
- package/dist/nodes/roadmap-node.js.map +1 -0
- package/dist/nodes/swimlane-node.d.ts +38 -0
- package/dist/nodes/swimlane-node.d.ts.map +1 -0
- package/dist/nodes/swimlane-node.js +308 -0
- package/dist/nodes/swimlane-node.js.map +1 -0
- package/dist/renderable.d.ts +44 -0
- package/dist/renderable.d.ts.map +1 -0
- package/dist/renderable.js +21 -0
- package/dist/renderable.js.map +1 -0
- package/dist/row-packer.d.ts +125 -0
- package/dist/row-packer.d.ts.map +1 -0
- package/dist/row-packer.js +169 -0
- package/dist/row-packer.js.map +1 -0
- package/dist/style-resolution.d.ts +14 -0
- package/dist/style-resolution.d.ts.map +1 -0
- package/dist/style-resolution.js +191 -0
- package/dist/style-resolution.js.map +1 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +241 -0
- package/dist/themes/dark.js.map +1 -0
- package/dist/themes/index.d.ts +15 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +30 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/light.d.ts +4 -0
- package/dist/themes/light.d.ts.map +1 -0
- package/dist/themes/light.js +248 -0
- package/dist/themes/light.js.map +1 -0
- package/dist/themes/shape.d.ts +194 -0
- package/dist/themes/shape.d.ts.map +1 -0
- package/dist/themes/shape.js +6 -0
- package/dist/themes/shape.js.map +1 -0
- package/dist/themes/shared.d.ts +145 -0
- package/dist/themes/shared.d.ts.map +1 -0
- package/dist/themes/shared.js +310 -0
- package/dist/themes/shared.js.map +1 -0
- package/dist/time-scale.d.ts +39 -0
- package/dist/time-scale.d.ts.map +1 -0
- package/dist/time-scale.js +62 -0
- package/dist/time-scale.js.map +1 -0
- package/dist/types.d.ts +483 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/view-preset.d.ts +23 -0
- package/dist/view-preset.d.ts.map +1 -0
- package/dist/view-preset.js +146 -0
- package/dist/view-preset.js.map +1 -0
- package/dist/working-calendar.d.ts +14 -0
- package/dist/working-calendar.d.ts.map +1 -0
- package/dist/working-calendar.js +74 -0
- package/dist/working-calendar.js.map +1 -0
- package/package.json +37 -0
- package/src/band-scale.ts +115 -0
- package/src/calendar.ts +244 -0
- package/src/capacity.ts +191 -0
- package/src/dsl-utils.ts +30 -0
- package/src/edge-routing.ts +550 -0
- package/src/frame-tab-geometry.ts +165 -0
- package/src/header-card-geometry.ts +48 -0
- package/src/i18n.ts +124 -0
- package/src/include-chrome-geometry.ts +156 -0
- package/src/index.ts +116 -0
- package/src/item-bar-geometry.ts +222 -0
- package/src/lane-utilization.ts +259 -0
- package/src/layout-context.ts +182 -0
- package/src/layout.ts +1446 -0
- package/src/nodes/anchor-node.ts +77 -0
- package/src/nodes/footnote-node.ts +60 -0
- package/src/nodes/group-node.ts +252 -0
- package/src/nodes/include-node.ts +168 -0
- package/src/nodes/item-node.ts +171 -0
- package/src/nodes/marker-geometry.ts +43 -0
- package/src/nodes/milestone-node.ts +263 -0
- package/src/nodes/parallel-node.ts +101 -0
- package/src/nodes/roadmap-node.ts +957 -0
- package/src/nodes/swimlane-node.ts +423 -0
- package/src/renderable.ts +68 -0
- package/src/row-packer.ts +271 -0
- package/src/style-resolution.ts +243 -0
- package/src/themes/dark.ts +244 -0
- package/src/themes/index.ts +36 -0
- package/src/themes/light.ts +251 -0
- package/src/themes/shape.ts +230 -0
- package/src/themes/shared.ts +369 -0
- package/src/time-scale.ts +78 -0
- package/src/types.ts +607 -0
- package/src/view-preset.ts +180 -0
- package/src/working-calendar.ts +91 -0
package/src/capacity.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Capacity parsing, number formatting, and capacity-icon resolution helpers.
|
|
2
|
+
//
|
|
3
|
+
// Layout owns the *contract* the renderer reads:
|
|
4
|
+
//
|
|
5
|
+
// - `parseCapacityValue` turns the DSL's three numeric forms (`5`, `0.5`,
|
|
6
|
+
// `50%`) into a single positive number โ percent literals are syntactic
|
|
7
|
+
// sugar for decimals (`50%` โ `0.5`) per specs/dsl.md ยง Capacity.
|
|
8
|
+
// - `formatCapacityNumber` produces the spec's display string: integers
|
|
9
|
+
// stay integers (`5`, not `5.0`); decimals trim trailing zeros (`0.5`,
|
|
10
|
+
// `1.25`).
|
|
11
|
+
// - `resolveCapacityIcon` collapses the three syntactic forms of the
|
|
12
|
+
// `capacity-icon:` style property (built-in name, custom `symbol` id,
|
|
13
|
+
// inline Unicode literal) into either a built-in name the renderer
|
|
14
|
+
// recognizes OR a literal string the renderer paints as text. Custom
|
|
15
|
+
// symbol ids are dereferenced via `ResolvedConfig.symbols` here so the
|
|
16
|
+
// renderer never has to walk the config map.
|
|
17
|
+
//
|
|
18
|
+
// All three helpers are pure โ no AST, no theme, no side effects โ so they
|
|
19
|
+
// can be tested in isolation and reused by future capacity consumers (e.g.
|
|
20
|
+
// the lane badge in m7 and the overload sweep in m8).
|
|
21
|
+
|
|
22
|
+
import type { SymbolDeclaration } from '@nowline/core';
|
|
23
|
+
import type { ResolvedCapacityIconRef } from './types.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
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.
|
|
32
|
+
*/
|
|
33
|
+
const BUILTIN_CAPACITY_ICONS = new Set<string>([
|
|
34
|
+
'none',
|
|
35
|
+
'multiplier',
|
|
36
|
+
'person',
|
|
37
|
+
'people',
|
|
38
|
+
'points',
|
|
39
|
+
'time',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const POSITIVE_INT_RE = /^\d+$/;
|
|
43
|
+
const POSITIVE_DECIMAL_RE = /^\d+\.\d+$/;
|
|
44
|
+
const POSITIVE_PERCENT_RE = /^\d+(?:\.\d+)?%$/;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse a `capacity:` value. Accepts the three forms the validator allows on
|
|
48
|
+
* items (positive int, positive decimal, positive percent) โ swimlanes only
|
|
49
|
+
* allow int/decimal but the validator already rejects percent on lanes, so a
|
|
50
|
+
* single parser is fine here.
|
|
51
|
+
*
|
|
52
|
+
* Returns `null` when the value is missing, malformed, or non-positive. The
|
|
53
|
+
* renderer must not draw a capacity suffix / badge in that case (per spec
|
|
54
|
+
* "the suffix appears only when the resolved capacity is `> 0`").
|
|
55
|
+
*/
|
|
56
|
+
export function parseCapacityValue(raw: string | undefined): number | null {
|
|
57
|
+
if (!raw) return null;
|
|
58
|
+
if (POSITIVE_INT_RE.test(raw)) {
|
|
59
|
+
const n = parseInt(raw, 10);
|
|
60
|
+
return n > 0 ? n : null;
|
|
61
|
+
}
|
|
62
|
+
if (POSITIVE_DECIMAL_RE.test(raw)) {
|
|
63
|
+
const n = parseFloat(raw);
|
|
64
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
65
|
+
}
|
|
66
|
+
if (POSITIVE_PERCENT_RE.test(raw)) {
|
|
67
|
+
const n = parseFloat(raw.slice(0, -1)) / 100;
|
|
68
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Format a parsed capacity number for display per specs/rendering.md ยง
|
|
75
|
+
* "Number formatting": integers render as integers (`5`, not `5.0`); decimals
|
|
76
|
+
* render with trailing zeros trimmed (`0.5`, `1.25`).
|
|
77
|
+
*
|
|
78
|
+
* The `toFixed(6)` cap guards against `0.1 + 0.2`-style float noise creeping
|
|
79
|
+
* into the rendered string. Six is more than the DSL grammar admits anyway โ
|
|
80
|
+
* `1.234567%` lexes but is far below the granularity any roadmap author cares
|
|
81
|
+
* about, so trimming there is safe.
|
|
82
|
+
*/
|
|
83
|
+
export function formatCapacityNumber(value: number): string {
|
|
84
|
+
if (Number.isInteger(value)) return String(value);
|
|
85
|
+
let s = value.toFixed(6);
|
|
86
|
+
s = s.replace(/0+$/, '');
|
|
87
|
+
s = s.replace(/\.$/, '');
|
|
88
|
+
return s;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolved capacity-icon ready for the renderer. Re-exported from
|
|
93
|
+
* `./types.js` so callers can `import { ResolvedCapacityIcon } from
|
|
94
|
+
* './capacity.js'` without reaching into the positioned-model module.
|
|
95
|
+
*
|
|
96
|
+
* Two flavors:
|
|
97
|
+
*
|
|
98
|
+
* - `kind: 'builtin'` โ the renderer looks up its SVG (person/people/
|
|
99
|
+
* points/time) or text representation (multiplier) via its icon library.
|
|
100
|
+
* `'none'` is collapsed to `null` upstream (no glyph rendered).
|
|
101
|
+
* - `kind: 'literal'` โ the renderer paints `text` as a `<text>` node.
|
|
102
|
+
* Covers inline Unicode literals (`capacity-icon:"๐ฐ"`) and dereferenced
|
|
103
|
+
* custom `symbol` declarations.
|
|
104
|
+
*/
|
|
105
|
+
export type ResolvedCapacityIcon = ResolvedCapacityIconRef;
|
|
106
|
+
|
|
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
|
+
function symbolUnicode(decl: SymbolDeclaration): string | undefined {
|
|
118
|
+
for (const p of decl.properties) {
|
|
119
|
+
if (propKey(p) === 'unicode' && p.value) return p.value;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve a `capacity-icon:` style value into a `ResolvedCapacityIcon` (or
|
|
126
|
+
* `null` for `'none'`).
|
|
127
|
+
*
|
|
128
|
+
* Resolution order matches specs/dsl.md ยง Style Properties for `icon:` and
|
|
129
|
+
* `capacity-icon:`:
|
|
130
|
+
*
|
|
131
|
+
* 1. `'none'` โ `null` (renderer emits no glyph).
|
|
132
|
+
* 2. Built-in name โ `{ kind: 'builtin', name }`.
|
|
133
|
+
* 3. Custom symbol id present in `symbols` โ `{ kind: 'literal', text:
|
|
134
|
+
* <unicode:> }`. The author wrote an identifier; we hand the
|
|
135
|
+
* renderer the underlying Unicode payload.
|
|
136
|
+
* 4. Anything else โ `{ kind: 'literal', text: icon }`. This is the inline
|
|
137
|
+
* Unicode literal form (`capacity-icon:"๐ฐ"`) โ Langium's
|
|
138
|
+
* ValueConverter has already stripped the surrounding quotes, so the
|
|
139
|
+
* raw payload arrives here.
|
|
140
|
+
*
|
|
141
|
+
* Validator rule 17 already rejects malformed combinations (unknown built-in
|
|
142
|
+
* with no matching symbol, symbol id collision with built-ins, etc.), so this
|
|
143
|
+
* function trusts its input shape.
|
|
144
|
+
*/
|
|
145
|
+
export function resolveCapacityIcon(
|
|
146
|
+
icon: string,
|
|
147
|
+
symbols: Map<string, SymbolDeclaration>,
|
|
148
|
+
): ResolvedCapacityIcon | null {
|
|
149
|
+
if (icon === 'none') return null;
|
|
150
|
+
if (BUILTIN_CAPACITY_ICONS.has(icon)) {
|
|
151
|
+
return {
|
|
152
|
+
kind: 'builtin',
|
|
153
|
+
name: icon as 'multiplier' | 'person' | 'people' | 'points' | 'time',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const custom = symbols.get(icon);
|
|
157
|
+
if (custom) {
|
|
158
|
+
const unicode = symbolUnicode(custom);
|
|
159
|
+
return { kind: 'literal', text: unicode ?? icon };
|
|
160
|
+
}
|
|
161
|
+
return { kind: 'literal', text: icon };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Estimate the on-screen width (px) the capacity suffix will occupy at the
|
|
166
|
+
* given font size, including the leading separator gap before the glyph.
|
|
167
|
+
*
|
|
168
|
+
* The renderer paints the suffix as `<num>{gap}<glyph>` (no leading space
|
|
169
|
+
* before the number โ callers handle that as an outer separator). Width
|
|
170
|
+
* estimates are intentionally pessimistic so borderline-fitting captions
|
|
171
|
+
* trigger spill rather than clip.
|
|
172
|
+
*/
|
|
173
|
+
export function estimateCapacitySuffixWidth(
|
|
174
|
+
text: string,
|
|
175
|
+
icon: ResolvedCapacityIcon | null,
|
|
176
|
+
fontSizePx: number,
|
|
177
|
+
): number {
|
|
178
|
+
if (!icon) return text.length * fontSizePx * 0.58;
|
|
179
|
+
if (icon.kind === 'builtin' && icon.name === 'multiplier') {
|
|
180
|
+
// Multiplier is a typographic operator with built-in side bearing โ
|
|
181
|
+
// no separator gap, glyph width approx. one character.
|
|
182
|
+
return (text.length + 1) * fontSizePx * 0.58;
|
|
183
|
+
}
|
|
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.
|
|
188
|
+
const glyphWidthEm = 1.0;
|
|
189
|
+
const gapEm = 0.1;
|
|
190
|
+
return text.length * fontSizePx * 0.58 + (gapEm + glyphWidthEm) * fontSizePx;
|
|
191
|
+
}
|
package/src/dsl-utils.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
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.
|
|
7
|
+
|
|
8
|
+
import type { EntityProperty } from '@nowline/core';
|
|
9
|
+
|
|
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;
|
|
30
|
+
}
|