@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.
- package/dist/axis-columns.d.ts +70 -0
- package/dist/axis-columns.d.ts.map +1 -0
- package/dist/axis-columns.js +104 -0
- package/dist/axis-columns.js.map +1 -0
- package/dist/calendar-resolver.d.ts +27 -0
- package/dist/calendar-resolver.d.ts.map +1 -0
- package/dist/calendar-resolver.js +132 -0
- package/dist/calendar-resolver.js.map +1 -0
- package/dist/calendar.d.ts +0 -19
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +4 -44
- package/dist/calendar.js.map +1 -1
- package/dist/canvas-width.d.ts +23 -0
- package/dist/canvas-width.d.ts.map +1 -0
- package/dist/canvas-width.js +26 -0
- package/dist/canvas-width.js.map +1 -0
- package/dist/capacity.d.ts +28 -0
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +64 -29
- package/dist/capacity.js.map +1 -1
- package/dist/chart-translation.d.ts +22 -0
- package/dist/chart-translation.d.ts.map +1 -0
- package/dist/chart-translation.js +83 -0
- package/dist/chart-translation.js.map +1 -0
- package/dist/container-header-geometry.d.ts +64 -0
- package/dist/container-header-geometry.d.ts.map +1 -0
- package/dist/container-header-geometry.js +89 -0
- package/dist/container-header-geometry.js.map +1 -0
- package/dist/dsl-utils.d.ts +3 -4
- package/dist/dsl-utils.d.ts.map +1 -1
- package/dist/dsl-utils.js +8 -25
- package/dist/dsl-utils.js.map +1 -1
- package/dist/edge-routing.d.ts +69 -78
- package/dist/edge-routing.d.ts.map +1 -1
- package/dist/edge-routing.js +271 -401
- package/dist/edge-routing.js.map +1 -1
- package/dist/frame-tab-geometry.d.ts +24 -14
- package/dist/frame-tab-geometry.d.ts.map +1 -1
- package/dist/frame-tab-geometry.js +69 -23
- package/dist/frame-tab-geometry.js.map +1 -1
- package/dist/header-card-geometry.d.ts +12 -0
- package/dist/header-card-geometry.d.ts.map +1 -1
- package/dist/header-card-geometry.js +14 -2
- package/dist/header-card-geometry.js.map +1 -1
- package/dist/header-logo.d.ts +28 -0
- package/dist/header-logo.d.ts.map +1 -0
- package/dist/header-logo.js +51 -0
- package/dist/header-logo.js.map +1 -0
- package/dist/i18n.d.ts +11 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +11 -1
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/inline-date-pin-geometry.d.ts +19 -11
- package/dist/inline-date-pin-geometry.d.ts.map +1 -1
- package/dist/inline-date-pin-geometry.js +45 -46
- package/dist/inline-date-pin-geometry.js.map +1 -1
- package/dist/item-bar-geometry.d.ts +192 -4
- package/dist/item-bar-geometry.d.ts.map +1 -1
- package/dist/item-bar-geometry.js +258 -5
- package/dist/item-bar-geometry.js.map +1 -1
- package/dist/item-port-geometry.d.ts +13 -0
- package/dist/item-port-geometry.d.ts.map +1 -0
- package/dist/item-port-geometry.js +63 -0
- package/dist/item-port-geometry.js.map +1 -0
- package/dist/layout-context.d.ts +115 -51
- package/dist/layout-context.d.ts.map +1 -1
- package/dist/layout-context.js +96 -5
- package/dist/layout-context.js.map +1 -1
- package/dist/layout-insights.d.ts.map +1 -1
- package/dist/layout-insights.js +73 -1
- package/dist/layout-insights.js.map +1 -1
- package/dist/layout.d.ts +35 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +18 -1315
- package/dist/layout.js.map +1 -1
- package/dist/nodes/anchor-node.d.ts.map +1 -1
- package/dist/nodes/anchor-node.js +11 -4
- package/dist/nodes/anchor-node.js.map +1 -1
- package/dist/nodes/content-extent.d.ts +24 -0
- package/dist/nodes/content-extent.d.ts.map +1 -0
- package/dist/nodes/content-extent.js +100 -0
- package/dist/nodes/content-extent.js.map +1 -0
- package/dist/nodes/dependency-edges.d.ts +6 -0
- package/dist/nodes/dependency-edges.d.ts.map +1 -0
- package/dist/nodes/dependency-edges.js +148 -0
- package/dist/nodes/dependency-edges.js.map +1 -0
- package/dist/nodes/footnote-node.d.ts +24 -3
- package/dist/nodes/footnote-node.d.ts.map +1 -1
- package/dist/nodes/footnote-node.js +129 -25
- package/dist/nodes/footnote-node.js.map +1 -1
- package/dist/nodes/group-node.d.ts +10 -21
- package/dist/nodes/group-node.d.ts.map +1 -1
- package/dist/nodes/group-node.js +79 -152
- package/dist/nodes/group-node.js.map +1 -1
- package/dist/nodes/header-node.d.ts +56 -0
- package/dist/nodes/header-node.d.ts.map +1 -0
- package/dist/nodes/header-node.js +163 -0
- package/dist/nodes/header-node.js.map +1 -0
- package/dist/nodes/include-node.d.ts +22 -12
- package/dist/nodes/include-node.d.ts.map +1 -1
- package/dist/nodes/include-node.js +95 -62
- package/dist/nodes/include-node.js.map +1 -1
- package/dist/nodes/item-measure.d.ts +135 -0
- package/dist/nodes/item-measure.d.ts.map +1 -0
- package/dist/nodes/item-measure.js +514 -0
- package/dist/nodes/item-measure.js.map +1 -0
- package/dist/nodes/item-node.d.ts +162 -9
- package/dist/nodes/item-node.d.ts.map +1 -1
- package/dist/nodes/item-node.js +256 -69
- package/dist/nodes/item-node.js.map +1 -1
- package/dist/nodes/marker-band.d.ts +118 -0
- package/dist/nodes/marker-band.d.ts.map +1 -0
- package/dist/nodes/marker-band.js +303 -0
- package/dist/nodes/marker-band.js.map +1 -0
- package/dist/nodes/marker-geometry.js +1 -1
- package/dist/nodes/marker-geometry.js.map +1 -1
- package/dist/nodes/milestone-node.d.ts +112 -15
- package/dist/nodes/milestone-node.d.ts.map +1 -1
- package/dist/nodes/milestone-node.js +246 -95
- package/dist/nodes/milestone-node.js.map +1 -1
- package/dist/nodes/nowline-node.d.ts +8 -0
- package/dist/nodes/nowline-node.d.ts.map +1 -0
- package/dist/nodes/nowline-node.js +74 -0
- package/dist/nodes/nowline-node.js.map +1 -0
- package/dist/nodes/parallel-node.d.ts +7 -12
- package/dist/nodes/parallel-node.d.ts.map +1 -1
- package/dist/nodes/parallel-node.js +66 -30
- package/dist/nodes/parallel-node.js.map +1 -1
- package/dist/nodes/region-markers.d.ts +41 -0
- package/dist/nodes/region-markers.d.ts.map +1 -0
- package/dist/nodes/region-markers.js +130 -0
- package/dist/nodes/region-markers.js.map +1 -0
- package/dist/nodes/roadmap-node.d.ts +9 -72
- package/dist/nodes/roadmap-node.d.ts.map +1 -1
- package/dist/nodes/roadmap-node.js +38 -788
- package/dist/nodes/roadmap-node.js.map +1 -1
- package/dist/nodes/row-track.d.ts +33 -0
- package/dist/nodes/row-track.d.ts.map +1 -0
- package/dist/nodes/row-track.js +170 -0
- package/dist/nodes/row-track.js.map +1 -0
- package/dist/nodes/sequence-item.d.ts +25 -0
- package/dist/nodes/sequence-item.d.ts.map +1 -0
- package/dist/nodes/sequence-item.js +396 -0
- package/dist/nodes/sequence-item.js.map +1 -0
- package/dist/nodes/sequence-one.d.ts +5 -0
- package/dist/nodes/sequence-one.d.ts.map +1 -0
- package/dist/nodes/sequence-one.js +26 -0
- package/dist/nodes/sequence-one.js.map +1 -0
- package/dist/nodes/swimlane-node.d.ts +5 -17
- package/dist/nodes/swimlane-node.d.ts.map +1 -1
- package/dist/nodes/swimlane-node.js +62 -145
- package/dist/nodes/swimlane-node.js.map +1 -1
- package/dist/nodes/wave-node.d.ts +52 -0
- package/dist/nodes/wave-node.d.ts.map +1 -0
- package/dist/nodes/wave-node.js +483 -0
- package/dist/nodes/wave-node.js.map +1 -0
- package/dist/non-working-display.d.ts +14 -0
- package/dist/non-working-display.d.ts.map +1 -0
- package/dist/non-working-display.js +28 -0
- package/dist/non-working-display.js.map +1 -0
- package/dist/phases/date-window.d.ts +48 -0
- package/dist/phases/date-window.d.ts.map +1 -0
- package/dist/phases/date-window.js +130 -0
- package/dist/phases/date-window.js.map +1 -0
- package/dist/phases/finalize.d.ts +198 -0
- package/dist/phases/finalize.d.ts.map +1 -0
- package/dist/phases/finalize.js +1058 -0
- package/dist/phases/finalize.js.map +1 -0
- package/dist/phases/measure.d.ts +77 -0
- package/dist/phases/measure.d.ts.map +1 -0
- package/dist/phases/measure.js +222 -0
- package/dist/phases/measure.js.map +1 -0
- package/dist/phases/place.d.ts +28 -0
- package/dist/phases/place.d.ts.map +1 -0
- package/dist/phases/place.js +225 -0
- package/dist/phases/place.js.map +1 -0
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +23 -3
- package/dist/renderable.js.map +1 -1
- package/dist/resolve-today.d.ts +11 -1
- package/dist/resolve-today.d.ts.map +1 -1
- package/dist/resolve-today.js +26 -12
- package/dist/resolve-today.js.map +1 -1
- package/dist/row-packer.d.ts +42 -16
- package/dist/row-packer.d.ts.map +1 -1
- package/dist/row-packer.js +51 -78
- package/dist/row-packer.js.map +1 -1
- package/dist/schedule.d.ts +58 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +280 -106
- package/dist/schedule.js.map +1 -1
- package/dist/start-rules.d.ts +27 -0
- package/dist/start-rules.d.ts.map +1 -0
- package/dist/start-rules.js +91 -0
- package/dist/start-rules.js.map +1 -0
- package/dist/start-terms.d.ts +23 -0
- package/dist/start-terms.d.ts.map +1 -0
- package/dist/start-terms.js +27 -0
- package/dist/start-terms.js.map +1 -0
- package/dist/status-dot.d.ts +46 -0
- package/dist/status-dot.d.ts.map +1 -0
- package/dist/status-dot.js +78 -0
- package/dist/status-dot.js.map +1 -0
- package/dist/style-resolution.d.ts.map +1 -1
- package/dist/style-resolution.js +23 -43
- package/dist/style-resolution.js.map +1 -1
- package/dist/text-measure.d.ts +28 -0
- package/dist/text-measure.d.ts.map +1 -0
- package/dist/text-measure.js +70 -0
- package/dist/text-measure.js.map +1 -0
- package/dist/themes/contrast.d.ts +4 -0
- package/dist/themes/contrast.d.ts.map +1 -0
- package/dist/themes/contrast.js +32 -0
- package/dist/themes/contrast.js.map +1 -0
- package/dist/themes/dark.d.ts.map +1 -1
- package/dist/themes/dark.js +28 -1
- package/dist/themes/dark.js.map +1 -1
- package/dist/themes/grayscale.d.ts.map +1 -1
- package/dist/themes/grayscale.js +28 -1
- package/dist/themes/grayscale.js.map +1 -1
- package/dist/themes/index.d.ts +4 -11
- package/dist/themes/index.d.ts.map +1 -1
- package/dist/themes/index.js +6 -4
- package/dist/themes/index.js.map +1 -1
- package/dist/themes/light.d.ts.map +1 -1
- package/dist/themes/light.js +28 -1
- package/dist/themes/light.js.map +1 -1
- package/dist/themes/shape.d.ts +31 -0
- package/dist/themes/shape.d.ts.map +1 -1
- package/dist/themes/shared.d.ts +44 -50
- package/dist/themes/shared.d.ts.map +1 -1
- package/dist/themes/shared.js +79 -83
- package/dist/themes/shared.js.map +1 -1
- package/dist/time-scale.d.ts +42 -6
- package/dist/time-scale.d.ts.map +1 -1
- package/dist/time-scale.js +109 -17
- package/dist/time-scale.js.map +1 -1
- package/dist/types.d.ts +528 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/view-preset.d.ts +61 -6
- package/dist/view-preset.d.ts.map +1 -1
- package/dist/view-preset.js +439 -24
- package/dist/view-preset.js.map +1 -1
- package/dist/wave-barrier.d.ts +107 -0
- package/dist/wave-barrier.d.ts.map +1 -0
- package/dist/wave-barrier.js +174 -0
- package/dist/wave-barrier.js.map +1 -0
- package/dist/wave-hatch.d.ts +4 -0
- package/dist/wave-hatch.d.ts.map +1 -0
- package/dist/wave-hatch.js +21 -0
- package/dist/wave-hatch.js.map +1 -0
- package/dist/wave-layout.d.ts +99 -0
- package/dist/wave-layout.d.ts.map +1 -0
- package/dist/wave-layout.js +130 -0
- package/dist/wave-layout.js.map +1 -0
- package/dist/working-calendar.d.ts +128 -4
- package/dist/working-calendar.d.ts.map +1 -1
- package/dist/working-calendar.js +725 -51
- package/dist/working-calendar.js.map +1 -1
- package/dist/working-span.d.ts +11 -0
- package/dist/working-span.d.ts.map +1 -0
- package/dist/working-span.js +18 -0
- package/dist/working-span.js.map +1 -0
- package/package.json +4 -4
- package/src/axis-columns.ts +140 -0
- package/src/calendar-resolver.ts +161 -0
- package/src/calendar.ts +4 -45
- package/src/canvas-width.ts +36 -0
- package/src/capacity.ts +71 -30
- package/src/chart-translation.ts +88 -0
- package/src/container-header-geometry.ts +116 -0
- package/src/dsl-utils.ts +8 -26
- package/src/edge-routing.ts +347 -491
- package/src/frame-tab-geometry.ts +78 -25
- package/src/header-card-geometry.ts +17 -2
- package/src/header-logo.ts +67 -0
- package/src/i18n.ts +22 -1
- package/src/index.ts +97 -12
- package/src/inline-date-pin-geometry.ts +55 -58
- package/src/item-bar-geometry.ts +341 -5
- package/src/item-port-geometry.ts +69 -0
- package/src/layout-context.ts +210 -72
- package/src/layout-insights.ts +109 -2
- package/src/layout.ts +57 -1530
- package/src/nodes/anchor-node.ts +11 -4
- package/src/nodes/content-extent.ts +107 -0
- package/src/nodes/dependency-edges.ts +171 -0
- package/src/nodes/footnote-node.ts +171 -31
- package/src/nodes/group-node.ts +92 -214
- package/src/nodes/header-node.ts +255 -0
- package/src/nodes/include-node.ts +124 -110
- package/src/nodes/item-measure.ts +723 -0
- package/src/nodes/item-node.ts +358 -80
- package/src/nodes/marker-band.ts +406 -0
- package/src/nodes/marker-geometry.ts +1 -1
- package/src/nodes/milestone-node.ts +321 -136
- package/src/nodes/nowline-node.ts +87 -0
- package/src/nodes/parallel-node.ts +69 -38
- package/src/nodes/region-markers.ts +194 -0
- package/src/nodes/roadmap-node.ts +46 -950
- package/src/nodes/row-track.ts +217 -0
- package/src/nodes/sequence-item.ts +433 -0
- package/src/nodes/sequence-one.ts +40 -0
- package/src/nodes/swimlane-node.ts +66 -205
- package/src/nodes/wave-node.ts +609 -0
- package/src/non-working-display.ts +32 -0
- package/src/phases/date-window.ts +191 -0
- package/src/phases/finalize.ts +1303 -0
- package/src/phases/measure.ts +329 -0
- package/src/phases/place.ts +318 -0
- package/src/renderable.ts +23 -3
- package/src/resolve-today.ts +28 -25
- package/src/row-packer.ts +85 -84
- package/src/schedule.ts +362 -114
- package/src/start-rules.ts +113 -0
- package/src/start-terms.ts +38 -0
- package/src/status-dot.ts +81 -0
- package/src/style-resolution.ts +27 -57
- package/src/text-measure.ts +71 -0
- package/src/themes/contrast.ts +34 -0
- package/src/themes/dark.ts +28 -1
- package/src/themes/grayscale.ts +28 -1
- package/src/themes/index.ts +10 -7
- package/src/themes/light.ts +28 -1
- package/src/themes/shape.ts +38 -0
- package/src/themes/shared.ts +85 -93
- package/src/time-scale.ts +117 -19
- package/src/types.ts +529 -20
- package/src/view-preset.ts +520 -27
- package/src/wave-barrier.ts +242 -0
- package/src/wave-hatch.ts +25 -0
- package/src/wave-layout.ts +203 -0
- package/src/working-calendar.ts +875 -57
- package/src/working-span.ts +19 -0
package/src/nodes/item-node.ts
CHANGED
|
@@ -1,19 +1,48 @@
|
|
|
1
|
-
// ItemNode —
|
|
2
|
-
//
|
|
3
|
-
// its caption x (inside vs. spilled past the bar).
|
|
1
|
+
// ItemNode — the Renderable for a single item bar: its visual box
|
|
2
|
+
// (insets + height) and its caption x (inside vs. spilled past the bar).
|
|
4
3
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
4
|
+
// Built from the item's measure (`measureItem`, `item-measure.ts`), which
|
|
5
|
+
// has already resolved the width, the caption fit and the bar's growth,
|
|
6
|
+
// so this node stays small and pure. The start resolution (`after:`
|
|
7
|
+
// chains, pins, the wave floor) and the `before:` caps stay in
|
|
8
|
+
// `sequenceItem`.
|
|
10
9
|
//
|
|
11
|
-
// `measure(ctx)` returns the bar's intrinsic size
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
10
|
+
// `measure(ctx)` returns the bar's intrinsic size: the logical width and
|
|
11
|
+
// the band height plus the growth a wrapped title or a chip column adds.
|
|
12
|
+
// `place(origin, ctx)` returns a `PositionedItem`-shaped fragment with the
|
|
13
|
+
// box at that grown height, `textX`, and the title lines to paint.
|
|
14
|
+
//
|
|
15
|
+
// Caption fit decision (`fitItemCaption`), in order:
|
|
16
|
+
// 1. Title fits one line AND the meta line fits: in-bar, one line.
|
|
17
|
+
// 2. Otherwise, if the meta line fits and a greedy whitespace
|
|
18
|
+
// word-wrap of the title gives at most
|
|
19
|
+
// `ITEM_CAPTION_TITLE_MAX_LINES` lines (>= 2), each within the
|
|
20
|
+
// inner width: in-bar, wrapped.
|
|
21
|
+
// 3. Otherwise: spill to the right of the bar as a single line.
|
|
22
|
+
//
|
|
23
|
+
// The caption's left edge (`itemCaptionInsetX`) clears the link tile and
|
|
24
|
+
// the `after:` inline-date glyph in the bar's upper-left. The inset
|
|
25
|
+
// applies to EVERY caption line, so the left edge stays straight.
|
|
26
|
+
//
|
|
27
|
+
// The FIRST title line has a narrower budget than the rest: it shares
|
|
28
|
+
// the bar's upper-right with the status dot, footnote digits and the
|
|
29
|
+
// `before:` glyph, so it stops short of that cluster
|
|
30
|
+
// (`itemTitleFirstLineRightReservePx`). Line 2+ and the meta line sit
|
|
31
|
+
// below the cluster and keep the full inner width.
|
|
32
|
+
//
|
|
33
|
+
// Explicit breaks (lolay/nowline#60): a newline in the title is a hard
|
|
34
|
+
// line break and the author's lines are authoritative. No auto-wrap
|
|
35
|
+
// runs on them and the two-line cap does not apply. The caption stays
|
|
36
|
+
// in-bar when line 1 fits the first-line width, every other line fits
|
|
37
|
+
// the inner width and the meta line fits; otherwise it spills right as
|
|
38
|
+
// a multi-line block with its breaks preserved.
|
|
15
39
|
|
|
16
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
ITEM_CAPTION_TITLE_MAX_LINES,
|
|
42
|
+
ITEM_DECORATION_SPILL_GAP_PX,
|
|
43
|
+
itemCaptionInsetX,
|
|
44
|
+
itemTitleFirstLineRightReservePx,
|
|
45
|
+
} from '../item-bar-geometry.js';
|
|
17
46
|
import type {
|
|
18
47
|
IntrinsicSize,
|
|
19
48
|
MeasureContext,
|
|
@@ -21,35 +50,34 @@ import type {
|
|
|
21
50
|
Point,
|
|
22
51
|
Renderable,
|
|
23
52
|
} from '../renderable.js';
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
53
|
+
import { estimateTextWidth, wrapText } from '../text-measure.js';
|
|
54
|
+
import { ITEM_SEAM_PX, MIN_ITEM_WIDTH } from '../themes/shared.js';
|
|
55
|
+
import type { BoundingBox, PositionedItem } from '../types.js';
|
|
26
56
|
|
|
27
57
|
/**
|
|
28
|
-
* Inner padding applied on
|
|
29
|
-
*
|
|
30
|
-
*
|
|
58
|
+
* Inner padding applied on the RIGHT of the title text (the left inset
|
|
59
|
+
* comes from `itemCaptionInsetX`, which adds the link-icon column and
|
|
60
|
+
* the `after:` glyph column when present) — the bar's inner-padded text area is
|
|
61
|
+
* `box.width - itemCaptionInsetX(..) - TEXT_INSET_PX` wide. Text wraps
|
|
62
|
+
* or spills past the bar when either the title or the meta line
|
|
31
63
|
* exceeds that area.
|
|
32
64
|
*/
|
|
33
65
|
const TEXT_INSET_PX = 12;
|
|
34
66
|
|
|
35
67
|
/**
|
|
36
68
|
* Gap (px) between the bar's right edge and overflow text. Smaller
|
|
37
|
-
* than `TEXT_INSET_PX` so the text reads as belonging to this bar
|
|
38
|
-
* adjacent bars are at least `2 * ITEM_INSET_PX = 12` away, so the
|
|
39
|
-
* text still has a clear visual home.
|
|
69
|
+
* than `TEXT_INSET_PX` so the text reads as belonging to this bar.
|
|
40
70
|
*/
|
|
41
71
|
const TEXT_OUTSIDE_GAP_PX = 4;
|
|
42
72
|
|
|
43
73
|
const TITLE_FONT_SIZE_PX = 13;
|
|
44
74
|
const META_FONT_SIZE_PX = 11;
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
/** What the caption fit needs to know about a bar. */
|
|
77
|
+
export interface ItemCaptionInput {
|
|
48
78
|
title: string;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
/** Logical right x of the column the bar lives in. */
|
|
52
|
-
logicalRightX: number;
|
|
79
|
+
/** Width of the visible bar box (the logical width less the insets). */
|
|
80
|
+
visualWidth: number;
|
|
53
81
|
/** Caption text shown under the title (e.g. "1w - 50% remaining"). */
|
|
54
82
|
metaText?: string;
|
|
55
83
|
/**
|
|
@@ -70,6 +98,46 @@ export interface ItemNodeInput {
|
|
|
70
98
|
* collide with the icon.
|
|
71
99
|
*/
|
|
72
100
|
hasLinkIcon?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* True when the item carries an inline `after:DATE`, which paints a
|
|
103
|
+
* calendar glyph in the bar's upper-left (right of the link tile
|
|
104
|
+
* when there is one). Caption text indents past the glyph so the
|
|
105
|
+
* title doesn't run under it. The glyph spills out of a bar narrower
|
|
106
|
+
* than `MIN_BAR_WIDTH_FOR_INLINE_DATE_PX`; the fit checks the bar
|
|
107
|
+
* width and reserves nothing in that case.
|
|
108
|
+
*/
|
|
109
|
+
hasAfterGlyph?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Space (px) the title's FIRST line leaves free at the bar's right
|
|
112
|
+
* edge, so it clears the status dot, footnote digits and `before:`
|
|
113
|
+
* glyph in the upper-right. Layout computes it with
|
|
114
|
+
* `itemTitleFirstLineRightReservePx` once it knows the item's
|
|
115
|
+
* footnotes and `before:` date. Defaults to the status-dot-only
|
|
116
|
+
* reserve for the bar width, because the dot is always present.
|
|
117
|
+
*/
|
|
118
|
+
titleFirstLineRightReservePx?: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The caption's left indent inside the bar and its fit decision. */
|
|
122
|
+
export interface ItemCaptionMeasure {
|
|
123
|
+
/** Indent (px) from the bar's left edge to an in-bar caption. */
|
|
124
|
+
leftInset: number;
|
|
125
|
+
fit: ItemCaptionFit;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The part of an item's measure (`ItemMeasure`, `item-measure.ts`) that
|
|
130
|
+
* `ItemNode` needs to report its size and place its box.
|
|
131
|
+
*/
|
|
132
|
+
export interface ItemNodeGeometry {
|
|
133
|
+
id: string;
|
|
134
|
+
/** Logical width: the item's span in time. */
|
|
135
|
+
naturalWidth: number;
|
|
136
|
+
/** Width of the visible bar box. */
|
|
137
|
+
visualWidth: number;
|
|
138
|
+
caption: ItemCaptionMeasure;
|
|
139
|
+
/** Growth over the band height (a wrapped title, a chip column); 0 for none. */
|
|
140
|
+
barExtra: number;
|
|
73
141
|
}
|
|
74
142
|
|
|
75
143
|
export interface PlacedItemGeometry {
|
|
@@ -84,88 +152,298 @@ export interface PlacedItemGeometry {
|
|
|
84
152
|
textX: number;
|
|
85
153
|
/** True when text spills past the bar's right edge. */
|
|
86
154
|
textSpills: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* The title as it should be painted, one entry per line. Always has
|
|
157
|
+
* at least one entry. Exactly two entries (never more than
|
|
158
|
+
* `ITEM_CAPTION_TITLE_MAX_LINES`) when the title auto-wrapped inside
|
|
159
|
+
* the bar; the author's own lines when the title has explicit breaks
|
|
160
|
+
* (any count, in-bar or spilled); otherwise `[title]`, including
|
|
161
|
+
* when a break-free title spills to the right.
|
|
162
|
+
*/
|
|
163
|
+
titleLines: string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Estimated width (px) of an item's whole meta line: `metaText` plus the
|
|
168
|
+
* trailing capacity suffix (`trailingWidth`, 0 when there is none). Zero
|
|
169
|
+
* when the item has no meta line. `measureItemCaption` feeds it to
|
|
170
|
+
* `fitItemCaption`.
|
|
171
|
+
*/
|
|
172
|
+
export function estimateItemMetaWidth(metaText: string | undefined, trailingWidth: number): number {
|
|
173
|
+
const metaTextWidth = metaText ? estimateTextWidth(metaText, META_FONT_SIZE_PX) : 0;
|
|
174
|
+
return metaTextWidth + trailingWidth;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The seam a bar gives up on each side, from inside its dates: `ITEM_SEAM_PX`,
|
|
179
|
+
* shrunk for a bar too short to keep both seams and the `MIN_ITEM_WIDTH`
|
|
180
|
+
* floor, so the floor stays centred in its dates.
|
|
181
|
+
*/
|
|
182
|
+
export function itemBarInsetPx(logicalWidth: number): number {
|
|
183
|
+
return Math.max(0, Math.min(ITEM_SEAM_PX, (logicalWidth - MIN_ITEM_WIDTH) / 2));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The visible bar width for a logical width: the span less a seam on each
|
|
188
|
+
* side, never under `MIN_ITEM_WIDTH` (specs/true-width-layout.md §3.1).
|
|
189
|
+
*/
|
|
190
|
+
export function itemVisualWidth(logicalWidth: number): number {
|
|
191
|
+
return Math.max(MIN_ITEM_WIDTH, logicalWidth - 2 * itemBarInsetPx(logicalWidth));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface ItemCaptionFit {
|
|
195
|
+
/** True when the caption must render to the right of the bar. */
|
|
196
|
+
textSpills: boolean;
|
|
197
|
+
/** Title lines to paint; see `PlacedItemGeometry.titleLines`. */
|
|
198
|
+
titleLines: string[];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The lines of a title that contains an explicit line break, or
|
|
203
|
+
* `undefined` for a title with none (the common case, left untouched).
|
|
204
|
+
*
|
|
205
|
+
* `\r\n` and a lone `\r` count as `\n`, each line is trimmed, and
|
|
206
|
+
* leading and trailing empty lines are dropped. Interior empty lines
|
|
207
|
+
* stay as blank lines. A title that is only breaks and whitespace gives
|
|
208
|
+
* `[]`; one that has a single line left after trimming (`"Plan\n"`)
|
|
209
|
+
* gives one line, and callers then treat it as an ordinary single-line
|
|
210
|
+
* title: a break at either end cannot separate anything.
|
|
211
|
+
*
|
|
212
|
+
* Langium's string converter has already turned the source escape `\n`
|
|
213
|
+
* into a real newline and `\\n` into the two characters `\` `n`, so
|
|
214
|
+
* only the former reaches here as a break.
|
|
215
|
+
*/
|
|
216
|
+
export function titleBreakLines(title: string): string[] | undefined {
|
|
217
|
+
if (!/[\r\n]/.test(title)) return undefined;
|
|
218
|
+
const lines = title
|
|
219
|
+
.replace(/\r\n?/g, '\n')
|
|
220
|
+
.split('\n')
|
|
221
|
+
.map((line) => line.trim());
|
|
222
|
+
let start = 0;
|
|
223
|
+
let end = lines.length;
|
|
224
|
+
while (start < end && lines[start] === '') start += 1;
|
|
225
|
+
while (end > start && lines[end - 1] === '') end -= 1;
|
|
226
|
+
return lines.slice(start, end);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Decide how an item's caption sits relative to its bar: one line
|
|
231
|
+
* in-bar, word-wrapped in-bar, or spilled to the right. This is the
|
|
232
|
+
* single source of truth for the decision; `measureItemCaption` calls it,
|
|
233
|
+
* and the row track and `sequenceItem` share that one measure, so the
|
|
234
|
+
* reserved row and the placed bar agree.
|
|
235
|
+
*
|
|
236
|
+
* `metaWidth` is the full estimated width of the meta line (text plus
|
|
237
|
+
* any trailing capacity suffix), or 0 when the item has no meta line.
|
|
238
|
+
* A meta line is never wrapped, so one that is wider than `innerWidth`
|
|
239
|
+
* forces a spill even when the title alone would wrap.
|
|
240
|
+
*
|
|
241
|
+
* `firstLineWidth` (default `innerWidth`) is the budget for the title's
|
|
242
|
+
* first line alone, narrower than `innerWidth` by the top-right
|
|
243
|
+
* decoration cluster. It governs both the single-line fit check and
|
|
244
|
+
* line 1 of a wrapped title; later lines and the meta line use
|
|
245
|
+
* `innerWidth`.
|
|
246
|
+
*
|
|
247
|
+
* `firstLineHardWidth` (default `firstLineWidth`) is the widest line 1 may
|
|
248
|
+
* be: it also uses the top-right decoration's `ITEM_DECORATION_SPILL_GAP_PX`
|
|
249
|
+
* clearance, up to that cluster's own left edge. It is a fallback, tried
|
|
250
|
+
* only for a wrap or explicit break that would otherwise spill, so a caption
|
|
251
|
+
* that fits with the gap never moves; it lets a first word that misses
|
|
252
|
+
* `firstLineWidth` by a hair wrap instead of spilling.
|
|
253
|
+
*
|
|
254
|
+
* Words never break mid-word: a single word wider than its line's
|
|
255
|
+
* budget, or a title that needs more than `ITEM_CAPTION_TITLE_MAX_LINES`
|
|
256
|
+
* lines, spills.
|
|
257
|
+
*
|
|
258
|
+
* A title with an explicit line break (see `titleBreakLines`) skips all
|
|
259
|
+
* of the above: its lines are authoritative and are returned as
|
|
260
|
+
* `titleLines` whether the caption stays in-bar or spills.
|
|
261
|
+
*/
|
|
262
|
+
export function fitItemCaption(
|
|
263
|
+
rawTitle: string,
|
|
264
|
+
innerWidth: number,
|
|
265
|
+
metaWidth: number,
|
|
266
|
+
firstLineWidth: number = innerWidth,
|
|
267
|
+
firstLineHardWidth: number = firstLineWidth,
|
|
268
|
+
): ItemCaptionFit {
|
|
269
|
+
let title = rawTitle;
|
|
270
|
+
const broken = titleBreakLines(rawTitle);
|
|
271
|
+
if (broken) {
|
|
272
|
+
if (broken.length >= 2) {
|
|
273
|
+
// Explicit breaks: the author's lines are authoritative, so no
|
|
274
|
+
// auto-wrap and no line cap. In-bar only when line 1 fits the
|
|
275
|
+
// first-line width, every other line fits the inner width and
|
|
276
|
+
// the meta line fits; otherwise the whole block spills right.
|
|
277
|
+
const fits =
|
|
278
|
+
metaWidth <= innerWidth &&
|
|
279
|
+
broken.every(
|
|
280
|
+
(line, n) =>
|
|
281
|
+
estimateTextWidth(line, TITLE_FONT_SIZE_PX) <=
|
|
282
|
+
(n === 0 ? firstLineHardWidth : innerWidth),
|
|
283
|
+
);
|
|
284
|
+
return { textSpills: !fits, titleLines: broken };
|
|
285
|
+
}
|
|
286
|
+
// Zero or one line left after trimming: an ordinary title, now
|
|
287
|
+
// free of its stray break.
|
|
288
|
+
title = broken[0] ?? '';
|
|
289
|
+
}
|
|
290
|
+
const titleOverflows =
|
|
291
|
+
title.length > 0 && estimateTextWidth(title, TITLE_FONT_SIZE_PX) > firstLineWidth;
|
|
292
|
+
const metaOverflows = metaWidth > innerWidth;
|
|
293
|
+
if (!titleOverflows && !metaOverflows) {
|
|
294
|
+
return { textSpills: false, titleLines: [title] };
|
|
295
|
+
}
|
|
296
|
+
if (titleOverflows && !metaOverflows) {
|
|
297
|
+
// Line 1 first keeps the dot's clearance; only when that spills does
|
|
298
|
+
// it try the hard limit.
|
|
299
|
+
for (const line1Width of [firstLineWidth, firstLineHardWidth]) {
|
|
300
|
+
const lines = wrapText(title, innerWidth, TITLE_FONT_SIZE_PX, line1Width);
|
|
301
|
+
// `lines.length >= 2` keeps single-line behavior byte-identical: a
|
|
302
|
+
// title that only "overflows" because of repeated whitespace in
|
|
303
|
+
// the raw string (the estimate counts every space) normalizes to
|
|
304
|
+
// one line, and that case still spills exactly as it always has.
|
|
305
|
+
if (
|
|
306
|
+
lines.length >= 2 &&
|
|
307
|
+
lines.length <= ITEM_CAPTION_TITLE_MAX_LINES &&
|
|
308
|
+
lines.every(
|
|
309
|
+
(line, n) =>
|
|
310
|
+
estimateTextWidth(line, TITLE_FONT_SIZE_PX) <=
|
|
311
|
+
(n === 0 ? line1Width : innerWidth),
|
|
312
|
+
)
|
|
313
|
+
) {
|
|
314
|
+
return { textSpills: false, titleLines: lines };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return { textSpills: true, titleLines: [title] };
|
|
87
319
|
}
|
|
88
320
|
|
|
89
321
|
/**
|
|
90
|
-
*
|
|
91
|
-
* `
|
|
92
|
-
*
|
|
322
|
+
* The title lines an item finally paints. `fit` is the caption-fit
|
|
323
|
+
* decision; `forcedSpill` is true when something outside it pushes the
|
|
324
|
+
* caption out of the bar anyway (a narrow-bar link icon, `iconSpills`).
|
|
325
|
+
* `measureItem` calls it; the line count sets the bar's growth.
|
|
326
|
+
*
|
|
327
|
+
* - Explicit breaks keep the author's lines, in-bar or spilled.
|
|
328
|
+
* - An auto-wrap (two lines) survives only while the caption stays
|
|
329
|
+
* in-bar; a spilled break-free title is one line.
|
|
330
|
+
* - Otherwise a single line: the title, minus any stray break at
|
|
331
|
+
* either end.
|
|
93
332
|
*/
|
|
94
|
-
function
|
|
95
|
-
|
|
333
|
+
export function resolveCaptionTitleLines(
|
|
334
|
+
title: string,
|
|
335
|
+
fit: ItemCaptionFit,
|
|
336
|
+
forcedSpill: boolean,
|
|
337
|
+
): string[] {
|
|
338
|
+
const broken = titleBreakLines(title);
|
|
339
|
+
if (broken && broken.length >= 2) return broken;
|
|
340
|
+
if (!fit.textSpills && !forcedSpill && fit.titleLines.length >= 2) return fit.titleLines;
|
|
341
|
+
return [broken ? (broken[0] ?? '') : title];
|
|
96
342
|
}
|
|
97
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Fit an item's caption to its bar: the left indent past the link tile
|
|
346
|
+
* and the `after:` glyph, the inner and first-line widths, and the
|
|
347
|
+
* `fitItemCaption` decision on them.
|
|
348
|
+
*/
|
|
349
|
+
export function measureItemCaption(input: ItemCaptionInput): ItemCaptionMeasure {
|
|
350
|
+
const { visualWidth } = input;
|
|
351
|
+
// The link icon and the `after:` glyph (when present) live in the
|
|
352
|
+
// bar's upper-left and share the title's vertical band. The
|
|
353
|
+
// caption indents past them so the title doesn't render on top.
|
|
354
|
+
const leftInset = itemCaptionInsetX(
|
|
355
|
+
!!input.hasLinkIcon,
|
|
356
|
+
input.hasAfterGlyph ? { barWidth: visualWidth } : undefined,
|
|
357
|
+
);
|
|
358
|
+
const innerWidth = Math.max(0, visualWidth - leftInset - TEXT_INSET_PX);
|
|
359
|
+
// Trailing decoration (capacity suffix) renders to the right of
|
|
360
|
+
// metaText. When metaText is empty the suffix sits at the
|
|
361
|
+
// caption's leading edge, so its width is the entire meta-line
|
|
362
|
+
// budget; when both exist the suffix needs a small separator gap
|
|
363
|
+
// (rendered via `<tspan dx>` later) included in trailingWidth.
|
|
364
|
+
const metaWidth = estimateItemMetaWidth(input.metaText, input.metaTrailingWidth ?? 0);
|
|
365
|
+
const firstLineReserve =
|
|
366
|
+
input.titleFirstLineRightReservePx ??
|
|
367
|
+
itemTitleFirstLineRightReservePx({
|
|
368
|
+
barWidth: visualWidth,
|
|
369
|
+
footnoteLabels: [],
|
|
370
|
+
hasBeforeGlyph: false,
|
|
371
|
+
});
|
|
372
|
+
const firstLineWidth = Math.max(0, visualWidth - leftInset - firstLineReserve);
|
|
373
|
+
// The same line without the cluster's clearance gap: its left edge is the limit.
|
|
374
|
+
const firstLineHardWidth = Math.max(
|
|
375
|
+
firstLineWidth,
|
|
376
|
+
visualWidth -
|
|
377
|
+
leftInset -
|
|
378
|
+
Math.max(TEXT_INSET_PX, firstLineReserve - ITEM_DECORATION_SPILL_GAP_PX),
|
|
379
|
+
);
|
|
380
|
+
return {
|
|
381
|
+
leftInset,
|
|
382
|
+
fit: fitItemCaption(input.title, innerWidth, metaWidth, firstLineWidth, firstLineHardWidth),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Optional observer of every `ItemNode` that `sequenceItem` places, with
|
|
388
|
+
* the context it was placed in and the item it became. Tests set it to
|
|
389
|
+
* check a node's measure against the item's final box; layout never does.
|
|
390
|
+
*/
|
|
391
|
+
export type ItemNodeTrace = (node: ItemNode, ctx: PlaceContext, item: PositionedItem) => void;
|
|
392
|
+
|
|
393
|
+
let itemNodeTrace: ItemNodeTrace | undefined;
|
|
394
|
+
|
|
395
|
+
/** Install (or, with `undefined`, remove) the `ItemNodeTrace`. */
|
|
396
|
+
export function setItemNodeTrace(trace: ItemNodeTrace | undefined): void {
|
|
397
|
+
itemNodeTrace = trace;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Report a placed node to the `ItemNodeTrace`, when one is installed. */
|
|
401
|
+
export function traceItemNode(node: ItemNode, ctx: PlaceContext, item: PositionedItem): void {
|
|
402
|
+
itemNodeTrace?.(node, ctx, item);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* One item bar, built from its measure. `measure` reports the logical
|
|
407
|
+
* width and the grown height (`bandwidth + barExtra`), and `place`
|
|
408
|
+
* returns the box at that height, so no caller patches it afterwards.
|
|
409
|
+
*/
|
|
98
410
|
export class ItemNode implements Renderable<PlacedItemGeometry> {
|
|
99
|
-
constructor(public readonly
|
|
411
|
+
constructor(public readonly geometry: ItemNodeGeometry) {}
|
|
100
412
|
|
|
101
413
|
get id(): string {
|
|
102
|
-
return this.
|
|
414
|
+
return this.geometry.id;
|
|
103
415
|
}
|
|
104
416
|
|
|
105
417
|
measure(ctx: MeasureContext): IntrinsicSize {
|
|
106
|
-
const naturalWidth = Math.max(
|
|
107
|
-
MIN_ITEM_WIDTH,
|
|
108
|
-
this.input.logicalRightX - this.input.logicalLeftX,
|
|
109
|
-
);
|
|
110
418
|
return {
|
|
111
|
-
width: naturalWidth,
|
|
112
|
-
height: ctx.bands.bandwidth(),
|
|
419
|
+
width: this.geometry.naturalWidth,
|
|
420
|
+
height: ctx.bands.bandwidth() + this.geometry.barExtra,
|
|
113
421
|
};
|
|
114
422
|
}
|
|
115
423
|
|
|
116
424
|
place(origin: Point, ctx: PlaceContext): PlacedItemGeometry {
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const boxX = origin.x +
|
|
425
|
+
const { naturalWidth, visualWidth, caption, barExtra } = this.geometry;
|
|
426
|
+
const bandwidth = ctx.bands.bandwidth();
|
|
427
|
+
const boxX = origin.x + itemBarInsetPx(naturalWidth);
|
|
120
428
|
const box: BoundingBox = {
|
|
121
429
|
x: boxX,
|
|
122
430
|
y: origin.y,
|
|
123
431
|
width: visualWidth,
|
|
124
|
-
|
|
432
|
+
// The bar grows downward to enclose a wrapped title or a chip
|
|
433
|
+
// column; an ungrown bar is exactly the band height.
|
|
434
|
+
height: barExtra > 0 ? bandwidth + barExtra : bandwidth,
|
|
125
435
|
};
|
|
126
|
-
|
|
127
|
-
// The link icon (when present) lives in the bar's upper-left
|
|
128
|
-
// and shares the title's vertical band. The caption indents
|
|
129
|
-
// past the icon so the title doesn't render on top of it.
|
|
130
|
-
const linkColumn = this.input.hasLinkIcon
|
|
131
|
-
? ITEM_LINK_ICON_INSET_PX +
|
|
132
|
-
ITEM_LINK_ICON_TILE_SIZE_PX +
|
|
133
|
-
LINK_ICON_TO_CAPTION_GAP_PX -
|
|
134
|
-
TEXT_INSET_PX
|
|
135
|
-
: 0;
|
|
136
|
-
const captionLeftInset = TEXT_INSET_PX + Math.max(0, linkColumn);
|
|
137
|
-
const innerWidth = Math.max(0, visualWidth - captionLeftInset - TEXT_INSET_PX);
|
|
138
|
-
const titleStr = this.input.title;
|
|
139
|
-
const titleWidth = titleStr ? estimateTextWidth(titleStr, TITLE_FONT_SIZE_PX) : 0;
|
|
140
|
-
const metaTextWidth = this.input.metaText
|
|
141
|
-
? estimateTextWidth(this.input.metaText, META_FONT_SIZE_PX)
|
|
142
|
-
: 0;
|
|
143
|
-
const trailingWidth = this.input.metaTrailingWidth ?? 0;
|
|
144
|
-
// Trailing decoration (capacity suffix) renders to the right of
|
|
145
|
-
// metaText. When metaText is empty the suffix sits at the
|
|
146
|
-
// caption's leading edge, so its width is the entire meta-line
|
|
147
|
-
// budget; when both exist the suffix needs a small separator gap
|
|
148
|
-
// (rendered via `<tspan dx>` later) included in trailingWidth.
|
|
149
|
-
const metaWidth = metaTextWidth + trailingWidth;
|
|
150
|
-
const hasMetaContent = this.input.metaText !== undefined || trailingWidth > 0;
|
|
151
|
-
const textSpills =
|
|
152
|
-
(titleStr.length > 0 && titleWidth > innerWidth) ||
|
|
153
|
-
(hasMetaContent && metaWidth > innerWidth);
|
|
436
|
+
const { textSpills, titleLines } = caption.fit;
|
|
154
437
|
const textX = textSpills
|
|
155
438
|
? boxX + visualWidth + TEXT_OUTSIDE_GAP_PX
|
|
156
|
-
: boxX +
|
|
439
|
+
: boxX + caption.leftInset;
|
|
157
440
|
|
|
158
441
|
return {
|
|
159
|
-
id: this.
|
|
442
|
+
id: this.geometry.id,
|
|
160
443
|
box,
|
|
161
444
|
textX,
|
|
162
445
|
textSpills,
|
|
446
|
+
titleLines,
|
|
163
447
|
};
|
|
164
448
|
}
|
|
165
449
|
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Horizontal gap (px) between the link-icon tile's right edge and the
|
|
169
|
-
* start of the caption text when both render inside the bar.
|
|
170
|
-
*/
|
|
171
|
-
const LINK_ICON_TO_CAPTION_GAP_PX = 4;
|